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 负责存储

相关文章
|
8天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
14天前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
15天前
|
消息中间件 缓存 Java
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
零拷贝技术 Zero-Copy 是指计算机执行操作时,可以直接从源(如文件或网络套接字)将数据传输到目标缓冲区, 而不需要 CPU 先将数据从某处内存复制到另一个特定区域,从而减少上下文切换以及 CPU 的拷贝时间。
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
|
20天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
58 5
|
17天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
29 1
|
24天前
|
Java 数据处理 开发者
Java多线程编程的艺术:从入门到精通####
【10月更文挑战第21天】 本文将深入探讨Java多线程编程的核心概念,通过生动实例和实用技巧,引导读者从基础认知迈向高效并发编程的殿堂。我们将一起揭开线程管理的神秘面纱,掌握同步机制的精髓,并学习如何在实际项目中灵活运用这些知识,以提升应用性能与响应速度。 ####
43 3
|
25天前
|
Java
Java中的多线程编程:从入门到精通
本文将带你深入了解Java中的多线程编程。我们将从基础概念开始,逐步深入探讨线程的创建、启动、同步和通信等关键知识点。通过阅读本文,你将能够掌握Java多线程编程的基本技能,为进一步学习和应用打下坚实的基础。
|
27天前
|
存储 安全 Java
从入门到精通:Java Map全攻略,一篇文章就够了!
【10月更文挑战第19天】本文介绍了Java编程中重要的数据结构——Map,通过问答形式讲解了Map的基本概念、创建、访问与修改、遍历方法、常用实现类(如HashMap、TreeMap、LinkedHashMap)及其特点,以及Map在多线程环境下的使用和性能优化技巧,适合初学者和进阶者学习。
44 4
|
26天前
|
Java
[Java]Socket套接字(网络编程入门)
本文介绍了基于Java Socket实现的一对一和多对多聊天模式。一对一模式通过Server和Client类实现简单的消息收发;多对多模式则通过Server类维护客户端集合,并使用多线程实现实时消息广播。文章旨在帮助读者理解Socket的基本原理和应用。
20 1
|
27天前
|
SQL IDE Java
入门Cloud Toolkit:简化你的Java应用开发与部署流程
【10月更文挑战第19天】作为一名长期从事Java开发的程序员,我一直致力于寻找能够简化日常开发工作的工具。在众多工具中,阿里巴巴推出的Cloud Toolkit引起了我的注意。这款免费的插件旨在帮助开发者更轻松地进行开发、测试及部署工作,尤其是在与云服务交互时表现尤为出色。本文将从个人的角度出发,介绍Cloud Toolkit的基本功能及其使用技巧,希望能帮助初学者快速上手这款实用工具。
20 1