前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java nio剖析

java nio剖析

作者头像
全栈程序员站长
发布2021-05-19 11:11:20
4730
发布2021-05-19 11:11:20
举报
文章被收录于专栏:全栈程序员必看

java nio 的全称是 java new I/O ,即一个全新的 I/O 控制系统,它的 API 的包名为 java.nio ,是在 jdk1.4 后引入的。

nio 之所以为为新,在于它并没在原来 I/O 的基础上进行开发,而是提供了全新的类和接口,除了原来的基本功能之外,它还提供了以下新的特征:

► 多路选择的非封锁式 I/O 设施

► 支持文件锁和内存映射

► 支持基于 Perl 风格正则表达式的模式匹配设施

► 字符集编码器和译码器

为了支持这些新的功能, nio 使用了两个新的概念:

1. 信道 (channel)

信道是一个连接,可用于接收或发送数据,如文件和套接字。因为信道连接的是底层的物理设备,他可以直接支持设备的读 / 写,或提供文件锁。对于文件、管道、套接字都存在相应的信道类。可以把信道看成是数据流的替代品。信道没有包装类,提高了性能。

所有的信道类都位于 java.nio.channels 包中。

2. 缓冲区 (buffer)

缓冲区是一个数据容器。可以把它看做内存中的一个大的数组,用来存储来自信道的同一类型的所有数据,因此,程序员可以使用字节、字符、整数等缓冲区。字节缓冲区提供必要的方法,可以提取或存入所有基本类型 (boolean 型除外 ) 的数据。

buffer 类的核心是一块内存区,便于核心代码和 java 代码同时访问,核心代码可以直接访问它, java 代码可以通过 API 访问它。

缓冲区基本上是一块内存区域,因而可以执行一些与内存有关的操作,如清除其中的内容,支持读写或只读操作等。

所有的 buffer 类都位于 java.nio 包中。

下面看如何使用它们:

1. 使用信道

在信道的使用中,文件的信道是最具有代表性的, API 也是最多的,下面我们以文件信道为例介绍它。

● 获取文件信道

文件的信道的类为FileChannel,遗憾的是他并没有向我们提供打开文件的方法,我们可以通过调用FileInputStream、FileOutputStream和RandomAccessFile类实例的getChannel()方法来获取其实例。例如:

RandomAccessFile raf = new RandomAccessFile(“data.txt”, “rw”);

FileChannel fc = raf.getChannel();

请注意,这里打开文件的方式如”rw”将适用于文件信道,FileInputStream实例的getChannel()方法所获得的通道将允许进行读取操作。通过FileOutputStream的getChannel方法所获得的通道将允许进行写入操作。最后,如果使用模式 "r" 创建 RandomAccessFil的实例,则通过该实例的getChannel()方法所获得的通道将允许进行读取操作,如果使用模式 "rw" 创建实例,则获得的通道将允许进行读取和写入操作。

● 从信道读取数据

读取的数据会默认放到字节缓冲区中。

FileChannel提供了四个API读取数据:

a. read(ByteBuffer dst) 将字节序列从此通道读入给定的缓冲区

b. read(ByteBuffer[] dsts) 将字节序列从此通道读入给定的缓冲区

c. read(ByteBuffer[] dsts, int offset, int length) 将字节序列从此通道读入给定缓冲区的子序列中

d. read(ByteBuffer dst, long position) 从给定的文件位置开始,从此通道读取字节序列,并写入给定的缓冲区

向信道写入数据

数据来源默认是字节缓冲区。

FileChannel 提供了四个 API 写入数据:

a. write(ByteBuffer src) 将字节序列从给定的缓冲区写入此通道

b. write(ByteBuffer[] srcs) 将字节序列从给定的缓冲区写入此通道

c. write(ByteBuffer[] srcs, int offset, int length) 将字节序列从给定缓冲区的子序列写入此通道

d. write(ByteBuffer src, long position) 从给定的文件位置开始,将字节序列从给定缓冲区写入此通道

● 使用文件锁

文件锁机制主要是在多线程同时读写某个文件资源时使用。

FileChannel 提供了两种加锁机制,lock和tryLock,两者的区别在于,lock是同步的,

直至成功才返回,tryLock是异步的,无论成不成功都会立即返回。

● 使用内存映射

FileChannel 提供的的 API 为:

MappedByteBuffer map(FileChannel.MapMode mode, long position, long size);

映射模式一个有三种:

a.只读: 试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException .(MapMode.READ_ONLY)

b.读 /写 对得到的缓冲区的更改最终将传播到文件;该更改对映射到同一文件的其他程序不一定是可见的。 ( MapMode.READ_WRITE)

c.专用 对得到的缓冲区的更改不会传播到文件,并且该更改对映射到同一文件的其他程序也不是可见的;相反,会创建缓冲区已修改部分的专用副本。 ( MapMode.PRIVATE)

代码语言:javascript
复制
2.      使用缓冲区
代码语言:javascript
复制
●     层次结构
代码语言:javascript
复制
所有缓冲区的基类都是Buffer,除Boolean类型外,其它数据类型都有对应的缓冲区类,
代码语言:javascript
复制
另有一个ByteOrder类,用来设置缓冲区的大小端顺序,即BigEndian或者是LittleEndian,
代码语言:javascript
复制
默认情况下是BigEndian。其层次结构图如下:
代码语言:javascript
复制
代码语言:javascript
复制
●     获取缓冲区对象
代码语言:javascript
复制
一共有两种类型的缓冲区,直接缓冲区和非直接缓冲区,两者区别在于直接缓冲区上的数据操作,
代码语言:javascript
复制
虚拟机将尽量使用本机I/O,并尽量避免使用中间缓冲区。判断一个缓冲区是否是直接缓冲区,
代码语言:javascript
复制
可以调用isDirect()方法。
代码语言:javascript
复制
有三种方式来获取一个缓冲区的对象:
代码语言:javascript
复制
a.      调用allocate()或者allocateDirect()方法直接分配,其中allocateDirect()
代码语言:javascript
复制
返回的是直接缓冲区。
代码语言:javascript
复制
b.      包装一个数组,如:
代码语言:javascript
复制
byte[] b = new byte[1024];
代码语言:javascript
复制
ByteBuffer bb = ByteBuffer.wrap(b);
代码语言:javascript
复制
c.       内存映射,即调用FileChannel的map()方法。
代码语言:javascript
复制
●     缓冲区基本属性
代码语言:javascript
复制
这几个属性是每个缓冲区都有的并且是常用的操作。
代码语言:javascript
复制
a.     容量(capacity),缓冲区大小
代码语言:javascript
复制
b.      限制(limit),第一个不应被读取或写入的字节的索引,总是小于容量。
代码语言:javascript
复制
c.       位置(position),下一个被读取或写入的字节的索引,总是小于限制。
代码语言:javascript
复制
d.      clear()方法:设置limit为capacity,position为0。
代码语言:javascript
复制
e.      filp()方法:设置limit为当前position,然后设置position为0。
代码语言:javascript
复制
f.        rewind()方法:保持limit不变,设置position为0。
代码语言:javascript
复制
●     缓冲区数据操作
代码语言:javascript
复制
操作包括了读取和写入数据两种。
代码语言:javascript
复制
读取数据使用get()及其系列方法,除boolean外,每一种类型包括了对应的get()方法,
代码语言:javascript
复制
如getInt(),getChar()等,get()方法用来读取字节,支持相对和绝对索引两种方式。
代码语言:javascript
复制
写入数据使用put()及其系列方法,和get()方法是对应的。
代码语言:javascript
复制
           下面这个例子演示了如何使用缓冲区和信道:
代码语言:javascript
复制
    package 
     nio;

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

 
    public 
      
    class 
     BufferDemo  
    ... 
    {
      

    
    public static void main(String[] args) throws Exception...{
       
        //分配一个非直接缓冲区
        ByteBuffer bb = ByteBuffer.allocate(100);
        //向缓冲区写入0到100的字节制
        for(int i = 0; i <100; i++)...{
        
            byte b = (byte) (Math.random() * 100);
            bb.put(b);
        }
        
        System.out.println("写入文件前的缓冲区数据");
        bb.flip();
        while(bb.hasRemaining())
            System.out.print(bb.get() + " ");
        System.out.println();
        
        //获取一个关联到文件buffer.txt的信道
        FileChannel fc = new FileOutputStream("buffer.txt").getChannel();
        //将缓冲区数据写到文件中
        bb.flip();
        fc.write(bb);
        //防止缓存
        fc.force(true);
        //关闭信道
        fc.close();
        bb = null;
        fc = null;
        
        //下面从文件中读取数据
        fc = new FileInputStream("buffer.txt").getChannel();
        ByteBuffer bb2 = ByteBuffer.allocate((int) fc.size());
        fc.read(bb2);
        System.out.println("从文件读取的缓冲区数据");
        bb2.flip();
        while(bb2.hasRemaining())
            System.out.print(bb2.get() + " ");
        System.out.println();
        fc.close();
        bb2 = null;
        fc = null;
        

    }

} 
代码语言:javascript
复制
         3.视图缓冲区
代码语言:javascript
复制
         上面我们的缓冲区都是基于字节的,像IntBuffer、LongBuffer等这些都可以调用ByteBuffer的
代码语言:javascript
复制
         as***Buffer(***表示某个数据类型)得到,所以这种类型的缓冲区又被称为视图缓冲区(View Buffer),
代码语言:javascript
复制
       视图缓冲区有以下特点:
代码语言:javascript
复制
a.     视图缓冲区有自己独立的position和limit,但它不是一个新的创建,只是原来字节缓冲区的一个逻辑缓冲区,字节缓冲区的任何修改都会影响视图缓冲区,反之亦然。
代码语言:javascript
复制
b.      视图缓冲区按照数据类型的大小进行索引,而不是字节顺序。
代码语言:javascript
复制
c.       也提供了put()和get()及其系列方法,用于数据的整块传输。
代码语言:javascript
复制
       下面这个例子演示了视图缓冲区:
代码语言:javascript
复制
    package 
     nio;

 
    import 
     java.io.FileInputStream;
 
    import 
     java.nio.ByteBuffer;
 
    import 
     java.nio.IntBuffer;
 
    import 
     java.nio.channels.FileChannel;

 
    public 
      
    class 
     ViewBufferDemo  
    ... 
    {
      

    public static void main(String[] args) throws Exception...{
       
        
        //将文件内容读到缓冲区中
        FileChannel fc = new FileInputStream("buffer.txt").getChannel();
        ByteBuffer bb = ByteBuffer.allocate((int) fc.size());
        fc.read(bb);
        fc.close();
        fc = null;
        
        System.out.println("从文件读取的字节缓冲区数据");
        bb.flip();
        while(bb.hasRemaining())
            System.out.print(bb.get() + " ");
        System.out.println();
        
        //获取视图缓冲区
        bb.flip();
        IntBuffer ib = bb.asIntBuffer();
        System.out.println("将字节缓冲区作为整形缓冲区的数据");
        while(ib.hasRemaining())
            System.out.print(ib.get() + " ");
        System.out.println();
        
        bb = null;
        ib = null;
        
    }

} 
代码语言:javascript
复制
       4.映射内存缓冲区
代码语言:javascript
复制
         调用信道的map()方法后,即可将文件的某一部分或全部映射到内存中,映射内存缓冲区是一
代码语言:javascript
复制
         个直接缓冲区,继承自ByteBuffer,但相对于ByteBuffer,它有更多的优点:
代码语言:javascript
复制
a.     内存映射I/O是对信道/缓冲区技术的改进。 当传输大量的数据时,内存映射I/O
代码语言:javascript
复制
速度相对较快,这是因为它使用虚拟内存把文件传输到进程的地址空间中。
代码语言:javascript
复制
b.      映射内存也成为共享内存,因此可以用于相关进程(均映射同一文件)之间的整块数据
代码语言:javascript
复制
传输,这些进程甚至可以不必位于同一系统上,只要每个都可以访问同一文件即可。
代码语言:javascript
复制
c.       当对FileChannel执行映射操作,把文件映射到内存中时,得到的是一个连接到文件的
代码语言:javascript
复制
映射的字节缓冲区,这种映射的结果是,当输出缓冲区的内容时,数据将出现在文件中,
代码语言:javascript
复制
当读入缓冲区时,相当于得到文件中的数据。
代码语言:javascript
复制
        下面这个例子演示了映射内存:
代码语言:javascript
复制
    package 
     nio;

 
    import 
     java.io.FileInputStream;
 
    import 
     java.io.FileOutputStream;
 
    import 
     java.nio.MappedByteBuffer;
 
    import 
     java.nio.channels.FileChannel;

 
    public 
      
    class 
     CopyFile  
    ... 
    {
      

    public static void main(String[] args) throws Exception ...{
       

        FileChannel fIChan, fOChan;
        MappedByteBuffer mBuf;

        fIChan = new FileInputStream("buffer.txt").getChannel();
        fOChan = new FileOutputStream("bufferTemp.txt").getChannel();

        mBuf = fIChan.map(FileChannel.MapMode.READ_ONLY, 0, fIChan.size());

        fOChan.write(mBuf);

        fIChan.close();
        fOChan.close();
        
        fIChan = null;
        fOChan = null;
        mBuf = null;

    }

} 

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/100528.html原文链接:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021年5月6日 p,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档