JDK NIO编程
我们首先需要澄清一个概念:NIO到底是什么的简称?有人称之为New I/O,因为它相对于之前的I/O类库是新增的,所以被称为New I/O,这是它的官方叫法。但是,由于之前老的I/O类库是阻塞I/O,New I/O类库的目标就是要让Java支持非阻塞I/O,所以,更多的人喜欢称之为非阻塞I/O(Non-block I/O),由于非阻塞I/O更能够体现NIO的特点。
与Socket类和ServerSocket类相对应,NIO也提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现。这两种新增的通道支持阻塞和非阻塞两种模式。阻塞模式使用非常简单,但是性能和可靠性都不好,非阻塞模式则正好相反。开发人员一般可以根据自己的需要来选择合适的模式,一般来说,低负载、低并发的应用程序可以选择同步阻塞I/O以降低编程复杂度,但是对于高负载、高并发的网络应用,需要使用NIO的非阻塞模式进行开发。
NIO类库简介
新的输入/输出(NIO)库是在JDK 1.4中引入的。NIO弥补了原来同步阻塞I/O的不足,它在标准Java代码中提供了高速的、面向块的I/O。通过定义包含数据的类,以及通过以块的形式处理这些数据,NIO不用使用本机代码就可以利用低级优化,这是原来的I/O包所无法做到的。
1.缓冲区Buffer
我们首先介绍缓冲区(Buffer)的概念,Buffer是一个对象,它包含一些要写入或者要读出的数据。在NIO类库中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中,可以将数据直接写入或者将数据直接读到Stream对象中。
在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。
缓冲区实质上是一个数组。通常它是一个字节数组(ByteBuffer),也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问以及维护读写位置(limit)等信息。
最常用的缓冲区是ByteBuffer,一个ByteBuffer提供了一组功能用于操作byte数组。除了ByteBuffer,还有其他的一些缓冲区,事实上,每一种Java基本类型(除了Boolean类型)都对应有一种缓冲区,具体如下:
ByteBuffer:字节缓冲区
CharBuffer:字符缓冲区
ShortBuffer:短整型缓冲区
IntBuffer:整形缓冲区
LongBuffer:长整形缓冲区
FloatBuffer:浮点型缓冲区
DoubleBuffer:双精度浮点型缓冲区
每一个Buffer类都是Buffer接口的一个子实例。除了ByteBuffer,每一个 Buffer类都有完全一样的操作,只是它们所处理的数据类型不一样。因为大多数标准I/O操作都使用ByteBuffer,所以它除了具有一般缓冲区的操作之外还提供一些特有的操作,方便网络读写。
2.通道Channel
Channel是一个通道,可以通过它读取和写入数据,它就像自来水管一样,网络数据通过Channel读取和写入。通道与流的不同之处在于通道是双向的,流只是在一个方向上移动(一个流必须是InputStream或者OutputStream的子类),而且通道可以用于读、写或者同时用于读写。
因为Channel是全双工的,所以它可以比流更好地映射底层操作系统的API。特别是在UNIX网络编程模型中,底层操作系统的通道都是全双工的,同时支持读写操作。
自顶向下看,前三层主要是Channel接口,用于定义它的功能,后面是一些具体的功能类(抽象类),从类图可以看出,实际上Channel可以分为两大类:分别是用于网络读写的SelectableChannel和用于文件操作的FileChannel。
3.多路复用器Selector
多路复用器Selector,它是Java NIO编程的基础,熟练地掌握Selector对于掌握NIO编程至关重要。多路复用器提供选择已经就绪的任务的能力。简单来讲,Selector会不断地轮询注册在其上的Channel,如果某个Channel上面有新的TCP连接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。
一个多路复用器Selector可以同时轮询多个Channel,由于JDK使用了epoll()代替传统的select实现,所以它并没有最大连接句柄1024/2048的限制。这也就意味着只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端,这确实是个非常巨大的进步。
NIO服务端序列图
下面,我们对NIO服务端的主要创建过程进行讲解和说明,作为NIO的基础入门,我们将忽略掉一些在生产环境中部署所需要的一些特性和功能。
步骤一:打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道,代码示例如下。
ServerSocketChannel acceptorSvr = ServerSocketChannel.open();
步骤二:绑定监听端口,设置连接为非阻塞模式,示例代码如下。
acceptorSvr.socket().bind(new InetSocketAddress(InetAddress.getByName(“IP”), port));
acceptorSvr.configureBlocking(false);
步骤三:创建Reactor线程,创建多路复用器并启动线程,代码如下。
Selector selector = Selector.open();
new Thread(new ReactorTask()).start();
步骤四:将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听ACCEPT事件,代码如下。
SelectionKey key = acceptorSvr.register( selector, SelectionKey.OP_ACCEPT, ioHandler);
步骤五:多路复用器在线程run方法的无限循环体内轮询准备就绪的Key,代码如下。
int num = selector.select();
Set selectedKeys = selector.selectedKeys();
Iterator it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey)it.next();
// ... deal with I/O event ...
}
步骤六:多路复用器监听到有新的客户端接入,处理新的接入请求,完成TCP三次握手,建立物理链路,代码示例如下。
SocketChannel channel = svrChannel.accept();
步骤七:设置客户端链路为非阻塞模式,示例代码如下。
channel.configureBlocking(false);
channel.socket().setReuseAddress(true);
......
步骤八:将新接入的客户端连接注册到Reactor线程的多路复用器上,监听读操作,用来读取客户端发送的网络消息,代码如下。
SelectionKey key = socketChannel.register( selector, SelectionKey.OP_READ, ioHandler);
步骤九:异步读取客户端请求消息到缓冲区,示例代码如下。
int readNumber = channel.read(receivedBuffer);
步骤十:对ByteBuffer进行编解码,如果有半包消息指针reset,继续读取后续的报文,将解码成功的消息封装成Task,投递到业务线程池中,进行业务逻辑编排。
Object message = null;
while(buffer.hasRemain())
{
byteBuffer.mark();
Object message = decode(byteBuffer);
if (message == null)
{
byteBuffer.reset();
break;
}
messageList.add(message );
}
if (!byteBuffer.hasRemain())
byteBuffer.clear();
else
byteBuffer.compact();
if (messageList != null & !messageList.isEmpty())
{
for(Object messageE : messageList)
handlerTask(messageE);
}
步骤十一:将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发送给客户端,示例代码如下。
socketChannel.write(buffer);
注意:如果发送区TCP缓冲区满,会导致写半包,此时,需要注册监听写操作位,循环写,直到整包消息写入TCP缓冲区。
服务端代码示例:
import java.io.IOException;public class TimeServer {public static void main(String[] args) throws IOException {int port = 8080;if (args != null && args.length > 0) {try {port = Integer.valueOf(args[0]);} catch (NumberFormatException e) {// 采用默认值 }}//MultiplexerTimeServer的多路复用类,它是个一个独立的线程,//负责轮询多路复用器Selector,可以处理多个客户端的并发接入。MultiplexerTimeServer timeServer = new MultiplexerTimeServer(port);new Thread (timeServer, "NIO-MultiplexerTimeServer-001").start();} }import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set;public class MultiplexerTimeServer implements Runnable {private Selector selector;private ServerSocketChannel servChannel;private volatile boolean stop;//在构造方法中进行资源初始化,创建多路复用器Selector、ServerSocketChannel,对Channel和TCP参数进行配置。//例如,将ServerSocketChannel设置为异步非阻塞模式,它的backlog设置为1024。//系统资源初始化成功后,将ServerSocket Channel注册到Selector,监听SelectionKey.OP_ACCEPT操作位;如果资源初始化失败(例如端口被占用),则退出。public MultiplexerTimeServer(int port) {try {selector = Selector.open();servChannel = ServerSocketChannel.open();servChannel.configureBlocking(false);servChannel.socket().bind(new InetSocketAddress(port), 1024);servChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("The time server is start in port : " + port);} catch (IOException e) {e.printStackTrace();System.exit(1);}}public void stop() {this.stop = true;}@Overridepublic void run() {while (!stop) {try {//在线程的run方法的while循环体中循环遍历selector,它的休眠时间为1s,//无论是否有读写等事件发生,selector每隔1s都被唤醒一次,selector也提供了一个无参的select方法。//当有处于就绪状态的Channel时,selector将返回就绪状态的Channel的SelectionKey集合,//通过对就绪状态的Channel集合进行迭代,可以进行网络的异步读写操作。selector.select(1000);Set selectedKeys = selector.selectedKeys();Iterator it = selectedKeys.iterator();SelectionKey key = null;while (it.hasNext()) {key = (SelectionKey) it.next();it.remove();try {handleInput(key);//这里可以用线程池启线程去单独处理客户端的请求业务} catch (Exception e) {if (key != null) {key.cancel();if (key.channel() != null)key.channel().close();}}}} catch (Throwable t) {t.printStackTrace();}}// 多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源if (selector != null)try {selector.close();} catch (IOException e) {e.printStackTrace();}}private void handleInput(SelectionKey key) throws IOException {if (key.isValid()) {//根据SelectionKey的操作位进行判断即可获知网络事件的类型,if (key.isAcceptable()) {//通过ServerSocketChannel的accept接收客户端的连接请求并创建SocketChannel实例,//完成上述操作后,相当于完成了TCP的三次握手,TCP物理链路正式建立。//注意,我们需要将新创建的SocketChannel设置为异步非阻塞,同时也可以对其TCP参数进行设置,//例如TCP接收和发送缓冲区的大小等,作为入门的例子,没有进行额外的参数设置。ServerSocketChannel ssc = (ServerSocketChannel) key.channel();SocketChannel sc = ssc.accept();sc.configureBlocking(false);// Add the new connection to the selector sc.register(selector, SelectionKey.OP_READ);}if (key.isReadable()) {//首先创建一个ByteBuffer,由于我们事先无法得知客户端发送的码流大小,//作为例程,我们开辟一个1M的缓冲区。然后调用SocketChannel的read方法读取请求码流。//注意,由于我们已经将SocketChannel设置为异步非阻塞模式,因此它的read是非阻塞的。//使用返回值进行判断,看读取到的字节数SocketChannel sc = (SocketChannel) key.channel();ByteBuffer readBuffer = ByteBuffer.allocate(1024);int readBytes = sc.read(readBuffer);//返回值有以下三种可能的结果//返回值大于0:读到了字节,对字节进行编解码;//返回值等于0:没有读取到字节,属于正常场景,忽略;//返回值为-1:链路已经关闭,需要关闭SocketChannel,释放资源。if (readBytes > 0) {//当读取到码流以后,我们进行解码,首先对readBuffer进行flip操作,//它的作用是将缓冲区当前的limit设置为position,position设置为0,用于后续对缓冲区的读取操作。//然后根据缓冲区可读的字节个数创建字节数组,//调用ByteBuffer的get操作将缓冲区可读的字节数组复制到新创建的字节数组中,//最后调用字符串的构造函数创建请求消息体并打印。//如果请求指令是"QUERY TIME ORDER"则把服务器的当前时间编码后返回给客户端 readBuffer.flip();byte[] bytes = new byte[readBuffer.remaining()];readBuffer.get(bytes);String body = new String(bytes, "UTF-8");System.out.println("The time server receive order : "+ body);String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new java.util.Date(System.currentTimeMillis()).toString(): "BAD ORDER";//异步发送应答消息给客户端 doWrite(sc, currentTime);} else if (readBytes < 0) {// 对端链路关闭 key.cancel();sc.close();} else; // 读到0字节,忽略 }}}private void doWrite(SocketChannel channel, String response)throws IOException {//首先将字符串编码成字节数组,根据字节数组的容量创建ByteBuffer,//调用ByteBuffer的put操作将字节数组复制到缓冲区中,然后对缓冲区进行flip操作,//最后调用SocketChannel的write方法将缓冲区中的字节数组发送出去。//需要指出的是,由于SocketChannel是异步非阻塞的,它并不保证一次能够把需要发送的字节数组发送完,//此时会出现“写半包”问题,我们需要注册写操作,不断轮询Selector将没有发送完的ByteBuffer发送完毕,//可以通过ByteBuffer的hasRemain()方法判断消息是否发送完成。//此处仅仅是个简单的入门级例程,没有演示如何处理“写半包”场景。if (response != null && response.trim().length() > 0) {byte[] bytes = response.getBytes();ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);writeBuffer.put(bytes);writeBuffer.flip();channel.write(writeBuffer);}} }
NIO客户端序列图
步骤一:打开SocketChannel,绑定客户端本地地址(可选,默认系统会随机分配一个可用的本地地址),示例代码如下。
SocketChannel clientChannel = SocketChannel.open();
步骤二:设置SocketChannel为非阻塞模式,同时设置客户端连接的TCP参数,示例代码如下。
clientChannel.configureBlocking(false);
socket.setReuseAddress(true);
socket.setReceiveBufferSize(BUFFER_SIZE);
socket.setSendBufferSize(BUFFER_SIZE);
步骤三:异步连接服务端,示例代码如下。
boolean connected=clientChannel.connect(new InetSocketAddress(“ip”,port));
步骤四:判断是否连接成功,如果连接成功,则直接注册读状态位到多路复用器中,如果当前没有连接成功(异步连接,返回false,说明客户端已经发送sync包,服务端没有返回ack包,物理链路还没有建立),示例代码如下。
if (connected)
{
clientChannel.register( selector, SelectionKey.OP_READ, ioHandler);
}
else
{
clientChannel.register( selector, SelectionKey.OP_CONNECT, ioHandler);
}
步骤五:向Reactor线程的多路复用器注册OP_CONNECT状态位,监听服务端的TCP ACK应答,示例代码如下。
clientChannel.register( selector, SelectionKey.OP_CONNECT, ioHandler);
步骤六:创建Reactor线程,创建多路复用器并启动线程,代码如下。
Selector selector = Selector.open();
new Thread(new ReactorTask()).start();
步骤七:多路复用器在线程run方法的无限循环体内轮询准备就绪的Key,代码如下。
int num = selector.select();
Set selectedKeys = selector.selectedKeys();
Iterator it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey)it.next();
// ... deal with I/O event ...
}
步骤八:接收connect事件进行处理,示例代码如下。
if (key.isConnectable())
handlerConnect();
步骤九:判断连接结果,如果连接成功,注册读事件到多路复用器,示例代码如下。
if (channel.finishConnect())
registerRead();
步骤十:注册读事件到多路复用器,示例代码如下。
clientChannel.register( selector, SelectionKey.OP_READ, ioHandler);
步骤十一:异步读客户端请求消息到缓冲区,示例代码如下。
int readNumber = channel.read(receivedBuffer);
步骤十二:对ByteBuffer进行编解码,如果有半包消息接收缓冲区Reset,继续读取后续的报文,将解码成功的消息封装成Task,投递到业务线程池中,进行业务逻辑编排,示例代码如下。
Object message = null;
while(buffer.hasRemain())
{
byteBuffer.mark();
Object message = decode(byteBuffer);
if (message == null)
{
byteBuffer.reset();
break;
}
messageList.add(message );
}
if (!byteBuffer.hasRemain())
byteBuffer.clear();
else
byteBuffer.compact();
if (messageList != null & !messageList.isEmpty())
{
for(Object messageE : messageList)
handlerTask(messageE);
}
步骤十三:将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发送给客户端,示例代码如下。
socketChannel.write(buffer);
客户端代码示例:
public class TimeClient {public static void main(String[] args) {int port = 8080;new Thread(new TimeClientHandle("127.0.0.1", port), "TimeClient- 001").start();} }import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set;public class TimeClientHandle implements Runnable {private String host;private int port;private Selector selector;private SocketChannel socketChannel;private volatile boolean stop;public TimeClientHandle(String host, int port) {//构造函数用于初始化NIO的多路复用器和SocketChannel对象。//需要注意的是,创建SocketChannel之后,需要将其设置为异步非阻塞模式。//我们可以设置SocketChannel的TCP参数,例如接收和发送的TCP缓冲区大小。this.host = host == null ? "127.0.0.1" : host;this.port = port;try {selector = Selector.open();socketChannel = SocketChannel.open();socketChannel.configureBlocking(false);} catch (IOException e) {e.printStackTrace();System.exit(1);}}@Overridepublic void run() {try {//作为示例,连接是成功的,所以不需要做重连操作,因此将其放到循环之前。 doConnect();} catch (IOException e) {e.printStackTrace();System.exit(1);}while (!stop) {try {//在循环体中轮询多路复用器Selector,当有就绪的Channel时,执行handleInput(key)方法selector.select(1000);Set selectedKeys = selector.selectedKeys();Iterator it = selectedKeys.iterator();SelectionKey key = null;while (it.hasNext()) {key = (SelectionKey) it.next();it.remove();try {handleInput(key);} catch (Exception e) {if (key != null) {key.cancel();if (key.channel() != null)key.channel().close();}}}} catch (Exception e) {e.printStackTrace();System.exit(1);}}//线程退出循环后,我们需要对连接资源进行释放,以实现“优雅退出”.//由于多路复用器上可能注册成千上万的Channel或者pipe,如果一一对这些资源进行释放显然不合适。//因此,JDK底层会自动释放所有跟此多路复用器关联的资源。//多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源if (selector != null)try {selector.close();} catch (IOException e) {e.printStackTrace();}}private void handleInput(SelectionKey key) throws IOException {//我们首先对SelectionKey进行判断,看它处于什么状态。if (key.isValid()) {// 判断是否连接成功SocketChannel sc = (SocketChannel) key.channel();//如果是处于连接状态,说明服务端已经返回ACK应答消息。//这时我们需要对连接结果进行判断,调用SocketChannel的finishConnect()方法,//如果返回值为true,说明客户端连接成功;如果返回值为false或者直接抛出IOException,说明连接失败。//在本例程中,返回值为true,说明连接成功。if (key.isConnectable()) {if (sc.finishConnect()) {//将SocketChannel注册到多路复用器上,注册SelectionKey.OP_READ操作位,//监听网络读操作,然后发送请求消息给服务端。 sc.register(selector, SelectionKey.OP_READ);doWrite(sc);} elseSystem.exit(1);// 连接失败,进程退出 }//客户端是如何读取时间服务器应答消息的。if (key.isReadable()) {//如果客户端接收到了服务端的应答消息,则SocketChannel是可读的,//由于无法事先判断应答码流的大小,我们就预分配1M的接收缓冲区用于读取应答消息,//调用SocketChannel的read()方法进行异步读取操作。由于是异步操作,所以必须对读取的结果进行判断。ByteBuffer readBuffer = ByteBuffer.allocate(1024);int readBytes = sc.read(readBuffer);if (readBytes > 0) {//如果读取到了消息,则对消息进行解码,最后打印结果。执行完成后将stop置为true,线程退出循环。 readBuffer.flip();byte[] bytes = new byte[readBuffer.remaining()];readBuffer.get(bytes);String body = new String(bytes, "UTF-8");System.out.println("Now is : " + body);this.stop = true;} else if (readBytes < 0) {// 对端链路关闭 key.cancel();sc.close();} else; // 读到0字节,忽略 }}}//首先对SocketChannel的connect()操作进行判断,如果连接成功,//则将SocketChannel注册到多路复用器Selector上,注册SelectionKey.OP_READ,//如果没有直接连接成功,则说明服务端没有返回TCP握手应答消息,//但这并不代表连接失败,我们需要将SocketChannel注册到多路复用器Selector上,//注册SelectionKey.OP_CONNECT,当服务端返回TCP syn-ack消息后,//Selector就能够轮询到这个SocketChannel处于连接就绪状态。private void doConnect() throws IOException {// 如果直接连接成功,则注册到多路复用器上,发送请求消息,读应答if (socketChannel.connect(new InetSocketAddress(host, port))) {socketChannel.register(selector, SelectionKey.OP_READ);doWrite(socketChannel);} else {socketChannel.register(selector, SelectionKey.OP_CONNECT);}}//构造请求消息体,然后对其编码,写入到发送缓冲区中,最后调用SocketChannel的write方法进行发送。//由于发送是异步的,所以会存在“半包写”问题。最后通过hasRemaining()方法对发送结果进行判断,//如果缓冲区中的消息全部发送完成,打印"Send order 2 server succeed."private void doWrite(SocketChannel sc) throws IOException {byte[] req = "QUERY TIME ORDER".getBytes();ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);writeBuffer.put(req);writeBuffer.flip();sc.write(writeBuffer);if (!writeBuffer.hasRemaining())System.out.println("Send order 2 server succeed.");} }
我们发现NIO编程难度确实比同步阻塞BIO大很多,我们的NIO例程并没有考虑“半包读”和“半包写”,如果加上这些,代码将会更加复杂。NIO代码既然这么复杂,为什么它的应用却越来越广泛呢,使用NIO编程的优点总结如下。
(1)客户端发起的连接操作是异步的,可以通过在多路复用器注册OP_CONNECT等待后续结果,不需要像之前的客户端那样被同步阻塞。
(2)SocketChannel的读写操作都是异步的,如果没有可读写的数据它不会同步等待,直接返回,这样I/O通信线程就可以处理其他的链路,不需要同步等待这个链路可用。
(3)线程模型的优化:由于JDK的Selector在Linux等主流操作系统上通过epoll实现,它没有连接句柄数的限制(只受限于操作系统的最大句柄数或者对单个进程的句柄限制),这意味着一个Selector线程可以同时处理成千上万个客户端连接,而且性能不会随着客户端的增加而线性下降,因此,它非常适合做高性能、高负载的网络服务器。
JDK1.7升级了NIO类库,升级后的NIO类库被称为NIO2.0,引人注目的是,Java正式提供了异步文件I/O操作,同时提供了与UNIX网络编程事件驱动I/O对应的AIO。
相关文章:

ASP.NET ViewState 初探
Susan Warren Microsoft Corporation 与刚接触 ASP.NET 页面的开发人员交谈时,他们通常向我提出的第一个问题就是:“那个 ViewState 到底是什么?”他们的语气中流露出的那种感觉,就象我来到一家异国情调的餐馆,侍者端…

[emuch.net]MatrixComputations(1-6)
matrixComputation转载于:https://www.cnblogs.com/stoneresearch/archive/2012/06/24/4336678.html
身为面向对象编程、移动计算机之父的他,为何说“计算机革命还没真正到来”?...
作者 | 年素清责编 | 李雪敬出品 | 程序人生(ID:coder_life) 艾伦凯(艾伦 Kay)是近代计算机革命先驱之一,他最早提出了“面向对象编程”的概念,也是“Dynabook”(笔记本电脑的雏形)的最早阐述者…

动态表单构建器——建造者模式
在编写一个弹出框时,它可以包含确定按钮,取消按钮,标题栏,关闭按钮,最小化按钮,内容,最大化按钮等内容,但这些内容在不同的需求下又不是必须存在的,不同的需求需要对这些组件自由组合…

网页素材大宝库:50套非常精美的图标素材
图标对网页设计师来说是宝贵的财富,高质量的图标素材既能为设计师节省时间,又能有很不错的效果。网上的免费图标素材非常多,可以说是琳琅满目,那些充满分享精神的设计师们把素材共享出来,让更多的人可以使用他们的优秀…
AI 面试“泛滥”的时代,HR该如何甄别真假“AI”?
作者 | 无缺编辑 | 王晓曼出品 | AI科技大本营(ID:rgznai100)在互联网、物联网蓬勃发展的中国,AI在商业化领域的运用,大家并不陌生。但AI在人力资源领域的发展前景如何?这是一个复杂且相对陌生的话题,也没有…

C语言里面%2d 意思
看看下面的说明就知道啦:修饰符 格式说明 意义 int a1;int b1234;double c1.2345678;printf("%2d\n",a);printf("%2d\n",a);printf("%4d\n",a);printf("%2d\n",b);printf("%2d\n",b);printf("%4d\n",b…

DatagridView自动充满屏幕,并能指定某列宽度
1、要使datagridview正好充满屏幕,设置其AutoSizeColumnsMode属性为fill 2、 同时,我们想要某列宽点,某列窄点,在AutoSizeColumnsMode属性为fill的前提下,设置FillWeight 属性 FillWeight :获取或设置一个值…

在网页中动态的生成一个gif图片
作者: love.net 大家知道股票网站的K线图是动态生成的定时刷新PHP 就有动态生成图片的功能 那么怎样用asp.net在网页中动态的生成一个图片呢? 下面我要举的例子是动态的生成一个图片显示当前时间 namespace Wmj { using System; using System.Drawing; u…
11项重大发布!百度大脑语言与知识技术峰会全程高能
AI正在向更深层次进化,语言与知识技术的重要性愈加凸显。8月25日,以“掌握知识、理解语言、拥有智能”为主题的百度大脑语言与知识技术峰会重磅开启,百度CTO王海峰发表主旨演讲,解读百度语言与知识技术的发展历程与最新成果&#…

MySQL 5.5.35 单机多实例配置详解
一、前言 二、概述 三、环境准备 四、安装MySQL 5.5.35 五、新建支持多实例的配置文件(我这里配置的是四个实例) 六、初始化多实例数据库 七、提供管理脚本 mysqld_multi.server 八、整体备份方便后续迁移 九、管理MySQL多实例 十、登录MySQL多实例 十一…

ASP.NET超凡的代码控制
crystal译yesky 适应性 肯定的是,通常任何一个全新的技术,在市场渗透都会花费一些时间。微软正在开始让ASP和IIS平台通过行业验证,以便让其作为其它网络服务器之外可以供选择的平台 对于在其基本构架上的如此巨大的改变,是很难说服…
老码农:这是我见过最操蛋的代码,切勿模仿!
作为一名老码农,我的心这次凉透了!事情起因很简单:我在全国最大ZZ的同性组织某Hub上浏览时候,发现这样的一条信息:Python 超过 C、JS 薪酬排行第一(最大招聘网站Indeed.com数据)噗,9…

QTP时间格式的转换(YYYYMMDDHHMMSS)
之前查了好多资料都是这样写的: sendTime year(sendTime) & right( "00 " & month(sendTime),2) & right( "00 " & day(sendTime),2) & right( "00 " & hour(sendTime),2) &…

Selenium2+python自动化25-js处理日历控件(修改readonly属性)
前言 日历控件是web网站上经常会遇到的一个场景,有些输入框是可以直接输入日期的,有些不能,以我们经常抢票的12306网站为例,详细讲解如何解决日历控件为readonly属性的问题。 基本思路:先用js去掉readonly属性…

ASP.NET强大的性能
crystal译 yesky 一个程序,速度是一件非常令人渴望的东西。一旦代码开始工作,接下来你就得尽可能的让它运作的快些,再快些, 在ASP中你只有尽可能拧干你的代码,以至于不得不将他们移植到一个仅有很少一点性能的部件中。…

POJ-1753 Flip Game 枚举 状态压缩
刚开始做这题时总是在想应该用何种的策略来进行翻装,最后还是没有想出来~~~ 这题过的代码的思路是用在考虑到每个点被翻装的次数只有0次或者是1次,所以对于16个点就只有2^16中请况了。再运用位运算将状态压缩到一个32位…
“半真半假”DeepFake换脸也能精准识别?阿里安全提出全新检测方法
一段包含多个人脸的视频中,攻击者只对一个或者几个人的人脸进行伪造,这种“半真半假”的伪造情况能否被检测识别?近日,阿里安全图灵实验室宣布,其已成功打造出针对这种换脸视频的DeepFake检测技术,阐述该技…

python 定时任务
Python 定时任务 最近学习到了 python 中两种开启定时任务的方法,和大家分享一下心得。 sched.scheduler()threading.Timer()sched 定时任务 使用sched的套路如下: s sched.scheduler(time.time, time.sleep) s.enter(delay, priority, func1, (arg1, a…

思科AP与交换机端口的配置
思科AP与交换机端口的配置。 思科AP可以分IOS AP 和LAP。 1、IOS AP 中如果AP上需要创建多个SSID,连接的交换机端口则需要: switch(config-interfa)# sw mod trunk switch(config-interfa)# sw trunk allow vlan 1,x,x,x (SSID对应的VLAN) 另外注意&…

Namespace(命名空间)的使用
作者:飞刀 关于Namespace(命名空间)的使用常用<% Import Namespace"System.Data" %>,这是在引用M$为我们提供的Namespace,这和ASP不同的,我们贏SP.net必须先引用与我们操作有关的Namespace后才能使用相应的功能。其实说白了ÿ…
“编程能力差!90%输在这点上”谷歌AI专家:其实都是瞎努力!
最近几年,我看过市面上很多 Python和人工智能的教程,基本都在这样讲:先介绍Python基本语法、dict、tuple 等基本库的使用,最后学习机器学习、深度学习的常用算法......但我与Google人工智能开发专家彭靖田老师沟通后发现ÿ…

NAS存储对称和非对称结构之前的区别概述
传统的系统利用紧耦合对称架构,这种架构的设计旨在解决HPC(高性能计算、超级运算)问题,现在其正在向外扩展成为云存储从而满足快速呈现的市场需求。下一代架构已经采用了松弛耦合非对称架构,集中元数据和控制操作&…

Lucene:基于Java的全文检索引擎简介(转载)
Lucene是一个基于Java的全文索引工具包。基于Java的全文索引引擎Lucene简介:关于作者和Lucene的历史全文检索的实现:Luene全文索引和数据库索引的比较中文切分词机制简介:基于词库和自动切分词算法的比较具体的安装和使用简介:系统…
昨天,我用 Python 写了一个婚介模型
作者 | 天元浪子来源 | CSDN(ID:CSDNnews)先声明一下:本文纯属七夕应景娱乐之作。如果有人因为遵循本模型提出的择偶理论而导致失恋或单身,除了同情,我不能补偿更多。在中国的传统节日里,七夕可…

WCF服务一:WCF服务简介
一、回顾开发历史: 软件架构的设计经历了:从面向对象程序,到面向组件程序设计,再到面向服务程序设计。这三种方式都致力于同一个目标:封装和重用。 面向对象程序设计:类封装功能并提供代码重用。 面向组件程…
MIT 更新最大自然灾害图像数据集,囊括 19 种灾害事件
作者 | 神经小兮来源 | HyperAI超神经(ID:HyperAI)内容提要:麻省理工学院在最近 ECCV 2020 上提交的一篇论文中,发布了一套自然灾害图像数据集。这是迄今为止规模最大、质量最高的自然灾害卫星图像数据集。2020 年&…

DataBind数据核心
作者:飞刀 这一节主要是要讲DataBind,这个在ASP.net中是很重要的东东,几乎所有的控件都需要它来控制数据的操作。也可以说是ASP.net的数据核心。 我们先来看一个简单的例子: <% Page Language"C#" %> <% …

convertViewsetTag方法的一点理解
转自:http://blog.163.com/freemanls126/blog/static/164585061201171210504864/ 前言 首先我们要知道setTag方法是干什么的,SDK解释为 Tags Unlike IDs, tags are not used to identify views. Tags are essentially an extra piece of information tha…

iOS使用Security.framework进行RSA 加密解密签名和验证签名
iOS 上 Security.framework为我们提供了安全方面相关的api; Security框架提供的RSA在iOS上使用的一些小结 支持的RSA keySize 大小有:512,768,1024,2048位支持的RSA 填充方式有三种:NOPadding,PKCS1,OAEP 三…