Java入门系列-23-NIO(使用缓冲区和通道对文件操作)

简介:

NIO 是什么

java.nio全称java non-blocking(非阻塞) IO(实际上是 new io),是指jdk1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。

NIO与IO的区别

IO NIO
面向流(Stream Oriented) 面向缓冲区(Buffer Oriented)
阻塞IO(Blocking IO) 非阻塞(Non Blocking IO)
选择器(Selectors)

NIO系统的核心是:通道(Channel)和缓冲区(Buffer)

缓冲区(Buffer)

位于 java.nio 包,所有缓冲区都是 Buffer 抽象类的子类,使用数组对数据进行缓冲。

除了 boolean 类型,Buffer 对每种基本数据类型都有针对的实现类:

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

创建缓冲区通过 xxxBuffer.allocate(int capacity)方法

ByteBuffer buf1 = ByteBuffer.allocate(512);
LongBuffer buf2 = LongBuffer.allocate(1024);
……

缓冲区的属性

容量(capacity):表示缓冲区存储数据的最大容量,不能为负数,创建后不可修改。

限制:第一个不可以读取或写入的数据的索引,即位于 limit 后的数据不能读写。不能为负数,不能大于容量。

位置(position):下一个要读取或写入的数据的索引,位置不能为负数,不能大于 limit

标记(mark):标记是一个索引,通过 Buffer 中的 mark() 方法指 Buffer 中一个特定的 position,之后可以通过 reset() 方法回到这个 postion。

Buffer 的常用方法

方法名称 说明
Buffer clear() 清空缓冲区并返回对缓冲区的引用
Buffer flip() 将缓冲区的 limit 设置为当前位置,并将当前位置重置为0
int capacity() 返回 Buffer 的容量大小
boolean hasRemaining() 判断缓冲区是否还有元素
int limit() 返回 限制的位置
Buffer limit(int n) 将设置缓冲区界限为 n,并返回一个具有新 limit 的缓冲区对象
Buffer mark() 对缓冲区设置标记
int position() 返回缓冲区的当前位置 position
Buffer position(int n) 将设置缓冲区的当前位置为 n,并返回修改后的 Buffer 对象
int remaining() 返回 position 和 limit 之间的元素个数
Buffer reset() 将位置 position 转到以前设置的 mark 所在的位置
Buffer rewind() 将位置设置为 0,取消设置的 mark

Buffer 所有子类提供了两个操作的数据的方法:get() 方法和 put() 方法

缓冲区存取数据操作

package testnio;

import java.nio.ByteBuffer;

public class TestBuffer1 {

    public static void main(String[] args) {
        testuse();
    }

    public static void testuse() {
        //1.分配一个指定大小的缓冲区
        ByteBuffer buf=ByteBuffer.allocate(1024);

        System.out.println("---------------allocate()----------------");
        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());

        //2.利用 put() 存入数据到缓冲区中
        String str="hello";
        //将字符串转为 byte 数组存入缓冲区
        buf.put(str.getBytes());
        System.out.println("---------------put()----------------");
        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());

        //3.切换读取数据模式
        buf.flip();
        System.out.println("---------------flip()----------------");
        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());

        //4.利用get() 读取缓冲区中的数据
        byte[] data=new byte[buf.limit()];
        System.out.println("---------------get()----------------");
        buf.get(data);
        System.out.println(new String(data,0,data.length));
        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());

        //5.rewind() 重复读
        buf.rewind();
        System.out.println("---------------rewind()----------------");
        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());

        //6.clear() 清空缓冲区,但缓冲区中的数据依然存在
        buf.clear();
        System.out.println("---------------clear()----------------");
        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());
        System.out.println((char)buf.get());
    }
}

使用 mark()方法标记

package testnio;

import java.nio.ByteBuffer;

public class TestBuffer2 {

    public static void main(String[] args) {
        testmark();
    }

    public static void testmark() {
        String str="jikedaquan.com";
        //创建缓冲区
        ByteBuffer buf=ByteBuffer.allocate(1024);
        //存入数据
        buf.put(str.getBytes());
        //切换模式
        buf.flip();
        //临时数组用于接收缓冲区获取的数据,长度与缓冲区 limit 一值
        byte[] data=new byte[buf.limit()];
        //获取缓冲区的数据从0开始获取4个,存入 data 数组中
        buf.get(data, 0, 4);
        //将数组转为字符串打印
        System.out.println(new String(data,0,4));
        //打印 position
        System.out.println(buf.position());

        //标记
        buf.mark();
        System.out.println("---------------再次获取----------------");
        //从索引4开始,获取6个字节(余下数据)
        buf.get(data, 4, 6);
        System.out.println(new String(data,4,6));
        System.out.println(buf.position());

        //恢复到标记位置
        buf.reset();
        System.out.println("---------------reset()----------------");
        System.out.println(buf.position());

        //判断缓冲区是是有还有剩余数据
        if (buf.hasRemaining()) {
            //获取缓冲区中可以操作的数量
            System.out.println("可操作数量:"+buf.remaining());
        }
    }
}

mark <= position <= limit <= capacity

虽然使用了缓冲区提高了一定的IO速度,但这样的效率仍然不是最高的。非直接缓冲区在与物理磁盘操作中需要经过内核地址空间copy操作,直接缓冲区不经过copy操作,直接操作物理内存映射文件,
使用直接缓冲区将大大提高效率。

直接缓冲区进行分配和取消分配所需成本工厂高于非直接缓冲区,一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。

直接缓冲区可以通过调用此类的 allocateDirect()工厂方法创建

创建直接缓冲区

package testnio;

import java.nio.ByteBuffer;

public class TestBuffer3 {

    public static void main(String[] args) {
        testAllocateDirect();
    }

    public static void testAllocateDirect() {
        //创建直接缓冲区
        ByteBuffer buf=ByteBuffer.allocateDirect(1024);
        
        //是否是直接缓冲区
        System.out.println(buf.isDirect());
    }
}

通道(Channel)

缓冲区仅是运载数据的容器,需要对数据读写还需要有一条通道,这两者是密不可分的。

Channel 接口的主要实现类:

  • FileChannel:用于读取、写入、映射和操作文件的通道
  • DatagramChannel:通过 UDP 读写网络中的数据通道
  • SocketChannel:通过 TCP 读写网络中的数据
  • ServerSocketChannel:可以监听新进来的 TCP 链接,对每一个新进来的连接都会创建一个 SocketChannel

如何获取通道?

1、通过支持通道的对象调用 getChannel() 方法

支持通道的类:

  • FileInputStream
  • FileOutputStream
  • RandomAccessFile
  • DatagramScoket
  • Socket
  • ServerSocket

使用通道和缓冲区实现文件读和写

package testnio;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class TestChannel {

    public static void main(String[] args) {
        FileInputStream fis=null;
        FileOutputStream fos=null;
        
        FileChannel inChannel=null;
        FileChannel outChannel=null;
        
        try {
            //创建输入流
            fis=new FileInputStream("F:/1.jpg");
            //创建输出流
            fos=new FileOutputStream("F:/2.jpg");
            //获取通道
            inChannel=fis.getChannel();
            outChannel=fos.getChannel();
            
            //分配指定大小的缓冲区
            ByteBuffer buf=ByteBuffer.allocate(1024);
            
            //将通道中的数据存入缓存区
            while(inChannel.read(buf)!=-1) {
                //切换读取数据的模式
                buf.flip();
                //将读入的缓冲区存入写数据的管道
                outChannel.write(buf);
                //清空缓存区(清空才能再次读入)
                buf.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (fis!=null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos!=null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(inChannel!=null) {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outChannel!=null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
}

2、通过通道类的静态方法 open()

package testnio;

import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class TestOpenAndMapped {

    public static void main(String[] args) {
        FileChannel inChannel=null;
        FileChannel outChannel=null;
        try {
            //通过open创建通道
            inChannel = FileChannel.open(Paths.get("F:/a.jpg"), StandardOpenOption.READ);
            outChannel = FileChannel.open(Paths.get("F:/b.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);

            //内存映射文件   直接缓冲区
            MappedByteBuffer inMappedBuf=inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
            MappedByteBuffer outMappedBuf=outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());

            //直接对缓冲区进行数据的读写操作
            byte[] data=new byte[inMappedBuf.limit()];
            inMappedBuf.get(data);//读
            outMappedBuf.put(data);//写
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (inChannel!=null) {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outChannel!=null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

JDK 1.7 新增的方法 open(),参数 path 通常代表一个依赖系统的文件路径,通过Paths.get()获取。

参数 StandardOpenOption 是一个枚举类型,常用值如下:

  • READ :打开读访问
  • WRITE:打开写访问
  • APPEND:向后追加
  • CREATE:创建新文件,存在则覆盖
  • CREATE_NEW:创建新文件,存在则报错

MappedByteBuffer:直接字节缓冲区,其内容是文件的内存映射区域。

通道数据传输

将数据从源通道传输到其他 Channel 中,transferTo() 和 transferFrom()

package testnio;

import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class TestChannelTransfer {

    public static void main(String[] args) {
        FileChannel inChannel=null;
        FileChannel outChannel=null;
        try {
            //通过open创建管道
            inChannel = FileChannel.open(Paths.get("F:/a.jpg"), StandardOpenOption.READ);
            outChannel = FileChannel.open(Paths.get("F:/b.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
            //将inChannel中所有数据发送到outChannel
            //inChannel.transferTo(0, inChannel.size(), outChannel);
            outChannel.transferFrom(inChannel, 0, inChannel.size());
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (inChannel!=null) {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outChannel!=null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Channel 负责传输,Buffer 负责存储

相关文章
|
1月前
|
存储 Java 数据处理
|
1月前
|
XML Java 数据库连接
谈谈Java反射:从入门到实践,再到原理
谈谈Java反射:从入门到实践,再到原理
58 0
|
19天前
|
关系型数据库 Java 开发工具
Java入门高频考查基础知识9(15问万字参考答案)
本文探讨了Spring Cloud的工作原理,包括注册中心的心跳机制、服务发现机制,以及Eureka默认的负载均衡策略。同时,概述了Spring Boot中常用的注解及其实现方式,并深入讨论了Spring事务的注解、回滚条件、传播性和隔离级别。文章还介绍了MySQL的存储引擎及其区别,特别关注了InnoDB如何实现MySQL的事务处理。此外,本文还详细探讨了MySQL索引,包括B+树的原理和设计索引的方法。最后,比较了Git和SVN的区别,并介绍了Git命令的底层原理及流程。
29 0
Java入门高频考查基础知识9(15问万字参考答案)
|
19天前
|
存储 缓存 算法
Java入门高频考查基础知识4(字节跳动面试题18题2.5万字参考答案)
最重要的是保持自信和冷静。提前准备,并对自己的知识和经验有自信,这样您就能在面试中展现出最佳的表现。祝您面试顺利!Java 是一种广泛使用的面向对象编程语言,在软件开发领域有着重要的地位。Java 提供了丰富的库和强大的特性,适用于多种应用场景,包括企业应用、移动应用、嵌入式系统等。下是几个面试技巧:复习核心概念、熟悉常见问题、编码实践、项目经验准备、注意优缺点、积极参与互动、准备好问题问对方和知其所以然等,多准备最好轻松能举一反三。
46 0
Java入门高频考查基础知识4(字节跳动面试题18题2.5万字参考答案)
|
19天前
|
存储 算法 JavaScript
Java入门高频考查算法逻辑基础知识3-编程篇(超详细18题1.8万字参考编程实现)
解决这类问题时,建议采取下面的步骤: 理解数学原理:确保你懂得基本的数学公式和法则,这对于制定解决方案至关重要。 优化算法:了解时间复杂度和空间复杂度,并寻找优化的机会。特别注意避免不必要的重复计算。 代码实践:多编写实践代码,并确保你的代码是高效、清晰且稳健的。 错误检查和测试:要为你的代码编写测试案例,测试标准的、边缘情况以及异常输入。 进行复杂问题简化:面对复杂的问题时,先尝试简化问题,然后逐步分析和解决。 沟通和解释:在编写代码的时候清晰地沟通你的思路,不仅要写出正确的代码,还要能向面试官解释你的
32 0
|
19天前
|
存储 Java 编译器
Java入门高频考查基础知识2(超详细28题2.5万字答案)
多态是面向对象编程中的一个重要概念,它允许不同类的对象对同一消息作出不同的响应。在具体实现上,多态允许一个父类的引用指向其子类的对象,并根据实际指向的对象的类型来调用相应的方法。在 Java 中,多态可以通过以下几种方式实现:在同一个类中,方法名相同,但形参列表不同,实现了多态。子类可以重写(覆盖)其父类的方法,实现多态。在父类引用中调用该方法时,根据实际指向的子类对象的类型来调用相应的方法实现。
38 0
|
1月前
|
算法 Java
Java必刷入门递归题×5(内附详细递归解析图)
Java必刷入门递归题×5(内附详细递归解析图)
20 1
|
1月前
|
Java
Java入门必刷的基础题1(八道)
Java入门必刷的基础题1(八道)
20 0
|
1月前
|
分布式计算 Oracle Java
第一篇CSDN博客——Java入门指南:学习Java编程的第一步
第一篇CSDN博客——Java入门指南:学习Java编程的第一步
C4.
|
1月前
|
存储 安全 Java
Java的字符串缓冲区
Java的字符串缓冲区
C4.
13 0