目录
与流式的IO不同,NIO是基于块的,以块为单位处理数据。NIO中最为重要的两个组件,通道(channel)和缓冲(buffer)。缓冲是一块连续的内存块,是NIO读写数据的中转地。通道表示缓冲数据的源头或目的地,用于向缓冲读取或者写入数据,是访问缓冲的接口。
Buffer缓冲区
重要参数
3个重要参数:position、capacity、limit。
各参数含义:
参数 | 写模式 | 读模式 |
---|---|---|
position(位置) | 当前缓冲区的位置,将从position的下一个位置写数据 | 当前缓冲区读取的位置,将从此位置后,读取数据 |
capacity(容量) | 缓冲区的总容量上限 | 缓冲区的总容量上限 |
limit(限制) | 缓冲区的实际上限,总是小于等于容量,通常情况下和容量相等 | 代表可读取的总容量,和上次写入的数据量相等 |
相关操作
创建Buffer
- allocate方法从堆中分配缓冲区
ByteBuffer buffer = ByteBuffer.allocate(100);
- 从一个既有数组中创建缓冲区
byte[] arr = new byte[100];
ByteBuffer buffer = ByteBuffer.wrap(arr);
重置和清空缓冲区
rewind(),为提取Buffer的有效数据作准备
/**
* Rewinds this buffer. The position is set to zero and the mark is
* discarded.
*
* <p> Invoke this method before a sequence of channel-write or <i>get</i>
* operations, assuming that the limit has already been set
* appropriately. For example:
*
* <blockquote><pre>
* out.write(buf); // Write remaining data
* buf.rewind(); // Rewind buffer
* buf.get(array); // Copy data into array</pre></blockquote>
*
* @return This buffer
*/
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
clear(),为重新写Buffer作准备
/**
* Clears this buffer. The position is set to zero, the limit is set to
* the capacity, and the mark is discarded.
*
* <p> Invoke this method before using a sequence of channel-read or
* <i>put</i> operations to fill this buffer. For example:
*
* <blockquote><pre>
* buf.clear(); // Prepare buffer for reading
* in.read(buf); // Read data</pre></blockquote>
*
* <p> This method does not actually erase the data in the buffer, but it
* is named as if it did because it will most often be used in situations
* in which that might as well be the case. </p>
*
* @return This buffer
*/
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
flip(),通常在读写转换时使用
/**
* Flips this buffer. The limit is set to the current position and then
* the position is set to zero. If the mark is defined then it is
* discarded.
*
* <p> After a sequence of channel-read or <i>put</i> operations, invoke
* this method to prepare for a sequence of channel-write or relative
* <i>get</i> operations. For example:
*
* <blockquote><pre>
* buf.put(magic); // Prepend header
* in.read(buf); // Read data into rest of buffer
* buf.flip(); // Flip buffer
* out.write(buf); // Write header + data to channel</pre></blockquote>
*
* <p> This method is often used in conjunction with the {@link
* java.nio.ByteBuffer#compact compact} method when transferring data from
* one place to another. </p>
*
* @return This buffer
*/
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
读写缓冲区
public byte get();
public ByteBuffer put(byte b);
public byte get(int index);
public ByteBuffer put(int index, byte b);
public ByteBuffer get(byte[] dst);
……
标志缓冲区
mark,相当于书签,使用reset恢复到mark位置。
/**
* Sets this buffer's mark at its position.
*
* @return This buffer
*/
public final Buffer mark() {
mark = position;
return this;
}
复制缓冲区
复制缓冲区是指以原缓冲区为基础,生成一个一样的新缓冲区。新的缓冲区与原缓冲区共享相同的内存数据。并且,对任意一方的数据改动都是相互可见的,维护各自的position、limit和mark
/**
* Creates a new byte buffer that shares this buffer's content.
*
* <p> The content of the new buffer will be that of this buffer. Changes
* to this buffer's content will be visible in the new buffer, and vice
* versa; the two buffers' position, limit, and mark values will be
* independent.
*
* <p> The new buffer's capacity, limit, position, and mark values will be
* identical to those of this buffer. The new buffer will be direct if,
* and only if, this buffer is direct, and it will be read-only if, and
* only if, this buffer is read-only. </p>
*
* @return The new byte buffer
*/
public ByteBuffer duplicate()
duplicate示例
ByteBuffer aBuffer = ByteBuffer.allocate(15);
for (int i = 0; i < 10; i++) {
aBuffer.put((byte)i);
}
ByteBuffer bBuffer = aBuffer.duplicate();
System.out.println(aBuffer);
System.out.println(bBuffer);
bBuffer.flip();
System.out.println(aBuffer);
System.out.println(bBuffer);
bBuffer.put((byte)100);
System.out.println(aBuffer.get(0));
System.out.println(bBuffer.get(0));
Output:
java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
java.nio.HeapByteBuffer[pos=0 lim=10 cap=15]
100
100
只读缓冲区
使用缓冲区对象的asReadOnlyBuffer方法得到一个与当前缓冲区一致的,并且共享内存数据的只读缓冲区。只读缓冲区对于数据安全非常有用。
ByteBuffer buffer = ByteBuffer.allocate(15);
for (int i = 0; i < 10; i++) {
buffer.put((byte)i);
}
ByteBuffer readOnly = buffer.asReadOnlyBuffer();
readOnly.flip();
while (readOnly.hasRemaining()){
System.out.print(readOnly.get() + " ");
}
System.out.println();
buffer.put(2,(byte)20);
readOnly.flip();
while (readOnly.hasRemaining()){
System.out.print(readOnly.get() + " ");
}
Output:
0 1 2 3 4 5 6 7 8 9
0 1 20 3 4 5 6 7 8 9
readOnly.put(2,(byte)20);
Exception in thread "main" java.nio.ReadOnlyBufferException
at java.nio.HeapByteBufferR.put(HeapByteBufferR.java:181)
at Main.main(Main.java:21)
文件映射内存
wanghaodeMacBook-Pro:~ wanghao$ pwd
/Users/wanghao
wanghaodeMacBook-Pro:~ wanghao$ cat mapfile.txt
hello world
RandomAccessFile raf = new RandomAccessFile("/Users/wanghao/mapfile.txt", "rw");
FileChannel channel = raf.getChannel();
//将文件映射到内存中
MappedByteBuffer mbb = channel.map(FileChannel.MapMode.READ_WRITE, 0, raf.length());
while (mbb.hasRemaining()){
System.out.print((char)mbb.get());
}
mbb.put(2, (byte)98); //修改文件
raf.close();
Output:
hello world
wanghaodeMacBook-Pro:~ wanghao$ cat mapfile.txt
heblo world
Scattering和Gathering
scattering是将数据读入一组buffer中,gathering是将数据写入一组buffer中。通过ScatteringByteChannel和GatheringByteChannel接口提供相关操作。
public interface ScatteringByteChannel
extends ReadableByteChannel
{
public long read(ByteBuffer[] dsts, int offset, int length)
throws IOException;
public long read(ByteBuffer[] dsts) throws IOException;
}
public interface GatheringByteChannel
extends WritableByteChannel
{
public long write(ByteBuffer[] srcs, int offset, int length)
throws IOException;
public long write(ByteBuffer[] srcs) throws IOException;
}
在散射读取中,通道依次填充每个缓冲区。填充一个后,就开始填充下一个。从某种意义上,缓冲区数组就是一个大的缓冲区。
散射/聚集IO对于处理结构化的数据非常有用。预先构造号大小合适的Buffer对象。
聚集写入示例
ByteBuffer headBuffer = ByteBuffer.wrap("head".getBytes("utf-8"));
ByteBuffer contentBuffer = ByteBuffer.wrap("content".getBytes("utf-8"));
ByteBuffer[] buffers = new ByteBuffer[]{headBuffer, contentBuffer};
File file = new File("/Users/wanghao/sg.txt");
if(!file.exists()){
file.createNewFile();
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
FileChannel fileChannel = fileOutputStream.getChannel();
fileChannel.write(buffers);
fileOutputStream.close();
wanghaodeMacBook-Pro:~ wanghao$ cat sg.txt
headcontent
散射读取示例:
ByteBuffer headBuffer = ByteBuffer.allocate(4);
ByteBuffer contentBuffer = ByteBuffer.allocate(7);
ByteBuffer[] buffers = new ByteBuffer[]{headBuffer, contentBuffer};
File file = new File("/Users/wanghao/sg.txt");
FileInputStream fileInputStream = new FileInputStream(file);
FileChannel fileChannel = fileInputStream.getChannel();
fileChannel.read(buffers);
System.out.println(new String(buffers[0].array(), "utf-8"));
System.out.println(new String(buffers[1].array(), "utf-8"));
fileInputStream.close();
Output:
head
content
MapperByteBuffer性能对比
分别使用IO流、ByteBuffer和MappedByteBuffer测试文件读写耗时。
使用IO流的方式:
long start = System.currentTimeMillis();
DataOutputStream dataOutputStream = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream("/Users/wanghao/io.txt")));
for (int i = 0; i < 10000000; i++) {
dataOutputStream.writeInt(i);
}
if(dataOutputStream != null){
dataOutputStream.close();
}
System.out.println("write cost time:" + (System.currentTimeMillis() -start));
start = System.currentTimeMillis();
DataInputStream dataInputStream = new DataInputStream(
new BufferedInputStream(new FileInputStream("/Users/wanghao/io.txt")));
for (int i = 0; i < 10000000; i++) {
dataInputStream.readInt();
}
if(dataInputStream != null){
dataInputStream.close();
}
System.out.println("read cost time:" + (System.currentTimeMillis() -start));
Output:
write cost time:1030
read cost time:657
使用Buffer
public static void main(String[] args) throws Exception{
long start = System.currentTimeMillis();
FileOutputStream fileOutputStream = new FileOutputStream(new File("/Users/wanghao/buffer.txt"));
FileChannel fileChannel = fileOutputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(10000000*4);
for (int i = 0; i < 10000000; i++) {
buffer.put(int2byte(i));
}
buffer.flip();
fileChannel.write(buffer);
System.out.println("write cost time:" + (System.currentTimeMillis() -start));
start = System.currentTimeMillis();
FileInputStream fileInputStream = new FileInputStream(new File("/Users/wanghao/buffer.txt"));
FileChannel fileChannel1 = fileInputStream.getChannel();
ByteBuffer buffer1 = ByteBuffer.allocate(10000000*4);
fileChannel1.read(buffer1);
buffer1.flip();
while (buffer1.hasRemaining()){
byte2int(buffer1.get(), buffer1.get(), buffer1.get(), buffer1.get());
}
System.out.println("read cost time:" + (System.currentTimeMillis() -start));
}
public static byte[] int2byte(int res){
byte[] targets = new byte[4];
targets[3] = (byte)(res & 0xff);
targets[2] = (byte)((res >> 8) & 0xff);
targets[1] = (byte)((res >> 16) & 0xff);
targets[0] = (byte)((res >> 24) & 0xff);
return targets;
}
public static int byte2int(byte b1,byte b2, byte b3,byte b4){
return ((b1 & 0xff) << 24) | ((b2 & 0xff) << 16) | ((b3 & 0xff) << 8) | (b4 & 0xff);
}
Output:
write cost time:531
read cost time:74
使用MappedByteBuffer,将文件直接映射到内存
long start = System.currentTimeMillis();
FileChannel fileChannel = new RandomAccessFile("/Users/wanghao/mbp.txt","rw").getChannel();
IntBuffer intBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 10000000*4).asIntBuffer();
for (int i = 0; i < 10000000; i++) {
intBuffer.put(i);
}
if(fileChannel != null){
fileChannel.close();
}
System.out.println("write cost time:" + (System.currentTimeMillis() -start));
start = System.currentTimeMillis();
FileChannel fileChannel1 = new FileInputStream("/Users/wanghao/mbp.txt").getChannel();
IntBuffer intBuffer1 = fileChannel1.map(FileChannel.MapMode.READ_ONLY, 0, 10000000*4).asIntBuffer();
while (intBuffer1.hasRemaining()){
intBuffer1.get();
}
if(fileChannel1 != null){
fileChannel1.close();
}
System.out.println("read cost time:" + (System.currentTimeMillis() -start));
Output:
write cost time:139
read cost time:41
耗时/io | Stream | Buffer | MappedByteBuffer |
---|---|---|---|
写 | 1030 | 531 | 139 |
读 | 657 | 74 | 41 |
直接内存
DirectBuffer,直接访问系统物理内存,不占用堆空间。普通的Buffer内存在JVM堆空间上分配。
申请方法:ByteBuffer.allocateDirect(capacity)
访问性能
使用DirectBuffer
ByteBuffer buffer = ByteBuffer.allocateDirect(500);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
for (int j = 0; j < 100; j++) {
buffer.putInt(j);
}
buffer.flip();
for (int j = 0; j < 100; j++) {
buffer.getInt();
}
buffer.clear();
}
System.out.println("cost time:" + (System.currentTimeMillis() -start));
Output:
cost time:282
使用普通Buffer
ByteBuffer buffer = ByteBuffer.allocate(500);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
for (int j = 0; j < 100; j++) {
buffer.putInt(j);
}
buffer.flip();
for (int j = 0; j < 100; j++) {
buffer.getInt();
}
buffer.clear();
}
System.out.println("cost time:" + (System.currentTimeMillis() -start));
Output:
cost time:467
通过对比,DirectBuffer比普通Buffer速度快了近1倍。
创建销毁性能
DirectBuffer的创建和销毁比普通Buffer慢