Java基础之文件概述

网友投稿 271 2022-10-26


Java基础之文件概述

一、基本概念和常识

下面,我们先介绍一些基本概念和常识,包括二进制思维、文件类型、文本文件的编码、文件系统和文件读写等。

1.1 二进制思维

为了透彻理解文件,我们首先要有一个二进制思维。 所有文件, 不论是可执行文件、图片文件、视频文件、Word文件、压缩文件、txt 文件,都没什么可神秘的,它们都是以0和1的二进制形式保存的。我们 所看到的图片、视频、文本,都是应用程序对这些二进制的解析结果。

作为程序员,我们应该有一个编辑器,能查看文件的二进制形式, 比如UltraEdit,它支持以十六进制进行查看和编辑。比如,一个文本文 件,看到的内容为:

打开十六进制编辑,看到的内容如图所示。

左边的部分就是其对应的十六进制,“hello"对应的十六进制 是"68656C 6C 6F”,对应ASCII码编号"104101108108111",“马"对应的 十六进制是"E9A9AC”,这是"马"的UTF-8编码。

1.2 文件类型

虽然所有数据都是以二进制形式保存的,但为了方便处理数据,高 级语言引入了数据类型的概念。文件处理也类似,所有文件都是以二进 制形式保存的,但为了便于理解和处理文件,文件也有文件类型的概 念。

文件类型通常以扩展名的形式体现,比如,PDF文件类型的扩展名 是.pdf,图片文件的一种常见扩展名是.jpg,压缩文件的一种常见扩展名 是.zip。每种文件类型都有一定的格式,代表着文件含义和二进制之间的映射关系。比如一个Word文件,其中有文本、图片、表格,文本可 能有颜色、字体、字号等,doc文件类型就定义了这些内容和二进制表 示之间的映射关系。有的文件类型的格式是公开的,有的可能是私有 的,我们也可以定义自己私有的文件格式。

对于一种文件类型,往往有一种或多种应用程序可以解读它,进行 查看和编辑,一个应用程序往往可以解读一种或多种文件类型。在操作 系统中,一种扩展名往往关联一个应用程序,比如.doc后缀关联Word应 用。用户通过双击试图打开某扩展名的文件时,操作系统查找关联的应 用程序,启动该程序,传递该文件路径给它,程序再打开该文件。

需要说明的是,给文件加正确的扩展名是一种惯例,但并不是强制 的,如果扩展名和文件类型不匹配,应用程序试图打开该文件时可能会 报错。另外,一个文件可以选择使用多种应用程序进行解读,在操作系 统中,一般通过右键单击文件,选择打开方式即可。

文件类型可以粗略分为两类:一类是文本文件;另一类是二进制文件。文本文件的例子有普通的文本文件(.txt),程序源代码文件 (.java)、HTML文件(.html)等;二进制文件的例子有压缩文件 (.zip)、PDF文件(.pdf)、MP3文件(.mp3)、Excel文件(.xlsx) 等。

基本上,文本文件里的每个二进制字节都是某个可打印字符的一部分,都可以用最基本的文本编辑器进行查看和编辑,如Windows上的notepad、linux上的vi。二进制文件中,每个字节就不一定表示字符, 可能表示颜色、字体、声音大小等,如果用基本的文本编辑器打开,一 般都是满屏的乱码,需要专门的应用程序进行查看和编辑。

1.3 文本文件的编码

对于文本文件,我们还必须注意文件的编码方式。文本文件中包含 的基本都是可打印字符,但字符到二进制的映射(即编码)却有多种方 式,如Ghttp://B18030、UTF-8,我们在第2章详细介绍过各种编码,这里就不 赘述了。

对于一个给定的文本文件,它采用的是什么编码方式呢?一般而言,我们是不知道的。那应用程序用什么编码方式进行解读呢?一般使 用某种默认的编码方式,可能是应用程序默认的,也可能是操作系统默 认的,当然也可能采用一些比较智能的算法自动推断编码方式。

对于UTF-8编码的文件,我们需要特别说明。有一种方式,可以标 记该文件是UTF-8编码的,那就是在文件最开头加入三个特殊字节 (0xEF 0xBB 0xBF),这三个特殊字节被称为BOM头,BOM是Byte Order Mark(即字节序标记) 的缩写。比如,对前面的hello.txt文件,带 BOM头的UTF-8编码的十六进制形式如图13-2所示。

图13-1和图13-2所示都是UTF-8编码,看到的字符内容也一样,但 二进制内容不一样,一个带BOM头,一个不带BOM头。

需要注意的是,不是所有应用程序都支持带BOM头的UTF-8编码文件,比如php就不支持BOM,如果PHP源代码文件带BOM头,PHP运行 就会出错。碰到这种问题时,前面介绍的二进制思维就特别重要,不要只看文件的显示,还要看文件背后的二进制。

另外,我们需要说明下文本文件的换行符。在Windows系统中,换 行符一般是两个字符"\r\n",即ASCII码的13('\r')和10('\n'),在 Linux系统中,换行符一般是一个字符"\n"。

1.4 文件系统

文件一般是放在硬盘上的,一个机器上可能有多个硬盘,但各种操作系统都会隐藏物理硬盘概念,提供一个逻辑上的统一结构。在 Windows中,可以有多个逻辑盘,如C、D、E等,每个盘可以被格式化 为一种不同的文件系统,常见的文件系统有FAT32和NTFS。在Linux 中,只有一个逻辑的根目录,用斜线/表示。Linux支持多种不同的文件 系统,如Ext2/Ext3/Ext4等。不同的文件系统有不同的文件组织方式、 结构和特点,不过,一般编程时,语言和类库为我们提供了统一的 API,我们并不需要关心其细节。

在逻辑上,Windows中有多个根目录,Linux中有一个根目录,每 个根目录下有一棵子目录和文件构成的树。每个文件都有文件路径 的概念,路径有两种形式:一种是绝对路径 ,另一种是相对路径 。

所谓绝对路径,是从根目录开始到当前文件的完整路径,在 Windows中,目录之间用反斜线分隔,如C:\code\hello.java,在Linux 中,目录之间用斜线分隔,如/Users/laoma/Desktop/code/hello.java。在 Java中,java.io.File类定义了一个静态变量File.separator,表示路径分隔 符,编程时应使用该变量而避免硬编码。

所谓相对路径,是相对于当前目录 而言的。在命令行终端上,通 过cd命令进入的目录就是当前目录;在Java中,通过 System.getProperty(“user.dir”)可以得到运行Java程序的当前目录。相对路径不以根目录开头,比如在Windows上,当前目录为D:\laoma, 相对路径为code\hello.java,则完整路径为D:\laoma\code\hello.java。

每个文件除了有具体内容,还有元数据信息 ,如文件名、创建时 间、修改时间、文件大小等。文件还有一个是否隐藏 的性质。在Linux 系统中,如果文件名以.开头,则为隐藏文件;在Windows系统中,隐藏 是文件的一个属性,可以进行设置。

大部分文件系统的文件和目录具有访问权限 的概念,对所有者、 用户组可以有不同的权限,具体权限包括读、写、执行。

文件名有大小写是否敏感 的概念。在Windows系统中,一般是大小 写不敏感的,而Linux则一般是大小写敏感的。也就是说,同一个目录下,abc.txt和ABC.txt在Windows中被视为同一个文件,而在Linux中则 被视为不同的文件。

操作系统中有一个临时文件 的概念。临时文件位于一个特定目 录,比如Windows 7中,临时文件一般位于“C:\Users\用户名 \AppData\Local\Temp”;Linux系统中,临时文件位于/tmp。操作系统会有一定的策略自动清理不用的临时文件。临时文件一般不是用户手工创建的,而是应用程序产生的,用于临时目的。

1.5 文件读写

文件是放在硬盘上的,程序处理文件需要将文件读入内存,修改后,需要写回硬盘。操作系统提供了对文件读写的基本API,不同操作 系统的接口和实现是不一样的,不过,有一些共同的概念。Java封装了 操作系统的功能,提供了统一的API。

一个基本常识是:硬盘的访问延时,相比内存,是很慢的 。操作系统和硬盘一般是按块批量传输,而不是按字节,以摊销延时开销,块 大小一般至少为512字节,即使应用程序只需要文件的一个字节,操作 系统也会至少将一个块读进来。一般而言,应尽量减少接触硬盘,接触 一次,就一次多做一些事情。对于网络请求和其他输入输出设备,原则 都是类似的。

另一个基本常识是:一般读写文件需要两次数据复制 ,比如读文件,需要先从硬盘复制到操作系统内核,再从内核复制到应用程序分配的内存中。操作系统运行所在的环境和应用程序是不一样的,操作系统所在的环境是内核态,应用程序是用户态,应用程序调用操作系统的功能,需要两次环境的切换,先从用户态切到内核态,再从内核态切到用 户态。这种用户态/内核态的切换是有开销的,应尽量减少这种切换。

为了提升文件操作的效率,应用程序经常使用一种常见的策略,即使用缓冲区 。读文件时,即使目前只需要少量内容,但预知还会接着读取,就一次读取比较多的内容,放到读缓冲区,下次读取时,如果缓 冲区有,就直接从缓冲区读,减少访问操作系统和硬盘。写文件时,先 写到写缓冲区,写缓冲区满了之后,再一次性调用操作系统写到硬盘。 不过,需要注意的是,在写结束的时候,要记住将缓冲区的剩余内容同步到硬盘。操作系统自身也会使用缓冲区,不过,应用程序更了解读写模式,恰当使用往往可以有更高的效率。

操作系统操作文件一般有打开和关闭的概念 。打开文件会在操作系统内核建立一个有关该文件的内存结构,这个结构一般通过一个整数索引来引用,这个索引一般称为文件描述符 。这个结构是消耗内存 的,操作系统能同时打开的文件一般也是有限的,在不用文件的时候, 应该记住关闭文件 。关闭文件一般会同步缓冲区内容到硬盘,并释放占据的内存结构。

操作系统一般支持一种称为内存映射文件 的高效的随机读写大文 件的方法,将文件直接映射到内存,操作内存就是操作文件。在内存映 射文件中,只有访问到的数据才会被实际复制到内存,且数据只会复制 一次,被操作系统以及多个应用程序共享。

二、Java文件概述

在Java中处理文件有一些基本概念和类,包括流、装饰器设计模 式、Reader/Writer、随机读写文件、File、NIO、序列化和反序列化,下 面分别介绍。

2.1 流

在Java中(很多其他语言也类似),文件一般不是单独处理的,而是视为输入输出(Input/Output,IO)设备的一种。Java使用基本统一的 概念处理所有的IO,包括键盘、显示终端、网络等。

这个统一的概念是流 ,流有输入流 和输出流之分。输入流就是可以从中获取数据,输入流的实际提供者可以是键盘、文件、网络等;输出流就是可以向其中写入数据,输出流的实际目的地可以是显示终端、 文件、网络等。

Java IO的基本类大多位于包java.io中。类InputStream表示输入流, OutputStream表示输出流,而FileInputStream表示文件输入流, FileOutputStream表示文件输出流。

有了流的概念,就有了很多面向流的代码,比如对流做加密、压缩、计算信息摘要、计算检验和等,这些代码接受的参数和返回结果都 是抽象的流,它们构成了一个协作体系,这类似于之前介绍的接口概 念、面向接口的编程,以及容器类协作体系。 一些实际上不是IO的数据源和目的地也转换为了流,以方便参与这种协作,比如字节数组,也包装为了流ByteArrayInputStream和ByteArrayOutputStream。

2.2 装饰器设计模式

基本的流按字节读写,没有缓冲区,这不方便使用。Java解决这个 问题的方法是使用装饰器设计模式,引入了很多装饰类,对基本的流增 加功能,以方便使用。一般一个类只关注一个方面,实际使用时,经常会需要多个装饰类。

Java中有很多装饰类,有两个基类:过滤器输入流FilterInputStream 和过滤器输出流FilterOutputStream。过滤类似于自来水管道,流入的是 水,流出的也是水,功能不变,或者只是增加功能。它有很多子类,这 里列举一些:

1)对流起缓冲装饰的子类是BufferedInputStream和 BufferedOutputStream。

2)可以按8种基本类型和字符串对流进行读写的子类是 DataInputStream和DataOutput-Stream。

3)可以对流进行压缩和解压缩的子类有GZIPInputStream、 ZipInputStream、GZIPOutput-Stream和ZipOutputStream。

4)可以将基本类型、对象输出为其字符串表示的子类有 PrintStream。

众多的装饰类使得整个类结构变得比较复杂,完成基本的操作也需 要比较多的代码;其优点是非常灵活,在解决某些问题时也很优雅。

2.3 Reader/Writer

以InputStream/OutputStream为基类的流基本都是以二进制形式处理 数据的,不能够方便地处理文本文件,没有编码的概念,能够方便地按字符处理文本数据的基类是Reader和Writer,它也有很多子类:

1)读写文件的子类是FileReader和FileWriter。

2)起缓冲装饰的子类是BufferedReader和BufferedWriter。

3)将字符数组包装为Reader/Writer的子类是CharArrayReader和 CharArrayWriter。

4)将字符串包装为Reader/Writer的子类是StringReader和 StringWriter。

5)将InputStream/OutputStream转换为Reader/Writer的子类是 InputStreamReader和OutputStreamWriter。

6)将基本类型、对象输出为其字符串表示的子类是PrintWriter。

2.4 随机读写文件

大部分情况下,使用流或Reader/Writer读写文件内容,但Java提供 了一个独立的可以随机读写文件的类RandomAccessFile,适用于大小已 知的记录组成的文件。该类在日常应用开发中用得比较少,但在一些系 统程序中用得比较多。

2.5 File

上面介绍的都是操作数据本身,而关于文件路径、文件元数据、文 件目录、临时文件、访问权限管理等,Java使用File这个类来表示。

2.6 NIO

以上介绍的类基本都位于包java.io下,Java还有一个关于IO操作的 包java.nio,nio表示New IO,这个包下同样包含大量的类。

NIO代表一种不同的看待IO的方式,它有缓冲区和通道的概念。 利 用缓冲区和通道往往可以达成和流类似的目的,不过,它们更接近操作系统的概念,某些操作的性能也更高。比如,复制文件到网络,通道可 以利用操作系统和硬件提供的DMA机制(Direct Memory Access,直接 内存存取),不用CPU和应用程序参与,直接将数据从硬盘复制到网卡。

除了看待方式不同,NIO还支持一些比较底层的功能,如内存映射 文件、文件加锁、自定义文件系统、非阻塞式IO、异步IO等。

不过,这些功能要么是比较底层,普通应用程序用到得比较少,要 么主要适用于网络IO操作,我们大多不会介绍,只会介绍内存映射文 件。

2.7 序列化和反序列化

简单来说,序列化就是将内存中的Java对象持久保存到一个流中, 反序列化就是从流中恢复Java对象到内存。序列化和反序列化主要有两个用处:一是对象状态持久化,二是网络远程调用,用于传递和返回对象。

Java主要通过接口Serializable和类 ObjectInputStream/ObjectOutputStream提供对序列化的支持,基本的使用 是比较简单的,但也有一些复杂的地方。不过,Javahttp://的默认序列化有一 些缺点,比如,序列化后的形式比较大、浪费空间,序列化/反序列化 的性能也比较低,更重要的问题是,它是Java特有的技术,不能与其他 语言交互。

XML是前几年最为流行的描述结构性数据的语言和格式,Java对象也可以序列化为XML格式。XML容易阅读和编辑,且可以方便地与其 他语言进行交互。XML强调格式化但比较“笨重”,jsON是近几年来逐 渐流行的轻量级的数据交换格式,在很多场合替代了XML,也非常容 易阅读和编辑。Java对象也可以序列化为JSON格式,且与其他语言进行交互。

XML和JSON都是文本格式,人容易阅读,但占用的空间相对大一 些,在只用于网络远程调用的情况下,有很多流行的、跨语言的、精简 且高效的对象序列化机制,如ProtoBuf、Thrift、MessagePack等。其 中,MessagePack是二进制形式的JSON,更小更快。

文件看起来是一件非常简单的事情,但实际却没有那么简单,Java 的设计也不是太完美,包含了大量的类,这使得对于文件的理解变得困 难。为便于理解,我们将采用以下思路在接下来的章节中进行探讨。

首先,我们介绍如何处理二进制文件,或者将所有文件看作二进制,介绍如何操作,对于常见操作,我们会封装,提供一些简单易用的方法。下一步,我们介绍如何处理文本文件,我们会考虑编码、按行处 理等,同样,对于常见操作,我们会封装,提供简单易用的方法。接下 来,我们介绍文件本身和目录操作File类,我们也会封装常见操作。以 上这些内容是文件处理的基本技术,我们会在本章进行讨论。

在日常编程中,我们经常会需要处理一些具体类型的文件,如属性 文件、CSV文件、Excel文件、HTML文件和压缩文件,直接使用字节 流/字符流来处理一般是很不方便的,往往有一些更为高层的API,关于 这些,我们下章介绍。此外,下章还会介绍比较底层的对文件的操作 RandomAccessFile类、内存映射文件,以及序列化。文件看上去应该很 简单,但实际却包含很多内容,让我们耐住性子,下一节,先从二进制 开始。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:小白看学习前端内容
下一篇:dubbo之rmi协议使用
相关文章

 发表评论

评论列表