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



1.Buffer简介

JAVA buffer推荐大小_JAVA buffer推荐大小

缓冲区成员:

private int mark = -1;//一个备忘位置,调用mark()方法时,mark = position;调用reset()方法时,position=mask;
 private int position = 0;//位置,下一个要被读取或写的元素的索引,位置会自动由相应的get(),put()函数更新。
 private int limit;//上界,缓冲区第一个不能被读取或写的元素。或者说缓冲区中现存元素的计数,或者代表有效数据的末端
 private int capacity;//容量,在缓冲区被创建时设定,且永远不能被改变
 long address;
 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



2.创建Buffer

    Buffer以及其子类都无法直接new,而必须把通过他们提供的工厂方法来创建。通常有两种方式:

1、allocate

CharBuffer charBuffer = CharBuffer.allocate (100);
CharBuffer charBuffer = CharBuffer.allocate (100);

将在堆上分配一个可以存储100个字符的数组作为backing store。

2、wrap,包装一个已有的数组:

char [] myArray = new char [100];
CharBuffer charbuffer = CharBuffer.wrap (myArray);
char [] myArray = new char [100];
CharBuffer charbuffer = CharBuffer.wrap (myArray);

注意:这样的方式创建的Buffer,将不会在堆上创建新的数组,而是直接利用myArray做backing store,这意味着任何对myArray或者buffer的修改都将影响到buffer或者myArray。可以通过public final boolean hasArray( )方法来判断是否拥有一个数组,通过array()方法取得这个数组。



3.操作buffer

JAVA buffer推荐大小_数组_02

1、填充:

现在将“Hello”填充到buffer:

b.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'0');
b.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'0');

JAVA buffer推荐大小_数据_03

2、修改:

将原来的“Hello”,修改为“Mellow”

b.put(0,(byte)'M').put((byte)'w');
b.put(0,(byte)'M').put((byte)'w');

3、翻转:

我们已经写满了缓冲区,现在我们必须将其清空,我们想把这个缓冲区传递给一个通道,以使内容全部被写出,我们可以人工实现代码

b.limit(b.position()).position(0);
b.limit(b.position()).position(0);

或者

b.flip();
b.flip();

flip()一般用于翻转缓冲区,翻转后的效果图为:翻转后的缓冲区无法写入,只能读取

注意:flip()通常用于read状态切换到write状态,或者write状态切换到read状态

int count = buffer.hasRemaining();
for (int i = 0; i<count; i++) { 
    myByteArray [i] = buffer.get( ); 
}
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()
buffer.compact()


调用compact()方法是丢弃已经释放掉的数据,保留未释放的数据,并使缓冲区对重新填充容量准备就绪

压缩前的缓冲区:

压缩后的缓冲区:

标记:使缓冲区能记住一个位置并在之后返回

调用mark()时,将当前位置作为标记

调用reset()时,返回到之前标记的地方

注意:

1,rewind( ),clear( ),flip( )总是丢弃标记

2,如果新设定的值小于当前的值,调用limit( )和position( )带有索引参数的版本会抛弃标记

执行下面代码后:设定标记为2

buffer.position(2).mark().position(4);
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();
//缓冲区复制
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();
//缓冲区分割
char [] myBuff = new char[100];
CharBuffer cb = CharBuffer.wrap(myBuff);
cb.position(12).limit(21);
CharBuffer sliceBuffer = cb.slice();