File以及IO流
File类以及IO流
File是java.io.包下的类,File类的对象,用于代表当前操作系统的文件(可以是文件、或文件夹。现在要学习的File类,它的就用来表示当前系统下的文件(也可以是文件夹),通过File类提供的方法可以获取文件大小、判断文件是否存在、创建文件、创建文件夹等。
但是需要我们注意:File对象只能对文件进行操作,不能操作文件中的内容。
所以,我们需要IO流来对文件中的数据进行读取并且写入。
File: 代表文本
IO流: 读写数据(可以读写文件,或网络中的数据
File类的使用
File类的创建对象用的构造方法:
构造器 | 说明 |
---|---|
public File(string pathname |
根据文件路径创建文件对象 |
public File(String parent,String child) |
根据父路径和子路径名字创建文件对象 |
public File(File parent,String child) |
根据父路径对应文件对象和子路径名字创建文件对象 |
下面我们演示一下,File类创建对象的代码
1 | 需求我们注意的是:路径中"\"要写成"\\", 路径中"/"可以直接用 |
1 | /** |
File判断和获取方法
刚才我们创建File对象的时候,会传递一个文件路径过来。但是File对象封装的路径是存在还是不存在,是文件还是文件夹其实是不清楚的。好在File类提供了方法可以帮我们做判断。
方法名称 | 说明 |
---|---|
public boolean exists() |
判断当前文件对象,对应的文件路径是否存在,存在返回true |
public boolean isFile() |
判断当前文件对象指代的是否是文件,是文件返回true,反之返回false |
public boolean isDirectory() |
判断当前文件对象指代的是否是文件,是文件返回true,反之返回false |
public boolean isDirectory() |
判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之返回false |
public String getName() |
获取文件的名称(包含后缀) |
public long length() |
获取文件的大小,返回字节个数 |
public long lastModified() |
获取文件的最后修改时间。 |
public String getPath() |
获取创建文件对象时,使用的路径 |
public String getAbsolutePath() |
获取绝对路径 |
话不多少,直接上代码
1 | /** |
除了判断功能还有一些获取功能,看代码
1 | File f1 = new File("D:/resource/ab.txt"); |
创建和删除方法
Java可以代码创建一个文件或者文件夹,不光可以创建还可以删除。
File类提供了创建和删除文件的方法,话不多少,看代码。
File类创建文件的功能
方法名称 | 说明 |
---|---|
public boolean createNewFile() |
创建一个新的空的文件 |
public boolean mkdir() |
只能创建一级文件夹 |
public boolean mkdirs() |
可以创建多级文件夹 |
File类删除文件的功能
方法名称 | 说明 |
---|---|
public boolean delete() |
删除文件、空文件夹 |
注意: delete方法默认只能删除文件和空文件夹,删除后的文件不会进入回收站
需要注意的是:
mkdir()
: 只能创建单级文件夹、mkdirs()
: 才能创建多级文件夹delete()
: 文件可以直接删除,但是文件夹只能删除空的文件夹,文件夹有内容删除不了。
1 | /** |
遍历文件夹方法
有人说,想获取到一个文件夹中的内容,有没有方法呀?也是有的,下面我们就学习两个这样的方法。
方法名称 | 说明 |
---|---|
public String[] list() |
获取当前目录下所有的”一级文件名称”到一个字符串数组中去返回 |
public Filel] istFiles() |
获取当前目录下所有的”一级文件对象”到一个文件对象数组中去返回(重点) |
话不多少上代码,演示一下
1 | /** |
这里需要注意几个问题:
- 当主调是文件时,或者路径不存在时,返回null
- 当主调是空文件夹时,返回一个长度为0的数组
- 当主调是一个有内容的文件夹时,将里面所有一级文件和文件夹路径放在File数组中,并把数组返回
- 当主调是一个文件夹,且里面有隐藏文件时,将里面所有文件和文件夹的路径放在File数组中,包含隐藏文件
- 当主调是一个文件夹,但是没有权限访问时,返回null
目前方法还只能获取当前目录下所有的”一级文件对象”,需要获取到下面一级目录的话,需要用到递归的算法来进行。
递归
什么是递归?
递归是一种算法,从形式上来说,方法调用自己的形式称之为递归。
递归的形式:有直接递归、间接递归,如下面的代码。
1 | /** |
如果直接执行上面的代码,会进入死循环,最终导致栈内存溢出
递归算法的执行流程
为了弄清楚递归的执行流程,接下来我们通过一个案例来学习一下。
案例需求:计算n的阶乘,比如5的阶乘 = 1 * 2 * 3 * 4 * 5 ; 6 的阶乘 = 1 * 2 * 3 * 4 * 5 * 6
分析需求用递归该怎么做
1 | 假设f(n)表示n的阶乘,那么我们可以推导出下面的式子 |
我们可以把f(n)当做一个方法,那么方法的写法如下
1 | /** |
这个代码的执行流程,我们用内存图的形式来分析一下,该案例中递归调用的特点是:一层一层调用,再一层一层往回返。
递归文件搜索
- 先调用文件夹的listFiles方法,获取文件夹的一级内容,得到一个数组
- 然后再遍历数组,获取数组中的File对象
- 因为File对象可能是文件也可能是文件夹,所以接下来就需要判断
- 判断File对象如果是文件,就获取文件名,如果文件名是
QQ.exe
则打印,否则不打印 - 判断File对象如果是文件夹,就递归执行1,2,3步骤
- 判断File对象如果是文件,就获取文件名,如果文件名是
- 所以:把1, 2, 3步骤写成方法,递归调用即可。
代码如下:
1 | /** |
字符集
前面我们已经了解了File类,通过File类的对象可以对文件进行操作,但是不能操作文件中的内容。要想操作文件中的内容,我们还得学习IO流。但是在正式学习IO流之前,我们还需要学习一个前置知识叫做字符集,只有我们把字符集搞明白了,再学习IO流才会更加丝滑。
字符集的来历
我们知道计算机是美国人发明的,由于计算机能够处理的数据只能是0和1组成的二进制数据,为了让计算机能够处理字符,于是美国人就把他们会用到的每一个字符进行了编码(所谓编码,就是为一个字符编一个二进制数据),如下图所示:
美国人常用的字符有英文字母、标点符号、数字以及一些特殊字符,这些字符一共也不到128个,所以他们用1个字节来存储1字符就够了。 美国人把他们用到的字符和字符对应的编码总结成了一张码表,这张码表叫做ASCII码表(也叫ASCII字符集)。
标准ASCII字符集:ASCII(American Standard Code for lnformation Interchange)
: 美国信息交换标准代码,包括了英文、符号等
标准ASCII使用1个字节存储一个字符,首尾是0,总共可表示128个字符,对美国人来说完全够用。
其实计算机只在美国用是没有问题的,但是计算机慢慢的普及到全世界,当普及到中国的时候,在计算机中想要存储中文,那ASCII字符集就不够用了,因为中文太多了,随便数一数也有几万个字符。
于是中国人为了在计算机中存储中文,也编了一个中国人用的字符集叫做GBK字符集,这里面包含2万多个汉字字符,GBK中一个汉字采用两个字节来存储,为了能够显示英文字母,GBK字符集也兼容了ASCII字符集,在GBK字符集中一个字母还是采用一个字节来存储。
GBK(汉字内码扩展规范,国标)
汉字编码字符集,包含了2万多个汉字等字符,GBK中一个中文字符编码成两个字节的形式存储。
注意: GBK兼容了ASCII字符集。
Unicode字符集(统一码,也叫万国码)
Unicode是国际组织制定的,可以容纳世界上所有文字、符号的字符集。
UTF-8
是Unicode字符集的一种编码方案,采取可变长编码方案,共分四个长度区:1个字节,2个字节,3个字节,4个字节。
- UTF-8是一种可变长的编码方案,工分为4个长度区
- 英文字母、数字占1个字节兼容(ASCII编码)
- 汉字字符占3个字节
- 极少数字符占4个字节
字符集小结
最后,我们将前面介绍过的字符集小结一下
- ASCII字符集:《美国信息交换标准代码》,包含英文字母、数字、标点符号、控制字符
- 特点:1个字符占1个字节
- GBK字符集:中国人自己的字符集,兼容ASCII字符集,还包含2万多个汉字
- 特点:1个字母占用1个字节;1个汉字占用2个字节
- Unicode字符集:包含世界上所有国家的文字,有三种编码方案,最常用的是UTF-8
- UTF-8编码方案:英文字母、数字占1个字节兼容(ASCII编码)、汉字字符占3个字节
注意1:字符编码时使用的字符集,和解码时使用的字符集必须一致,否则会出现乱码
注意2:英文,数字一般不会乱码,因为很多字符集都兼容了ASCII编码
编码和解码
接下来,我们来使用Java代码完成编码和解码的操作。
其实String类类中就提供了相应的方法,可以完成编码和解码的操作。
- 编码:把字符串按照指定的字符集转换为字节数组
- 解码:把字节数组按照指定的字符集转换为字符串
java代码完成对字符的编码:
string提供了如下方法 | 说明 |
---|---|
byte[] getBytes() |
使用平台的默认字符集将该 string编码为一系列字节,将结果存储到新的字节数组中 |
byte[] getBytes(String charsetName) |
使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中 |
java代码完成对字符的解码:
string提供了如下方法 | 说明 |
---|---|
String(byte[] bytes) |
通过使用平台的默认字符集解码指定的字节数组来构造新的 string |
String(byte[] bytes,String charsetName) |
通过指定的字符集解码指定的字节数组来构造新的 string |
代码示例:
1 | /** |
IO流(字节流)
IO流的作用:就是可以对文件或者网络中的数据进行读、写的操作。如下图所示
- 把数据从磁盘、网络中读取到程序中来,用到的是输入流。
- 把程序中的数据写入磁盘、网络中,用到的是输出流。
- 简单记:输入流(读数据)、输出流(写数据)
IO流在Java中有很多种,不同的流来干不同的事情。Java把各种流用不同的类来表示,这些流的继承体系如下图所示:
IO流分为两大派系:
- 字节流:字节流又分为字节输入流、字节输出流
- 字符流:字符流由分为字符输入流、字符输出流
总结流的四大类:
字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流
字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流
字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流
字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流。
FileInputStream读取一个字节
字节流中的字节输入流,用InputStream
来表示。但是InputStream
是抽象类,我们用的是它的子类,叫FileInputStream
。
构造方法、成员方法如下图所示:
构造器 | 说明 |
---|---|
public FileInputStream(File file) |
创建字节输入流管道与源文件接通 |
public FileInputStream(String pathname) |
创建字节输入流管道与源文件接通 |
方法名称 | 说明 |
---|---|
public int read() |
每次读取一个字节返回,如果发现没有数据可读会返回-1 |
public int read(byte[] buffer) |
每次用一个字节数组去读取数据,返回字节数组读取了多少个字节,如果发现没有数据可读会返回-1 |
使用FileInputStream
读取文件中的字节数据,步骤如下
第一步:创建FileInputStream文件字节输入流管道,与源文件接通。
第二步:调用read()方法开始读取文件的字节数据。
第三步:调用close()方法释放资源
1 | /** |
这里需要注意一个问题:由于一个中文在UTF-8编码方案中是占3个字节,采用一次读取一个字节的方式,读一个字节就相当于读了1/3个汉字,此时将这个字节转换为字符,是会有乱码的。
FileInputStream读取多个字节
上面我们用了FileInputStream调用read()方法,可以一次读取一个字节。但是这种读取方式效率太太太太慢了。 为了提高效率,我们可以使用另一个read(byte[] bytes)的重载方法,可以一次读取多个字节,至于一次读多少个字节,就在于你传递的数组有多大。
使用FileInputStream一次读取多个字节的步骤如下
第一步:创建FileInputStream文件字节输入流管道,与源文件接通。
第二步:调用read(byte[] bytes)方法开始读取文件的字节数据。
第三步:调用close()方法释放资源
代码如下:
1 | /** |
- 需要我们注意的是:read(byte[] bytes)它的返回值,表示当前这一次读取的字节个数。
假设有一个a.txt文件如下:abcde
每次读取过程如下
也就是说,并不是每次读取的时候都把数组装满,比如数组是 byte[] bytes = new byte[3];
第一次调用read(bytes)读取了3个字节(分别是97,98,99),并且往数组中存,此时返回值就是3
第二次调用read(bytes)读取了2个字节(分别是99,100),并且往数组中存,此时返回值是2
第三次调用read(bytes)文件中后面已经没有数据了,此时返回值为-1
注意事项:
- 还需要注意一个问题:采用一次读取多个字节的方式,也是可能有乱码的。因为也有可能读取到半个汉字的情况。
FileInputStream读取全部字节
前面我们到的读取方式,不管是一次读取一个字节,还是一次读取多个字节,都有可能有乱码。那么接下来我们介绍一种,不出现乱码的读取方式。
我们可以一次性读取文件中的全部字节,然后把全部字节转换为一个字符串,就不会有乱码了。
- 方式一:
1 | // 1、一次性读取完文件的全部字节到一个字节数组中去。 |
- 方式二:
1 | // 1、一次性读取完文件的全部字节到一个字节数组中去。 |
最后,还是要注意一个问题:一次读取所有字节虽然可以解决乱码问题,但是文件不能过大,如果文件过大,可能导致内存溢出。
FileOutputStream写字节
往文件中写数据需要用到OutputStream
下面的一个子类FileOutputStream
。写输入的流程如下图所示
使用FileOutputStream
往文件中写数据的步骤如下:
第一步:创建FileOutputStream文件字节输出流管道,与目标文件接通。
第二步:调用wirte()方法往文件中写数据
第三步:调用close()方法释放资源
代码如下:
1 | /** |
字节流复制文件
下面做一个文件复制的综合案例。
比如:我们要复制一张图片,从磁盘D:/resource/meinv.png
的一个位置,复制到C:/data/meinv.png
位置。
- 需要创建一个FileInputStream流与源文件接通,创建FileOutputStream与目标文件接通
- 然后创建一个数组,使用FileInputStream每次读取一个字节数组的数据,存如数组中
- 然后再使用FileOutputStream把字节数组中的有效元素,写入到目标文件中
代码如下:
1 | /** |
IO流资源释放
在我们之前写的代码中,可能会遇到一些bug,导致流无法被正常关闭。
我们现在知道这个问题了,那这个问题怎么解决呢? 在JDK7以前,和JDK7以后分别给出了不同的处理方案。
JDK7以前的资源释放
在JDK7版本以前,我们可以使用try...catch...finally
语句来处理。格式如下
1 | try{ |
改造上面的代码:
1 | public class Test2 { |
代码写到这里,就变得非常冗余了。是的,我也看不下去,本来几行代码就写完了的,加上try...catch...finally
之后代码多了十几行,而且阅读性并不高。
JDK7以后的资源释放
在了解了try...catch...finally
处理异常,并释放资源代码比较繁琐。Java在JDK7版本为我们提供了一种简化的是否资源的操作,它会自动是否资源。代码写起来也想当简单。
格式如下:
1 | try(资源对象1; 资源对象2;){ |
代码如下:
1 | /** |
字符流
前面我们学习了字节流,使用字节流可以读取文件中的字节数据。但是如果文件中有中文使用字节流来读取,就有可能读到半个汉字的情况,这样会导致乱码。虽然使用读取全部字节的方法不会出现乱码,但是如果文件过大又不太合适。
所以Java专门为我们提供了另外一种流,叫字符流,可以字符流是专门为读取文本数据而生的。
FileReader类
先类学习字符流中的FileReader类,这是字符输入流,用来将文件中的字符数据读取到程序中来。
FileReader读取文件的步骤如下:
第一步:创建FileReader对象与要读取的源文件接通
第二步:调用read()方法读取文件中的字符
第三步:调用close()方法关闭流
需要用到的方法:先通过构造器创建对象,再通过read方法读取数据(注意:两个read方法的返回值,含义不一样)
构造器 | 说明 |
---|---|
public FileReader(File file) |
创建字符输入流管道与源文件接通 |
public FileReader(String pathname) |
创建字符输入流管道与源文件接通 |
方法名称 | 说明 |
---|---|
public int read() |
每次读取一个字符返回,如果发现没有数据可读会返回-1。 |
public int read(char[] buffer) |
每次用一个字符数组去读取数据,返回字符数组读取了多少个字符,如果发现没有数据可读会返回-1。 |
1 | /** |
FileWriter类
学习了FileReader,它可以将文件中的字符数据读取到程序中来。接下来,我们就要学习FileWriter了,它可以将程序中的字符数据写入文件。
FileWriter往文件中写字符数据的步骤如下:
第一步:创建FileWirter对象与要读取的目标文件接通
第二步:调用write(字符数据/字符数组/字符串)方法读取文件中的字符
第三步:调用close()方法关闭流
需要用到的方法如下:构造器是用来创建FileWriter对象的,有了对象才能调用write方法写数据到文件。
构造器 | 说明 |
---|---|
public FileWriter(File file) |
创建字节输出流管道与源文件对象接通 |
public Filewriter(String filepath) |
创建字节输出流管道与源文件路径接通 |
public FileWriter(File file,boolean append) |
创建字节输出流管道与源文件对象接通,可追加数据 |
public FileWriter(String filepath,boolean append) |
创建字节输出流管道与源文件路径接通,可追加数据 |
构造器 | 说明 |
---|---|
void write(int c) |
写一个字符 |
void write(String str) |
写一个字符串 |
void write(String str, int off, int len) |
写一个字符串的一部分 |
void write(char[] cbuf) |
写入一个字符数组 |
void write(char[] cbuf, int off, int len) |
写入字符数组的一部分 |
接下来,用代码演示一下:
1 | /** |
FileWriter写的注意事项
刚才我们已经学习了FileWriter字符输出流的基本使用。但是,这里有一个小问题需要和同学们说下一:FileWriter写完数据之后,必须刷新或者关闭,写出去的数据才能生效。
比如:下面的代码只调用了写数据的方法,没有关流的方法。当你打开目标文件时,是看不到任何数据的。
方法名称 | 说明 |
---|---|
public void flush() throws IOException |
刷新流,就是将内存中缓存的数据立即写到文件中去生效! |
public void close() throws IOException |
关闭流的操作,包含了刷新! |
1 | //1.创建FileWriter对象 |
而下面的代码,加上了flush()方法之后,数据就会立即到目标文件中去。
1 | //1.创建FileWriter对象 |
下面的代码,调用了close()方法,数据也会立即到文件中去。因为close()方法在关闭流之前,会将内存中缓存的数据先刷新到文件,再关流。
1 | //1.创建FileWriter对象 |
但是需要注意的是,关闭流之后,就不能在对流进行操作了。否则会出异常。
字节流、字符流的使用场景小结
字节流适合做一切文件数据的拷贝(音视频,文本);字节流不适合读取中文内容输出
字符流适合做文本文件的操作(读,写)。
缓冲流
学习完字符流之后,接下来我们学习一下缓冲流。我们还是先来认识一下缓存流,再来说一下它的作用。缓冲流有四种,如下图所示
缓冲流的作用:可以对原始流进行包装,提高原始流读写数据的性能。
缓冲字节流
我们先来学习字节缓冲流是如何提高读写数据的性能的,原理如下图所示。是因为在缓冲流的底层自己封装了一个长度为8KB(8129byte)的字节数组,但是缓冲流不能单独使用,它需要依赖于原始流。
- 读数据时:它先用原始字节输入流一次性读取8KB的数据存入缓冲流内部的数组中(ps: 先一次多囤点货),再从8KB的字节数组中读取一个字节或者多个字节(把消耗屯的货)。
- 写数据时: 它是先把数据写到缓冲流内部的8BK的数组中(ps: 先攒一车货),等数组存满了,再通过原始的字节输出流,一次性写到目标文件中去(把囤好的货,一次性运走)。
在创建缓冲字节流对象时,需要封装一个原始流对象进来。构造方法如下
构造器 | 说明 |
---|---|
public BufferedInputStream(InputStream is) |
把低级的字节输入流包装成一个高级的缓冲字节输入流,从而提高读数据的性能 |
public BufferedOutputStream(OutputStream os) |
把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能 |
用缓冲流复制文件,代码写法如下:
1 | public class BufferedInputStreamTest1 { |
字符缓冲流
字符缓冲流。它的原理和字节缓冲流是类似的,它底层也会有一个8KB的数组,但是这里是字符数组。字符缓冲流也不能单独使用,它需要依赖于原始字符流一起使用。
BufferedReader读数据时:它先原始字符输入流一次性读取8KB的数据存入缓冲流内部的数组中(ps: 先一次多囤点货),再从8KB的字符数组中读取一个字符或者多个字符(把消耗屯的货)。
创建BufferedReader对象需要用到BufferedReader的构造方法,内部需要封装一个原始的字符输入流,我们可以传入FileReader.
构造器 | 说明 |
---|---|
public BufferedReader(Reader r) |
把低级的字符输入流包装成字符缓冲输入流管道,从而提高字符输入流读字符数据的性能 |
而且BufferedReader还要特有的方法,一次可以读取文本文件中的一行
方法 | 说明 |
---|---|
public String readLine() |
读取一行数据返回,如果没有数据可读了,会返回nu11 |
使用BufferedReader读取数据的代码如下
1 | public class BufferedReaderTest2 { |
BufferedWriter写数据时: 它是先把数据写到字符缓冲流内部的8BK的数组中(ps: 先攒一车货),等数组存满了,再通过原始的字符输出流,一次性写到目标文件中去(把囤好的货,一次性运走)
创建BufferedWriter
对象时需要用到BufferedWriter
的构造方法,而且内部需要封装一个原始的字符输出流,我们这里可以传递FileWriter
。
构造器 | 说明 |
---|---|
public Bufferedwriter(Wrter r) |
把低级的字符输出流包装成一个高级的缓冲字符输出流管道,从而提高字符输出流写数据的性能 |
而且BufferedWriter新增了一个功能,可以用来写一个换行符
方法 | 说明 |
---|---|
public void newLine() |
换行 |
接下来,用代码演示一下,使用BufferedWriter
往文件中写入字符数据。
1 | public class BufferedWriterTest3 { |
缓冲流性能分析
我们说缓冲流内部多了一个数组,可以提高原始流的读写性能。但是缓冲流不一定能提高性能。
下面我们用一个比较大文件(889MB)复制,做性能测试,分别使用下面四种方式来完成文件复制,并记录文件复制的时间。
使用低级流一个字节一个字节的复制
使用低级流按照字节数组的形式复制
使用缓冲流一个字节一个字节的复制
使用缓冲流按照字节数组的形式复制
1 | 低级流一个字节复制: 慢得简直让人无法忍受 |
经过上面的测试,我们可以得出一个结论:默认情况下,采用一次复制1024个字节,缓冲流完胜。
但是,缓冲流就一定性能高吗?我们采用一次复制8192个字节试试
1 | 低级流按照字节数组复制(数组长度8192): 2.535s |
经过上面的测试,我们可以得出一个结论:一次读取8192个字节时,低级流和缓冲流性能相当。相差的那几毫秒可以忽略不计。
继续把数组变大,看一看缓冲流就一定性能高吗?现在采用一次读取1024*32个字节数据试试
1 | 低级流按照字节数组复制(数组长度8192): 1.128s |
经过上面的测试,我们可以得出一个结论:数组越大性能越高,低级流和缓冲流性能相当。相差的那几秒可以忽略不计。
继续把数组变大,看一看缓冲流就一定性能高吗?现在采用一次读取1024*6个字节数据试试
1 | 低级流按照字节数组复制(数组长度8192): 1.039s |
此时你会发现,当数组大到一定程度,性能已经提高了多少了,甚至缓冲流的性能还没有低级流高。
最终总结一下:缓冲流的性能不一定比低级流高,其实低级流自己加一个数组,性能其实是不差。只不过缓冲流帮你加了一个相对而言大小比较合理的数组 。
转换流
前面我们学习过FileReader读取文件中的字符,但是我们需要注意,FileReader默认只能读取UTF-8编码格式的文件。如果使用FileReader读取GBK格式的文件,可能存在乱码,因为FileReader它遇到汉字默认是按照3个字节来读取的,而GBK格式的文件一个汉字是占2个字节,这样就会导致乱码。
Java给我们提供了另外两种流InputStreamReader
,OutputStreamWriter
,这两个流我们把它叫做转换流。它们可以将字节流转换为字符流,并且可以指定编码方案。
InputStreamReader类
接下来,我们先学习InputStreamReader
类,你看这个类名就比较有意思,前面是InputStream表示字节输入流,后面是Reader表示字符输入流,合在一起意思就是表示可以把InputStream转换为Reader,最终InputStreamReader其实也是Reader的子类,所以也算是字符输入流。
InputStreamReader也是不能单独使用的,它内部需要封装一个InputStream的子类对象,再指定一个编码表,如果不指定编码表,默认会按照UTF-8形式进行转换。
需求:我们可以先准备一个GBK格式的文件,然后使用下面的代码进行读取,看是是否有乱码。
1 | //GBK格式 |
1 | public class InputStreamReaderTest2 { |
执行完之后,你会发现没有乱码。
OutputStreamWriter类
接下来,我们学习OutputStreamWriter
类,你看这个类名也比较有意思,前面是OutputStream表示字节输出流,后面是Writer表示字符输出流,合在一起意思就是表示可以把OutputStream转换为Writer,最终OutputStreamWriter其实也是Writer的子类,所以也算是字符输出流。
OutputStreamReader也是不能单独使用的,它内部需要封装一个OutputStream的子类对象,再指定一个编码表,如果不指定编码表,默认会按照UTF-8形式进行转换。
需求:我们可以先准备一个GBK格式的文件,使用下面代码往文件中写字符数据。
1 | public class OutputStreamWriterTest3 { |
打印流
打印流,这里所说的打印其实就是写数据的意思,它和普通的write方法写数据还不太一样,一般会使用打印流特有的方法叫print(数据)
或者println(数据)
,它打印啥就输出啥。
打印流有两个,一个是字节打印流PrintStream
,一个是字符打印流PrintWriter
,如下图所示
PrintStream和PrintWriter的用法是一样的,所以这里就一块演示了。
1 | public class PrintTest1 { |
重定向输出语句
其实我们开学java的时候,就讲过System.out.println()
这句话表示打印输出,但是至于为什么能够输出,其实我们一直不清楚。
以前是因为知识储备还不够,无法解释,到现在就可以给同学们揭晓谜底了,因为System里面有一个静态变量叫out,out的数据类型就是PrintStream,它就是一个打印流,而且这个打印流的默认输出目的地是控制台,所以我们调用System.out.pirnln()
就可以往控制台打印输出任意类型的数据,而且打印啥就输出啥。
而且System还提供了一个方法,可以修改底层的打印流,这样我们就可以重定向打印语句的输出目的地了。我们玩一下, 直接上代码。
1 | public class PrintTest2 { |
此时打印语句,将往文件中打印数据,而不在控制台。
数据流
接下我们再学习一种流,这种流在开发中偶尔也会用到。比如,我们想把数据和数据的类型一并写到文件中去,读取的时候也将数据和数据类型一并读出来。这就可以用到数据流,有两个DataInputStream
和DataOutputStream
。
DataOutputStream类
我们先学习DataOutputStream
类,它也是一种包装流,创建DataOutputStream对象时,底层需要依赖于一个原始的OutputStream流对象。然后调用它的wirteXxx方法,写的是特定类型的数据。
构造器 | 说明 |
---|---|
public DataOutputstream(Outputstream out) |
创建新数据输出流包装基础的字节输出流 |
方法 | 说明 |
---|---|
public final void writeByte(int v) throws IOException |
将double类型的数据写入基础的字节输出流 |
public final void writeInt(int v) throws IOException |
将byte类型的数据写入基础的字节输出流 |
public final void writeDouble(Double v) throws IOException |
将int类型的数据写入基础的字节输出流 |
public final void writeUTF(String str) throws IOException |
将字符串数据以UTF-8编码成字节写入基础的字节输出流 |
public void write(int/byte[]/byte[]一部分) |
支持写字节数据出去 |
代码如下:往文件中写整数、小数、布尔类型数据、字符串数据
1 | public class DataOutputStreamTest1 { |
DataInputStream类
学习完DataOutputStream后,再学习DataIntputStream
类,它也是一种包装流,创建DataInputStream对象时,底层需要依赖于一个原始的InputStream流对象。然后调用它的readXxx()方法就可以读取特定类型的数据。
构造器 | 说明 |
---|---|
public DataInputstream(Inputstream is) |
创建新数据输入流包装基础的字节输入流 |
方法 | 说明 |
---|---|
public final byte peadByte() throws IOException |
读取字节数据返回 |
public final int readInt() throws IOException |
读取int类型的数据返回 |
public final double readDouble() throws IOException |
读取double类型的数据返回 |
public final String readuTF() throws IOException |
读取字符串数(UTF-8)据返回 |
public int readInt()/read(byte[]) |
支持读字节数据进来 |
代码如下:读取文件中特定类型的数据(整数、小数、字符串等)
1 | public class DataInputStreamTest2 { |
序列化流
目前最后一个流要学习,叫做序列化流。 我们知道字节流是以字节为单位来读写数据、字符流是按照字符为单位来读写数据、而对象流是以对象为单位来读写数据。也就是把对象当做一个整体,可以写一个对象到文件,也可以从文件中把对象读取出来。
序列化的解释:
序列化:意思就是把对象写到文件或者网络中去。(简单记:写对象)
反序列化:意思就是把对象从文件或者网络中读取出来。(简单记:读对象)
ObjectOutputStraem类
接下来,先学习ObjectOutputStream流,它也是一个包装流,不能单独使用,需要结合原始的字节输出流使用。
代码如下:将一个User对象写到文件中去
- 第一步:先准备一个User类,必须让其实现Serializable接口。
1 | // 注意:对象如果需要序列化,必须实现序列化接口。 |
- 第二步:再创建ObjectOutputStream流对象,调用writeObject方法对象到文件。
1 | public class Test1ObjectOutputStream { |
注意:写到文件中的对象,是不能用记事本打开看的。因为对象本身就不是文本数据,打开是乱码
怎样才能读懂文件中的对象是什么呢?这里必须用反序列化,自己写代码读。
补充知识:IO框架
最后,再补充讲解一个知识,叫做IO框架。现在如果让我们自己写复制文件夹的代码需要用到递归,还是比较麻烦的。为了简化对IO操作,由apache开源基金组织提供了一组有关IO流小框架,可以提高IO流的开发效率。
这个框架的名字叫commons-io:其本质是别人写好的一些字节码文件(class文件),打包成了一个jar包。我们只需要把jar包引入到我们的项目中,就可以直接用了。
这里介绍一个jar包中提供的工具类叫FileUtils,它的部分功能如下,很方便,你一看名字就知道怎么用了。
FileUtils类提供的部分方法展示 | 说明 |
---|---|
public static void copyFile(File srcFile,File destFile) |
复制文件 |
public static void copyDirectory(File srcDir,File destDir) |
复制文件夹 |
public static void deleteDirectory(File directory) |
删除文件夹 |
public static String readFileToString(File file,String encoding) |
读数据 |
public static void writestringToFile(File file, String data, String charname, boolean append) |
写数据 |
louUtils类提供的部分方法展示 | 说明 |
---|---|
public static int copy(InputStream inputStream, OutputStream outputStream) |
复制文件 |
public static int copy(Reader reader, Writer writer) |
复制文件 |
public static void write(String data, OutputStream output, String charsetName) |
写数据 |
在写代码之前,先需要引入jar包,具体步骤如下
- 在模块的目录下,新建一个lib文件夹
- 把jar包复制粘贴到lib文件夹下
- 选择lib下的jar包,右键点击Add As Library,然后就可以用了。
代码如下:
1 | public class CommonsIOTest1 { |