一. 流
1. 什么是流?
在程序中所有的数据都是以流的方式进行传输或保存的,程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要使用输出流完成。程序中的输入输出都是以流的形式保存的,流中保存的实际上全都是字节文件。
2. 流分类
按照流向可以分为:输入流和输出流
按照传输单位可以分为:字节流和字符流
3.字节流和字符流
- 字节流:以字节(8位)为单位进行数据传输,适用于处理任何类型的二进制数据,如图片、音频、视频等。Java中的
InputStream
和OutputStream
是字节流的抽象基类。 - 字符流:以字符(16位Unicode)为单位进行数据传输,主要用于处理文本数据。
Reader
和Writer
是字符流的抽象基类。
事实上,字节流也可以操作一部分文本文件,但不提倡这样做,因为用字节流操作文本文件如果文件中有汉字,可能会出现乱码。这是因为字节流不能直接操作unicode 字符所致。
最常见的区分方式就是将不同的文件在记事本当中打开,如果看得懂那就是字符流,看不懂的话那就是字节流。
二. 使用 InputStream
和 OutputStream
流
字节流主要是操作byte类型数据,以byte数组为准,主要操作类就是OutputStream、InputStream。
1.InputStream流类
方法 | 说明 |
---|---|
public int read() | 从输入流中的当前位置读入一个字节(8bit)的二进制数据,然后以此数据为低位字节,配上8个全0的高位字节合成一个16位的整型量(0~255)返回给调用此方法的语句,若输人流中的当前位置没有数据,则返回 -1 |
public int read(byte[ ] b) | 从输入流中的当前位置连续读人多个字节保存在数组b中,同时返回所读到的字节数 |
public int read(byte[ ] b,int off,int len) | 从输人流中的当前位置连续读人len个字节,从数组b的第off+1个元素位置处开始存放,同时返回所读到的字节数 |
public void close() | 关闭输入流与外设的连接并释放所占用的系统资源 |
以上罗列了最常用的InputStream中的方法,当程序员需要从外设、网络请求等不同来源读入数据时,应该创建对应的输入流来读取数据。
由于InputStream是一个抽象类,所以应该根据不同的使用选择与之对应的实现类,并使用继承或重写的read方法进行读取。
2.OutputStream流类
方法 | 说明 |
---|---|
public void write(int b) | 将参数b的低位字节写入到输出流 |
public void write(byte[ ] b) | 将字节数组b中的全部字节按顺序写人到输出流 |
public void write ( byte[ ] b, int off,int len) | 将字节数组b中第off+1个元素开始的len个数据,顺序地写人到输出流 |
public void flush( ) | 强制清空缓冲区并执行向外设写操作 |
public void close() | 关闭输出流与外设的连接并释放所占用的系统资源 |
以上罗列了OutputStream常用的几个方法,主要是通过调用write方法对数据进行写入,以及关闭流的方式。
同样的,要根据不同的情况选择不同的子类进行操作。
3.使用案例
public static void main(String[] args) {
try {
//创建一个输入流
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\f\\Desktop\\123.txt");
//创建一个输出流
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\f\\Desktop\\2.txt");
//创建一个缓冲暂存区
byte[] bytes = new byte[1024];
int temp;
//循环传输
while((temp=fileInputStream.read(bytes))!=-1){
//写文件
fileOutputStream.write(bytes);
}
//出循环以后传输完成关闭流
fileOutputStream.close();
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
三. 使用 Reader 和 Writer 流类
在程序中一个字符等于两个字节,java提供了Reader、Writer两个专门操作字符流的类。
1.Writer流类
常用方法 | 说明 |
---|---|
public void write(int c) | 将单一字符c输出到流中 |
public void write(String str) | 将字符串输出到流中 |
public void write(char[] cbuf) | 将字符数组cbuf输出到流 |
public void write(char[] cbuf, int off, int len) | 将字符数组按指定格式输出 |
public void flush() | 将缓冲区的数组写到文件 |
public void close() | 关闭输出流 |
2.Reader流类
常用方法 | 说明 |
---|---|
public int read() | 从输入流中读一个字符 |
publici int read(char[ ] cbuf) | 从输入流中读最多cbuf.length 个字符,存入字符数组cbuf 中 |
public int read(char[ ] cbuffer, int off, int len) | 从输入流中读最多len 个字符,存入字符数组cbuffer 中从off 开始的位置 |
public void close() | 关闭输入流 |
3.使用示例
import java.io.FileWriter;
import java.io.IOException;
public class IoDemo {
public static void main(String[] args) throws IOException {
//定义文件地址
String filePath = "D:\\f\\text.txt";
//写入内容
String content = "欢迎来到Java~";
//因为是写入文件,所以要使用FileWriter方法
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter(filePath);
fileWriter.write(content);
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭流
fileWriter.close();
}
}
}
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
4.子类补充
4.1 FileReader类
4.1.1 创建输入流对象
FileReader(File file): 创建一个新的 FileReader,给定要读入的File对象。
FileReader(String fileName): 创建一个新的 FileReader ,给定要读入的文件名称。
4.1.2 读入字符数据
read(char[] cbuf):每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回-1
,代码使用演示:
4.1.3 实例
public class FISRead {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileReader fr = new FileReader("read.txt");
// 定义变量,保存有效字符个数
int len ;
// 定义字符数组,作为装字符数据的容器
char[] cbuf = new char[2];
// 循环读取
while ((len = fr.read(cbuf)) != -1) {
System.out.println(new String(cbuf, 0, len));
}
// 关闭资源
fr.close();
}
}
4.2 FileWriter类
4.2.1 创建输出流对象
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的File对象。FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取文件的名称。FileWriter(File file, boolean append)
:在写入文件时,如果文件已经存在,FileWriter
会默认覆盖该文件。如果想要追加内容,可以在构造函数中传入true
作为第二个参数。
4.2.2 写出字符数据
write(int b) :写出一个字符
4.2.3 关闭和刷新
这里有一个问题:为什么我们要在输出流这里不断刷新呢?
字符输出流需要刷新,主要是因为字符流在操作时使用了缓冲区,通过缓冲区再操作文件。如果不刷新输出流,可能会导致输出的顺序与预期不同,甚至有些输出内容被吞掉。这是因为字符流在输出时,会将数据先写入缓冲区,然后在适当的时机再将缓冲区的内容写入文件或设备。如果程序在写入数据后立即结束,而缓冲区中的数据尚未被完全写入,那么这些未写入的数据就会丢失。因此,为了确保数据的完整性和正确性,需要在适当的时候刷新输出流,即将缓冲区中的数据强制写入文件或设备,避免数据丢失或顺序混乱
flush
:刷新缓冲区,流对象可以继续使用。
close
:先刷新缓冲区,然后通知系统释放资源,流对象不可以再被使用了。
public class FWWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 写出数据,通过flush
fw.write('刷'); // 写出第1个字符
fw.flush();
fw.write('新'); // 继续写出第2个字符,写出成功
fw.flush();
// 写出数据,通过close
fw.write('关'); // 写出第1个字符
fw.close();
fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed
fw.close();
}
}
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterExample {
public static void main(String[] args) {
try {
// 创建文件字符输出流
FileWriter writer = new FileWriter("output.txt");
String str = "Hello, World!";
// 写入数据
writer.write(str);
// 关闭流
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
四.注意点
1.在使用字节流处理文本时,如果不指定或忽略编码,可能导致乱码。
解决方案是明确指定编码,如使用FileInputStream
时配合InputStreamReader
指定编码。
FileInputStream fis = new FileInputStream("file.txt");
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
2.处理二进制数据时误用字符流,或处理文本数据时误用字节流,可能导致数据丢失或错误。确保根据数据类型选择正确的流类型。
3.在操作完流后忘记关闭,可能导致资源泄漏。
使用try-with-resources
语句可以自动关闭流。(这是JDK1.7提供的新方法,让代码的可读性更高,但是并不是所有的对象都可以这样,当调用的类实现了closable接口就可以使用此种方式)
4.在使用字节流操作中,即使没有关闭资源(close方法),也能输出;而字符流不使用close方法的话,不会输出任何内容。