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

相关文章
|
7天前
|
存储 网络协议 Java
Java NIO 开发
本文介绍了Java NIO(New IO)及其主要组件,包括Channel、Buffer和Selector,并对比了NIO与传统IO的优势。文章详细讲解了FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel及Pipe.SinkChannel和Pipe.SourceChannel等Channel实现类,并提供了示例代码。通过这些示例,读者可以了解如何使用不同类型的通道进行数据读写操作。
Java NIO 开发
|
23天前
|
Java
Java文件操作
本文介绍了Java中`File`类的使用方法,包括构造方法、常见成员方法及其实现功能。`File`对象可以表示文件或文件夹,支持绝对路径和相对路径。构造方法有三种:基于完整路径、父级路径与子路径组合、`File`对象与字符串组合。成员方法涵盖判断类型、获取大小、获取路径、创建与删除文件/文件夹、获取文件列表、重命名等操作。文章通过示例代码详细展示了各个方法的应用场景及注意事项。
28 1
Java文件操作
|
9天前
|
Java 程序员 UED
Java中的异常处理:从入门到精通
【9月更文挑战第23天】在Java编程的世界中,异常是程序执行过程中不可避免的事件,它们可能会中断正常的流程并导致程序崩溃。本文将通过浅显易懂的方式,引导你理解Java异常处理的基本概念和高级技巧,帮助你编写更健壮、更可靠的代码。我们将一起探索如何捕获和处理异常,以及如何使用自定义异常来增强程序的逻辑和用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供有价值的见解和实用的技巧。
28 4
|
2月前
|
算法 Java 开发者
Java 编程入门:从零到一的旅程
本文将带领读者开启Java编程之旅,从最基础的语法入手,逐步深入到面向对象的核心概念。通过实例代码演示,我们将一起探索如何定义类和对象、实现继承与多态,并解决常见的编程挑战。无论你是编程新手还是希望巩固基础的开发者,这篇文章都将为你提供有价值的指导和灵感。
|
2月前
|
存储 Java 程序员
Java中的集合框架:从入门到精通
【8月更文挑战第30天】在Java的世界里,集合框架是一块基石,它不仅承载着数据的存储和操作,还体现了面向对象编程的精髓。本篇文章将带你遨游Java集合框架的海洋,从基础概念到高级应用,一步步揭示它的奥秘。你将学会如何选择合适的集合类型,掌握集合的遍历技巧,以及理解集合框架背后的设计哲学。让我们一起探索这个强大工具,解锁数据结构的新视角。
|
22天前
|
Java 程序员
Java中的异常处理:从入门到精通
在Java编程的世界中,异常处理是保持程序稳定性和可靠性的关键。本文将通过一个独特的视角—把异常处理比作一场“捉迷藏”游戏—来探讨如何在Java中有效管理异常。我们将一起学习如何识别、捕捉以及处理可能出现的异常,确保你的程序即使在面对不可预见的错误时也能优雅地运行。准备好了吗?让我们开始这场寻找并解决Java异常的冒险吧!
|
2月前
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
69 0
|
2月前
|
Java 程序员 UED
Java 中的异常处理:从入门到精通
【8月更文挑战第31天】在Java编程的世界中,异常处理是保持应用稳定性的重要机制。本文将引导你理解异常的本质,学会如何使用try-catch语句来捕获和处理异常,并探索自定义异常类的魅力。我们将一起深入异常的世界,让你的代码更加健壮和用户友好。
|
2月前
|
Java 数据库连接 开发者
Java中的异常处理:从入门到精通
【8月更文挑战第31天】 在编程世界中,错误和异常就像是不请自来的客人,总是在不经意间打扰我们的程序运行。Java语言通过其异常处理机制,为开发者提供了一套优雅的“待客之道”。本文将带你走进Java异常处理的世界,从基础语法到高级技巧,再到最佳实践,让你的程序在面对意外时,也能从容不迫,优雅应对。
|
2月前
|
Java 开发者
Java 中的异常处理:从入门到精通
【8月更文挑战第31天】在Java的世界中,异常处理是保持程序健壮性的基石。本文将带你探索Java异常处理的奥秘,从基本的try-catch语句到深入理解自定义异常和最佳实践。你将学会如何优雅地处理错误,确保你的代码不仅能够面对意外情况,还能从中恢复。让我们一起开启这段旅程,掌握让程序更加稳定和可靠的技巧吧!
下一篇
无影云桌面