1. 文件流
顾名思义, 就是用来访问和操作文件的流对象.
文件流根据四大基流可以分为四种:
FileInputStream
- 文件字节输入流FileOutputStream
- 文件字节输出流FileReader
- 文件字符输入流FileWriter
- 文件字符输出流
System
类中存在一种方法getProperties()
可以获取当前系统的属性.其中
user.dir
这个key
所对应的value
则是用户的当前项目的根路径;因此在创建
File
对象时若没有指定文件的绝对路径, 只写入文件名称或相对路径,
得到的File
对象的所对应的文件绝对路径就是user.dir
加上文件名或相对路径.例如当
user.dir = "C:/abc"
的时候,- 创建文件对象只给参数
String s = "123.txt"
, 调用getAbsoluteFile()
方法得到的路径则为"C:/abc/123.txt"
, - 如果给的参数为
String s = "file/123.txt"
, 那么得到的路径则为"C:/abc/file/123.txt"
.
- 创建文件对象只给参数
1.1. FileOutputStream 文件字节输出流
- 文件输出流是用于将数据写入
File
或FileDescriptor
的输出流. FileOutputStream
用于写入诸如图像数据之类的原始字节的流, 如二进制操作.- 如果要写入字符流, 请考虑使用
FileWriter
进行操作, 如写入文字操作.
1.1.1. 常用构造器
FileOutputStream(File file)
- 作用:
创建一个向指定File
对象表示的文件中写入数据的文件输出流
- 作用:
FileOutputStream(File file, boolean append)
- 作用:
创建一个向指定File
对象表示的文件中写入数据的文件输出流 - 备注:
- 若第二个参数为
true
, 则将字节写入文件内容末尾处, 即接上原有的字节续写. - 若第二个参数为
false
, 则将字节写入文件内容开始处, 将之前的字节全部覆盖. - 在同一次开启文件输出流进行写入数据的时候, 会一直接着文件内容进行写入,
只要输入流还没关闭, 不论调用多少次写入, 都是进行续写, 不会覆盖内容;
只有不同次开启同一文件时, 才需要进行判定是否需要续写还是覆盖.
- 若第二个参数为
- 作用:
1.1.2. 常用方法
void write(int b)
- 作用:
将指定字节写入此文件输出流
- 作用:
void write(byte[] b)
- 作用:
将b.length
个字节从指定byte
数组写入此文件输出流中.
- 作用:
void write(byte[] b, int off, int len)
- 作用:
将指定byte
数组中从off
索引开始的len
个字节写入此文件输出流
注意, 是包括了off
索引所指向的那一位.
- 作用:
1.1.3. 操作实例
public static void main(String[] args){
//1. 创建目标对象, 表示存放到哪个文件. 如果文件不存在, 就会创建新文件.
// 但是目录若不存在则并不会进行创建, 因此写入的子文件夹或子目录必须要存在, 否则报错.
File file = new Flie("file/testOutputStream.txt");
//2. 创建文件字节输出流对象, 用来为字节流指向目标文件.
OutputStream os = new FileOutputStream(file);
//3. 具体的输出操作
os.write(65); //将 `A` 写入目标文件当中
os.write("ABCD".getBytes()); //将 "ABCD" 写入目标文件当中
os.write("ABCD".getBytes(), 0, "ABCD".length()); //将 "ABCD" 写入目标文件当中
//4. 关闭资源对象, 以防资源占用.
os.close();
}
1.2. FileInputStream 文件字节输入流
- 文件字节输入流用于从文件系统中的某个文件中获得输入字节.
- 文件字节输入流用于读取诸如图像数据之类的原始字节流.
- 若要读取字符流, 请考虑使用
FileReader
.
1.2.1. 常用构造器
FileInputStream(File file)
- 作用:
通过打开一个到实际文件的连接来创建一个FileInputStream
, 该文件通过文件系统中的File
对象file
指定.
- 作用:
1.2.2. 常用方法
int read()
- 作用:
从此输入流中读取一个数据字节 - 返回:
连续调用将会把文件中的字节逐个读取返回, 直到没有字节后返回 -1.
- 作用:
int read(byte[] b)
- 作用:
从输入流中将最多b.length
个字节的数据读取并存入到一个byte
数组中.
这个byte
数组相当于一个缓冲区用于临时存放字节数据. - 返回:
读入缓冲区的字节总数, 如果因为已经到达文件末尾而没有更多的数据, 则返回 -1.
- 作用:
int read(byte[] b, int off, int len)
- 作用:
从输入流中将最多len
个字节的数据进行读取,
并将字节存入一个byte
数组中, 存放位置从off
索引所对应的数组位置开始. - 备注:
如果len
不为 0, 则在输入可用之前, 该方法将阻塞;否则, 不读取任何字节并返回 0.
- 作用:
1.2.3. 操作实例
public static void main(String[] args){
//1. 创建目标对象, 从哪个文件进行读取数据. 假设文件中存放 "ABCDEFG" 字符串.
File file = new Flie("file/testInputStream.txt");
//2. 创建文件字节输入流对象, 用来为字节流指向目标文件.
InputStream is = new FileInputStream(file);
//同时定义一个byte数组, 用作缓冲区, 容量为10
byte[] byte = new byte[10];
//3. 具体的输出操作
is.read(); //将 `A` 写入byte数组当中
is.read(byte); //将 "ABCDEFG" 写入byte数组当中, 结果为 {65, 66, 67, 68, 69, 70, 71, 0, 0, 0}
is.read(byte, 2, 6); //将 "ABCDEF" 写入byte数组当中, 从数组索引位置2开始存放, 结果为 {0, 0, 65, 66, 67, 68, 69, 70, 0}
//4. 关闭资源对象, 以防资源占用.
os.close();
}
1.3. 文件拷贝操作实例
public class Test {
public static void main(String[] args) throws Exception {
//1. 创建读取源和输出点
File srcFile = new File("/Users/leon9dragon/Desktop/test.txt");
File desFile = new File("/Users/leon9dragon/Desktop/copy.txt");
//2. 创建输入流和输出流
InputStream is = new FileInputStream(srcFile);
OutputStream os = new FileOutputStream(desFile, false);
//假设源文件内容为 "ABCDEFGHIJKL", 长度为12, 输出流的参数是false,
//而byte数组长度为10, 因此会循环两遍调用write方法进行写入操作,
//那么不论循环多少次调用这个主方法, 拷贝的文件内容都是 "ABCDEFGHIJKL";
//如果将参数改为true, 然后循环调用这个主方法,
//那么拷贝文件将会写入循环次数遍的"ABCDEFGHIJKL".
//因此这个参数是针对不同输出流来判断是否需要续写或者覆盖, 相同输出流必然是进行续写.
//3. 定义缓冲区和长度接收变量
byte[] bytes = new byte[10];
int len = 0;
//4. 循环读取并写入数据到输出点
while ((len = is.read(bytes)) > 0) {
os.write(bytes, 0, len);
}
//5. 关闭所有流的资源
is.close();
os.close();
}
}
1.4. 正确地关闭资源和处理异常
低于 JAVA7 使用的资源关闭方法, 以上面的拷贝文件代码为例.
public class Test { public static void main(String[] args){ //0. 先定义输入输出流, 赋值为空, 为了后面关闭资源能进行调用这俩对象. InputStream is = null; OutputStream os = null; //1. 创建读取源和输出点 File srcFile = new File("/Users/leon9dragon/Desktop/test.txt"); File desFile = new File("/Users/leon9dragon/Desktop/copy.txt"); try { //2. 创建输入流和输出流 is = new FileInputStream(srcFile); os = new FileOutputStream(desFile, false); //3. 定义缓冲区和长度接收变量 byte[] bytes = new byte[10]; int len = 0; //4. 循环读取并写入数据到输出点 while ((len = is.read(bytes)) > 0) os.write(bytes, 0, len); } catch (Exception e) { e.printStackTrace(); } finally { //5. 关闭所有流的资源 try { is.close(); } catch (Exception e) { e.printStackTrace(); } try { os.close(); } catch (Exception e) { e.printStackTrace(); } } } }
JAVA7 版本中存在新特性, 能够自动资源关闭, 可以让上述代码更简洁.
public class Test { public static void main(String[] args){ //1. 创建读取源和输出点 File srcFile = new File("/Users/leon9dragon/Desktop/test.txt"); File desFile = new File("/Users/leon9dragon/Desktop/copy.txt"); try ( //2. 定义输入输出流. InputStream is = new FileInputStream(srcFile); OutputStream os = new FileOutputStream(desFile, false); ) { //3. 定义缓冲区和长度接收变量 byte[] bytes = new byte[10]; int len = 0; //4. 循环读取并写入数据到输出点 while ((len = is.read(bytes)) > 0) os.write(bytes, 0, len); } catch (Exception e) { e.printStackTrace(); } } }
1.5. FileReader 文件字符输入流
如果使用文件字节输入流来读取字符, 有可能会出现乱码的情况.
通常类似汉字等特殊字符根据编码基本是两个字节或以上的长度,
而字节流是逐个字节读取的并逐个字节进行存放在缓冲区当中的,
因此在读取这些字符的时候会把字符所组成的字节拆开逐个读取,
从而造成获取的文件内容乱码或文本不对的情况出现.字符流是对字节流的一种补充, 先有字节流再出现的字符流.
1.5.1. 常用构造器
FileReader(File file)
- 作用:
在给定从中读取数据的File
的情况下创建一个新FileReader
.
- 作用:
1.5.2. 常用方法
int read()
- 作用:
读取单个字符并返回字符所对应的整数, 读到末尾则返回 -1
- 作用:
int read(char[] cbuf)
- 作用:
将字符读入数组, 并返回存入数组的字符数量, 如果读到末尾不能再存入, 则返回 -1
- 作用:
abstract int read(char[] cbuf, int off, int len)
- 作用:
将字符读入数组的某一部分, 将len
个字符存入字符数组当中, 从索引位off
开始存.
- 作用:
1.6. FileWriter 文件字符输出流
1.6.1. 常用构造器
FileWriter(File file)
- 作用:
根据给定的File
对象构造一个FileWriter
对象
- 作用:
FileWriter(File file, boolean append)
- 作用:
根据给定的File
对象构造一个FileWriter
对象
- 作用:
1.6.2. 常用写入方法
void write(char[] cbuf)
- 作用:
写入字符数组
- 作用:
abstract void write(char[] cbuf, int off, int len)
- 作用:
写入字符数组的某一部分, 写入从数组索引位off
开始的len
个字符
- 作用:
void write(int c)
- 作用:
写入单个字符
- 作用:
void write(String str)
- 作用:
写入字符串
- 作用:
1.6.3. flush(刷新)操作
输出流中都有
fush
方法, 该方法用于刷新输出流的缓冲区.缓冲区的必要性:
- 计算机访问外部设备(磁盘文件), 要比直接访问内存慢很多,
如果每次write
都要直接写出到磁盘文件中,CPU
都会花更多的时间. - 此时我们可以准备一个内存缓冲区程序, 每次
write
方法都是直接写到内存缓冲区中,
当内存缓冲区满后, 系统才把缓冲区内容一次性写出给磁盘文件.
- 计算机访问外部设备(磁盘文件), 要比直接访问内存慢很多,
缓冲区的好处:
- 提高
CPU
使用率. - 有机会回滚写入的数据.
- 提高
缓冲区的备注:
对于字节流,
flush
方法不是都有作用, 但是对于字符流来说都起作用.在使用字符输出流时, 若调用了
close
方法, 系统会在关闭资源前先调用flush
方法.因此不需手动取调用该方法刷新缓冲区, 系统会自动调用, 前提是先调用
close
方法.如果在使用字符输出流时没有调用
close
方法关闭资源,
也没有手动调用flush
方法刷新缓冲区, 那么将会导致文件写入失败,
生成文件是空文件, 因此无论是什么流, 一定要调用close
方法及时关闭资源.