@Test
void test() throws IOException {
/*Java NIO
* 字节缓冲区:ByteBuffer (内部使用字节数组存储数据)
* 构建一个指定长度的ByteBuffer*/
ByteBuffer buffer = ByteBuffer.allocate(1024);
/*其中Buffer中的重要属性
private int position = 0; //指针,表示当前正在操作数据的索引位置
private int limit; // 当前可操作的数据量,即数组中实际的元素个数
private int capacity; //记录构建时传递的参数作为字节数组的长度
private int mark; //记录指针前一次的位置
* */
/*一些用于操作数据的方法*/
/*追加一个字节*/
buffer.put((byte) 11);
/*追加一个字节数组*/
buffer.put(new byte[]{1,2,3});
/*追加其他字节缓冲区*/
buffer.put(ByteBuffer.allocate(10));
/*在索引0的位置插入一个字节20*/
buffer.put(0,(byte)20);
/*添加一个字节数组,从该数组的索引1到索引2的数据*/
buffer.put(new byte[]{1,1,2},1,2);
/*追加一个字符,中文字符在不同的字符集下占用的字节长度并不统一*/
buffer.putChar('啊');
/*在索引为1的位置添加一个字符s*/
buffer.putChar(1,'s');
/*创建一个新的字节缓冲区,长度为当前缓冲区空闲的字节数量*/
ByteBuffer buffer1 = buffer.slice();
/*返回剩余字节组成的字节数组*/
byte[] bytes = buffer.array();
/*切换模式,刚被创建的缓冲区默认是写模式*/
buffer.flip();
/*可重复读(将指针还原到读模式的初始位置)*/
buffer.rewind();
/*读取一个字节,同时指针移动到下一个位置*/
buffer.get();
/*读取指定索引位置的字节*/
buffer.get(0);
/*读取数据到指定字节数组中,同时指针位移到当前位置+该数组.length的位置
* 如果没有到达尾部的话*/
byte[] bys = new byte[3];
buffer.get(bys);
/*清空缓冲区,但数据依然存在
* 只是处于一个被遗忘状态*/
buffer.clear();
/*通过mark记录的前一次位置使指针回到本次更改前的状态,但不可回到更早的状态
* 若指针未被移动过则会抛出异常*/
// buffer.reset();
/*通道,Channel
* 类似于流,但他本身不具备直接访问数据的功能,需要与缓冲区配合
* 几个通道的实现类
* FileChannel: 操作本地文件
* SocketChannel: TCP
* ServerSocketChannel: TCP
* DatagramChannel : UDP
* */
/*FileChannel
* JDK 7之后在这些结构中提供了获取Channel的方法
* FileInputStream
* FileOutputStream
* RandomAccessFile*/
File file = new File("src/main/resources/banner.txt");
/*创建一个字节缓冲区(实际上就是一个被封装的byte[])
直接将长度设置为文件长度,不用循环了
* 如果文件长度超出int型最大值呢?
* 那这个文件得有2个G了*/
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
/*将流放在try(..)中,可以自动关闭
* 准确来说是Closeable类型的变量*/
try(FileInputStream fis = new FileInputStream(file);
/*连通通道*/
FileChannel fc = fis.getChannel();
FileOutputStream fos = new FileOutputStream(new File("D:/a"),true);
FileChannel fc2 = fos.getChannel();
)
{
/*将数据读入缓冲区*/
fc.read(byteBuffer);
/*获取包含数据的字节数组*/
byte[] bytes1 = byteBuffer.array();
System.out.println(new String(bytes1));
/*将读取的内容复制到另一个文件中*/
byteBuffer.flip();
fc2.write(byteBuffer);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
/*使用直接缓冲区
* 直接与内存中的文件映射交互,效率极高*/
/*open方法:打开或创建一个文件并返回一个文件通道
* 参数1: 获取指定文件Path
* 参数2:可变形参,枚举了构建通道时一些可能的选项
* READ:打开读取访问
* WRITE:打开写入访问
* APPEND:追加写入
* TRUNCATE_EXISTING:如果文件打开用于写访问则截断长度为0,读访问则被忽略
* CREATE:如果文件不存在则创建,否则此选项无效果
* CREATE_NEW:直接创建文件,如果当前目录存在同名文件则抛出异常
* SPARSE:与CREATE_NEW配合使用,表明创建文件是稀疏的,若文件系统不支持稀疏文件则此选项无效
* SYNC:对文件内容或元数据的每次操作都同步到底层存储设备
* DSYNC:仅对文件内容的操作同步到底层存储设备
*/
/*Path :表示文件系统中的一个路径,他可以是相对的也可以是绝对的*/
Path path = Paths.get("D:/a");
/*toFile方法可以返回一个对应的File*/
ByteBuffer buf = ByteBuffer.allocate((int)path.toFile().length());
try (FileChannel in = FileChannel.open(path, StandardOpenOption.READ);
FileChannel out = FileChannel.open(Paths.get("D:/b"),StandardOpenOption.APPEND,
StandardOpenOption.CREATE)
)
{
in.read(buf);
String s = new String(buf.array());
System.out.println(s);
buf.flip();
out.write(buf);
} catch (IOException e) {
e.printStackTrace();
}
/*内存映射文件*/
FileChannel fc = FileChannel.open(Paths.get("D:/b"), StandardOpenOption.READ);
/*注意,此时获取的通道必须兼备读和写两种模式,
因为通过通道获取的映射文件操作模式只有读写而没有单独的写模式*/
FileChannel fc2 = FileChannel.open(Paths.get("D:/c"),StandardOpenOption.READ,
StandardOpenOption.WRITE,
StandardOpenOption.CREATE);
/*使用map方法获取通道连通的映射文件
* 参数1,MapMode,枚举类,FileChannel中的一个静态内部类,提供了一些操作映射文件时的模式
* READ_ONLY : 只读模式
* READ_WRITE : 读写模式
* PRIVATE : 私有映射 复制=写入的模式
* 参数2:要操作文件的起始位置
* 参数3:映射区域的大小,该参数类型为long但实际传递的值不可超过Integer.MAX_VALUE*/
/*size():获取通道的大小(即连通文件的大小)*/
/*该方法会返回一个包含文件信息的缓冲区,缓冲区只能为ByteBuffer*/
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY,0,fc.size());
MappedByteBuffer mbb2 = fc2.map(FileChannel.MapMode.READ_WRITE,0,fc.size());
/*现在,所有的操作便不依赖于道,读写操作均在物理内存中进行*/
mbb2.put(mbb);
/*效率高到可能程序还没结束运行就已经完成了写入
* 但操作一些大型文件时(一个缓冲区装不下,因为JVM会限制数组的长度,
* 实际长度达不到Integer.MAX_VALUE)对内存的开销是巨大的*/
/*通道间的数据传输(同样属于直接缓冲区)*/
FileChannel fc3 = FileChannel.open(Paths.get("D:/d"),StandardOpenOption.WRITE,
StandardOpenOption.READ,
StandardOpenOption.CREATE);
/*transferTo,将通道的内容传递给另一个通道
* 参数1:传递数据的起始位置
* 参数2:传递的数据大小
* 参数3:目标通道*/
fc2.transferTo(0,fc2.size(),fc3);
/*transferFrom,与transferTo类似,但操作逻辑相反
* 该方法是将有数据的通道作为参数,接收之*/
fc3.transferFrom(fc2,0,fc2.size());
/*编码与解码*/
/*创建指定字符集的CharSet*/
Charset charset = Charset.forName("GBK");
/*获取一个CharSet的解码器*/
CharsetDecoder cd = charset.newDecoder();
/*获取一个CharSet的编码器*/
CharsetEncoder ce = charset.newEncoder();
/*为指定到的字符缓冲区编码*/
CharBuffer charBuffer = CharBuffer.allocate(3);
charBuffer.put("啊喔鹅");
charBuffer.flip();
ByteBuffer ceBuffer = ce.encode(charBuffer);
System.out.println(charBuffer.array());
/*此时解码时不使用GBK字符集就会出现乱码*/
System.out.println(new String(ceBuffer.array(),0,ceBuffer.limit(),"GBK"));
/*最后,别忘了关闭通道*/
fc.close();
fc2.close();
fc3.close();
}
控制台: