NIO包(java.nio.*)引入了四个关键的抽象数据类型,它们共同解决传统的I/O类中的一些问题。
1. Buffer:它是包含数据且用于读写的线形表结构。其中还提供了一个特殊类用于内存映射文件的I/O操作。
2. Charset:它提供Unicode字符串影射到字节序列以及逆影射的操作。
3. Channels:包含socket,file和pipe三种管道,它实际上是双向交流的通道。
4. Selector:它将多元异步I/O操作集中到一个或多个线程中(它可以被看成是Unix中select()函数或Win32中WaitForSingleEvent()函数的面向对象版本)。
其中,Channel(通道)和Buffer(缓冲)是NIO中的两个核心对象
Channel是对传统的输入/输出系统的模拟,在新IO系统中,所有的数据都需要通过通道传输:Channel与传统的InputStream,OutputStream最大的区别在于它提供了一个map()方法,通过该map()方法可以直接将“一块数据”映射到内存中。如果说传统的输入/输出系统是面向流的处理,则新IO则是面向块的处理。
Buffer可以被理解成一个容器,它的本质是一个数组,发送到Channel中的所有对象都必须首先发到Buffer中,而从Channel中读取的数据也必须先放到Buffer中。
推荐:
java NIO与传统IO区别,click here
一个nio学习博客,click here
缓冲区成员:
private int mark = -1;//一个备忘位置,调用mark()方法时,mark = position;调用reset()方法时,position=mask; private int position = 0;//位置,下一个要被读取或写的元素的索引,位置会自动由相应的get(),put()函数更新。 private int limit;//上界,缓冲区第一个不能被读取或写的元素。或者说缓冲区中现存元素的计数,或者代表有效数据的末端 private int capacity;//容量,在缓冲区被创建时设定,且永远不能被改变 long address;
buffer中遵循这样的关系:
0<= mark <= position <=limit <=capacity
Buffer以及其子类都无法直接new,而必须把通过他们提供的工厂方法来创建。通常有两种方式:
1、allocate
CharBuffer charBuffer = CharBuffer.allocate (100);
将在堆上分配一个可以存储100个字符的数组作为backing store。
2、wrap,包装一个已有的数组:
char [] myArray = new char [100]; CharBuffer charbuffer = CharBuffer.wrap (myArray);
注意:这样的方式创建的Buffer,将不会在堆上创建新的数组,而是直接利用myArray做backing store,这意味着任何对myArray或者buffer的修改都将影响到buffer或者myArray。可以通过public final boolean hasArray( )方法来判断是否拥有一个数组,通过array()方法取得这个数组。
1、填充:
现在将“Hello”填充到buffer:
b.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'0');
2、修改:
将原来的“Hello”,修改为“Mellow”
b.put(0,(byte)'M').put((byte)'w');
3、翻转:
我们已经写满了缓冲区,现在我们必须将其清空,我们想把这个缓冲区传递给一个通道,以使内容全部被写出,我们可以人工实现代码
b.limit(b.position()).position(0);
或者
b.flip();
flip()一般用于翻转缓冲区,翻转后的效果图为:翻转后的缓冲区无法写入,只能读取
注意:flip()通常用于read状态切换到write状态,或者write状态切换到read状态
int count = buffer.hasRemaining(); for (int i = 0; i<count; i++) { myByteArray [i] = buffer.get( ); }
值得注意的是:缓冲区不是线程安全的,在需要并发操作时,需要先同步
使用b.clear()可以将缓冲区重置为空状态,b.clear() 只是将position置为0,limit设置为capacity,并不真正清空数据
4、压缩缓冲区:
buffer.compact()
调用compact()方法是丢弃已经释放掉的数据,保留未释放的数据,并使缓冲区对重新填充容量准备就绪
压缩前的缓冲区:
压缩后的缓冲区:
标记:使缓冲区能记住一个位置并在之后返回
调用mark()时,将当前位置作为标记
调用reset()时,返回到之前标记的地方
注意:
1,rewind( ),clear( ),flip( )总是丢弃标记
2,如果新设定的值小于当前的值,调用limit( )和position( )带有索引参数的版本会抛弃标记
执行下面代码后:设定标记为2
buffer.position(2).mark().position(4);
如果此时将缓冲区传递给管道,则“ow”将被发送,而且position会前进到6
如果此时调用reset( );则position后退到2,若此时将缓冲区传递给管道,这"llow"将被发送,而且position会前进到6
5、判断相等:
判断两个缓冲区相等的充要条件是:
1,两个对象类型相同
2,两个缓冲区剩余同样数量的元素
3,每个缓冲区被get( )函数返回的数据元素序列必须一致
6、复制缓冲区
//缓冲区复制 CharBuffer buffer = CharBuffer.allocate(10); buffer.position(3).limit(6).mark().position(5); CharBuffer duBuffer = buffer.duplicate(); buffer.clear();
7、分割缓冲区
//缓冲区分割 char [] myBuff = new char[100]; CharBuffer cb = CharBuffer.wrap(myBuff); cb.position(12).limit(21); CharBuffer sliceBuffer = cb.slice();