NIO的骚操作一、FileChannel

定义

在JDK 1.4中定义了一个全新的API,即New IO,与标准的IO API有不同的工作方式,NIO的核心组件包括Channel(通道)、Buffer(缓冲区)、Selectors(选择器)。

传统的IO是基于字节流和字符流操作,是阻塞式,而NIO是基于Channel和Buffer,是非阻塞式,Selector用于监听多个通道的事件。单个线程可以监听多个数据通道。

Channel和Buffer

Channel和IO中的流相似,只不过流是单向的,InputStream只能输入不能输出,OutputStream又只能输出不能做输入,而Channel就厉害了,既可以用输出,又可以输入,又当爹又当妈的。

Channel的主要实现有:
FileChannel: 文件的数据读写
DatagramChannel: UDP的数据读写
SocketChannel: TCP的数据读写,一般客户端实现
ServerSocketChannel: 一般是服务器实现。

FileChannel

FileChannel读取写入依靠ByteBuffer来完成,调用read把数据读到ByteBuffer中,调用write把ByteBuffer中数据写回文件中。

写入一个文件,使用ByteBuffer.wrap把字节数据包装成ByteBuffer,也可以调用Charset类来编码一个字符串,返回ByteBuffer对象。

private static void test0(String path) {
   try {
       FileOutputStream fileOutputStream = new FileOutputStream(path);
       FileChannel channel = fileOutputStream.getChannel();
       
       byte[] bytes ="abc听风逝夜def".getBytes();
       ByteBuffer byteBuffer =ByteBuffer.wrap(bytes);
       channel.write(byteBuffer);
         channel.write(Charset.forName("utf-8").encode("abc听风逝夜def"));
   }catch (IOException e){
        e.toString();
   }
}

读取一个文件,要从FileInputStream获取FileChannel,否则会报java.nio.channels.NonReadableChannelException异常。读取操作就比较有学问了。首先定义一个缓存区,使用read方法读取到缓存区,read()返回读到的大小,如果返回-1,则表示读玩了。通过array()方法获取读到的字节数据。最后要清除缓存区中已读到的数据,准备下一次读。

 try {
     FileInputStream fileInputStream = new FileInputStream(path);
     FileChannel channel = fileInputStream.getChannel();
     ByteBuffer byteBuffer =ByteBuffer.allocate(1024);
     while (channel.read(byteBuffer)>0){
         System.out.println(new String(byteBuffer.array()));
         byteBuffer.clear();
     }
 }catch (IOException e){
     e.toString();
 }

但是上述代码会出现这种情况。
在这里插入图片描述
会有很多空数据被输出,这是因为ByteBuffer大小是1024,而文件大小是18字节,其余的1024-18的地方没有存放东西。解决办法很简单,就是截取ByteBuffer中前N个数据。

private static void test1(String path) {
 try {
     FileInputStream fileInputStream = new FileInputStream(path);
     FileChannel channel = fileInputStream.getChannel();
     ByteBuffer byteBuffer =ByteBuffer.allocate(1024);
     int post=0;
     while (( post=channel.read(byteBuffer))>0){
         byte[] data =new byte[post];
         System.arraycopy(byteBuffer.array(),0,data,0,post);
         System.out.println(new String(data));
         byteBuffer.clear();
     }
 }catch (IOException e){
     e.toString();
 }
}

还有一种骚操作,里面又有一个学问,flip()方法和limit,在下面会说。

private static void test1(String path) {
    try {
     FileInputStream fileInputStream = new FileInputStream(path);
     FileChannel channel = fileInputStream.getChannel();
     ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
     while (channel.read(byteBuffer) > 0) {
         byteBuffer.flip();
         int limit = byteBuffer.limit();
         byte[] datas = new byte[limit];
         byteBuffer.get(datas);
         System.out.println(new String(datas));
         byteBuffer.clear();
     }
 } catch (IOException e) {
     e.toString();
 }

flip()

ByteBuffer有两种模式,一种要写,一种要读。

写的时候,内部的pos属性会随着写入的大小移动,相当于一个指针,如下代码,随着put一个字节,pos会往后移1,但是在putInt的时候,会移动4字节,因为int就是4字节,其他类型也一样,putLong会移动8字节。内部limit=capacity。当put的大小超过limit时,将报错。

 ByteBuffer byteBuffer =ByteBuffer.allocate(100);
 System.out.println(byteBuffer);
 byteBuffer.put("1".getBytes());
 System.out.println(byteBuffer);
 byteBuffer.putInt(1);
  System.out.println(byteBuffer);
  byteBuffer.putLong(1);
  System.out.println(byteBuffer);
java.nio.HeapByteBuffer[pos=0 lim=100 cap=100]
java.nio.HeapByteBuffer[pos=1 lim=100 cap=100]
java.nio.HeapByteBuffer[pos=5 lim=100 cap=100]
java.nio.HeapByteBuffer[pos=13 lim=100 cap=100]

切换读的时候,就是通过flip方法,此时limit会变成写入后的大小,并且pos为0,每get数据的时候,post往下移,下次从这个位置后读取,直到多余limit时就报错。

 ByteBuffer byteBuffer =ByteBuffer.allocate(100);
 System.out.println(byteBuffer);
 byteBuffer.put("1".getBytes());
 System.out.println(byteBuffer);
 byteBuffer.putInt(1);
  System.out.println(byteBuffer);
  byteBuffer.putLong(1);
  System.out.println(byteBuffer);
  byteBuffer.flip();
  System.out.println(byteBuffer);
java.nio.HeapByteBuffer[pos=0 lim=100 cap=100]
java.nio.HeapByteBuffer[pos=1 lim=100 cap=100]
java.nio.HeapByteBuffer[pos=5 lim=100 cap=100]
java.nio.HeapByteBuffer[pos=13 lim=100 cap=100]
java.nio.HeapByteBuffer[pos=0 lim=13 cap=100]

但是上述代码有一个通病,面对中文时候可能乱码,别看上面没出现,如下例子。只是把缓存区调小了,输出就会乱码。原因是缓冲区大小是2,文件中数据是“abc听风逝夜def”,第一次读取是ab,第二次读取是c和听字的一半,所以就乱码了。有人说缓存区调大啊,其实也没用。算一下就知道了,比如缓冲区大小是1024,一个汉字3字节,可以一次读取<=341个汉字,但是3*341=1023啊,如果此时是342个汉字,肯定读到乱码。因为剩余一个字节不够存一个汉字,这个道理其实和FileInputStream直接read一样,也会出现乱码,通过FileReader或者BufferedReader能解决。

try {
    FileInputStream fileInputStream = new FileInputStream(path);
    FileChannel channel = fileInputStream.getChannel();
    ByteBuffer byteBuffer = ByteBuffer.allocate(2);
    while (channel.read(byteBuffer) > 0) {
        byteBuffer.flip();
        int limit = byteBuffer.limit();
        byte[] datas = new byte[limit];
        byteBuffer.get(datas);
        System.out.println(new String(datas));
        byteBuffer.clear();
    }
} catch (IOException e) {
    e.toString();
}

在这里插入图片描述
网上解决这个办法更离谱,没几个有用的,也是在极端情况下会出现乱码,或者修改一下缓冲区大小就乱码。来上我写的,在这里不管文件有多少个字符,或者缓冲区有多大,都通吃!!!,思路还简单。

 private static void test4(String path) {
     try {
         FileInputStream fileOutputStream = new FileInputStream(path);
         FileChannel channel = fileOutputStream.getChannel();
         List<byte[]> data = new ArrayList<>();
         ByteBuffer byteBuffer = ByteBuffer.allocate(1);
         int totalSize = 0;
         while (channel.read(byteBuffer) > 0) {
             byteBuffer.flip();
             byte[] soure = new byte[byteBuffer.limit()];
             System.arraycopy(byteBuffer.array(), 0, soure, 0, soure.length);
             totalSize += soure.length;
             data.add(soure);
             byteBuffer.clear();
         }
         byte[] bytesData = new byte[totalSize];
         int destPos = 0;
         for (int i = 0; i < data.size(); i++) {
             System.arraycopy(data.get(i), 0, bytesData, destPos, data.get(i).length);
             destPos += data.get(i).length;
         }
         System.out.println(new String(bytesData));
     } catch (
             IOException e) {
     }
 }

Buffer

Buffer有很多数据类型实现,当然对Stirng的操作就是StringBuffer了。
在上面说了Buffer的工作状态,但是有个问题要说明一下,如果缓冲区大小为10,先往里面put了5个数据,并切换到读模式,当读取了2个数后,在往里面存放几个数会报错?

答案是三个,当切换到读模式后,pos=0,limit=5,get两次后pos是2,紧接着put是不报错的,但是超过limit后就会报。

其他API就简单了,多练就懂了。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值