本文纲要
IO概述IO分类- 字节流 – 字节输出流快速入门
- 字节流 – 注意事项
- 字节流 – 一次写多个数据
- 字节流 – 两个问题:换行与追加写入
- 字节流 –
try-catch捕获异常 - 字节流 – 字节输出流小结
- 字节流 – 字节输入流基本学习
- 字节流 – 读多个字节
- 字节流 – 文件复制
- 字节流 – 定义小数组拷贝
- 字节流 – 小数组拷贝原理
IO概述
在Java中,IO(Input/Output)用于实现程序与外部数据(通常是文件)的交互。
早期的数据存储方式往往依赖变量、数组或集合,这些数据保存在内存中,程序结束后便会丢失,无法实现永久化存储。
引入IO后,我们可以将数据写出到硬盘文件,实现持久化;也可以从文件中读取数据到内存中使用。
IO流的两个核心操作:
- 写数据:将内存中的数据写入文件(Output)。
- 读数据:将文件中的数据读入内存(Input)。
以内存为参照物的数据流动方向:
- 输入(
Input):硬盘 → 内存,由内存进行读操作。 - 输出(
Output):内存 → 硬盘,由内存进行写操作。
因此,学习IO的重点就是掌握如何读和如何写。
IO分类
根据不同的维度,Java的IO流可以分成以下几种类型:
| 分类依据 | 类型 | 说明 |
|---|---|---|
| 流向 | 输入流(InputStream) | 硬盘 → 内存(读) |
输出流(OutputStream) | 内存 → 硬盘(写) | |
| 操作的数据类型 | 字节流(Byte Stream) | 可以操作所有类型的文件(文本、图片、音视频等) |
字符流(Character Stream) | 只能操作纯文本文件 |
纯文本文件的判断标准:
用Windows自带的记事本(Notepad)打开文件,如果能读懂内容,就是纯文本文件;否则就是非纯文本文件,必须使用字节流操作。
例如:
.txt文件:纯文本 → 可用字符流.doc,.avi,.mp3,.jpg等:非纯文本 → 必须用字节流
注意:Office文件(Word、Excel)虽然包含文字,但内部含有格式、图片等二进制数据,用记事本打开会显示乱码,因此不是纯文本,只能使用字节流。
字节流 – 字节输出流快速入门
写数据的三个步骤:
- 创建字节输出流对象,关联目标文件。
- 调用
write方法写入数据。 - 释放资源(调用
close方法)。
代码示例(项目结构):
bytestream/
└── src/
└── com/
└── wb/
└── output/
├── OutputDemo1.java
├── OutputDemo2.java
├── ...
└── OutputDemo10.java
OutputDemo1.java 展示了最基本的写操作:
package com.wb.output;
import java.io.FileOutputStream;
import java.io.IOException;
public class OutputDemo1 {
public static void main(String[] args) throws IOException {
//1.创建字节输出流对象 --- 告诉虚拟机我要往哪个文件中写数据
FileOutputStream fos = new FileOutputStream("D:\\a.txt");
//也可以使用File对象
//FileOutputStream fos = new FileOutputStream(new File("D:\\a.txt"));
//2.写数据
fos.write(97); //97对应ASCII码中的'a'
//3.释放资源
fos.close();
}
}
运行后,D:\a.txt被创建,内容为字母a。
字节流 – 注意事项
OutputDemo2.java 演示了以下注意点:
package com.wb.output;
import java.io.FileOutputStream;
import java.io.IOException;
public class OutputDemo2 {
public static void main(String[] args) throws IOException {
//1.创建字节输出流对象
//注意:如果文件不存在,会自动创建
// 如果文件存在,会清空原有内容
FileOutputStream fos = new FileOutputStream("C:\\wb\\a.txt");
//2.写数据:传递一个整数,实际写入的是该整数在码表中对应的字符
fos.write(98); //'b'
//3.释放资源:告诉操作系统不再使用该文件
fos.close();
}
}
关键结论:
- 文件不存在 → 自动创建。
- 文件已存在 → 内容会被清空(除非开启追加模式)。
write(int)写入的是整数对应的字符(ASCII/Unicode码表)。close()必须调用,否则文件可能被占用而无法删除(可运行一个死循环测试,不调用close时文件无法删除)。
字节流 – 一次写多个数据
字节输出流提供了三种写数据的方式:
| 方法 | 说明 |
|---|---|
write(int b) | 一次写一个字节 |
write(byte[] b) | 一次写一个字节数组(全部) |
write(byte[] b, int off, int len) | 写字节数组的一部分,从off索引开始,连续写len个字节 |
OutputDemo3.java – 一次写多个单字节:
FileOutputStream fos = new FileOutputStream("bytestream\\a.txt");
fos.write(97); //'a'
fos.write(98); //'b'
fos.write(99); //'c'
fos.close();
OutputDemo4.java – 一次写一个字节数组,以及写部分数组:
FileOutputStream fos = new FileOutputStream("bytestream\\a.txt");
//一次写整个数组
byte[] bys = {97, 98, 99};
fos.write(bys); //写入abc
//一次写数组的一部分
byte[] bys2 = {97, 98, 99, 100, 101, 102, 103};
fos.write(bys2, 1, 2); //从索引1开始写2个字节,即98('b')和99('c')
fos.close();
注意:第三个参数len是写入的字节个数,不是结束索引。
字节流 – 两个问题:换行与追加写入
1 ) 如何实现换行?
不同操作系统的换行符:
- Windows:\r\n
- Linux:\n
- Mac:\r
将换行符转换成字节数组后写入即可:
fos.write("\r\n".getBytes());
OutputDemo5.java 示例(包含追加和换行):
FileOutputStream fos = new FileOutputStream("bytestream\\a.txt", true); //追加模式
fos.write(97);
fos.write("\r\n".getBytes());
fos.write(98);
fos.write("\r\n".getBytes());
fos.write(99);
fos.write("\r\n".getBytes());
fos.write(100);
fos.write("\r\n".getBytes());
fos.write(101);
fos.write("\r\n".getBytes());
fos.close();
2 ) 如何实现追加写入?
构造方法:FileOutputStream(String path, boolean append)
append = false(默认):清空原文件内容append = true:打开续写开关,新内容追加到文件末尾
使用true参数后,每次运行都不会清空原有数据,实现追加效果。
字节流 – try-catch捕获异常
之前的代码一直使用throws抛出异常,但更规范的做法是使用try-catch-finally自行处理,特别是保证close()一定被执行。
标准异常处理结构:
FileOutputStream fos = null;
try {
fos = new FileOutputStream("D:\\a.txt");
fos.write(97);
} catch (IOException e) {
e.printStackTrace();
} finally {
//finally中的代码一定会执行(除非JVM退出)
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意要点:
fos必须声明在try外部,否则finally中无法访问。- 若
new FileOutputStream或write出现异常,fos可能仍为null,调用close会引发空指针异常,因此需做非空判断。 close本身也可能抛出异常,需要再次捕获。
字节流 – 字节输出流小结
| 步骤 | 要点 |
|---|---|
| 创建对象 | 关联目标文件;文件不存在则创建,存在则清空(除非append=true) |
| 写数据 | 可写单个字节、整个字节数组或数组的一部分;换行需写 \r\n 的字节形式 |
| 释放资源 | close()必须执行,通常放在finally 中并做非空判断 |
字节流 – 字节输入流基本学习
读取文件的三步骤:
- 创建
FileInputStream对象,关联源文件。 - 调用
read方法读取字节。 - 释放资源。
OutputDemo7.java:
FileInputStream fis = new FileInputStream("bytestream\\a.txt");
int read = fis.read();
//read方法一次读取一个字节,返回该字节对应的ASCII码(0~255)
//若想看到字符,需强转为char
System.out.println((char) read); // 输出'a'(假设文件内容为a)
fis.close();
注意:
- 若文件不存在,直接抛出
FileNotFoundException。 read()返回的是字节数据(int类型),到达文件末尾时返回-1。
字节流 – 读多个字节
文件中通常有多个字节,需要循环读取,直到返回-1。
OutputDemo8.java(标准循环读取):
FileInputStream fis = new FileInputStream("bytestream\\a.txt");
int b;
while ((b = fis.read()) != -1) {
System.out.print((char) b);
}
fis.close();
错误示范(死循环读取):
//错误:无法停止,且一直读到文件结束后会打印大量-1或乱码
while (true) {
int i = fis.read();
System.out.println(i);
}
正确做法中,以read()返回值是否为-1作为循环结束条件。
字节流 – 文件复制
文件复制的本质:从源文件(数据源)读取字节,写入目标文件(目的地)。
需要同时使用 FileInputStream 和 FileOutputStream。
OutputDemo9.java:
FileInputStream fis = new FileInputStream("C:\\wb\\a.avi");
FileOutputStream fos = new FileOutputStream("bytestream\\a.avi");
int b;
while ((b = fis.read()) != -1) {
fos.write(b); //读一个字节,立即写一个字节
}
fis.close();
fos.close();
该方式每次读写一个字节,当文件较大时效率较低。
字节流 – 定义小数组拷贝
解决大文件拷贝效率问题:一次性读写多个字节,类似“用篮子一次买多个鸡蛋”。
提高效率的方案:
使用read(byte[] b):一次读取多个字节存入数组,返回值是本次读取的有效字节数。
使用write(byte[] b, int off, int len):将数组中指定部分写出。
OutputDemo10.java:
FileInputStream fis = new FileInputStream("C:\\wb\\a.avi");
FileOutputStream fos = new FileOutputStream("bytestream\\a.avi");
byte[] bytes = new byte[1024]; //相当于“篮子”,一次读取1024字节
int len; //本次读到的实际字节数
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len); //只写出实际读到的有效字节
}
fis.close();
fos.close();
为什么不用整个数组作为写入长度?
最后一次读取可能填不满数组(文件剩余字节不足1024),len记录的就是实际数据量,若写成fos.write(bytes) 会将数组末尾脏数据一并写入,导致文件错误。
字节流 – 小数组拷贝原理
用示意图解释小数组拷贝过程。假设数组长度为2,源文件内容为ABCDE五个字节。
len始终表示实际读到的字节数,第3次只有1个字节,所以写出时长度必须为1,否则会把上一次残留的D也写入
这正是为什么在write中必须指定0到len的范围。
总结
通过上述内容,我们已经掌握了Java中最基础的字节流操作,包括文件读写、异常处理、效率优化等核心技能。后续可进一步学习字符流、缓冲流等高级特性。
1万+

被折叠的 条评论
为什么被折叠?



