likes
comments
collection
share

彻底理解Java中的文件与IO(一)

作者站长头像
站长
· 阅读数 18

本文章是彻底理解Java文件和IO系列文章第一篇,阅读完本文章,你将对文件构建出一个比较深刻和全局的认识,它将会帮助你更好的理解Java中IO的设计,建议对于Java文件和IO系列文件对照阅读,你会有更加深刻的收获。

文件概述

对于很多介绍Java IO的文章,很多喜欢直接切入主题讲述Java IO各个类,然后又把其中的主要类各个方法的使用演示一遍,这.....说实话,这样做的意义并不是很大,因为即使当时看了知道IO中主要类的用法,过了一阵子还是会忘记,人的大脑喜欢记住有逻辑的东西

所以,为了更加清晰有逻辑的介绍Java中的IO,我觉得有必要先介绍下文件,先对文件的基本概念和常识有一个整体的认识,然后带着对文件整体的认识理解去学习Java中的IO。 你会更加理解Java中IO的设计,你会有一种感觉:对,就应该这样设计,因为换作是你,你也会这样设计。而想要有这样一种站在制高点的全局认识,你需要至少对文件有一个基本的认识。

对于开发人员来说,对于文件我觉得有必要对文件建立的全局认识包括以下:

  1. 二进制思维
  2. 编码思维
  3. 文件类型认识
  4. 文件读写常识

二进制思维

!!!!所有文件,不论是可执行文件、图片文件、视频文件、Word文件、压缩文件、txt文件,都没什么可神秘的,它们都是以0和1的二进制形式保存的

那既然所有文件都是以0和1的二进制形式保存的,那为什么我们打开不同的文件,展现出来的形式不一样呢?因为不同的文件编码不一样,当我们使用应用程序打开一个文件时,其实就是对应的程序对文件进行解码的结果。

彻底理解Java中的文件与IO(一)

编码思维

每种文件都有自己特定文件含义和二进制之间的映射关系

  • 编码:根据映射关系把文件中的内容以二进制形式保存
  • 解码:根据映射关系把二进制数据转换成特定的文件内容

可以粗暴的理解成,对于每一个特定类型的文件,都有对应的二进制到其特定内容的映射表,比如说对于Word文件,它肯定也有这样一个映射表:

2进制含义
01101000h
01100101e
01101100l
01101111o
01110010r
01101100l
01100100d
01011011红色字体
10100110粗体
............

以上映射表我瞎编的,只是为了理解

当我们在word文件里添加完内容之后,应用程序就开始根据映射表依次把我们填写的内容换成对应的二进制然后保存起来,相反,当我们用应用程序把word文件打开时,同样也是去映射表中查找,把对应的二进制转换成实际的内容。

所以基于以上思维,其实我们可以开发出自己的文件类型,只要我们把映射表能够完全设计出来,然后把对应的编码和解码程序完成即可。可以看到很多的软件它都有自己特定的文件类型,比如.xmind .java 等等

想进一步理解计算机中的编码思维,可以去看《编码,隐匿在计算机硬件背后的语言》这本书

编码思维验证

现在我们以一个.txt文件简单感受下上一小节中的文件编码思维,简单理解2点:

  • 任何文件都是以0和1的二进制形式保存的
  • 我们人类约定好某种文件的编码,然后应用程序就可以根据文件的编码进行解码

既然是验证.txt文件的编码和解码,就需要知道它的编码是什么。

txt文件的编码:

txt文件的编码很简单,就是以每8bit来表示ASCll中的一个字符。

以下表是我从www.ascii-code.com/ 网站上摘抄的对于"hello world"字符串每个字符对应的二进制值以及16进制值的映射

16进制2进制字符
6801101000h
6501100101e
6C01101100l
6F01101111o
2000100000
7701110111w
6F01101111o
7201110010r
6C01101100l
6401100100d

所以我们根据上面的映射表可以轻松的知道,对于字符串"hello world"的16进制形式:

68 65 6C 6F 20 77 6F 72 6C 64

(使用16进制表示二进制更简短,仅此而已)

接下来我们进行验证:

  1. 准备一个txt文件

如图,现在我桌面上有一个hello.txt文件,它里面存放的ASCII字符为:hello world 彻底理解Java中的文件与IO(一)

  1. 查看hello.txt文件二进制值

彻底理解Java中的文件与IO(一)

验证完全正确,和我们上面推算出来16进制值的完全一致。

有很多工具可以直接查看一个文件的二进制形式,比如使用notepad++(安装HEX-editor插件)可以直接查看

文件类型

这一小节我们就来简单理解下文件类型,文件类型通常以扩展名的形式体现,比如:.pdf,.jpg,.zip等,为什么要引入文件类型呢?这样也很好理解啦,在计算机思维里,都喜欢额外添加一些“标识”,只是为了方便人类高效率的处理,引入文件类型这个概念同样是更方便处理数据:

  • 不同文件的编码方式是不一样的,引入文件类型之后,那么我们就可以根据不同文件类型来使用不同的方式来解码。

接下来进入重点,下面内容对于理解Java IO中的字节流(InputStream/OutStream)和字符流(Reader/Writer)有帮助。

文件类型可以粗略分为两类:

  1. 一类是文本文件:文本文件的例子有普通的文本文件(.txt),程序源代码文件(.java)、HTML文件(.html)等;
  2. 另一类是二进制文件:二进制文件的例子有压缩文件(.zip)、PDF文件(.pdf)、MP3文件(.mp3)、Excel文件(.xlsx)等。基本上,对于文本文件里的每个二进制字节都是某个可打印字符的一部分,都可以用最基本的文本编辑器进行查看和编辑。二进制文件中,每个字节就不一定表示字符,可能表示颜色、字体、声音大小等,如果用基本的文本编辑器打开,一般都是满屏的乱码,需要专门的应用程序进行查看和编辑。

而Java中的字符流(Reader/Writer)就是专门用来处理文本文件,而字节流(InputStream/OutStream)就是专门用来处理二进制文件。

前面我们已经说过了,所有的文件都是以0和1的二进制形式保存的,那为什么还要进一步区分出文本文件呢?

  • 不区分就不行嘛,当然可以!
  • 直接用字节流去处理文本文件不可以?当然可以!(二进制思维:所有文件都是二进制形式保存)

所以区分是为了什么?这又不得不扯出计算机设计里的另外一个思维:我们喜欢把经常要用到的程序抽离出来单独形成一个组件

在开发过程中,对于绝大多数文本文件,或者说字符文件,我们很多时候需要看一下,既然拿来看,很定要以更加友好的形式展现在我们人类面前,所以就需要先进行解码/编码,然后再展现在我们人类面前。

而对于那些二进制文件,在开发过程中我们很少去看,只是传输,读写,传来传去,写来写去,从一个地方传输另外一个地方,从一个文件读然后写到另外一个文件。

综上所述,对于字符文件,由于我们经常有这样一个需求:需要把字符打印出来观察一下,或者直接把字符写入文件中,而这就需要在打印出来之间进行解码,在写入之前进行编码。

所以呢,基于上面提到的计算机设计思维,Java就把对文本文件的编码和解码工作单独抽离出来单独设计出了字符流(Reader/Writer),用于方便的处理字符文件,它和字节流有什么区别嘛?没有区别,只不过是帮我们事先做了编码和解码工作而已,所以我们直接用字节流去处理文本文件没有任何问题,只不过需要我们自己做编码和解码工作,仅此而已。

文件读写常识

对于文件的读写也有几个常识,这对于我们后面认识Java提供的各种文件处理类有一定帮助。

(1)文件读写发生2次数据复制:

彻底理解Java中的文件与IO(一)

操作系统所在的环境是内核态,应用程序是用户态,应用程序调用操作系统的功能,需要两次环境的切换,先从用户态切到内核态,再从内核态切到用户态,用户态/内核态的切换是有开销的。

  1. 硬盘相比内存访问速度很慢

操作系统和硬盘一般是按块批量传输,而不是按字节,以摊销延时开销,块大小一般至少为512字节,即使应用程序只需要文件的一个字节,操作系统也会至少将一个块读进来。

基于上面的2个常识:

因为用户态/内核态的切换是有开销的,且访问硬盘比较慢,所以我们每次读操作,应该尽量多读一点, 减少磁盘的访问。同样每次写操作,应该多写一点,最后一次性写入磁盘, 减少磁盘的访问

基于以上,应用程序经常使用一种常见的策略,即使用缓冲区。读文件时,即使目前只需要少量内容,但预知还会接着读取,就一次读取比较多的内容,放到读缓冲区,下次读取时,如果缓冲区有,就直接从缓冲区读,减少访问操作系统和硬盘。写文件时,先写到写缓冲区,写缓冲区满了之后,再一次性调用操作系统写到硬盘

彻底理解Java中的文件与IO(一) 3. 资源的操作都有打开和关闭的概念

像数据库的连接,文件操作,网络等资源打开之后都应该关闭。

比如说文件操作,打开文件会在操作系统内核建立一个有关该文件的内存结构,这个结构一般通过一个整数索引来引用,这个索引一般称为文件描述符。这个结构是消耗内存的,操作系统能同时打开的文件一般也是有限的,在不用文件的时候,应该记住关闭文件。

再比如说数据库和网络的连接,每创建一个连接都需要创建一个线程,而创建一个线程是需要给它分配一定内存的,如果建立连接之后不用了也应该要关闭掉,不然内存迟早会用完。

总的来说,资源的打开基本都是会消耗内存的,所以不再使用时应该关闭,避免无限制导致内存用完。

总结

本篇对文件这一概念就介绍到这,希望对你有帮助,下一篇正式开始讲述Java中的IO。努力更新中~

转载自:https://juejin.cn/post/7270120943288500224
评论
请登录