一、概述
IO是Input、Output的缩写,IO流技术是非常实用的技术,用于处理数据传输,例如读写文件,网络通讯等。在Java程序中,对于数据的输入/输出操作以"流(stream)"的方式进行,java.io包下提供了各种"流"类和接口,用以获取不同种类的数据,并通过相关API实现输入或者输出数据。
1.1、IO流原理
1.2、IO流图解
1.3、流的分类
- 按操作数据单位不同分为:字节流(8bit)二进制文件,字符流(按字符)文本文件;
- 按数据流的流向不同分为:输入流、输出流
- 按流的角色不同分为:节点流、处理流/包装流
- Java中的IO流共涉及到40多个类,实际上非常有规律,都是从如上4个抽象基类派生出来的
- 由这四个抽象派生基类派生出来的子类名称都是以其父类名作为子类名的后缀
1.4、体系图
二、字节流
2.1、一切皆为字节
一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存的,都是一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。
2.2、字节输出流(OutputStream)
java.io.OutputStream
抽象类是所有字节输出流的超类,用于将指定的字节信息写出到目的地。它定义了字节输出流的基本共性和功能方法 。
# 关闭此输出流并释放与此流相关联的任何系统资源
public void close()
# 刷新此输出流并强制任何缓冲的输出字节被写出
public void flush()
# 将 b.length字节从指定的字节数组写入此输出流
public void write(byte[] b)
# 从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流
public void write(byte[] b, int off, int len)
# 将指定的字节输出流
public abstract void write(int b)
# 注意事项
close方法,当完成流的操作时,必须调用此方法,释放系统资源
2.2.1、FileOutputStream
OutputStream
有很多的子类,我们从最简单的一个子类开始,java.io.FileOutputStream
是文件输出流,用于将数据写出到文件。
2.2.2、构造方法
# 创建文件输出流
public FileOutputStream(File file)
# 创建文件输出流
public FileOutputStream(String name)
# 注意事项
当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件中的数据。
/**
* 当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件中的数据
* @throws FileNotFoundException
*/
@Test
public void testConstructor() throws FileNotFoundException {
// 使用File对象创建字节输出流
File file = new File("明朝那些事儿.txt");
FileOutputStream fos1 = new FileOutputStream(file);
FileOutputStream fos2 = new FileOutputStream("宋朝那些事儿.txt");
}
2.2.3、写出字节数据(一)
# 每次可以写出一个字节的数据
write(int b)
/**
* write(int b) : 每次可以写出一个字节的数据
*/
@Test
public void testWrite1() throws IOException {
FileOutputStream fos = new FileOutputStream("明朝那些事儿.txt");
fos.write(97);
fos.write(98);
fos.write(99);
fos.close();
}
2.2.4、写出字节数据(二)
# 每次可以写出数组中的数据
write(byte[] b)
/**
* write(byte[] b) : 每次可以写出数组中的数据
*/
@Test
public void testWrite2() throws IOException {
FileOutputStream fos = new FileOutputStream("明朝那些事儿.txt");
byte[] arr = "滚滚长江东逝水".getBytes(StandardCharsets.UTF_8);
fos.write(arr);
fos.close();
}
2.2.5、写出字节数据(三)
# 写出指定长度的字节数组,每次写出从索引off开始,到len结束
write(byte[] b, int off, int len)
/**
* write(byte[] b, int off, int len) : 写出指定长度的字节数组,每次写出从索引off开始,到len结束
*/
@Test
public void testWrite3() throws IOException {
FileOutputStream fos = new FileOutputStream("明朝那些事儿.txt");
byte[] arr = "nice to meet you".getBytes(StandardCharsets.UTF_8);
fos.write(arr,0,4);
fos.close();
}
2.2.6、数据追加
经过以上的演示,每次程序运行,创建输出流对象,都会清空目标文件中的数据。如何保留目标文件中数据,还能继续添加新数据呢?
public FileOutputStream(File file, boolean append) 创建文件输出流
public FileOutputStream(String name, boolean append) 创建文件输出流
这两个构造方法,参数中都需要传入一个boolean类型的值,true 表示追加数据,false 表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了
/**
* 数据追加
*/
@Test
public void testWrite4() throws IOException {
FileOutputStream fos = new FileOutputStream("明朝那些事儿.txt",true);
byte[] arr = "滚滚长江东逝水".getBytes(StandardCharsets.UTF_8);
fos.write(arr);
fos.close();
}
2.2.7、写出换行符
回车符`\r`和换行符`\n` :
回车符:回到一行的开头(return)
换行符:下一行(newline)
系统中的换行:
Windows系统里,每行结尾是 `回车+换行` ,即`\r\n`;
Unix系统里,每行结尾只有 `换行` ,即`\n`;
Mac系统里,每行结尾是 `回车` ,即`\r`,从 Mac OS X开始与Linux统一
/**
* 写出换行符
*/
@Test
public void testWrite5() throws IOException {
FileOutputStream fos = new FileOutputStream("明朝那些事儿.txt",true);
byte[] arr = "\r\n滚滚长江东逝水".getBytes(StandardCharsets.UTF_8);
fos.write(arr);
fos.close();
}
2.3、InputStream(字节输入流)
java.io.InputStream
抽象类是所有字节输入流的超类,用于读取字节信息到内存中。它定义了字节输入流的基本共性和功能方法。
# 关闭此输入流并释放与此流相关联的任何系统资源
public void close()
# 从输入流读取数据的下一个字节
public abstract int read()
# 从输入流中读取一些字节数,并将它们存储到字节数组b中
public int read(byte[] b)
小贴士:close方法,当完成流的操作时,必须调用此方法,释放系统资源。
2.3、字节输入流(FileInputStream)
java.io.FileInputStream
是文件输入流类,用于从文件中读取字节。
2.3.1、构造方法
FileInputStream(File file)
FileInputStream(String name)
# 注意事项
当创建一个流对象时,必须传入一个文件路径。该路径下如果没有此文件,将会抛出FileNotFoundException
/**
* 创建FileInputStream的2种方式
*/
@Test
public void testCreateFileInputStream() throws FileNotFoundException {
File file1 = new File("D:\\三国演义.txt");
FileInputStream fis1 = new FileInputStream(file1);
FileInputStream fis2 = new FileInputStream("D:\\三国演义.txt");
}
2.3.2、读取字节数据(一)
# 每次读取一个字节的数据
read()
/**
* 需求:读取 D:\\三国演义.txt 文件中的内容,并将结果打印在控制台上
* 注意事项:由于FileInputStream是按照字节读取的,UTF-8编码格式下,一个英文字母占用一个字节,一个中文占用3个字节,所以当 三国演义.txt 文件中存在中文时,会出现乱码
* 存在的问题:单个字节的读取,效率比较低
*/
@Test
public void test1() {
String filePath = "D:\\三国演义.txt";
FileInputStream fis = null;
int readData = 0;
try {
// 创建FileInputStream对象,用于读取文件
fis = new FileInputStream(filePath);
// 从该输入流中读取一个字节的数据,如果没有输入可用此方法将阻止,如果返回-1表示读取完毕
while ((readData = fis.read()) != -1) {
System.out.print((char) readData);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭文件流释放资源
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.3.3、读取字节数据(二)
# 使用字节数组读取数据
read(byte[] arr)
/**
* 需求:同test1
* 优化:使用字节数组进行读取,提高效率
*/
@Test
public void test2() {
String filePath = "D:\\三国演义.txt";
FileInputStream fis = null;
int length = 0;
byte[] buf = new byte[8];
try {
// 创建FileInputStream对象,用于读取文件
fis = new FileInputStream(filePath);
/*
从该输入流中读取最多buf.length字节的数据到字节数组,如果没有输入可用此方法将阻止,如果返回-1表示读取完毕
如果读取正常,返回实际读取的字节数
*/
while ((length = fis.read(buf)) != -1) {
System.out.print(new String(buf,0,length));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭文件流释放资源
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
三、字符流
3.1、概述
当使用字节流读取文本文件时,会有一个小问题,当遇到中文字符时,不会显示完整的字符,UTF-8编码下一个中文字符占用3个字节进行存储。所以Java提供了字符流类,以字符为单位进行读写数据,专门用于处理文本文件。
3.2、字符输入流(Reader)
java.io.Reader
抽象类是用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性和功能方法。
# 关闭此流并释放与此流相关联的任何系统资源
public void close()
# 从输入流读取一个字符
public int read()
# 从输入流中读取一些字符,并将它们存储到字符数组buf中
public int read(char[] buf)
3.2.1、FileReader
java.io.FileReader
是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
注意事项:
(1)字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码,idea中是UTF-8
(2)字节缓冲区:一个字节数组,用来临时存储字节数据
3.2.2、构造方法
FileReader(File file)
FileReader(String fileName)
# 每次读取单个字符就返回,如果读取到文件末尾返回-1
read()
# 批量读取多个字符到数组,返回读取的字节数,如果读取到文件末尾返回-1
read(char[])
# 相关API
(1)new String(char[]):将char[]转换为String字符串
(2)new String(char[],off,len):将char[]的指定部分转换为String字符串
# 注意事项
当你创建一个流对象时,必须传入一个文件路径,类似于FileInputStream,否则将会报FileNotFoundException
/**
* 说明:创建FileReader的2种方式演示
*/
@Test
public void testCreateFileReader() throws FileNotFoundException {
File f1 = new File("D:\\三国演义.txt");
FileReader fileReader1 = new FileReader(f1);
FileReader fileReader2 = new FileReader("D:\\三国演义.txt");
}
3.2.3、读取字符数据(一)
# 每次读取一个字符的数据
read()
/**
* 需求:读取 D:\\三国演义.txt 文件,将里边的内容输出
* 单个字符读取
*/
@Test
public void test1() {
String filePath = "D:\\三国演义.txt";
File file = new File(filePath);
if (!file.exists()) {
System.out.println("文件不存在!");
return;
}
FileReader fileReader = null;
int data = 0;
try {
fileReader = new FileReader(filePath);
// 单个字符循环读取
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}
System.out.println();
System.out.println("读取完毕");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.2.4、读取字符数据(二)
# 使用字符数组读取
read(char[] arr)
/**
* 需求:读取 D:\\三国演义.txt 文件,将里边的内容输出
* 使用字符数组进行读取
*/
@Test
public void test2() {
String filePath = "D:\\三国演义.txt";
File file = new File(filePath);
if (!file.exists()) {
System.out.println("文件不存在!");
return;
}
FileReader fileReader = null;
int length = 0;
// 定义字符数组,作为装载字符数据的容器
char[] buf = new char[8];
try {
fileReader = new FileReader(filePath);
/*
使用字符数组循环读取。返回的是实际读取到的字符数,如果返回-1,说明读取到了文件结尾
*/
while ((length = fileReader.read(buf)) != -1) {
System.out.print(new String(buf,0,length));
}
System.out.println();
System.out.println("读取完毕");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.3、字符输出流(Writer)
3.3.1、概述
java.io.Writer
抽象类是写出字符流的所有类的超类,可以将指定的字符信息写出到目的地。它定义了字节输出流的基本共性和功能方法。
# 写入单个字符
void write(int c)
# 写入字符数组
void write(char[] cbuf)
# 写入字符数组的某一部分,off数组的开始索引,len写的字符个数
abstract void write(char[] cbuf, int off, int len)
# 写入字符串
void write(String str)
# 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
void write(String str, int off, int len)
# 刷新该流的缓冲
void flush()
# 关闭此流前,需要先刷新它
void close()
3.3.2、FileWriter
java.io.FileWriter
是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
3.3.3、构造方法
FileWriter(File file)
FileWriter(String fileName)
# 注意事项
当你创建一个流对象时,必须传入一个文件路径,类似于FileInputStream,否则将会报FileNotFoundException
/**
* 说明:测试创建FileWriter的2种方式
*/
@Test
public void testCreateFileWriter() throws IOException {
File f1 = new File("D:\\三国演义.txt");
FileWriter writer1 = new FileWriter(f1);
FileWriter writer2 = new FileWriter("D:\\三国演义.txt");
}
3.3.4、写出数据
# 覆盖模式,相当于流的指针在首端
new FileWriter(File/String)
# 追加模式,相当于流的指针在尾端
new FileWriter(File/String,true)
# 写入单个字符
write(int)
# 写入指定数组
write(char[])
# 写入指定数组的指定部分
write(char[],off,len)
# 写入整个字符串
write(String)
# 写入字符串的指定部分
write(String,off,len)
# 相关API String
toCharArray() : 将String转换为char[]
# 注意事项
(1)字符流只能用来操作文本文件,不能操作图片,视频等非文本文件
(2)当我们单纯读或者写文本文件时 使用字符流,其他情况使用字节流
(3)FileWriter使用后,必须要关闭(close)或者刷新(flush),否则写入不到指定的文件中
/**
* 需求:往 D:\\三国演义.txt 文件中追加内容 "哦耶今天星期五!"
* 注意事项:使用FileWriter往文件中写入数据时,如果不关闭流或者刷新流的话,将会导致数据无法写入
*/
@Test
public void test1() {
String filePath = "D:\\三国演义.txt";
File file = new File(filePath);
FileWriter fileWriter = null;
try {
if (!file.exists()) {
file.createNewFile();
}
fileWriter = new FileWriter(filePath,true);
fileWriter.write("\n哦耶今天星期五!");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fileWriter != null) {
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}