Java IO

简介: Java IO流是一个庞大的生态环境,其内部提供了很多不同的输入流和输出流,细分下去还有字节流和字符流,甚至还有缓冲流提高 IO 性能,转换流将字节流转换为字符流······看到这些就已经对 IO 产生恐惧了,在日常开发中少不了对文件的 IO 操作,虽然 apache 已经提供了 `Commons IO` 这种封装好的组件,但面对特殊场景时,我们仍需要自己去封装一个高性能的文件 IO 工具类,本文将会解析 Java IO 中涉及到的各个类,以及讲解如何正确、高效地使用它们

1.IO体系

1.1 IO简介

IO 的操作方式常分为:同步阻塞BIO、同步非阻塞NIO、异步非阻塞AIO

Java IO流是一个庞大的生态环境,其内部提供了很多不同的输入流和输出流,细分下去还有字节流和字符流,甚至还有缓冲流提高 IO 性能,转换流将字节流转换为字符流······看到这些就已经对 IO 产生恐惧了,在日常开发中少不了对文件的 IO 操作,虽然 apache 已经提供了 Commons IO 这种封装好的组件,但面对特殊场景时,我们仍需要自己去封装一个高性能的文件 IO 工具类,本文将会解析 Java IO 中涉及到的各个类,以及讲解如何正确、高效地使用它们。

1.2 什么是IO流

知识科普:我们知道任何一个文件都是以二进制形式存在于设备中,计算机就只有 01,你能看见的东西全部都是由这两个数字组成,你看这篇文章时,这篇文章也是由01组成,只不过这些二进制串经过各种转换演变成一个个文字、一张张图片跃然屏幕上。

而流就是将这些二进制串在各种设备之间进行传输,如果你觉得有些抽象,我举个例子就会好理解一些:

下图是一张图片,它由01串组成,我们可以通过程序把一张图片拷贝到一个文件夹中,

把图片转化成二进制数据集,把数据一点一点地传递到文件夹中 , 类似于水的流动 , 这样整体的数据就是一个数据流

img

IO 流读写数据的特点:

  • 顺序读写。读写数据时,大部分情况下都是按照顺序读写,读取时从文件开头的第一个字节到最后一个字节,写出时也是也如此(RandomAccessFile 可以实现随机读写, NIO的channel既可以RandomAccessFile获取)
  • 字节数组。读写数据时本质上都是对字节数组做读取和写出操作,即使是字符流,也是在字节流基础上转化为一个个字符,所以字节数组是 IO 流读写数据的本质。

项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用

Github地址https://github.com/plasticene/plasticene-boot-starter-parent

Gitee地址https://gitee.com/plasticene3/plasticene-boot-starter-parent

微信公众号Shepherd进阶笔记

交流探讨群:Shepherd_126

1.3 流的分类

根据数据流向不同分类:输入流 和 输出流

  • 输入流:从磁盘或者其它设备中将数据输入到进程中
  • 输出流:将进程中的数据输出到磁盘或其它设备上保存

img

1

图示中的硬盘只是其中一种设备,还有非常多的设备都可以应用在IO流中,例如:打印机、硬盘、显示器、手机······

根据处理数据的基本单位不同分类:字节流 和 字符流

  • 字节流:以字节(8 bit)为单位做数据的传输
  • 字符流:以字符为单位(1字符 = 2字节)做数据的传输

字符流的本质也是通过字节流读取,Java 中的字符采用 Unicode 标准,在读取和输出的过程中,通过以字符为单位,查找对应的码表将字节转换为对应的字符。

为什么 I/O 流操作要分为字节流操作和字符流操作呢?

回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。

面对字节流和字符流,很多读者都有疑惑:什么时候需要用字节流,什么时候又要用字符流?

我这里做一个简单的概括,你可以按照这个标准去使用:

字符流只针对字符数据进行传输,所以如果是文本数据,优先采用字符流传输;除此之外,其它类型的数据(图片、音频等),最好还是以字节流传输。

根据这两种不同的分类,我们就可以做出下面这个表格,里面包含了 IO 中最核心的 4 个顶层抽象类:

数据流向 / 数据类型 字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer

img

看到 Stream 就知道是字节流,看到 Reader / Writer 就知道是字符流。

这里还要额外补充一点:Java IO 提供了字节流转换为字符流的转换类,称为转换流。

转换流 / 数据类型 字节流与字符流之间的转换
(输入)字节流 => 字符流 InputStreamReader
(输出)字符流 => 字节流 OutputStreamWriter

2.三种不同实现的IO操作方式

以一个经典的烧开水的例子通俗地讲解它们之间的区别

类型 烧开水
BIO 一直监测着某个水壶,该水壶烧开水后再监测下一个水壶
NIO 每隔一段时间就看看所有水壶的状态,哪个水壶烧开水就去处理哪个水壶
AIO 不用监测水壶,每个水壶烧开水后都会主动通知线程说:“我的水烧开了,来处理我吧”

接下来我详解讲述一下BIO的使用

3.BIO (Blocking I/O)

BIO 属于同步阻塞 IO 模型

同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量,所以才有了后续的NIO,AIO

下面通过文件复制看一下io操作示例:

package com.shepherd.example.bio;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

import java.io.*;

/**
 * @author fjzheng
 * @version 1.0
 * @date 2022/3/20 00:48
 */
public class BioDemo {
   
   
    public static void main(String[] args) throws IOException {
   
   
        String source = "/Users/shepherdmy/baiduYunDownload/72-Elasticsearch核心技术与实战/13丨通过Analyzer进行分词.mp4";
        String destination = "/Users/shepherdmy/Desktop/bio/bak/es.mp4";
        long start = System.currentTimeMillis();
        copyWithFileInputStream(source, destination);
        long end = System.currentTimeMillis();
        System.out.println(end-start);
    }

    public static void copyWithFileInputStream(String source, String destination) throws IOException {
   
   
        // 创建输入流,读取文件内容
        InputStream inputStream = new FileInputStream(source);
        OutputStream outputStream = new FileOutputStream(destination);
        byte[] buf = new byte[8192];
        int len;
        while ((len = inputStream.read(buf)) > 0) {
   
   
            outputStream.write(buf, 0, len);
        }
        inputStream.close();
        outputStream.close();
    }

    public static void copyWithBufferInputStream(String source, String destination) throws IOException {
   
   

        InputStream in = new FileInputStream(source);
        OutputStream out = new FileOutputStream(destination);
        BufferedInputStream bufferedInputStream = new BufferedInputStream(in);
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out);
        // bufferedInputStream默认缓冲区大小为8192
        // BufferedInputStream比FileInputStream多了一个缓冲区,执行read时先从缓冲区读取,当缓冲区数据读完时再把缓冲区填满。
        // 因此,当每次读取的数据量很小时,FileInputStream每次都是从硬盘读入,而BufferedInputStream大部分是从缓冲区读入。读取内存速度比
        // 读取硬盘速度快得多,因此BufferedInputStream效率高。
        // BufferedInputStream的默认缓冲区大小是8192字节。当每次读取数据量接近或远超这个值时,两者效率就没有明显差别了。
        byte[] buf = new byte[8192];
        int len;
        while ((len = bufferedInputStream.read(buf)) > 0) {
   
   
            bufferedOutputStream.write(buf, 0, len);
        }
        bufferedInputStream.close();
        bufferedOutputStream.close();
    }

      public static void copyWithFileChannel(String source, String destination) throws IOException {
   
   
        // 打开文件输入流
        FileChannel inChannel = new FileInputStream(source).getChannel();
        // 打开文件输出流
        FileChannel outChannel = new FileOutputStream(destination).getChannel();
        // 分配 1024 个字节大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(8192);
        // 将数据从通道读入缓冲区
        while (inChannel.read(buf) != -1) {
   
   
            // 切换缓冲区的读写模式
            buf.flip();
            // 将缓冲区的数据通过通道写到目的地
            outChannel.write(buf);
            // 清空缓冲区,准备下一次读
            buf.clear();
        }
        inChannel.close();
        outChannel.close();

    }

    public static void copyWithUtils(String source, String destination) throws IOException {
   
   
        FileUtils.copyFile(new File(source), new File(destination));
    }
}

注意:apache给我们提供的IOUtils和FileUtils提供了很多对流和文件的封装方法,我们可以直接,但是底层实现有可能是BIO,也有可能是NIO,如上面的FileUtils.copyFile()方法,其核心逻辑是:

    private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException {
   
   
        if (destFile.exists() && destFile.isDirectory()) {
   
   
            throw new IOException("Destination '" + destFile + "' exists but is a directory");
        }

        FileInputStream fis = null;
        FileOutputStream fos = null;
        FileChannel input = null;
        FileChannel output = null;
        try {
   
   
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);
            input  = fis.getChannel();
            output = fos.getChannel();
            long size = input.size();
            long pos = 0;
            long count = 0;
            while (pos < size) {
   
   
                count = size - pos > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : size - pos;
                pos += output.transferFrom(input, pos, count);
            }
        } finally {
   
   
            IOUtils.closeQuietly(output);
            IOUtils.closeQuietly(fos);
            IOUtils.closeQuietly(input);
            IOUtils.closeQuietly(fis);
        }

        if (srcFile.length() != destFile.length()) {
   
   
            throw new IOException("Failed to copy full contents from '" +
                    srcFile + "' to '" + destFile + "'");
        }
        if (preserveFileDate) {
   
   
            destFile.setLastModified(srcFile.lastModified());
        }
    }

使用channel通道进行读写,所以应该是NIO实现。

目录
相关文章
|
22天前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
2月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
8天前
|
数据采集 Java 数据挖掘
Java IO异常处理:在Web爬虫开发中的实践
Java IO异常处理:在Web爬虫开发中的实践
|
21天前
|
Java 数据处理
Java IO 接口(Input)究竟隐藏着怎样的神秘用法?快来一探究竟,解锁高效编程新境界!
【8月更文挑战第22天】Java的输入输出(IO)操作至关重要,它支持从多种来源读取数据,如文件、网络等。常用输入流包括`FileInputStream`,适用于按字节读取文件;结合`BufferedInputStream`可提升读取效率。此外,通过`Socket`和相关输入流,还能实现网络数据读取。合理选用这些流能有效支持程序的数据处理需求。
23 2
|
22天前
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。
|
22天前
|
缓存 Java
【IO面试题 一】、介绍一下Java中的IO流
Java中的IO流是对数据输入输出操作的抽象,分为输入流和输出流,字节流和字符流,节点流和处理流,提供了多种类支持不同数据源和操作,如文件流、数组流、管道流、字符串流、缓冲流、转换流、对象流、打印流、推回输入流和数据流等。
【IO面试题 一】、介绍一下Java中的IO流
|
24天前
|
Java
"揭秘Java IO三大模式:BIO、NIO、AIO背后的秘密!为何AIO成为高并发时代的宠儿,你的选择对了吗?"
【8月更文挑战第19天】在Java的IO编程中,BIO、NIO与AIO代表了三种不同的IO处理机制。BIO采用同步阻塞模型,每个连接需单独线程处理,适用于连接少且稳定的场景。NIO引入了非阻塞性质,利用Channel、Buffer与Selector实现多路复用,提升了效率与吞吐量。AIO则是真正的异步IO,在JDK 7中引入,通过回调或Future机制在IO操作完成后通知应用,适合高并发场景。选择合适的模型对构建高效网络应用至关重要。
25 2
|
30天前
|
Java Android开发
解决Android编译报错:Unable to make field private final java.lang.String java.io.File.path accessible
解决Android编译报错:Unable to make field private final java.lang.String java.io.File.path accessible
64 1
|
1月前
|
存储 缓存 Java
15 Java IO流(File类+IO流+字节流+字符流+字节编码)
15 Java IO流(File类+IO流+字节流+字符流+字节编码)
40 3
|
18天前
|
NoSQL Java Redis
【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常
【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常