Java I/O流面试之道

先赞后看,Java进阶一大半

南哥在国外 stackoverflow 看到13年前的这么一个问题:如何使用 Java 逐行读取大型文本文件。大家有什么思路吗?评论区一起讨论讨论。

I need to read a large text file of around 5-6 GB line by line using Java.

How can I do this quickly?

最高赞的回答是名为Peter Lawrey的老哥回答的。

在这里插入图片描述

我是南哥,相信对你通关面试、拿下Offer有所帮助。

敲黑板:本文总结了Java I/O流、Java NIO常见的面试题!

⭐⭐⭐收录在《Java学习/进阶/面试指南》:https://github/JavaSouth

1. I/O流

1.1 I/O的理解

面试官:你说下对Java IO的理解?

Java I/O有两个参与对象,一个是I/O源端,一个是想要和I/O源端通信的各种接收端,比如程序控制IDEA控制台输出、读取文件A写入文件B等,我们程序要保证的就是IO流的顺利读取和顺利写入。JDK把对Java IO的支持都放在了package java.io包下,南哥数了数,一个有86个类和接口。

我们看下package java.io包最常用的Reader和Writer接口,他们的作者都是Mark Reinhold。这位老哥是谁?他是Oracle Java平台组的首席架构师,也是字符流读取器和写入器的首席工程师。这么有来头,看来Java I/O的程序设计不简单,我们可以从中学到不少好用的东西。

/** * @author      Mark Reinhold* @since       JDK1.1*/
public abstract class Reader implements Readable, Closeable { }
public abstract class Writer implements Appendable, Closeable, Flushable { }

1.2 字节输入流抽象基类

面试官:那要怎么读取字节流?

我们先讲输入流,后面再讲下输出流。输入流又分为字节流和字符流,顾名思义,字节流按字节来读取,操作的数据单元是8位的字节;而字符流按字符来读取,操作的数据单元是16位的字符。

读取字节的抽象基类是InputStream,这个基类提供了3个方法给我们来读取字节流。

  1. 从输入流读取下一个数据字节,值字节以0到255范围内的int返回。

    public abstract int read() throws IOException
    
  2. 从输入流读取一定数量的字节并将它们存储到缓冲区数组b中。

    public int read(byte b[]) throws IOException
    
  3. 从输入流读取最多len个字节的数据到字节数组中。

    public int read(byte b[], int off, int len) throws IOException
    

大家注意以上方法的返回参数都是int类型,当正常读取时,int返回的是读取的字节个数;而当int返回-1,就表明输入流到达了末尾。

1.3 字节输入流读取

面试官:你说的这些不是实例,我要的是能真正读取的?

上文的是抽象的接口,本身并不具备实际的功能。真正能够读取文件的是InputStream抽象基类的子类实现,例如文件流FileInputStream,有了他,我们读取音频、视频、gif等等都不是问题。

// 文件流读取文件
FileInputStream stream = new FileInputStream(SOURCE_PATH);

我们还可以在外面加一层缓存字节流来提高读取效率,在外层套上BufferedInputStream对象,为什么可以提高读取效率我下文会讲到。

BufferedInputStream stream = new BufferedInputStream(new FileInputStream(SOURCE_PATH));

以上通过字节流我们是以n个字节来读取的,如果要用readLine()读取某一行这种场景下就不适用了。我们可以把缓存字节流换成缓存字符流来承接,使用InputStreamReader转换流把字节输入流转换成字符输入流。

如下代码所示。

BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(SOURCE_PATH)));

1.4 提高读取效率

面试官:为什么加一层缓存流就能提高读取效率?

为什么加一层缓存流就能提高读取效率?因为直接使用 FileInputStream 读取文件的话,每次调用 read() 都是从磁盘读取一个字节,而每次读取都是一次系统调用。系统调用是操作系统层面的调用,涉及到用户空间和内核空间之间的上下文切换,这些切换的成本是很昂贵的。

而如果使用缓存流,一次性从文件里读取多个字节到缓存中,减少系统调用同时也减少了磁盘读取,读取的效率明显提高了。

除了Java I/O采用缓存流来提高读取效率,大多应用程序也采用缓存来提升程序性能,例如我们后端在业务开发会使用Redis缓存来减少数据库压力。关于为什么使用缓存来提高应用程序效率,大家也可以看看国外Quora的回答,解释得很详细。

1.5 字符输入流

面试官:那字符流读取呢?

字符输入流的抽象基类是Reader,同样是提供了3个方法来支持字符流读取。

  1. 读取单个字符。

    public int read() throws IOException
    
  2. 将字符读入数组。

    public int read(char cbuf[]) throws IOException
    
  3. 将字符读入数组的一部分。

    abstract public int read(char cbuf[], int off, int len) throws IOException

字符流读取的实例是FileReader,同样可以使用缓存字符流提高读取效率。

BufferedReader reader = new BufferedReader(new FileReader(new File(SOURCE_PATH)));

我们来具体实操下,读取C:\\Users\\Desktop\\JavaProGuide\\read下的所有文件,把他们合并在一起,写入到C:\\Users\\Desktop\\JavaProGuide\\write下的PRODUCT.txt文件中。

public class Client {private static final String PATH = "C:\\Users\\Desktop\\JavaProGuide\\read";private static final String FILE_OUT = "C:\\Users\\Desktop\\JavaProGuide\\write\\PRODUCT.txt";public static void main(String[] args) throws IOException {File file = new File(PATH);File[] files = file.listFiles();BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_OUT));for (File curFile : files) {BufferedReader reader = new BufferedReader(new FileReader(curFile));String line;while ((line = reader.readLine()) != null) {writer.write(line);writer.newLine();}reader.close();}writer.close();}}

1.6 输出流

面试官:输出流你也讲一讲?

字节输出流的抽象基类是OutputStream,字符输出流的抽象基类是Writer。他们分别提供了以下方法。

字节输出流OutputStream

  1. 将指定字节写入此输出流。

    public abstract void write(int b) throws IOException
    
  2. 将指定字节数组中的b.length字节写入此输出流。

    public void write(byte b[]) throws IOException 
    
  3. 将指定字节数组中从偏移量off开始的len个字节写入此输出流。

    public void write(byte b[], int off, int len) throws IOException
    

字符输出流Writer

  1. 写入单个字符。

    public void write(int c) throws IOException
    
  2. 写入字符数组。

    public void write(char cbuf[]) throws IOException
    
  3. 写入字符数组的一部分。

    abstract public void write(char cbuf[], int off, int len) throws IOException
    

另外字符输出流是使用字符来操作数据,所以可以用字符串来代替字符数组,JDK还支持以下入参是字符串的方法。

  1. 写入一个字符串。

    public void write(String str) throws IOException
    
  2. 写入字符串的一部分。

    public void write(String str, int off, int len) throws IOException
    

1.7 字节流和字符流区别

面试官:那字节流和字符流有什么区别?

字节流和字符流的区别主要是三个方面。

  • 基本单位不同。字节流以字节(8位二进制数)为基本单位来处理数据,字符流以字符为单位处理数据。
  • 使用场景不同。字节流操作可以所有类型的数据,包括文本数据,和非文本数据如图片、音频等;而字符流只适用于处理文本数据。
  • 关于性能方面。因为字节流不处理字符编码,所以处理大量文本数据时可能不如字符流高效;而字符流使用到内存缓冲区处理文本数据可以优化读写操作。

2. Java NIO

2.1 NIO介绍

NIO的出现在于提高IO的速度,它相比传统的输入/输出流速度更快。NIO通过管道Channel和缓冲器Buffer来处理数据,可以把管道当成一个矿藏,缓冲器就是矿藏里的卡车。

程序通过管道里的缓冲器进行数据交互,而不直接处理数据。程序要么从缓冲器获取数据,要么输入数据到缓冲器。

2.2 通道和缓冲器

NIO提供了通道和缓冲器这两个核心对象。

(1)管道Channel

与传统的IO流只能只读或只写的单向流不同,NIO通道是双向的,也就是说读写操作可以同时进行,使得数据的处理效率也更高。

(2)缓冲器Buffer

传统的输入/输出流一次只处理一个字节,而每一次字节读取都是一次系统调用,涉及到用户空间和内核空间之间的上下文切换,通常来说效率不高。

NIO采用内存映射文件方式来处理输入/输出,Channel通过map()方法把一块数据映射到内存中。程序通过Buffer进行数据交互,减少了与原始数据源的直接访问。NIO面向块的处理方式使得效率更高。

2.3 非阻塞IO模型

传统的输入/输出流是同步阻塞IO模型,如果数据源没有数据了,此时程序将进行阻塞。

NIO是I/O多路复用模型,线程可以询问通道有没可用的数据,而不需要在没有数据时阻塞掉线程。

2.4 字符流处理字符吗

所有数据包括文本数据最终都是以字节形式存储的,因为计算机底层只能理解二进制数据。

字符最终也是要转换成字节形式,之所以可以在文本文件看到字符,是因为系统将底层的二进制序列转换成了字符。

2.5 Buffer

Buffer里有3个关键变量

在这里插入图片描述

  1. capcity:表示缓冲器Buffer的最大数据容量。
  2. position:用来指出下一个可以读出/写入Buffer的索引位置,也就是记录指针的作用。
  3. limit:用来表示在Buffer里第一个不能被读出/写入的索引位置。

在这里插入图片描述

另外Buffer还提供了getput方法来供我们操作数据,而使用get/put后,position的指针位置也会随之移动。

public abstract byte get();public abstract ByteBuffer put(byte b);

2.6 Channel

Channel有常见的3个方法,map()、read()和write()。

// 将通道文件的区域直接映射到字节缓冲区中
public abstract MappedByteBuffer map(MapMode mode, long position, long size)// 从此Channel通道读取字节序列到给定缓冲区dst
public abstract int read(ByteBuffer dst)// 将给定缓冲区中src的字节序列写入此Channel通道
public abstract int write(ByteBuffer src)

以下是Channel的简单使用代码。

public class TestFileChannel {public static void main(String[] args) {File f = new File("D:\\JavaGetOffer\\TestFileChannel.java");try {FileChannel inChannel = new FileInputStream(f).getChannel();FileChannel outChannel = new FileOutputStream("a.txt").getChannel();MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());outChannel.write(buffer);buffer.clear();CharBuffer charBuffer = StandardCharsets.UTF_8.newDecoder().decode(buffer);System.out.println(charBuffer);} catch (IOException ex) {ex.printStackTrace();}}
}

2.7 NIO零拷贝

NIO零拷贝出现之前,一个I/O操作会将同一份数据进行多次拷贝。可以看下图,一次I/O操作对数据进行了四次复制,同时来伴随两次内核态和用户态的上下文切换,众所周知上下文切换是很耗费性能的操作。

在这里插入图片描述

而零拷贝技术改善了上述的问题。可以对比下图,零拷贝技术减少了对一份数据的拷贝次数,不再需要将数据在内核态和用户态之间进行拷贝,也意味不再进行上下文切换,让数据传输变得更加高效。

在这里插入图片描述

⭐⭐⭐本文收录在《Java学习/进阶/面试指南》:https://github/JavaSouth

我是南哥,南就南在Get到你的点赞点赞点赞。

在这里插入图片描述

创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/467455.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

精选 Top10 开源调度工具,解锁高效工作负裁自动化

在大数据和现代 IT 环境中,任务调度与工作负载自动化(WLA)工具是优化资源利用、提升生产效率的核心驱动力。随着企业对数据分析、实时处理和多地域任务调度需求的增加,这些工具成为关键技术。 本文将介绍当前技术发展背景下的Top …

微软域名邮箱:如何设置管理烽火域名邮箱?

微软域名邮箱的设置技巧?免费域名邮箱注册设置教程? 微软域名邮箱为企业提供了一个强大且灵活的解决方案,帮助企业轻松管理其域名邮箱。烽火将详细介绍如何设置和管理微软域名邮箱,确保您的团队能够高效地使用这一工具。 微软域…

VS ssh连接linux无法运行的问题 GDB 的解决方法

Unable to start debugging. Program path ... is missing or invalid. GDB failed with message:/home/zsy/projects/是一个目录 把这个将解决方案和项目放在同一目录中勾选

Python酷库之旅-第三方库Pandas(203)

目录 一、用法精讲 946、pandas.IntervalIndex类 946-1、语法 946-2、参数 946-3、功能 946-4、返回值 946-5、说明 946-6、用法 946-6-1、数据准备 946-6-2、代码示例 946-6-3、结果输出 947、pandas.IntervalIndex.closed属性 947-1、语法 947-2、参数 947-3、…

Trimble X12三维激光扫描仪正在改变游戏规则【上海沪敖3D】

Trimble X12 三维激光扫描仪凭借清晰、纯净的点云数据和亚毫米级的精度正在改变游戏规则。今天的案例我们将与您分享,X12是如何帮助专业测量咨询公司OR3D完成的一个模拟受损平转桥运动的项目。 由于习惯于以微米为单位工作,专业测量机构OR3D是一家要求…

【大数据学习 | kafka】简述kafka的消费者consumer

1. 消费者的结构 能够在kafka中拉取数据进行消费的组件或者程序都叫做消费者。 这里面要涉及到一个动作叫做拉取。 首先我们要知道kafka这个消息队列主要的功能就是起到缓冲的作用,比如flume采集数据然后交给spark或者flink进行计算分析,但是flume采用的…

uniapp发布到微信小程序,提示接口未配置在app.json文件中

使用uniapp打包上传微信小程序发布,在提交审核时提示 “接口未配置在app.json文件中” 如下图所示 解决方法:在manifest.json文件中打开源码视图,添加 requiredPrivateInfos 字段键入所需要的接口(数组)

重新下载Window11系统中的mfc100.dll文件

环境 Xshell6Xftp6Window11 前言 最近下载了一款绿色版本的Xshell远程客户端软件,用来登录Linux服务器,在Window11使用,点击时候提示很多dll文件缺失,所以比较纠结,因为是绿色版本软件,所以不能重装&…

js基础篇笔记 (万字速通)

此笔记来自于黑马程序员,仅供笔者复习 JavaScript 基础 - 第1天 了解变量、数据类型、运算符等基础概念,能够实现数据类型的转换,结合四则运算体会如何编程。 体会现实世界中的事物与计算机的关系理解什么是数据并知道数据的分类理解变量存储数据的“容…

vue3+ts+element-ui实现的可编辑table表格组件 插入单行多行 组件代码可直接使用

最近需求越来越离谱,加班越来越严重,干活的牛马也越来越卑微。写了一个可编辑表格,并已封装好组件,可直接使用。 基于这位大佬的 动态表格自由编辑 方法和思路,于是参考和重写了表格,在基础上增加和删除了…

决策树(部分)

目录 信息熵 总结: 特征选择 信息增益:ID3算法 增益率:C4.5 基尼指数 剪枝处理 预剪枝 后剪枝 信息熵 信息熵 (entropy)是 用于度量样本集合“ 纯度 ” 最常用的一种指标,其中 “ 熵 ” 是事物的不确定性,假定…

webpack 执行流程 — 实现 myWebpack

前言 实现 myWebpack 主要是为了更好的理解,webpack 中的工作流程,一切都是最简单的实现,不包含细节内容和边界处理,涉及到 ast 抽象语法树和编译代码部分,最好可以打印出来观察一下,方便后续的理解。 re…

【python】Flask

文章目录 1、Flask 介绍2、Flask 实现网页版美颜效果3、参考 1、Flask 介绍 Flask 是一个用 Python 编写的轻量级 Web 应用框架。它设计简单且易于扩展,非常适合小型项目到大型应用的开发。 以下是一些 Flask 库中常用的函数和组件: 一、Flask 应用对…

AI大模型如何重塑软件开发流程?

《AI大模型对软件开发流程的重塑:变革、优势、挑战与展望》 一、传统软件开发流程与模式(一)传统软件开发流程(二)传统软件开发模式面临的问题(一)AI在软件开发中的应用场景(二&…

OceanBase 应用实践:如何处理数据空洞,降低存储空间

问题描述 某保险行业客户的核心系统,从Oracle 迁移到OceanBase之后,发现数据存储空间出现膨胀问题,数据空间 datasize9857715.48M,实际存储占用空间17790702.00M。根据 required_mb - data_mb 值判断,数据空洞较为严重…

Zookeeper运维秘籍:四字命令基础、详解及业务应用全解析

文章目录 一、四字命令基础二、四字命令详解三、四字命令的开启与配置四、结合业务解读四字命令confconsenvi命令Stat命令MNTR命令ruok命令dump命令wchswchp ZooKeeper,作为一款分布式协调服务,提供了丰富的四字命令(也称为四字短语&#xff…

MATLAB大数计算工具箱及其用法

1. MATLAB大数工具箱Variable Precision Integer Arithmetic介绍 Variable Precision Integer Arithmetic是John DErrico 开发的大数运算工具箱,可以用完全任意大小的整数进行算术运算。支持vpi定义的数组和向量。 2.MATLAB代码 完整代码见: https://download.cs…

【野生动物识别系统】Python+深度学习+人工智能+卷积神经网络算法+TensorFlow+ResNet+图像识别

一、介绍 动物识别系统,使用Python作为主要开发语言,基于深度学习TensorFlow框架,搭建卷积神经网络算法。并通过对18种动物数据集进行训练,最后得到一个识别精度较高的模型。并基于Django框架,开发网页端操作平台&…

数据库_SQLite3

下载 1、更新软件源: sudo apt-get update 2、下载SQLite3: sudo apt-get install sqlite3 3、验证: sqlite3启动数据库,出现以下界面代表运行正常。输入 .exit 可以退出数据库 4、安装sqlite3的库 sudo apt-get install l…

PyTorch核心概念:从梯度、计算图到连续性的全面解析(三)

文章目录 Contiguous vs Non-Contiguous TensorTensor and ViewStrides非连续数据结构:Transpose( )在 PyTorch 中检查Contiguous and Non-Contiguous将不连续张量(或视图)转换为连续张量view() 和 reshape() 之间的区别总结 参考文献 Contig…