初探Java NIO

来源:互联网 发布:取消数据流量套餐 编辑:程序博客网 时间:2024/06/05 23:43

[NIO介绍]

javanio是从jdk1.4起引入的.其目的只有一个:提高速度.实际上当我们使用”io包的时候,我们已经在跟nio打交道了.

io包已经用nio重构过,即使不直接使用nio, 也一样能得到性能的提高.

之所以nio能有性能上的提高,是因为nio使用了跟操作系统io很相近的io处理方式:使用信道(channel)和缓冲(buffer)

    我们不妨把buffer比作运煤的小车,channel比成矿井.你不会直接下井把煤块手工地搬上来, 你要用矿车把煤矿一车一车拉出来. 同样,面对nio,你一般不会直接操作channel信道,而是读/buffer.

    需要注意的是, nio的底层特性是面向字节,不是面向字符的.

    nio有四个核心概念:

1.  缓冲区Buffer:在用户和信道之间进行信息传输。是大幅提高IO效率的精髓

2. 信道Channel:通往IO实体的连接

3. 字符集Charset:字符集以及对应的编码器(Encoder)解码器(decoder)一同行使字节向Unicode字符的转化。

4. 选择器Selector:和支持选择器的信道(Selectable Channel)一同构成多路复用的、非阻塞的IO信道(Multiplexed,non-blocking IO),典型例子就是Pipe.SinkChannel(管道信道的写终端),Pipe.SourceChannel(管道信道的读终端),

                  ServerSocketChannel(通往Socket监听者的信道),SocketChannel(通往Socket链路的信道)

     

 

[NIOIO的相互转换]

    由于nio底层是面向字节,三个文件操作的老io:FileInputStream, FileOutputStream,RandomAccessFile能够转换成FileChannel.

    而所有的ReaderWriter,因其是面向字符的,不能转化成Channel. 但是有一个功能类:java.nio.channels.Channels提供了一些静态方法,能够把Reader/Writer转化成Channel.
   
下面给出Thinking in Java上面的一个例子,演示三种访问权限Channel:writable,readable, writable/readable

     

import java.io.*;import java.nio.*;import java.nio.channels.*;public class GetChannel{private static final int BSIZE = 1024;public static void main(String[] args) throws IOException{// 新建并写入文件FileChannel fc = new FileOutputStream("data.txt").getChannel();fc.write(ByteBuffer.wrap("Some Text".getBytes()));fc.close();// 追加新内容fc = new RandomAccessFile("data.txt","rw").getChannel();fc.position(fc.size());//把文件指针放到文件尾. 实现"追加"fc.write(ByteBuffer.wrap("Some more".getBytes()));fc.close();// 读文件fc = new FileInputStream("data.txt").getChannel();ByteBuffer buff = ByteBuffer.allocate(BSIZE);fc.read(buff);buff.flip();while(buff.hasRemaining())System.out.print((char)buff.get());}}

[NIOBuffer用法详解]

    因为我们直接操作的不是Channel而是Buffer,所以Buffer的用法在NIO-IO转换中就显得至关重要。

为了高效利用Buffer内部的byte[]数组,Java语言设计者在Buffer里面定义了四个重要的索引变量(广义指针)markpositionlimitcapacityBuffer的精髓就在这四个指针身上

   

ByteBuffer的用法

成员方法/成员变量

类别

说明

static ByteBuffer

allocate(int size)

构造

申请一个size大小的byte[]数组,并且用ByteBuffer封装起来

例如:ByteBuffer bb = ByteBuffer.allocate(1024);

申请一个1024字节大小的ByteBuffer.

static ByteBuffer

wrap(byte[] src)

把已有的byte数组封装成ByteBuffer。类似于c++move construct.

private int mark

索引

保存某个时刻的position指针的值,通过调用mark()实现;当mark被置为负值时,表示废弃标记。

private int position

A buffer's position is the index of the next element to be read or written.

位置指针。微观上,指向底层字节数组byte[] hb的某个索引位置;宏观上,是ByteBuffer的操作位置,如get()完成后,position指向当前(取出)元素的下一位,put()方法执行完成后,position指向当前(存入)元素的下一位;它是核心位置指针

private int limit

A buffer's limit is the index of the first element that should not be read or written.

也是位置指针,表示待操作数据的界限,它总是和读取或存入操作相关联,limit指针可以被 改变,可以认为limit<=capacity

private int capacity

表示ByteBuffer的总长度/总容量,也即底层字节数组byte[] hb的容量,一般不可变,用于读取

 

注意:mark <= position <= limit <= capacity

public int capacity()

public int limit()

public int position()

getter

返回相应的capacity/limit/position

public Buffer

clear()

 

clear() makes a buffer ready for a new sequence of channel-read or relative put operations: It sets the limit to the capacity and the position to zero.

-------------------------------------------
clear()
为新的信道读取/推入缓冲做准备。

public Buffer

flip()

 

makes a buffer ready for a new sequence of channel-write or relative get operations: It sets the limit to the current position and then sets the position to zero.

-------------------------------------------

socketwrite操作完成后,若需要read刚才write到的数据,则需要在read执行前执行flip(),以重置操作位置指针,保存操作数据的界限,保证read数据准确。

limit=position; position = 0; mark=-1;

public Buffer

rewind()

 

makes a buffer ready for re-reading the data that it already contains: It leaves the limit unchanged and sets the position to zero.

------------------------------------------

flip()相比,只是没有改变limit

public Buffer

reset()

 

Resets this buffer's position to the previously-marked position.

position重新设置为之前标志过的mark

public Buffer

limit(int lim)

setter

limit设置成指定值。limit必须是[0,capacity]区间内的整数。

public void mark()

mark设置为position。作用是保存某时刻的mark

public Buffer

position(int pos)

position设置成指定值。position必须是[0,limit]之间的整数

public int

remaining()

getter

返回(limit-position)

   

/** * Powered By * Thinking in Java 4th edition */import java.nio.*;public class UsingBuffers{private static void symmetricScramble (CharBuffer buffer){while(buffer.hasRemaining()){buffer.mark();char c1 = buffer.get();char c2 = buffer.get();buffer.reset();buffer.put(c2).put(c1);}}public static void main(String[] args){char[] data = "UsingBuffers".toCharArray();ByteBuffer bb = ByteBuffer.allocate(data.length*2);CharBuffer cb = bb.asCharBuffer();cb.put(data);print(cb.rewind().array());symmetricScramble(cb);print(cb.rewind().array());symmetricScramble(cb);print(cb.rewind().array());}}

    这是第一次进入symmetricScramble时缓冲区的状态

    

    第一次执行symmetricScramble的时候,while循环在position==limit的时候终止。当你调用getput之类的方法时,缓冲区的position指针会在方法返回前移动后移。当然你也可以指定getput的操作位置。

    进入while循环的时候,mark()mark设置为position。此时的buffer状态是

     

    两次get()调用之后:

    

    两次put()之后

    

    执行下一轮循环。

    

    这个过程会一直重复直到position到达limit。结果如下

    

    注意到每次打印的时候都要rewind一下。因为如果想要打印整个buffer的内容,你需要把position设置到数组的起点。

    rewind示意图:

     

0 0
原创粉丝点击