FileChannel 读取数据到 Buffer
(读取数据可能会出现中文乱码,如下防止乱码)
public static void main(String[] args) throws IOException {
RandomAccessFile raFile = new RandomAccessFile("d:\\TEST\\666.txt", "rw");
FileChannel fChannel = raFile.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(32);
CharBuffer charBuffer = CharBuffer.allocate(32);
while ((fChannel.read(byteBuffer)) != -1) {read()
//方法返回的 int 值表示了有多少字节被读到了 Buffer 中。如果返回-1,表示到了文件末尾。
byteBuffer.flip();// 切换buffer从读模式到写模式
StandardCharsets.UTF_8.newDecoder().decode(byteBuffer, charBuffer, true);// 以utf8编码转换ByteBuffer到CharBuffer(**防止中文数据中文乱码**)
charBuffer.flip();// 切换buffer从读模式到写模式
while (charBuffer.hasRemaining()) {
System.out.print(charBuffer.get());
}
byteBuffer.compact();//(该方法清除被读取的缓冲区,没有读取的会放在缓冲区首部)
charBuffer.clear();
}
raFile.close();
}
buffer向FileChannel 写数据
public static void main(String[] args) throws IOException {
//打开FileChannel
RandomAccessFile randomAccessFile = new RandomAccessFile("d:\\TEST\\666.txt","rw");
FileChannel channel = randomAccessFile.getChannel();
//创建buffer对象
ByteBuffer buffer = ByteBuffer.allocate(1024);
String newData = "写入数据12346566test";
//写入内容
buffer.put(newData.getBytes());
buffer.flip();
// FileChannel.write()是在 while 循环中调用的。因为无法保证 write()方法一次能向 FileChannel写入多少字节,因此需要重复调用 write()方法,直到 Buffer 中已经没有尚未写入通道的字节。
while (buffer.hasRemaining()) {
channel.write(buffer);
}
//关闭
channel.close();
}
其它方法:
Position
当读取或写入FileChannel时,需要在特定position执行。你可以通过调用position()方法来获得FileChannel的当前position。
你还可以通过调用position(long pos)来设置FileChannel的position。
long pos = channel.position();
channel.position(pos + 66);
如果你设置的position超过了文件的大小,并且尝试从Channel读取数据,则会返回-1代表文件结尾。
如果你设置的position超过了文件的大小,并且尝试往Channel写入数据,文件会自动扩张至能放下position以及写入的数据。这个可能导致"file hole",即磁盘上的物理文件在写入的数据中存在漏洞(即中间有一段完全没有任何数据)。
Size
FileChannel的size()方法返回这个Channel连接的文件大小。如下:
long fileSize = channel.size();
FileChannel Truncate
通过调用FileChannel.truncate()方法,你可以truncate一个文件。当你truncate一个文件,你会把其截断为指定的长度。如下:
channel.truncate(1024);
这个例子将文件截断为1024字节。
Force
FileChannel.force()方法会将Channel里面还未写入的数据全部刷新到磁盘。操作系统可能会将数据缓存在内存里以提升性能,因此我们无法保证你写入Channel的数据都被写到了磁盘,直到你调用force()方法。
force()方法有一个boolean类型的参数,代表是否将文件元数据(比如权限等)也刷新到磁盘。
以下是刷新数据以及元数据到磁盘的例子:
channel.force(true);
通道之间的数据传输:
如果两个通道中有一个是 FileChannel,那你可以直接将数据从一个 channel 传输到
另外一个 channel
transferFrom()
public static void main(String[] args) throws IOException {
//打开FileChannel
RandomAccessFile randomAccessFile = new RandomAccessFile("d:/TEST/666.txt","rw");
RandomAccessFile aFile = new
RandomAccessFile("d:\\TEST\\666.txt", "rw");
FileChannel fromChannel = aFile.getChannel();
RandomAccessFile bFile = new
RandomAccessFile("d:\\TEST\\777.txt", "rw");
FileChannel toChannel = bFile.getChannel();
long position = 0;
long count = fromChannel.size();
toChannel.transferFrom(fromChannel, position, count);
aFile.close();
bFile.close();
System.out.println("完成");
}
方法的输入参数 position 表示从 position 处开始向目标文件写入数据,count 表示最
多传输的字节数。如果源通道的剩余空间小于 count 个字节,则所传输的字节数要小
于请求的字节数。此外要注意,在 SoketChannel 的实现中,SocketChannel 只会传
输此刻准备好的数据(可能不足 count 字节)。因此,SocketChannel 可能不会将请
求的所有数据(count 个字节)全部传输到 FileChannel
transferTo()
transferTo()方法将数据从 FileChannel 传输到其他的 channel 中。
public static void main(String[] args) throws IOException {
RandomAccessFile aFile = new
RandomAccessFile("d:\\TEST\\666.txt", "rw");
FileChannel fromChannel = aFile.getChannel();
RandomAccessFile bFile = new
RandomAccessFile("d:\\TEST\\777.txt", "rw");
FileChannel toChannel = bFile.getChannel();
long position = 0;
long count = fromChannel.size();
fromChannel.transferTo(position, count, toChannel);
aFile.close();
bFile.close();
System.out.println("完成");
}
Scatter/Gather
Java NIO 开始支持 scatter/gather,scatter/gather 用于描述从 Channel 中读取或
者写入到 Channel 的操作。
分散(scatter)从 Channel 中读取是指在读操作时将读取的数据写入多个 buffer 中。
因此,Channel 将从 Channel 中读取的数据“分散(scatter)”到多个 Buffer 中。
聚集(gather)写入 Channel 是指在写操作时将多个 buffer 的数据写入同一个
Channel,因此,Channel 将多个 Buffer 中的数据“聚集(gather)”后发送到
Channel。
scatter / gather 经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头
和消息体组成的消息,你可能会将消息体和消息头分散到不同的 buffer 中,这样你可
以方便的处理消息头和消息体。
Scattering Reads
Scattering Reads 是指数据从一个 channel 读取到多个 buffer 中。如
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);
注意 buffer 首先被插入到数组,然后再将数组作为 channel.read() 的输入参数。
read()方法按照 buffer 在数组中的顺序将从 channel 中读取的数据写入到 buffer,当
一个 buffer 被写满后,channel 紧接着向另一个 buffer 中写。
Scattering Reads 在移动下一个 buffer 前,必须填满当前的 buffer,这也意味着它
不适用于动态消息(译者注:消息大小不固定)。换句话说,如果存在消息头和消息体,
消息头必须完成填充(例如 128byte),Scattering Reads 才能正常工作。
Gathering Writes
Gathering Writes 是指数据从多个 buffer 写入到同一个 channel。如下图描述:
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);
buffers 数组是 write()方法的入参,write()方法会按照 buffer 在数组中的顺序,将数
据写入到 channel,注意只有 position 和 limit 之间的数据才会被写入。因此,如果
一个 buffer 的容量为 128byte,但是仅仅包含 58byte 的数据,那么这 58byte 的数
据将被写入到 channel 中。因此与 Scattering Reads 相反,Gathering Writes 能较
好的处理动态消息。