I/O流输入输出流,输入输出流又分为字符流和字节流。其中字节流适合所有文件的输入输出,字符流使用范围有限,通常用来处理文本的输入输出。并且用字符流处理文本时不会出现乱码,而用字节流可能会产生乱码。
字节流
字节流写入时是按照字节进行写入/读取的,一个字符可能由多个字节组成,比如一个汉字按照UTF-8编码时有三个字节,因此按照字节写入时有可能会出现乱码。
InputStream
inputStream是一个抽象类,包含多种实现类,FileInputStream就是最常用的一个实现类。
FileInputStream
文件输入流FileInputStream,从磁盘将内容输入到内存中,用来读取磁盘文件。
构造方法
FileInputStream(String name)//name为文件路径,可以是绝对路径也可以是相对路径
FileInputStream(File file)//通过文件对象创建输入流
FileInputStream(FileDescriptor fdObj)//不常用
常用函数
read()
读取一个字节,返回读取到的字节,如果读取完毕再读取时返回-1
// 根据文件路径创建输入流
InputStream is = new FileInputStream("D://test.txt");
// 一个字节一个字节的读取
int readed;//读取到的字节
while((readed = is.read())!=-1){//将读取到的字节赋给readed,如果readed返回-1表示读取完毕
System.out.printf(String.valueOf((char)readed));
}
单个字节读取时,如果有中文读取到的字节会变成乱码。如果采用UTF-8编码,一个中文有三个字节,分开读取时就会变成乱码。如果采用的是GBK编码,一个汉字有两个字节,同样会变成乱码。但是ASCAII里面的字符不会乱码,因为ASCAII总字符数有限,编码不会超过一个字节,UTF-8、GBK都兼容ASCAII。
读取结果:
read(byte[] b)
读取多个字节到字节数组中,返回读取到的字节数,如果读完了返回-1。byte是用来装读取字节的容易。
示例代码:
// 根据文件路径创建输入流
InputStream is = new FileInputStream("D://test.txt");
int len;//读取到的字节数
byte[] buffer = new byte[1024];
while((len = is.read(buffer))!=-1){// 读取到的字节数为-1表示读取完毕
System.out.printf(new String(buffer,0,len));// 只打印buffer中0到读取到的字节长度的内容
}
打印时用了len,只打印buffer中从0到len的内容。因为每次读取时不会把上一次buffer中的内容清空,而且从前往后替换。比如假设buffer容量为3字节,读取的内容是“abcd",第一次读取后buffer内容为"abc",下一次读取会成为:“dbc”,因为只剩下一个字节了,会把第一个替换掉,后面的仍然保留上一次的,所以读取时需要通过len来记录读取到的字节数。
BufferedInputStream
BufferedInputStream是对InputStream的一层包装,BufferedInputStream带有一个8KB的默认缓存,无论是单字节写入还是用字节数组写入,都会先把写入到的数据放到缓存中,然后再从磁盘写入到内存中,可以减少系统调用次数。
构造方法:
OutputStream os = new FileOutputStream("C:\\Users\\Windows\\IdeaProjects\\FileAndIo\\test.txt");
byte[] buffer = new byte[1024];
BufferedOutputStream bos = new BufferedOutputStream(os);
InputStream is = new FileInputStream("C:\\Users\\Windows\\IdeaProjects\\FileAndIo\\test1.txt");
BufferedInputStream bis = new BufferedInputStream(is);
int len;
while ((len = bis.read(buffer)) !=-1) {
bos.write(buffer, 0, len);
}
OutputStream
类似于InputStream,只是数据流向不同,是从内存输出到磁盘。该类也是一个抽象类,有很多实现类,FileOutputStream是常用的一个实现类。
FileOutputStream
将文件从内存输出到磁盘。构造方法和InputStream类似
构造方法
FileOutputStream(String name)//name为文件路径,可以是绝对路径也可以是相对路径
FileOutputStream(File file)//通过文件对象创建输入流
FileOutputStream(FileDescriptor fdObj)//不常用
常用函数
FileOutputStream主要用来输出文件到磁盘,通过write方法写入到磁盘,共包括以下几个重载方法:
第一个是单个字节写入,不常用。第二个是通过字节数组进行写入,第三个也是通过字节数组进行写入,但是传入了字节数组需要写入的起点和终点。
示例代码:
单个字节写入:
try {
OutputStream os = new FileOutputStream("C:\\Users\\Windows\\IdeaProjects\\FileAndIo\\test.txt");
os.write('从');
os.write('a');
os.write(97);
} catch (Exception e) {
e.printStackTrace();
}
结果:
�aa
write方法可以传入汉字,但是只会截取汉字中的第一个字节进行写入,因此在解码时会成为乱码。也可以写入字符的代码。
使用字节数组写入
try {
OutputStream os = new FileOutputStream("C:\\Users\\Windows\\IdeaProjects\\FileAndIo\\test.txt");
byte[] buffer = new byte[1024];
InputStream is = new FileInputStream("C:\\Users\\Windows\\IdeaProjects\\FileAndIo\\test1.txt");
int len;
while ((len = is.read(buffer)) !=-1) {
os.write(buffer, 0, len);
}
os.close();
is.close();
} catch (Exception e) {
e.printStackTrace();
}
BufferedOutputStream
BufferedOutputStream类似于BufferedInputStream,只是BufferedOutputStream是对OutputStream的包装,BufferedOutputStream同样也有一个默认8KB的缓存。构造方法中需要传入一个OutputStream,也可以传入一个大小,表示缓存的大小,默认是8KB。
OutputStream os = new FileOutputStream("C:\\Users\\Windows\\IdeaProjects\\FileAndIo\\test.txt");
byte[] buffer = new byte[1024];
BufferedOutputStream bos = new BufferedOutputStream(os);
InputStream is = new FileInputStream("C:\\Users\\Windows\\IdeaProjects\\FileAndIo\\test1.txt");
BufferedInputStream bis = new BufferedInputStream(is);
int len;
while ((len = bis.read(buffer)) !=-1) {
bos.write(buffer, 0, len);
}
字符流
字符流写入时是根据字符进行写入的,写入时不会出现乱码的情况。
Reader
reader是一个抽象类,FileReader是其常用的一个实现类。
FileReader
逐个字符读取:
Reader reader = new FileReader("C:\\Users\\Windows\\IdeaProjects\\FileAndIo\\test.txt");
System.out.printf(String.valueOf((char)reader.read()));
System.out.printf(String.valueOf((char)reader.read()));
System.out.printf(String.valueOf((char)reader.read()));
System.out.printf(String.valueOf((char)reader.read()));
System.out.printf(String.valueOf((char)reader.read()));
System.out.printf(String.valueOf((char)reader.read()));
因为是按字符读取,因此不会出现乱码。
按照字符数组进行读取:
Reader reader = new FileReader("C:\\Users\\Windows\\IdeaProjects\\FileAndIo\\test.txt");
char[] buffer = new char[1024];
int len;
while ((len = reader.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, len));
}
BufferedReader
BufferedReader是Reader的一个包装类,传入参数reader和size,size表示BufferedReader的缓存大小,可以不用传,不传默认为8KB。
Reader reader = new FileReader("C:\\Users\\Windows\\IdeaProjects\\FileAndIo\\test.txt");
BufferedReader br = new BufferedReader(reader,8*1024);
char[] buffer = new char[1024];
int len;
while ((len = reader.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, len));
}
InputStreamReader
字符转换流,可以将字节流转换为字符流,并且可以设置编码方式。
InputStream is = new FileInputStream("C:\\Users\\Windows\\IdeaProjects\\FileAndIo\\test.txt");
InputStreamReader isr = new InputStreamReader(is,"GBK");//按照GBK编码读取
BufferedReader br = new BufferedReader(isr);// 构造BufferedReader
char[] buffer = new char[1024];
int len;
while ((len = br.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, len));
}
因为我原文件的内容是用UTF-8编码,所以改成GBK会变成乱码。
aaab杩欐槸涓�娈垫祴璇曟枃鏈琣
Writer
抽象类
FileWriter
writer实现类,可以将文件从内存写入到磁盘。
Writer writer = new FileWriter("C:\\Users\\Windows\\IdeaProjects\\FileAndIo\\output.txt");
Reader reader = new FileReader("C:\\Users\\Windows\\IdeaProjects\\FileAndIo\\test.txt");
char[] buffer = new char[1024];
int len;
while ((len = reader.read(buffer)) != -1) {
writer.write(buffer, 0, len);
System.out.printf(String.valueOf(buffer, 0, len));
}
如果运行这段代码,会发现能够创造出output.txt文件,日志中打印的内容也不是空的,但是output.txt中的文件是空的,这是因为通过writer写入到磁盘时存在一个缓冲区,写入时会把内容放到缓冲区,并不会马上写入到磁盘中,如果需要马上写入到磁盘中,需要调用FileWriter.flush()方法才会写入,如果还没有调用flush进行写入到磁盘的时候缓冲区已经满了,则会自动写入。当然也可以不用通过flush方法来刷新写入到磁盘,可以直接调用Writer的close方法,在close时会将缓存中的内容写入到磁盘中。这也提醒我们,在使用IO流时需要做好资源的释放,避免产生内存泄漏问题。
BufferedWriter
BufferedWriter带有缓冲,默认为8KB,通过Writer创建。
OutputStreamWriter
OutputStream os = new FileOutputStream("C:\\Users\\Windows\\IdeaProjects\\FileAndIo\\output.txt");
OutputStreamWriter osw = new OutputStreamWriter(os,"GBK");// 通过GBK编码方式写入
BufferedWriter bw = new BufferedWriter(osw);
Reader reader = new FileReader("C:\\Users\\Windows\\IdeaProjects\\FileAndIo\\test.txt");
char[] buffer = new char[1024];
int len;
while ((len = reader.read(buffer)) != -1) {
bw.write(buffer, 0, len);
System.out.printf(String.valueOf(buffer, 0, len));
}
性能对比
字节流
单字节输入流读取
// 单字节输入流读取
public static void singleInputStream() {
long startTime = System.currentTimeMillis();
InputStream inputStream = null;
try {
inputStream = new FileInputStream("D:\\test.txt"); // 请替换为实际路径
int readed;
StringBuffer sb = new StringBuffer();
while ((readed = inputStream.read()) != -1) {
sb.append((char) readed); // 将读取的字节转换为字符并添加到 StringBuffer
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 释放资源
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
long endTime = System.currentTimeMillis();
System.out.println("singleInputStream耗时: " + (endTime - startTime) + " ms");
}
字节数组输入流读取
// 字节数组输入流读取
public static void multiInputStream() {
long startTime = System.currentTimeMillis();
InputStream inputStream = null;
try {
inputStream = new FileInputStream("D:\\test.txt"); // 请替换为实际路径
byte[] buffer = new byte[1024];
int readed;
while ((readed = inputStream.read(buffer)) != -1) {
// 处理读取的字节
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 释放资源
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
long endTime = System.currentTimeMillis();
System.out.println("multiInputStream耗时: " + (endTime - startTime) + " ms");
}
单字节缓冲输入流读取
// 单字节缓冲输入流读取
public static void singleBufferedInputStream() {
long startTime = System.currentTimeMillis();
InputStream inputStream = null;
try {
inputStream = new BufferedInputStream(new FileInputStream("D:\\test.txt")); // 请替换为实际路径
int readed;
StringBuffer sb = new StringBuffer();
while ((readed = inputStream.read()) != -1) {
sb.append((char) readed); // 将读取的字节转换为字符并添加到 StringBuffer
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 释放资源
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
long endTime = System.currentTimeMillis();
System.out.println("singleBufferedInputStream耗时: " + (endTime - startTime) + " ms");
}
字节数组缓冲输入流读取
// 字节数组缓冲输入流读取
public static void multiBufferedInputStream() {
long startTime = System.currentTimeMillis();
InputStream inputStream = null;
try {
inputStream = new BufferedInputStream(new FileInputStream("D:\\test.txt")); // 请替换为实际路径
byte[] buffer = new byte[1024];
int readed;
while ((readed = inputStream.read(buffer)) != -1) {
// 处理读取的字节
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 释放资源
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
long endTime = System.currentTimeMillis();
System.out.println("multiBufferedInputStream耗时: " + (endTime - startTime) + " ms");
}
字符流
单字符读取
// 单字符读取
public static void singleReader() {
long startTime = System.currentTimeMillis();
Reader reader = null;
try {
reader = new FileReader("D:\\test.txt"); // 请替换为实际路径
int readed;
StringBuffer sb = new StringBuffer();
while ((readed = reader.read()) != -1) {
sb.append((char) readed); // 将读取的字符添加到 StringBuffer
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 释放资源
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
long endTime = System.currentTimeMillis();
System.out.println("singleReader耗时: " + (endTime - startTime) + " ms");
}
字符数组读取
// 字符数组读取
public static void multiReader() {
long startTime = System.currentTimeMillis();
Reader reader = null;
try {
reader = new FileReader("D:\\test.txt"); // 请替换为实际路径
char[] buffer = new char[1024];
int readed;
while ((readed = reader.read(buffer)) != -1) {
// 处理读取的字符
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 释放资源
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
long endTime = System.currentTimeMillis();
System.out.println("multiReader耗时: " + (endTime - startTime) + " ms");
}
单字符缓冲读取
// 单字符缓冲读取
public static void singleBufferedReader() {
long startTime = System.currentTimeMillis();
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("D:\\test.txt")); // 请替换为实际路径
int readed;
StringBuffer sb = new StringBuffer();
while ((readed = reader.read()) != -1) {
sb.append((char) readed); // 将读取的字符添加到 StringBuffer
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 释放资源
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
long endTime = System.currentTimeMillis();
System.out.println("singleBufferedReader耗时: " + (endTime - startTime) + " ms");
}
字符数组缓冲读取
// 字符数组缓冲读取
public static void multiBufferedReader() {
long startTime = System.currentTimeMillis();
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("D:\\test.txt")); // 请替换为实际路径
char[] buffer = new char[1024];
int readed;
while ((readed = reader.read(buffer)) != -1) {
// 处理读取的字符
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 释放资源
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
long endTime = System.currentTimeMillis();
System.out.println("multiBufferedReader耗时: " + (endTime - startTime) + " ms");
}
运行结果
开启多个线程执行所有方法
public static void main(String[] args) {
new Thread(() -> singleInputStream()).start();
new Thread(() -> multiInputStream()).start();
new Thread(() -> singleBufferedInputStream()).start();
new Thread(() -> multiBufferedInputStream()).start();
new Thread(() -> singleReader()).start();
new Thread(() -> multiReader()).start();
new Thread(() -> singleBufferedReader()).start();
new Thread(() -> multiBufferedReader()).start();
}
用线程池执行
public static void main(String[] args) {
// 创建 ThreadPoolExecutor,核心线程数、最大线程数、空闲时间和单位、工作队列
ThreadPoolExecutor executor = new ThreadPoolExecutor(
4, // 核心线程数
8, // 最大线程数
60, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>() // 工作队列
);
// 提交任务
executor.execute(Main::singleInputStream);
executor.execute(Main::multiInputStream);
executor.execute(Main::singleBufferedInputStream);
executor.execute(Main::multiBufferedInputStream);
executor.execute(Main::singleReader);
executor.execute(Main::multiReader);
executor.execute(Main::singleBufferedReader);
executor.execute(Main::multiBufferedReader);
// 关闭线程池
executor.shutdown();
try {
// 等待所有任务完成
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow(); // 超时后强制关闭
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt(); // 恢复中断状态
}
}
multiBufferedInputStream耗时: 157 ms
multiReader耗时: 536 ms
multiBufferedReader耗时: 551 ms
multiInputStream耗时: 563 ms
singleBufferedReader耗时: 3506 ms
singleReader耗时: 5593 ms
singleBufferedInputStream耗时: 7698 ms
singleInputStream耗时: 296171 ms
可以带有缓冲流同时通过字节数组来多个读取的速度最快,单个字节读取并且不用缓冲流速度最慢。 接下来再做进一步测试,由于单个字节/字符读取使用并不是很多,字节流使用场景有一定的限制,只能用来读取文本,所以只保留用字节流数组读取的非缓冲流和缓冲流读取的方法。
因为缓冲字节流默认的缓冲大小是8KB,所以先将不带缓冲的字节数组大小改为8KB,看看两者耗时如何。
完整代码:
public static void main(String[] args) {
new Thread(() -> multiInputStream()).start();
new Thread(() -> multiBufferedInputStream()).start();
}
// 字节数组输入流读取
public static void multiInputStream() {
long startTime = System.currentTimeMillis();
InputStream inputStream = null;
try {
inputStream = new FileInputStream("D:\\test.txt"); // 请替换为实际路径
byte[] buffer = new byte[1024];
int readed;
while ((readed = inputStream.read(buffer)) != -1) {
// 处理读取的字节
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 释放资源
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
long endTime = System.currentTimeMillis();
System.out.println("非缓冲流读取耗时: " + (endTime - startTime) + " ms");
}
// 字节数组缓冲输入流读取
public static void multiBufferedInputStream() {
long startTime = System.currentTimeMillis();
InputStream inputStream = null;
try {
inputStream = new BufferedInputStream(new FileInputStream("D:\\test.txt")); // 请替换为实际路径
byte[] buffer = new byte[1024];
int readed;
while ((readed = inputStream.read(buffer)) != -1) {
// 处理读取的字节
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 释放资源
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
long endTime = System.currentTimeMillis();
System.out.println("缓冲流读取耗时: " + (endTime - startTime) + " ms");
}
字节数组大小为1KB时耗时:
缓冲流读取耗时: 99 ms
非缓冲流读取耗时: 296 ms
将buffer改成new byte[1024*8]之后的耗时:
非缓冲流读取耗时: 90 ms
缓冲流读取耗时: 92 ms
此时两者耗时几乎一致,改成16KB之后再次测试:
缓冲流读取耗时: 68 ms
非缓冲流读取耗时: 67 ms
读取速度提升还是比较明显
改成32KB:
非缓冲流读取耗时: 63 ms
缓冲流读取耗时: 65 ms
读取速度有所提升,但是提升有限。