NIO

来源:互联网 发布:天意网络魔域一条龙 编辑:程序博客网 时间:2024/06/05 06:53

NIO最重要的组件是Buffer与Channel。Buffer是一个抽象类,原生类型都有一个Buffer(int,char,float,Double等)

NIO的Buffer提供了一个可以直接访问物理内存的类DirectBuffer,DirectBuffer继承自ByteBuffer。普通的ByteBuffer在JVM堆上分空间,收到最大堆的限制。但DirectBuffer直接分配在物理内存中。DirectBuffer替代了传统ByteBuffer的“内核缓冲”,更接近系统底层,故读写更快。但是开辟与销毁的时间交传统buffer更长。GC只记录了堆空间内存的回收,但DirectBuffer占用的空间不在堆中,故GC信息不可见。(Java程序性能优化 P118)

System.arraycopy是native的函数,数组复制效率最高。

Object的clone方法可以绕过对象的construct,但是在默认情况时,clone是浅拷贝。

对于一些工具类,应该使用static方法,较实例方法更快,因为缺少了多态的虚表;static方法也不需要生成实例。调用更易。

标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。

所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 

JAVA NIO中的一些主要Channel的实现:

FileChannel从文件中读写数据。

DatagramChannel UDP

SocketChannel   TCP

ServerSocketChannel监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。

Java NIO的Channel通道类似流,但又有些不同:(双向,异步,带buffer)

既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。

通道可以异步地读写。

通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。

 

使用Buffer读写数据一般遵循以下四个步骤:

写入数据到Buffer

调用flip()方法:切换模式,flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。

从Buffer中读取数据

调用clear()方法或者compact()方法,清空,或清空已读

当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。在读模式下,可以读取之前写入到buffer的所有数据。

一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()或compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

从通道读取字节到ByteBuffer。当这个方法调用返回时,你不知道你所需的所有数据是否在缓冲区内。你所知道的是,该缓冲区包含一些字节。

若buffer需多次被读入不同的channel,就需rewind()方法

Buffer.rewind()将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)。

 

scatter/gather:

scatter:

ByteBuffer header =ByteBuffer.allocate(128);

ByteBuffer body   = ByteBuffer.allocate(1024);

ByteBuffer[] bufferArray = { header, body};

channel.read(bufferArray);

 

gather:

ByteBuffer header =ByteBuffer.allocate(128);

ByteBuffer body   = ByteBuffer.allocate(1024);

//write data into buffers

ByteBuffer[] bufferArray = { header, body};

channel.write(bufferArray);

 

在channel中,使用transferTo,transferFrom 可以将一个通道的数据传送至另一个通道。NIO复制

RandomAccessFile fromFile = newRandomAccessFile("fromFile.txt", "rw");

FileChannel      fromChannel = fromFile.getChannel();

RandomAccessFile toFile = newRandomAccessFile("toFile.txt", "rw");

FileChannel      toChannel = toFile.getChannel();

long position = 0;

long count = fromChannel.size();

toChannel.transferFrom(position, count,fromChannel);

 

selector:

用单个线程来处理多个Channels的好处是,线程之间上下文切换的开销很大。

为了将Channel和Selector配合使用,必须将channel注册到selector上。通过SelectableChannel.register()方法来实现,如下:

channel.configureBlocking(false);

SelectionKey key =channel.register(selector,         Selectionkey.OP_READ);

Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。

注意register()方法的第二个参数。这是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:连接,接受,读写

Connect

Accept

Read

Write

 

close():用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。

 

 

FileChannel:

FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。

通过使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例。

FileChannel实例的size()方法将返回该实例所关联文件的大小;

使用FileChannel.truncate()方法截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除。channel.truncate(1024);

FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。

 

RandomAccessFile:

RandomAccessFile是用来访问那些保存数据记录的文件的,你就可以用seek( )方法来访问记录,并进行读写了。RandomAccessFile不属于InputStream和OutputStream类系的。

只有RandomAccessFile才有seek搜寻方法,而这个方法也只适用于文件。

RandomAccessFile的绝大多数功能,但不是全部,已经被JDK 1.4的nio的"内存映射文件(memory-mapped files)"给取代了,应该用"内存映射文件"来代替RandomAccessFile了。

原创粉丝点击