javaNIO 学习笔记(三)
Java NIO Buffer
缓冲区,可以通过channel
将数据写入缓冲区,也可以从通道中读取数据到缓冲区
缓冲区本质上是一个内存块,您可以将数据写入其中,然后再读取数据。这个内存块包装在一个NIO缓冲区对象中,该对象提供了一组方法,使使用内存块变得更容易。
使用缓冲区读取和写数据一般分为下面四个步骤:
- 将数据写入缓冲区
- 调用
flip()
方法 - 从缓冲区读取数据
- 调用
clear()
或者compact()
当数据写入数据到缓冲区时,缓冲区会记录对应数据量的信息(这个需要看缓冲区大小和数据量大小)。
数据读取后需要使用flip()方法切换缓冲区的读写模式(换个角度可以理解为存取模式)。在读取模式下,缓可以通过相应的方法将数据从缓冲区中读取出来。读取完数据后需要将缓冲区清空。这样缓冲区就会再次进入写入模式。(这里调用的方式就是
clear
和compact
,clear
是清空,compact
则是清理掉已读取的数据,未读取的数据则放入缓冲区头)
使用的例子上一篇学习笔记已经写了。
我们来看下buffer
的工作模式。buffer
有三个主要属性
-
capacity(buffer容量):这是
buffer
的大小即容量。在调用buffer.allocate()指定大小即是这个属性 -
position(读取模式的位置,在读写模式中表现不一样)
-
limit(读写模式中表现也不一样)
在写入模式下,position
初始值为0.当一个字节,没写入一个字节数字都会将position
+1.limit
初始值为capacity
。在读取模式下,会将limit
位置设置在```position位置放置为0,此时读取数据是从
position到limit
。看下clear
和flip
的源码大概就可以理解了
public final Buffer clear() {
position = 0; //设置当前下标为0
limit = capacity; //设置写越界位置与和Buffer容量相同
mark = -1; //取消标记
return this;
}
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
buffer的主要类型如下:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- MappedByteBuffer
可以看到,这些缓冲区类型表示不同的数据类型。换句话说,它们允许您将缓冲区中的字节改为char、short、int、long、float或double。
学习下buffer
的几个常见方法:
// 可以通过调用Buffer.mark()方法标记缓冲区中的给定位置。然后,您可以通过调用Buffer.reset()方法将位置重置回标记的位置。
public final Buffer mark() {
mark = position;
return this;
}
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
-
equals()和compareTo ()
可以使用equals()和compareTo()比较两个缓冲区。
equals ()
两个缓冲区是相等的,如果:
它们是相同类型的(字节、char、int等)。
它们在缓冲区中有相同数量的剩余字节、字符等。
所有剩余的字节、字符等都是相等的。
可以看到,equals只比较缓冲区的一部分,而不是其中的每个元素。实际上,它只是比较缓冲区中剩余的元素。
compareTo ()
方法比较两个缓冲区的剩余元素(字节,字符等),用于排序例程。一个缓冲区被认为比另一个缓冲区“小”,如果:
第一个元素等于另一个缓冲区中对应的元素,小于另一个缓冲区中的元素。
所有的元素都是相等的,但是第一个缓冲区比第二个缓冲区早耗尽元素(它的元素更少)。
Java NIO Scatter / Gather
Java NIO 提供了内置功能 Scatter/Gather。可以将数据从一个通道写入多个缓冲区。通道也可以从多个缓冲区收集数据。在需要分别处理传输数据的各个部分的情况下,Scatter/Gather非常有用。例如,如果消息由消息头和消息体组成,则可以将消息头和消息体保存在单独的缓冲区中。这样做可以更容易分别使用标题和主体。
package jniolearn;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @Author: jimmy
* @Date: 2020/6/14 14:50
* @Description:
*/
public class NioScatter {
public static void main(String[] args) throws IOException {
// 创建一个rw模式的随机文件
RandomAccessFile randomAccessFile =new RandomAccessFile("D:\\nioFile.txt", "rw");
// 获取fileChinnel
FileChannel fileChannel = randomAccessFile.getChannel();
// 1、分配缓冲区
ByteBuffer header = ByteBuffer.allocate(10);
ByteBuffer body = ByteBuffer.allocate(128);
// 缓冲数组
ByteBuffer[] bufferArray = {header, body};
// 2、将数组读入到数组
fileChannel.read(bufferArray);
// Buffer切换模式之前,即处于写模式下,打印Buffer,查看position, limit, capacity属性
System.out.println(header.toString());
System.out.println(body.toString());
// 3、切换模式
header.flip();
body.flip();
// 4、获取数组
System.out.println("header:");
while (header.hasRemaining()) {
System.out.print((char) header.get());
}
System.out.println("\nbody:");
while (body.hasRemaining()) {
System.out.print((char) body.get());
}
header.clear();
body.clear();
fileChannel.close();
}
}
// 返回结果
java.nio.HeapByteBuffer[pos=10 lim=10 cap=10]
java.nio.HeapByteBuffer[pos=24 lim=128 cap=128]
header:
Hi, I am j
body:
immy;
be happy everyday
read
按照buffer
数组中的顺序将从channel中读取的数据写入到buffer
,当一个buffer
被写满后,channel
紧接着向另一个buffer
中写。Scattering Reads
在移动下一个buffer
前,必须填满当前的buffer
,这也意味着它不适用于动态消息。这样若要是想在消息中使用那么就必须规定好每一段的字节长度,并且按照这个规定严格执行,不能存在偏差。不然就会出现部分信息少读或者多读的情况。
package jniolearn;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @Author: jimmy
* @Date: 2020/6/14 15:07
* @Description:
*/
public class NioGather {
public static void main(String[] args) throws IOException {
RandomAccessFile randomAccessFile =new RandomAccessFile("D:\\gaterFile.txt", "rw");
// 获取fileChinnel
FileChannel fileChannel = randomAccessFile.getChannel();
// 1、分配缓冲区
ByteBuffer header = ByteBuffer.allocate(32);
ByteBuffer body = ByteBuffer.allocate(128);
// 2、放入数据
header.put("hi, weather ?".getBytes());
header.put("body:raining".getBytes());
// 3、存入buffer数组
ByteBuffer[] bufferArray = {header, body};
System.out.println(header.toString());
System.out.println(body.toString());
header.flip();
body.flip();
fileChannel.write(bufferArray);
fileChannel.close();
}
}
查看文件信息:hi, weather ?body:raining
write
方法同样是按照buffer
数组的顺序,将数据写入到channel
,注意只有position和limit之间的数据才会被写入。因此,如果一个buffer的容量为128byte,但是仅仅包含14byte的数据,那么这14byte的数据将被写入到channel
中。和Scattering Reads
相反,Gathering Writes
能较好的处理动态消息。
Java NIO Channel to Channel Transfers
Java NIO还提供了一种通道间传输数据的方法,FileChannel
类有一个transferTo()
和一个transferFrom()
方法
---- transferFrom ----
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
toChannel.transferFrom(fromChannel, position, count);
---- transferTo ----
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
fromChannel.transferTo(position, count, toChannel);
小结下前面几篇学习的内容:
-
如何使用
buffer
- 需要先分配一个缓冲区(我这里理解为初始化一个指定大小的缓冲区)使用
ByteBuffer.allocate()
- 读则是从
channel read
,写则是先要将数据放入缓冲区,然偶使用channel write
把数据写入通道 - 注意
buffer
的模式切换使用flip
方法。 - 另外还有
clear
compact
rewind
等方法
- 需要先分配一个缓冲区(我这里理解为初始化一个指定大小的缓冲区)使用
-
buffer
的几个主要属性- capacity 缓冲区大小(比如缓冲区长度48,则capacity也是48.但是这里要注意capacity下表是从0开始,所以这个可以理解为内存溢出位)
- position 在读模式的时候初始值为0,读数据的时候会逐步增加最大能读到capacity -1。切换到写模式的时候position为0
- limit 读模式的时候默认为capacity。写模式的时候,会将limit设置为读模式的position的位置。
-
scatter
和gather
- 可以理解为分散收集,一个是读,一个是写。这个是内置的。就是一个
channel
和多个buffer
的读写。
- 可以理解为分散收集,一个是读,一个是写。这个是内置的。就是一个
-
channel
如何获取- 可以直接使用open方法,fileChannel则还可以通过类
RandomAccessFile
的实例getChannel
- 可以直接使用open方法,fileChannel则还可以通过类