JAVA文件IO操作

发布于2019年09月06日 ⇲禁止转载于copy

笔记属于应用和基础原理层面,为浅谈;不涉及源代码解析。

引子

当我们要将Java程序中产生的数据永久性地保存起来的时候,应该想到的是将数据存到文件当中。将数据存入文件和将数据从文件中取出就涉及到了I/O的输入输出技术。Java提供了I/O操作可以将数据保存到多种类型的文件当中(当然每种特定格式的文件有其独特的构造方式和存取方式),本篇笔记将带你了解操作各种类型文件都具有的共同部分,了解了这些共同之处,就算遇到鲜有接触的文件格式也能灵活驾驭。

包括内容:File类、各种流类、字符的编码、对象序列化

1、File类

File类

是IO包中唯一代表磁盘文件本身的对象,进一步说就是File类起到了一个文件句柄的作用,所有对磁盘文件层面的操作都需要通过它,所有对磁盘文件的存取操作都需要先通过获取File类来完成。还有值得注意的是File类仅代表的是外部设备中的磁盘文件,不包括网络等其他外部设备文件。

此外File类定义了与平台无关的方法来操作文件。通过File类提供的方法,可以创建(包括设置可读性)、删除、重命名文件,可以判断文件读写权限、判断文件是否存在、设置和查询文件最近修改时间、判断当前文件是否是目录(也就是常说的文件夹)、获得文件名、获得文件的父目录名、获得文件的长度、获得文件的路径/相对路径/绝对路径。

所以,文件层面的操作一般都需要经过File类。

代码示例:

		File file = new File("d:\\testFileClass.txt");
		System.out.print("生成File对象之后文件是否存在:");
		System.out.println(file.exists());
		System.out.print("调用creatNewFile方法之后文件是否存在:");
		file.createNewFile();
		System.out.println(file.exists());
		System.out.print("获得文件对象名称:");
		System.out.println(file.getName());
		System.out.print("获得文件对象的路径:");
		System.out.println(file.getPath());
		System.out.print("获得文件对象的绝对路径:");
		System.out.println(file.getAbsolutePath());
		System.out.print("获得文件对象的父目录:");
		System.out.println(file.getParent());
		System.out.print("判断文件对象是否可读:");
		System.out.println(file.canRead());
		System.out.print("判断文件对象是否可写:");
		System.out.println(file.canWrite());
		System.out.print("判断文件对象是否是目录:");
		System.out.println(file.isDirectory());
		System.out.print("判断文件对象是否据对路径生成的对象:");
		System.out.println(file.isAbsolute());
		System.out.print("判断文件对象是否文件(可能是目录):");
		System.out.println(file.isFile());
		System.out.print("获得文件对象最后的修改时间:");
		System.out.println(file.lastModified());
		System.out.print("获得文件对象的大小:");
		System.out.println(file.length());
		System.out.print("调用delete()方法之后对象是否存在:");
		file.delete();
		System.out.println(file.exists());

代码结果:
在这里插入图片描述

这里再提一个类:RandomAccessFile类(随机访问文件类),有别于File类属于文件句柄,RandomAccessFile类属于文件访问类。

RandomAccessFile类

①RandomAccessFile类的优势:它像数组和数组列表(ArrayList)那样支持随机下标访问,当文件具有数据记录等长格式的特点时RandomAccessFile类访问文件具有很大的优势。
②RandomAccessFile类的劣势:RandomAccessFile类仅限操作磁盘文件,不能操作像网络、内存映像等IO文件。

备注:RandomAccessFile类随机访问文件采用的是字节级别的,不能直接读写字符和字符串,注意转化,随机坐标的跳转单位也是以字节为单位的。

2、流类

书中并没有直接给出流类的精确定义,可以将流理解为有序的一串字节序列,流类对文件的输入输出操作方法提供了标准的方法。它的存在使得对文件的操作转化为了对字节或字符序列的操作,标准统一。

流类概览图
流类的体系:有四个顶层的抽象类,分别是InputStream、OutputStream、Reader、Writer。InputStream和OutputStream分别对应的是输入字节流和输入字节流,Reader和Writer分别对应的是输入字符流和输出字符流。

读者可能会深感越学越混乱,学到后面各个类的英文名字傻傻分不清,特别是在工作中遇到得代码里,这时你需要先分理解好以下几点:
①字节流和字符流分别是什么,一般8个比特位为一字节,二进制文件为了方便操作都以字节为单位进行读写,而不是以比特位为单位进行读写,不然那样的话会很繁琐;一个字符等于和多少字节等价需要看各类字符编码方式的“各种规矩”了。字节流更方便的胜任于处理二进制文件;字符流更胜任于处理字符文件。因此字节流和字符流是两个都具有重要作用的流类。
②InputStream和OutputStream属于字节流,以后大部分名字里带有这些英文的都属于字节流(像FileInputStream就是一个很好的例子),把这一长串英文和字节流的冗长结合起来就容易记住了。Reader和Writer属于字节流,以后大部分名字里带有这些英文的都属于字符流(像FileReader就是一个很好的例子),Read和Write有朗读和手写的意思,而朗读和手写只能是读写字符,总不能读写字节,这样去理解很快就记住了。

流类读写文件的共同操作步骤

①通过File类获得一个文件对象
②通过获得的文件对象去实例化一个字节流或字符流的子类
③进行字节或字符的读写
④不需要进行读写时关闭流类打开的读写流(也就是常说的关闭资源,避免占用资源造成浪费)。

字节流(InputStream和OutputStream)的子类FileInputStream和FileOutputStream用于文件的字节输入输出操作,字符流(Reader和Writer)的子类FileReader和FileWriter用于文件的字符输入输出操作。大多数操作都在InputStream、OutPutStream中Reader、Writer中定义好了,并且部分操作查阅API即可看懂,故这里不再赘述了。

值得注意:在字符输出流类中定义了一个flush()方法,用来清除缓存,对于初学者来说这是无法理解的。会提问为什么字节输入输出流不需要用到内存中的缓存,而字符输入输出流类却需要用到内存缓存呢?可以看下图,

在这里插入图片描述
FileWrite类没有直接继承Write类,转而继承了OnputStreamWriter类,其实这是字节流和字符流相互转换的类,字符的写本质上还是先在内存的缓存中转化为字节,字符的读也是同理。写缓存不会在将字符转化为字节后马上将其写入文件中,它会等写出操作将内存填充满之后写到文件中,这时便会出现一种情况:在java程序做最后一次字符文件流写操作后,由于代码逻辑不需要调用再写操作而直接将流关闭,如果不调用flush()方法强制清空缓存的话会使部分信息没有成功写入文件。

字节流输出流代码展示:

		File file = new File("d:\\testFileOutputStream.txt");
		FileOutputStream fouts = new FileOutputStream(file);
		byte[] temp = new String("hello,for test!").getBytes();
		fouts.write(temp);
		fouts.close();

代码结果:
在这里插入图片描述

字符流输出流代码展示:

		File file = new File("d:\\testFileWriter.txt");
		FileWriter fWriter = new FileWriter(file);
		String str = new String("hello, for test!");
		fWriter.write(str);
		fWriter.close();//fWriter.flush();

代码展示
在这里插入图片描述

你以为流类的知识到这就结束了?

怎么可能~

除了文件流之外,还有管道流

管道流

主要用于两个线程之间通信。管道流分为字节管道流和字符管道流,PipedInputStream(管道字节输入流)、PipedOutputStream(管道字节输出流)、PipedReader(管道字符输入流)、PipedWriter(管道字符输出流)。线程之间通信一般以管道字节流为主,所以我们主要介绍一下管道字节流,字符流同理即可。
管道字节流中一个PipedInputStream对象需与另一个PipedOutputStream对象进行链接操作才可以形成一个管道流,管道输出流只需调用connect方法并传入管道输入流就能形成链接,Java会自动生成管道字节流。如下图。

在这里插入图片描述
完成链接形成管道流之后,PipedInputStream可以获取PipedOutputStream写入的字节数据,PipedInputStream和PipedOutputStream可位于两个不同的线程里。

此外,还有字节数组流

ByteArrayInputStream和ByteArrayOutputStream

其中ByteArrayInputStream是输入法的一种实现,它的构造方法有点不一样,需要传入一个字节数组对象来作为数据源。从ByteArrayInputStream读入内存A的字节,到ByteArrayOutputStream输出中写入数据到内存B,最后将内存B的数据获取出来即可。

字节数组流优势场景:当程序运行过程中要产生了一些临时文件,可以采用虚拟文件方式实现,字节数组流两个类可以实现类似于内存虚拟文件的功能。

System.in 和 System.out这两个类分别用来表示键盘和显示器。这两个类用来支持标准的输入输出设备。System.in属于字节流InputStream,所以我们一般不直接使用System.in,而是会采用包装类,使他适合符合我们习惯的字符输入的方式。System.out属于PrintStream,PrintStream是字节流OutputStream的一个子类。

故键盘可以被当做一个特殊的输入流,显示器可以被当作一个特殊的输出流。

System.out类的print()和println()方法实现了将字符转化为字节的转化操作,所以入门写helloworld时可以不用采用包装类我们也能使用System.out.println()打印字符。

DataInputStream和DataOutputStream(数据字节输入流和数据字节输出流)

DataInputStream和DataOutputStream提供了与平台无关的数据操作,通常先通过DataOutputStream按照一定的数据格式将数据输出,然后通过DataInputStream按照一定的数据格式读入。由于可以直接输出和读入java的数据基本类型(甚至是字符串),这种方便性使得它适用于通过协议传输的信息的网络通信。

SequenceInputStream文件合并流

简单来说就是可以将两个文件简单的读入并按先后顺序合并为一个文件流,可直接读取文件流,也可以通过文件输出流将其输出到一个文件中。

字节流与字符流的转化

Java支持字节流和字符流,他们各有应用的场景,有时候会出现这样的情况:我们希望程序按照字符流来处理,因为字符流的处理更符合我们的习惯,能够降低代码编写的复杂度;但是往往有些场景只允许我们使用字节流,如果我们拥有字节流和字符流转化的类就能轻松解决这些难处。这便是字节流和字符流转化类存在的意义。

InputStreamReader 和 OutputStreamWriter这两个类是字节类和字符类之间相互转化的类,InputStreamReader用于将一个输入字节流中的字节编码成字符,OutputStreamWriter用于将一个输出的字符解码成字节后写入一个字节流中。

为了达到更高的效率,因尽量采用BufferReader类包装InputStreamReader,用BufferWriter类包装OutputStreamWriter。顾名思义,BufferReader就是带缓冲的输入字符流Reader,即将字符流进一步包装,提高效率,BufferWriter即带缓冲的Writer。

注意:记得前面讲过System.in和System.out是字节流,通过转化类再通过包装类即可实现。如下图所示代码:

代码展示:

		BufferedReader bReader = new BufferedReader(new InputStreamReader(System.in));
		System.out.println("enter some string and end with \\n:");
		String reString = bReader.readLine();
		System.out.println("the message you enter is:"+reString);

代码结果:
在这里插入图片描述

3、字符编码

主要了解各种编码的由来即可:
常见的字符有大小写英文字符、像各种标点符号特殊符号、还有制表符换行符等、当然还包括中文字、各过语言的基本组成文字。

ASCII码是最早出现的编码,英文当时早期计算机系统只存在英文字符和有限的特殊字符,所以1个字节(8比特位)的容量2的8次方足以容纳这些字符了,所以ASCII编码里一个字节就足以表示一个字符了。后来随着中文和各中语言文字的加入,为了规范统一而不造成乱码,出现了更多的编码方式,像GBK,GBK编码中对于ASCII码原有的字符编码保留,但是新增的字符有两个字节来表示,为了能够好的实现,将新增字符的每个字节的最高为都置为1(由此可见ASCII码的字节最高位都为0),后来有扩展到了Unicod编码,两个字节代表一个字符,现在好像到了3字节。

4、对象序列化

所谓对象序列化就是指将对象转换成二进制数据流的一种实现手段。如果对象是声明了可以序列化的,可以方便的实现对象的传输和存储。

Java中提供了ObjectOutputStream和ObjectInputStream这两个类用于序列化对象。这两个类是用于存储和读取对象的输入输出类。

对象需要实现Serializable接口,仅仅做一个标记,已被编译器特殊处理。

如果对象中某些属性不想被序列化,可以属性声明之前加上Transient即可,那么在存储中这个属性将会呈现null值。

学习笔记参考书籍:
《Java从入门到精通》 作者:国家863中部软件孵化器

本文如有不正确的地方,欢迎指出,欢迎交流。

猜你喜欢

转载自blog.csdn.net/liangcheng0523/article/details/100587848