writing相关知识
-
php对于数据库的基本用法This is the first time I have been before writing in the school laboratory which is very strange is that I have been before writing C, including the development of microcontroller applications, but a chance I was exposed to Python and PHP, I found it very
-
Golang 匿名结构体及测试代码编写技巧这两天在学习 Golang 如何 TDD ,了解到匿名结构体切片在 TableDrivenTests 中经常用到。Writing good tests is not trivial, but in many situations a lot of ground can be covered with table-driven tests: Each table entry is a complete test case with inputs and expected results, and sometimes with additional information such as a test name to make the test output easily readable. If you ever find yourself using copy and paste when writing a test, think about whether refactoring into a tabl
-
Understanding PythonOk, perhaps I jest. As a Python instructor, understanding decorators is a topic I find students consistently struggle with upon first exposure. That’s because decorators are hard to understand! Getting decorators requires understanding several functional programming concepts as well as feeling comfortable with some unique features of Python’s function definition and function calling syntax. Using decorators is easy (see Section 10)! But writing them
-
garbage collection brief introductionBefore get started, I want to tell you a joke: A few weeks ago, a conceited idiot told others that he was planning to design and implement a new programming language IN HALF A YEAR when he didn’t even understand how the runtime manages the memory! What is the conceited idiot doing now? He is writing the article you are reading. So today, I want to talk about garbage collection. language
writing相关课程
writing相关教程
- 2. InnoDB 的热备 目前,比较流行的 InnoDB 存储引擎的热备工具是 Xtrabackup。 xtrabackup 是 Percona 公司开发的一个用于 MySQL 数据库物理热备的备份工具,支持 MySQL、Percona server 和 MariaDB,开源免费,是目前较为受欢迎的主流备份工具 ,它能对 InnoDB 数据库和 XtraDB 存储引擎的数据库非阻塞地备份(对于 MyISAM 的备份同样需要加表锁)。Xtrabackup 优点:备份速度快,物理备份可靠;备份过程不会打断正在执行的事务(无需锁表);能够基于压缩等功能节约磁盘空间和流量;自动备份校验;还原速度快;可以流传将备份传输到另外一台机器上;在不增加服务器负载的情况备份数据。实际案例:Xtrabackup 全量备份:[root@localhost ~]# innobackupex --defaults-file=/etc/my.cnf --user=root --password=MyNewPass4! --port=3306 --no-timestamp /mysql/dbbackup/200823 21:11:33 innobackupex: Starting the backup operationIMPORTANT: Please check that the backup run completes successfully. At the end of a successful backup run innobackupex prints "completed OK!".200823 21:11:33 version_check Connecting to MySQL server with DSN 'dbi:mysql:;mysql_read_default_group=xtrabackup;port=3306;mysql_socket=/mysql/tmp/mysql.sock' as 'root' (using password: YES).200823 21:11:33 version_check Connected to MySQL server200823 21:11:33 version_check Executing a version check against the server...200823 21:11:33 version_check Done.200823 21:11:33 Connecting to MySQL server host: localhost, user: root, password: set, port: 3306, socket: /mysql/tmp/mysql.sock......200823 21:11:48 Finished backing up non-InnoDB tables and files200823 21:11:48 [00] Writing /mysql/dbbackup/xtrabackup_binlog_info200823 21:11:48 [00] ...done200823 21:11:48 Executing FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS...xtrabackup: The latest check point (for incremental): '14533834254'xtrabackup: Stopping log copying thread..200823 21:11:48 >> log scanned up to (14533834263)200823 21:11:49 Executing UNLOCK TABLES200823 21:11:49 All tables unlocked200823 21:11:49 [00] Copying ib_buffer_pool to /mysql/dbbackup/ib_buffer_pool200823 21:11:49 [00] ...done200823 21:11:49 Backup created in directory '/mysql/dbbackup/'MySQL binlog position: filename 'mysql-bin.000022', position '190', GTID of the last change '294ae4cf-be0f-11e7-8269-fa163e665653:1-2769,a373c879-3a2c-11e8-bb78-fa163e665653:1-5,aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-40:1000012-1000013'200823 21:11:49 [00] Writing /mysql/dbbackup/backup-my.cnf200823 21:11:49 [00] ...done200823 21:11:49 [00] Writing /mysql/dbbackup/xtrabackup_info200823 21:11:49 [00] ...donextrabackup: Transaction log of lsn (14533834254) to (14533834263) was copied.200823 21:11:49 completed OK!备份完成后,备份目录将产生如下文件:[root@localhost ~]$ ls -lh /mysql/dbbackup/total 101M-rw-r----- 1 root root 433 Aug 23 21:11 backup-my.cnf-rw-r----- 1 root root 42K Aug 23 21:11 ib_buffer_pool-rw-r----- 1 root root 100M Aug 23 21:11 ibdata1drwxr-x--- 2 root root 4.0K Aug 23 21:11 mysqldrwxr-x--- 2 root root 4.0K Aug 23 21:11 performance_schemadrwxr-x--- 2 root root 12K Aug 23 21:11 sysdrwxr-x--- 2 root root 4.0K Aug 23 21:11 tempdb-rw-r----- 1 root root 166 Aug 23 21:11 xtrabackup_binlog_info-rw-r----- 1 root root 121 Aug 23 21:11 xtrabackup_checkpoints-rw-r----- 1 root root 703 Aug 23 21:11 xtrabackup_info-rw-r----- 1 root root 2.5K Aug 23 21:11 xtrabackup_logfile实际案例:Xtrabackup 增量备份:在进行增量备份之前,首先要有一次全量备份,第一次增量是基于全备份,之后的增量是基于上一次的增量备份。[root@localhost ~]# innobackupex --defaults-file=/etc/my.cnf --user=root --password=MyNewPass4! --port=3306 --no-timestamp /mysql/hotbackup/base-- 从xtrabackup_checkpoints文件可以看出,备份类型为全备份,lsn号从0至14533834254[root@localhost ~]# cat /mysql/hotbackup/base/xtrabackup_checkpoints backup_type = full-backupedfrom_lsn = 0to_lsn = 14533834254last_lsn = 14533834263compact = 0recover_binlog_info = 0表 customer 插入新的数据:mysql> insert into customer(id,last_name,first_name,birth_date,gender,balance) values(3,333,333,'2020-08-10',1,30);Query OK, 1 row affected (0.00 sec)mysql> insert into customer(id,last_name,first_name,birth_date,gender,balance) values(4,444,444,'2020-09-10',1,40);Query OK, 1 row affected (0.00 sec)第一次增量备份:[root@localhost ~]# innobackupex --defaults-file=/etc/my.cnf --user=root --password=MyNewPass4! --port=3306 --incremental --no-timestamp /mysql/hotbackup/inc1 --incremental-basedir=/mysql/hotbackup/base-- 从xtrabackup_checkpoints文件可以看出,备份类型为增量备份,lsn号从lsn 14533834254至lsn 14533838790,14533834254为全备份的to_lsn,表示备份从lsn 14533834254以来的增量变化。[root@localhost ~]# cat /mysql/hotbackup/inc1/xtrabackup_checkpoints backup_type = incrementalfrom_lsn = 14533834254to_lsn = 14533838790last_lsn = 14533838799compact = 0recover_binlog_info = 0
- 3. CSS3 各个模块发展进程 时间名称最后状态模块1999.01.27 - 2019.08.13文本修饰模块候选推荐css-text-decor-31999.06.22 - 2018.10.18分页媒体模块工作草案css-page-31999.06.23 - 2019.10.15多列布局工作草案css-multicol-11999.06.22 - 2018.06.19颜色模块推荐css-color-31999.06.25 - 2014.03.20命名空间模块推荐css-namespaces-31999.08.03 - 2018.11.06选择器推荐selectors-32001.04.04 - 2012.06.19媒体查询推荐css3-mediaqueries2001.05.17 - 2019.11.13文本模块工作草案css-text-32001.07.13 - 2018.08.28级联和继承候选推荐css-cascade-32001.07.13 - 2019.06.06取值和单位模块候选推荐css-values-32001.07.26 - 2018.12.18基本盒子模型工作草案css-box-32001.07.31- 2018.09.20字体模块推荐css-fonts-32001.09.24 - 2017.10.17背景和边框模块候选推荐css-backgrounds-32002.02.20 - 2019.08-17列表模块工作草案css-lists-32002.05.15 - 2018.08.08行内布局模块工作草案css-inline-32002.08.02 - 2018.06.21基本用户界面模块推荐css-ui-32003.05.14 - 2019.08.02生成内容模块工作草案css-content-32003.08.13 - 2019.07.16语法模块候选推荐css-syntax-32004.02.24 - 2014.10.14超链接显示模块工作组笔记css3-hyperlinks2005.12.15 - 2015.03.26模板布局模块工作组笔记css-template-32006.06.12 - 2014.05.13分页媒体模块生成内容工作草案css-gcpm-32008.08.01 - 2014.10.14Marquee模块工作组笔记css3-marquee2009.07.23 - 2019.10.10图像模块候选推荐css-images-32010.12.02 - 2019.12.10书写模式推荐css-writing-modes-32011.09.01 - 2013.04.04条件规则模块候选推荐css3-conditionalr2012.02.07 - 2016.05.17定位布局模块工作草案css-position-32012.02.28 - 2018.12.04片段模块候选推荐css-break-32012.09.27 - 2019.05.22宽高大小模块工作草案css-sizing-32012.10.09 - 2017.12.14计数器风格候选推荐css-counter-styles-32013.04.18 - 2018.07.31溢出模块工作草案css-overflow-32014.02.20- 2019.07.11显示类型模块候选推荐css-display-3参考文献:百度百科
- 4. Java NIO 服务器端实现步骤 因为服务端采用非阻塞模式,需要用到Java NIO 的 Selector 组件,这是 Java NIO 的 I/O 多路复用机制,可以同时监听多个 SocketChannel 是否有读写事件。创建 Java NIO 的 Selector 实例selector = Selector.open();打开服务器 ServerSocketChannelserverChannel = ServerSocketChannel.open();给 ServerSocketChannel 绑定监听的 socket 地址,监听 any_addr serverChannel.socket().bind(new InetSocketAddress(PORT));设置 SO_REUSEADDR 选项,作为服务器,这是基本的要求 serverChannel.socket().setReuseAddress(true);设置非阻塞模式,这是服务器的基本要求,也是本小节的重点 serverChannel.configureBlocking(false);向 Selector 注册 accept 事件 serverChannel.register(selector, SelectionKey.OP_ACCEPT, serverChannel);编写事件循环。所有需要读写数据的 SocketChannel,需要将读写事件注册到 Selector。调用 Selector 的 select 方法,调用线程会进入 I/O 事件监听状态。如果没有事件发生,调用线程会被阻塞,进入事件等待状态;如果有事件发生,Selector 的 select 方法会返回发生了 I/O 事件的 SocketChannel 个数。Selector 的 selectedKeys 方法返回一个 java.util.Set 类,集合中包含的是 SelectionKey 结构,SelectionKey 和 SocketChannel 是一一对应的,表示发生了 I/O 事件的 SocketChannel。所以需要 遍历 Set,分别处理每个 SelectionKey。 while (true) { int readyChannels = selector.select(); if (readyChannels == 0) { System.out.println("No socket has i/o events"); continue; } Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key != null) { } keyIterator.remove(); } }抽象一个内部类 Client,表示一个客户端连接,每当一个新连接建立的时候,创建一个此类的对象。private static class Client{ public void sendData(); public int recvData(); public void close();}通过 key.isAcceptable() 处理连接接收事件。if (key.isAcceptable()) { // a connection was accepted by a ServerSocketChannel. ServerSocketChannel ssc = (ServerSocketChannel) key.attachment(); SocketChannel newSock = ssc.accept(); newSock.configureBlocking(false); Client client = new Client(selector, newSock);} 通过 key.isReadable() 处理读事件if (key.isReadable()) { // a channel is ready for reading Client client = (Client) key.attachment(); int rc = client.recvData(); if (rc == 0) { client.sendData(); }}通过 key.isReadable() 处理“写”事件if (key.isWritable()) { // a channel is ready for writing Client client = (Client) key.attachment(); client.cancelEvent(SelectionKey.OP_WRITE); client.sendData();}服务器端完整代码如下:import java.io.*;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.*;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Iterator;import java.util.Set;public class NonblockTCPServer { // 服务器监听的端口 private final static int PORT = 9082; private Selector selector = null; private ServerSocketChannel serverChannel = null; private static class Client{ // 接收 buffer 长度 private final static int RECV_BUF_LEN = 1024; // 接收buffer 声明 private ByteBuffer recvBuff = null; // 发送 buffer 长度 private static final int SEND_BUFF_LEN = 1024; // 发送 buffer 声明 private ByteBuffer sendBuff = null; // the Selector private Selector selector = null; // SocketChannel 引用声明,表示一个连接 private SocketChannel socketChannel = null; private SelectionKey sk_ = null; private boolean canSend = true; public Client(Selector selector, SocketChannel newSock){ this.selector = selector; this.socketChannel = newSock; this.recvBuff = ByteBuffer.allocate(RECV_BUF_LEN); this.sendBuff = ByteBuffer.allocate(SEND_BUFF_LEN); this.register(SelectionKey.OP_READ); } private void register(int op){ try { if (sk_ == null){ sk_ = this.socketChannel.register(selector, op, this); } else { sk_.interestOps(op | sk_.interestOps()); } } catch (ClosedChannelException e) { e.printStackTrace(); } } public void cancelEvent(int ops){ if (sk_ == null) return; sk_.interestOps(sk_.interestOps() & (~ops)); } public void sendData() { try { int totalSendBytes = 0; String resp = null; if (canSend){ //设置日期格式 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); resp = "The server time : " + df.format(new Date()); sendBuff.putInt(resp.length()); sendBuff.put(resp.getBytes()); totalSendBytes = resp.length() + 4; sendBuff.flip(); }else { totalSendBytes = sendBuff.remaining(); } int sbytes = this.socketChannel.write(sendBuff); System.out.println("Send to client about message :" + resp); if (sbytes < totalSendBytes) { this.register(SelectionKey.OP_WRITE); canSend = false; } else { if (!canSend){ canSend = true; } sendBuff.rewind(); } } catch (IOException e) { e.printStackTrace(); } } public int recvData(){ try { int recvBytes = this.socketChannel.read(this.recvBuff); if (recvBytes < 0){ System.out.println("Meet error or the end of stream"); close(); return -1; }else if (recvBytes == 0){ return 0;// eagain } this.recvBuff.flip(); while (this.recvBuff.remaining() > 0) { // Incomplete message header if (this.recvBuff.remaining() < 4) { break; } int bodyLen = this.recvBuff.getInt(); if (bodyLen > this.recvBuff.remaining()) { // Incomplete message body break; } byte[] body = new byte[bodyLen]; this.recvBuff.get(body, 0, bodyLen); System.out.println("Recv message from client: " + new String(body, 0, bodyLen)); } // flip recv buffer this.recvBuff.compact(); return 0; } catch (IOException e) { e.printStackTrace(); close(); } return -1; } public void close(){ try { cancelEvent(SelectionKey.OP_WRITE | SelectionKey.OP_READ); if (this.socketChannel != null){ this.socketChannel.close(); } } catch (IOException e) { e.printStackTrace(); } } } public void start(){ try { selector = Selector.open(); serverChannel = ServerSocketChannel.open(); // 绑定监听的 socket 地址,监听 any_addr serverChannel.socket().bind(new InetSocketAddress(PORT)); // 设置 SO_REUSEADDR 选项,作为服务器,这是基本的要求 serverChannel.socket().setReuseAddress(true); // 设置非阻塞模式,作为服务器,也是基本要求 serverChannel.configureBlocking(false); // 注册 accept 事件 serverChannel.register(selector, SelectionKey.OP_ACCEPT, serverChannel); } catch (IOException e) { e.printStackTrace(); stop(); } } public void process() { try { while (true) { int readyChannels = selector.select(); if (readyChannels == 0) { System.out.println("No socket has i/o events"); continue; } Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key != null) { if (key.isAcceptable()) { // a connection was accepted by a ServerSocketChannel. ServerSocketChannel ssc = (ServerSocketChannel) key.attachment(); SocketChannel newSock = ssc.accept(); newSock.configureBlocking(false); Client client = new Client(selector, newSock); } else if (key.isConnectable()) { // a connection was established with a remote server. } else if (key.isReadable()) { // a channel is ready for reading Client client = (Client) key.attachment(); int rc = client.recvData(); if (rc == 0) { client.sendData(); } } else if (key.isWritable()) { // a channel is ready for writing Client client = (Client) key.attachment(); client.cancelEvent(SelectionKey.OP_WRITE); client.sendData(); } } keyIterator.remove(); } } } catch (IOException e) { e.printStackTrace(); } } public void stop(){ try { if (serverChannel != null){ serverChannel.close(); serverChannel = null; } if (selector != null) { selector.close(); selector = null; } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { NonblockTCPServer tcp = new NonblockTCPServer(); tcp.start(); tcp.process(); }}对于非阻塞式 Socket,需要处理发送 Buffer 满和接收 Buffer 为空的情况。服务器样例代码的 320 ~ 390 行的主要逻辑就是在处理非阻塞模式下,发送 Buffer 满和接收 Buffer 为空的逻辑。
- 3. HttpUrlConnection 的使用步骤 首先还是引用一下 Google 官方的使用文档:A URLConnection with support for HTTP-specific features. See the spec for details.Uses of this class follow a pattern:Obtain a new HttpURLConnection by calling [URL#openConnection()](https://developer.android.com/reference/java/net/URL#openConnection()) and casting the result to HttpURLConnection.Prepare the request. The primary property of a request is its URI. Request headers may also include metadata such as credentials, preferred content types, and session cookies.Optionally upload a request body. Instances must be configured with [setDoOutput(true)](https://developer.android.com/reference/java/net/URLConnection#setDoOutput(boolean)) if they include a request body. Transmit data by writing to the stream returned by [URLConnection.getOutputStream()](https://developer.android.com/reference/java/net/URLConnection#getOutputStream()).Read the response. Response headers typically include metadata such as the response body’s content type and length, modified dates and session cookies. The response body may be read from the stream returned by [URLConnection.getInputStream()](https://developer.android.com/reference/java/net/URLConnection#getInputStream()). If the response has no body, that method returns an empty stream.Disconnect. Once the response body has been read, the HttpURLConnection should be closed by calling [disconnect()](https://developer.android.com/reference/java/net/HttpURLConnection#disconnect()). Disconnecting releases the resources held by a connection so they may be closed or reused.官方文档没有对 Http 协议本身做什么解释(如果对 Http 协议不太了解的同学,可以参考慕课网上网络相关课程),主要是围绕 HttpUrlConnection 的用法展开了一步步的描述,结合官网的解释以及我个人的总结,大体上可以分为一下几步:通过openConnection()方法创建一个HttpURLConnection:URL url = new URL(https://www.baidu.com);HttpURLConnection conn = (HttpURLConnection) url.openConnection();首先创建一个 URL 对象,参数就是我们要打开的地址,然后使用 url 对象的openConnection()来打开 Http 连接,拿到一个HttpURLConnection对象。2. 设置 Http 请求类型设置本次 Http 请求的方法类型,Http 有以下几种类型:GETPOSTHEADCONNECTOPTIONSTRACEPATCHPUT**DELETE这里就不做详细的解释了,可自行百度。最常用的就是前两种:GET和POST:conn.setRequestMethod("GET");设置 Http 相关参数这一步主要是设置请求头的参数,我们前面那张大表就可以派上用场了。此时可以设置 Cookie、Content-Type、超时时间等等参数。比如设置超时时间为 3 秒:conn.setConnectTimeout(3*1000); conn.setWirteTimeout(3 * 1000);获取输入流通过getInputStream()方法获取网络输入流,此后可以通过此对象获取网络数据,如下:InputStream in = conn.getInputStream();关闭流网络流比较消耗资源,在使用完毕之后一定要将 Http 连接关掉:conn.disconnect();
- 2. 使用 JConsole 监控 Zookeeper JConsole 是 JDK 自带的 Java 进程监控工具,Zookeeper 是基于 Java 的应用程序,也支持 JMX( Java Management Extensions ),所以我们可以通过 JConsole 来对 Zookeeper 的运行状态进行监控。在使用 JConsole 开启监控之前,我们需要修改 Zookeeper 关于 JMX 的配置。如果是 Windows 平台的需要修改启动文件 zkServer.cmd,如果是 Linux 平台则需要修改启动文件 zkServer.sh。Windows 平台修改 zkServer.cmd在 call %JAVA% 这一行中加入以下配置:# 对 jmx 开放的端口,要注意避免和其它端口冲突"-Dcom.sun.management.jmxremote.port=21810"# 关闭 ssl"-Dcom.sun.management.jmxremote.ssl=false"# 关闭身份验证"-Dcom.sun.management.jmxremote.authenticate=false"zkServer.cmd 完整的配置如下:@echo offREM Licensed to the Apache Software Foundation (ASF) under one or moreREM contributor license agreements. See the NOTICE file distributed withREM this work for additional information regarding copyright ownership.REM The ASF licenses this file to You under the Apache License, Version 2.0REM (the "License"); you may not use this file except in compliance withREM the License. You may obtain a copy of the License atREMREM http://www.apache.org/licenses/LICENSE-2.0REMREM Unless required by applicable law or agreed to in writing, softwareREM distributed under the License is distributed on an "AS IS" BASIS,REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.REM See the License for the specific language governing permissions andREM limitations under the License.setlocalcall "%~dp0zkEnv.cmd"set ZOOMAIN=org.apache.zookeeper.server.quorum.QuorumPeerMainset ZOO_LOG_FILE=zookeeper-%USERNAME%-server-%COMPUTERNAME%.logecho oncall %JAVA% "-Dcom.sun.management.jmxremote.port=21810" "-Dcom.sun.management.jmxremote.ssl=false" "-Dcom.sun.management.jmxremote.authenticate=false" "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%" "-Dzookeeper.log.file=%ZOO_LOG_FILE%" "-XX:+HeapDumpOnOutOfMemoryError" "-XX:OnOutOfMemoryError=cmd /c taskkill /pid %%%%p /t /f" -cp "%CLASSPATH%" %ZOOMAIN% "%ZOOCFG%" %*endlocal Linux 平台修改 zkServer.sh在第一个 ZOOMAIN 中添加以下配置:# 关闭仅本地连接-Dcom.sun.management.jmxremote.local.only=false# zookeeper地址-Djava.rmi.server.hostname=127.0.0.1 # 对 jmx 开放的端口,要注意避免和其它端口冲突-Dcom.sun.management.jmxremote.port=21810 # 关闭 ssl-Dcom.sun.management.jmxremote.ssl=false # 关闭身份验证-Dcom.sun.management.jmxremote.authenticate=false # 开启日志-Dzookeeper.jmx.log4j.disable=true添加完毕后第一个 ZOOMAIN 配置如下:ZOOMAIN="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.port=21810 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dzookeeper.jmx.log4j.disable=true org.apache.zookeeper.server.quorum.QuorumPeerMain"配置完启动文件,我们就可以重启 Zookeeper 服务端,打开 JConsole 界面,输入 Zookeeper 地址和开放的 JMX 端口,然后就能监控 Zookeeper 的 Java 进程了。除了使用 JConsole 来监控 Zookeeper 进程的运行状态之外,我们还可以使用 Zookeeper 提供的四字监控命令来查看Zookeeper 进程的运行状态,那么接下来我们就来学习 Zookeeper 的四字监控命令。
- 过渡动画和帧动画的区别 零基础学习性能优化及帧动画必学技能
writing相关搜索
-
w3cshool
w3c标准
w3c菜鸟
w3c验证
walk
wall
warn
web
web py
web service
web services
webbrowser
webgl
webmaster
webservices
webservice教程
webservice接口
webservice调用
websocket
webview