第7章:IO流
- 使用字节流和字符流读写文件操作
- 使用File类访问文件系统
- NIO和NIO.2的基本概念和用法
OVERVIEW
7.1 IO流概述
-
IO流的概念:IO(Input&Output)流即输入输出流,是Java中实现输入输出的基础,可方便的实现数据的IN&OUT
-
IO流的分类:
分类方式 | 分类结果 |
---|---|
根据流操作的数据单位 | 字节流、字符流 |
根据流传输的方向 | 输入流、输出流 |
根据流的功能 | 节点流、处理流 |
- IO流顶级类的分类:
注意:4个顶级类都是抽象类,并且是所有流类型的父类
7.2 字节流
(1)字节流概述:
- 字节流概念:在计算机中文本、图片、音频包括视频都是以二进制(字节)形式存在,IO流中针对字节的IN&OUT提供了一系列的流,统称为字节流
- 字节输入流InputStream常用方法★:
方法声明 | 功能描述 |
---|---|
int read() | 读入一个8位的字节,转化为0-255间的整数并返回。当没有可用字节时返回 -1 |
int read(byte[] b) | 读入若干字节,保存到参数b指定的字节数组中,返回读取字节的数目 |
int read(byte[] b, int off, int len) | 在int read(byte[] b) 基础上,off指定字节数组起始下标,len为读取字节数目 |
void close() | 关闭输入流,并释放系统资源 |
- 字节输出流OutputStream常用方法★:
方法声明 | 功能描述 |
---|---|
void write(int b) | 向输出流写入一个字节 |
void write(byte[] b) | 把参数b指定的字节数组的所有字节写到输入流 |
void write(byte[] b, int off, int len) | 将指定的byte 数组中从偏移量off开始的len个字节写入输入流 |
void flush() | 刷新此输出流,并强制写出所有缓冲的输出字节 |
void close() | 关闭此输出流,并释放系统资源 |
- InputStream和OutputStream的子类
由于
InputStream
和OutputStream
类是抽象类不能被实例化,针对不同的功能IS和OS提供了不同的子类
(2)字节流读写文件:
由于计算机中的数据基本都保存在硬盘文件中,因此操作文件中的数据是一种很常见的操作
- 使用FileInputStream类操作文件字节输入流,读取文件数据:
package Example01;
import java.io.*;
public class Example01{
public static void main(String[] args) throws Exception{
//创建一个文件字节输入流来读取文件
FileInputStream in = new FileInputStream("D:\\programming\\Java_workingspace\\test01.txt");
//定义一个int类型变量b
int b = 0;
//通过循环来读取文件,当返回值为-1时结束循环
while((b = in.read()) != -1) {
System.out.println(b);
}
//关闭流
in.close();
}
}
- 使用FileOutputStream类操作文件字符输出流,将数据写入文件:
package Example02;
import java.io.*;
public class Example02 {
public static void main(String[] args) throws Exception{
//创建文件输出流对象,并指定文件输出名
FileOutputStream out = new FileOutputStream("7-2out.txt");
//定义一个字符串
String str = "hello";
//将字符串转换为字节数组进行写入操作
out.write(str.getBytes());
//关闭流
out.close();
}
}
通过
FileOutputStream
向一个已经存在的文件写入,该文件数据首先将被删除,再重新写入新的数据
- 使用FileOutputStream类的构造函数
FileOutputStream(String fileName, boolean append)
追加数据:
package Example03;
import java.io.*;
public class Example03 {
public static void main(String[] args) throws Exception {
//创建文件输出流对象,并指定输出文件名
FileOutputStream out = new FileOutputStream("7-2out.txt", true);
//定义一个字符串
String str = " world";
//将字符串转化为字符数组进行写入操作
out.write(str.getBytes());
out.close();
}
}
(3)文件的拷贝:
- 使用输入输出流进行文件的拷贝:
package Example04;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Example04 {
public static void main(String[] args) throws IOException {
//创建文件输入流对象读取指定目录下的文件
FileInputStream in = new FileInputStream("source/src.jpg");
//创建文件输出流对象将读取到的文件内容写入到指定目录文件中
FileOutputStream out = new FileOutputStream("target/dest.jpg");
//定义一个int类型的变量len
int len = 0;
//获取拷贝文件前的系统时间
long beginTime = System.currentTimeMillis();
//通过循环将文件读取到的字节信息写入到目标文件
while((len = in.read()) != -1){
out.write(len);
}
//获取拷贝之后的系统时间
long endTime = System.currentTimeMillis();
//输出拷贝花费的时间
System.out.println("花费时间为:" + (endTime - beginTime) + "毫秒");
//关闭流
in.close();
out.close();
}
}
(4)字节流的缓冲区:
虽然Example04实现了文件的拷贝但需要频繁的操作文件,效率很低,可以定义一个字节数组作为缓冲区
- 使用字节数组作为缓冲区拷贝文件:
package Example05;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Example05 {
public static void main(String[] args) throws IOException {
//创建文件输入流对象取指定目录下的文件
FileInputStream in = new FileInputStream("source/src.jpg");
//创建文件输出流对象将读取到的文件内容写入到指定目录中
FileOutputStream out = new FileOutputStream("target/dest2.jpg");
//定义一个int类型的变量
int len = 0;
//定义一个长度为1024的字节数组作为缓冲区
byte[] buff = new byte[1024];
//获取拷贝文件前的系统时间
long beginTime = System.currentTimeMillis();
//通过循环将读取到的文件字节信息写入到新文件
while((len = in.read(buff)) != -1){
//每循环一次读取一次字节数组,将所有读取到的内容写入到文件
out.write(buff, 0, len);
}
//获取拷贝之后的系统时间
long endTime = System.currentTimeMillis();
//输出拷贝花费的时间
System.out.println("花费的时间为:" + (endTime - beginTime) + "毫秒");
//关闭流
in.close();
out.close();
}
}
程序中的缓冲区就是一块内存,用于暂时存放输入输出的数据减少对文件的操作次数,提高读写数据效率
(5)字节缓冲流:
在IO包中提供了两个带缓冲的字节流
BufferedInputStream
和BufferedOutputStream
,构造方法中分别接收InputStream
和OutputStream
类型的参数作为对象,在读写数据时提供缓冲功能
- 使用字节缓冲流拷贝文件:
//利用字节缓冲流拷贝文件
package Example06;
import java.io.*;
public class Example06 {
public static void main(String[] args) throws IOException {
//创建用于输入和输出的字节缓冲流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("source/src.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("target/dest3.jpg"));
//定义一个int类型的变量
int len = 0;
//获取拷贝文件前的系统时间
long beginTime = System.currentTimeMillis();
while ((bis.read()) != -1){
bos.write(len);
}
//获取拷贝文件后的系统时间
long endTime = System.currentTimeMillis();
//输出拷贝花费的时间
System.out.println("花费时间为:" + (endTime - beginTime) + "毫秒");
//关闭流
bis.close();
bos.close();
}
}
BufferedInputStream
和BufferedOutputStream
中都定义了一个大小为8192的字节数组,调用read和write方读写数据时,先将数据存入字节数组再一次性读写到文件中
7.3 字符流
(1)字符流概述:
由于Reader和Writer类是抽象类不能被实例化,针对不同的功能字符输入/输出流提供了一些常用的子类
- Reader和Writer常用的子类:
(2)字符流操作文件:
在程序开发中,经常需要对文本文件的内容进行读取,可使用
FileReader
从文件中读取一个或一组字符
- 使用FileReader文件字符输入流读取文件中的字符:
package Example07;
import java.io.FileReader;
public class Example07 {
public static void main(String[] args)throws Exception {
//创建FileReader对象,并指定需要读取的文件
FileReader fileReader = new FileReader("7-7reader.txt");
//定义一个int类型变量len,其初始化值为0
int len = 0;
//通过循环来判断是否读到了文件末尾
while((len = fileReader.read()) != -1) {
//输出读取到的字符
System.out.print((char)len);
}
//关闭流
fileReader.close();
}
}
通过while循环每次从文件中读取一个字符并打印,实现了文件字符的读取,由于字符输入流reader的read方法返回的是int类型,获得字符需要进行强制转换
- 使用FileWriter文件字符输出流将字符写入文件:
package Example08;
import java.io.FileWriter;
import java.io.IOException;
public class Example08 {
public static void main(String[] args) throws IOException {
//创建字符输出流对象,并指定输出文件
FileWriter fileWriter = new FileWriter("7-8writer.txt");
//将定义的字符写入文件
fileWriter.write("轻轻的我走了,\r\n");
fileWriter.write("正如我轻轻的来;\r\n");
fileWriter.write("我轻轻的招手,\r\n");
fileWriter.write("作别西天的云彩。\r\n");
//关闭流
fileWriter.close();
}
}
FileWriter
和FileOutputStream
相似,如果指定文件不存在,就会先创建文件再写入数据;如果文件存在,就会先清空文件内容再进行写入;文件数据的追加也相似
- 使用字符流的缓冲区实现文件的拷贝:
通过字符流进行文件的读写操作,也是逐个字符进行读写效率很低,可以利用字符流缓冲区提高效率
package Example09;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Example09 {
public static void main(String[] args) throws IOException {
//创建FileReader对象,并指定需要读取的文件
FileReader filereader = new FileReader("7-9reader.txt");
//创建FileWriter对象,并指定需要写入数据的目标文件
FileWriter fileWriter = new FileWriter("7-9writer.txt");
//定义一个int类型变量len,并初始化值为0
int len = 0;
//定义一个长度为1024的字符数组
char[] buff = new char[1024];
//通过循环来判断是否读取到了文件末尾
while ((len = filereader.read(buff)) != -1){
//输出读取到的字符
fileWriter.write(buff, 0, len);
}
//关闭流
filereader.close();
fileWriter.close();
}
}
- 使用BufferedReader和BufferedWriter缓冲流实现文件的拷贝:
package Example10;
import java.io.*;
public class Example10 {
public static void main(String[] args) throws IOException {
//创建一个字符输入缓冲流对象
BufferedReader br = new BufferedReader(new FileReader("7-10reader.txt"));
//创建一个字符输出缓冲流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("7-10writer.txt"));
//声明一个字符串变量str
String str = null;
while ((str = br.readLine()) != null){
//通过缓冲流对象写入文件
bw.write(str);
//写入一个换行符,该方法会根据操作系统不同生成相应的换行符
bw.newLine();
}
br.close();
bw.close();
}
}
值得注意
- 每次循环使用
readline()
方法读取一行,然后通过wirter()
方法写入文件;循环判断条件为(str != null)
- 程序中使用
newLine()
方法进行换行,否则所有内容都会追加写入到一行中- 当调用
writer()
方法写入字符时字符会先被写入缓冲区,当缓冲区写满或调用close()
方法时,缓冲区字符才会被写入目标文件
(3)转换流:
有时字符流与字节流之间需要进行转换,再JDK中提供了
InputStreamReader
和OutputStreamWriter
两个类用于转换
- 使用InputStreamReader和OutputStreamWriter实现字节流与字符流间的转换:
package Example11;
import java.io.*;
public class Example11 {
public static void main(String[] args) throws IOException {
//1.创建字节输入流对象,获取源文件
FileInputStream in = new FileInputStream("7-11reader.txt");
//将字节输入流对象转换成字符输入流对象
InputStreamReader isr = new InputStreamReader(in);
//创建字符输入缓冲流对象
BufferedReader br = new BufferedReader(isr);
//2.创建字节输出流对象,指定目标文件
FileOutputStream out = new FileOutputStream("7-11writer.txt");
//将字节输出流对象转换成字符输出流对象
OutputStreamWriter osw = new OutputStreamWriter(out);
//创建字符输出缓冲流对象
BufferedWriter bw = new BufferedWriter(osw);
//定义一个字符串变量
String line = null;
//通过循环判断是否读到文件末尾
while((line = br.readLine()) != null){
//输出读取到的文件
bw.write(line);
bw.newLine();
}
//关闭流
br.close();
bw.close();
}
}
需要注意在进行转化时只针对操作文本文件的字节流转换,如果操作的是字节码内容的文件(图片、视频)将造成数据丢失
7.4 File类
通过IO流可对文件内容进行读写操作,但对文件本身的常规操作(创建、删除、重命名、判断存在等)无法实现,需要
File
类实现
(1)File类常用方法:
- File类概念:用于封装一个路径(绝对路径或相对路径),封装的路径可以指向一个文件也可以指向一个目录
- File类常用的构造方法:
方法声明 | 功能描述 |
---|---|
File(String pathname) | 通过指定一个String类型的文件路径来创建一个新的File 对象 |
File(String pathname, String child) | 根据一个String类型的父路径和一个String类型的子路径创建一个File 对象 |
File(File parent, String child) | 根据一个File 类型的父路径和一个String类型的子路径创建一个File 对象 |
-
File类中常用的方法:
-
使用File类常用方法查看文件信息:
package Example12;
import java.io.File;
public class Example12 {
public static void main(String[] args) {
//创建File类文件对象
File file = new File("7-12example.txt");
System.out.println("文件名称:" + file.getName());
System.out.println("文件相对路径" + file.getPath());
System.out.println("文件绝对路径" + file.getAbsolutePath());
System.out.println("文件的父路径" + file.getParent());
System.out.println(file.canRead() ? "文件可读" : "文件不可读");
System.out.println(file.canWrite() ? "文件可写" : "文件不可写");
System.out.println(file.isFile() ? "是一个文件" : "不是一个文件");
System.out.println(file.isDirectory() ? "是一个目录" : "不是一个目录");
System.out.println(file.isAbsolute() ? "是一个绝对路径" : "不是一个绝对路径");
System.out.println("最后修改时间为:" + file.lastModified());
System.out.println("文件大小为:" + file.length() + "bytes");
System.out.println("是否成功删除文件" + file.delete());
}
}
(2)遍历目录下的文件:
- 使用File类中的
list()
方法遍历指定目录下的所有文件的名称:
package Example13;
import java.io.File;
import java.util.Arrays;
public class Example13 {
public static void main(String[] args) {
//创建File对象,并指定文件路径
File file = new File("C:");
//判断是否是目录
if(file.isDirectory()) {
//获取目录中的所有文件的名称
String[] fileNames = file.list();
//对指定路径下的文件或目录进行遍历
Arrays.stream(fileNames).forEach(f->System.out.println(f));
}
}
}
- 使用File中的
list(FilenameFilter filter)
方法筛选遍历指定目录下指定类型的文件:
package Example14;
import java.io.File;
import java.util.Arrays;
public class Example14 {
public static void main(String[] args) {
//创建File对象,并指定文件路径
File file = new File("D:\\programming\\Java_workingspace\\Example");
//判断是否是目录
if(file.isDirectory()) {
//使用Lambda表达式过滤目录中所有以.txt结尾的文件的名称
String[] fileNames = file.list((dir,name)->name.endsWith(".txt"));
//对指定路径下的文件或目录进行遍历
Arrays.stream(fileNames).forEach(f->System.out.println(f));
}
}
}
- 使用File中的
listFiles()
方法递归筛选深度遍历指定目录下指定类型的文件:
package Example15;
import java.io.*;
public class Example15 {
public static void main(String[] args) {
//创建File对象,并指定文件路径
File file = new File("D:\\programming");
//调用fileDir()方法,遍历目录
fileDir(file);
}
//遍历目录及其子目录
private static void fileDir(File file) {
//获得目录下所有文件,并赋值给数组
File[] listFiles = file.listFiles();
//循环遍历数组
for(File files : listFiles){
//如果遍历的是数组,则调用递归fileDir()方法
if(files.isDirectory()){
fileDir(files);
}
//输出文件路径
System.out.println(files);
}
}
}
(3)删除文件及目录:
在使用
File
类中的delete()
删除文件时,需要判断目录下是否存在文件,需要先删除内部文件再删除空文件
- 使用File类中的
delete()
删除指定目录下的文件和文件夹:
package Example16;
import java.io.File;
public class Example16 {
public static void main(String[] args) {
//创建File类,并指定文件路径
File files = new File("D:\\programming\\Java_workingspace\\Example\\target\\delete.txt");
//调用删除方法
deleteDir(files);
}
private static void deleteDir(File files) {
//获取file对象中所有的文件,并将其放在数组中
File[] listFiles = files.listFiles();
//循环遍历数组
for(File file : listFiles){
//如果是目录文件,则递归调用删除方法
if(file.isDirectory()){
deleteDir(file);
}
//如果是文件则删除
file.delete();
}
//删除文件夹内所有文件后,再删除文件夹
files.delete();
}
}
7.5 RandomAccessFile随机读写文件
如果希望从文件的任意位置开始执行读写操作,字节流和字符流都无法实现,使用
RandomAccessFile
可以实现,它不是流类但具有读写文件数据的功能
- RandomAccessFile的构造方法:
方法声明 | 功能描述 |
---|---|
RandomAccessFile(File file, String mode) | 使用参数file 指定访问的文件,并使用mode 来指定访问模式 |
RandomAccessFile(String name, String mode) | 使用参数name 指定访问文件路径,并使用mode 来指定访问模式 |
- 参数mode
参数mode | 含义 |
---|---|
r | 以只读的方式打开文件 |
rw | 以读写的方式打开文件,如果文件不存在将自动创建文件 |
rws | 以读写的方式打开文件,要求文件内容或元数据的每个更新,都同步写入底层底层存储设备 |
rwd | 以读写的方式打开文件,要求文件内容的每个更新,都同步写入底层底层存储设备 |
- RandomAccessFile的常用方法:
方法声明 | 功能描述 |
---|---|
long getFilePointer() | 返回当前读写指针所处位置 |
void seek(long pos) | 设定读写指针的位置,与开头文件相隔pos 个字节数 |
int skipBytes(int n) | 使读写指针从当前位置开始,跳过n个字节 |
void write(byte[] b) | 将指定的字节数组写入到文件,并从当前文件指针开始 |
void setLength(long newLength) | 设置此文件的长度 |
final String readLine() | 从指定文件当前指针读取下一行内容 |
- 使用RandomAccessFile类来模拟实现记录软件使用次数的过程:
package Example17;
import java.io.RandomAccessFile;
public class Example17 {
public static void main(String[] args) throws Exception {
//创建RandomAccessFile对象,并以读写方式打开time.txt文件
RandomAccessFile raf = new RandomAccessFile("7-17time.txt","rw");
//读取软件剩余使用次数
int times = Integer.parseInt(raf.readLine())-1;
//判断剩余次数
if(times > 0) {
//每执行一次代表使用一次,次数就减少一次
System.out.println("您还可以试用"+times+"次");
//将记录指针重新指向文件开头
raf.seek(0);
//将剩余次数再次写入文件
raf.write((times+"").getBytes());
}else {
System.out.println("使用次数已经用完!");
}
//关闭这个随机存取文件流并释放任何系统
raf.close();
}
}
7.6 对象序列化
数据在Java中都是保存在对象中的,如果需要将数据保存到磁盘上,需要使用对象序列化
- 对象序列化概念:将一个Java对象转换成一个IO流中字节序列的过程,目的是将对象保存在磁盘中或允许在网络中直接传输对象
可序列化的必须实现
Serializable
或Exteralizable
两个接口之一
- 实现Serializable或Exteralizable接口对比:
实现Serializable接口 | 实现Exteralizable接口 |
---|---|
系统自动存储必要信息 | 程序员决定存储信息 |
Java内部类支持,易于实现,不需要其他代码支持 | 接口中提供了两个空方法,必须为两个空方法提供实现 |
性能较差 | 性能较好 |