1.缓冲区(Buffer)
Java NIO中数据的读写操作始终是与缓冲区相关联的.数据是从通道读入缓冲区,从缓冲区写入到通道中的。缓冲区是定长的,基本上它只是一个列表,它的所有元素都是基本数据类型.
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。
Buffer的基本用法
ByteBuffer是最常用的缓冲区,它提供了读写其他数据类型的方法,且信道的读写方法只接收ByteBuffer.因此ByteBuffer的用法是有必要牢固掌握的.
1.创建ByteBuffer
1.1 使用allocate()静态方法
ByteBuffer buffer=ByteBuffer.allocate(1024);
以上方法将创建一个容量为1024字节的ByteBuffer,如果发现创建的缓冲区容量太小,唯一的选择就是重新创建一个大小合适的缓冲区.
1.2使用put()方法来填充ByteBuffer
通过put()方法可以给ByteBuffer填充一个或者多个字节或者基本数据类型的值.
public void readAllData(byte[] bytes){
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(bytes);
}
- 1
- 2
- 3
- 4
1.3 通过包装一个已有的数组来创建
如下,通过包装的方法创建的缓冲区保留了被包装数组内保存的数据.
ByteBuffer buffer=ByteBuffer.wrap(byteArray);
如果要将一个字符串存入ByteBuffer,可以如下操作:
ByetBuffer buffer = ByteBuffer.warp(“Hello World NIO”.getBytes());
2.回绕缓冲区与清除缓冲区
buffer.flip();
buffer.clear();
- 1
- 2
一旦调用read()来告知FileChannel向ByteBuffer存储字节,就必须调用缓存器上的flip()方法,这个方法用来将缓冲区准备为数据传出状态,执行以上方法后,输出通道会从数据的开头而不是末尾开始.回绕保持缓冲区中的数据不变,只是准备写入而不是读取。通俗的理解就是让它做好被读取的准备(看起来很麻烦,但是为了最大的传输速度)。如果我们打算使用缓冲器执行进一步的read()操作,我们也必须得调用clear()来为每个read()做准备。
在前面的一片文章中我们只是使用ByteBuffer来读取指定字节的大小的文件,现在我们通过ByteBuffer来读取一个文本的全部内容并写入到另一个文件中:
try {
FileChannel inputFileChannel = new FileInputStream("/home/wang/bigchat.sql").getChannel();
//newBigChat.sql可以不存在。系统会自动创建一个
FileChannel outputFileChannel = new FileOutputStream("/home/wang/newBigchat.sql").getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int len = 0;
//当read返回-1时表示读到了文件末尾
while ((len=inputFileChannel.read(byteBuffer))!=-1){
byteBuffer.flip();//准备好写
outputFileChannel.write(byteBuffer);
byteBuffer.clear();//准备好读
}
} catch (IOException e) {
e.printStackTrace();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
flip()则是准备缓冲器以便它的信息可以由write()提取。write()之后数据依然在缓冲器中,接着clear()方法则对所有内部指针重新安排,以便缓存器在另一个read()操作期间能够做好接受数据的准备。
其实上面的代码并不是最理想,我们可以使用transferTo()和transferFrom()则允许我们将一个通道和另一个通道直接相连:
try {
FileChannel inputFileChannel = new FileInputStream("/home/wang/bigchat.sql").getChannel();
//newBigChat.sql可以不存在。系统会自动创建一个
FileChannel outputFileChannel = new FileOutputStream("/home/wang/newBigchat1.sql").getChannel();
inputFileChannel.transferTo(0,inputFileChannel.size(),outputFileChannel);
outputFileChannel.transferFrom(inputFileChannel,0,inputFileChannel.size());
}catch (IOException e){}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
这种方法了解了解即可。
ByteBuffer俗称缓冲器, 是将数据移进移出通道的唯一方式,并且我们只能创建一个独立的基本类型缓冲器。ByteBuffer 中存放的是字节,如果要将它们转换成字符流则需要使用 Charset , Charset 是字符编码,它提供了把字节流转换成字符流( 解码 ) 和将字符流转换成字节流 ( 编码) 的方法。
private byte[] getBytes (char[] chars) {//将字符转为字节(编码)
Charset cs = Charset.forName ("UTF-8");
CharBuffer cb = CharBuffer.allocate (chars.length);
cb.put (chars);
cb.flip ();
ByteBuffer bb = cs.encode (cb)
return bb.array();
}
private char[] getChars (byte[] bytes) {//将字节转为字符(解码)
Charset cs = Charset.forName ("UTF-8");
ByteBuffer bb = ByteBuffer.allocate (bytes.length);
bb.put (bytes);
bb.flip ();
CharBuffer cb = cs.decode (bb);
return cb.array();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
通过ByteBuffer读取文件后转化为CharBuffer并输出:
FileChannel fc = new FileInputStream(PATH).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(SIZE);
fc.read(buffer);
//重值ByteBuffer中的数组,调用方法后输出通道会从数据的开头而不是末尾开始
buffer.flip();
//使用UTF-8编码为CharBuffer
Charset charset = Charset.forName("UTF-8");
CharBuffer cb = charset.decode(buffer);
System.out.println(cb);//正确输出数据
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2.通道(Channel)
Java NIO的通道比缓冲区好理解多了,它就类似于流,但又有些不同:
- 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
- 通道可以异步地读写。
- 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。
正如之前所说,从通道读取数据到缓冲区,从缓冲区写入数据到通道。
前面我们通过三个基础流调用getChannel()方法获取了一个Channel的实现FileChannel。下面我们来看NIO中的其它Channel的实现:
- FileChannel:从文件中读写数据。
- DatagramChannel:能通过UDP读写网络中的数据。
- SocketChannel:能通过TCP读写网络中的数据。
- ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。