目录
一、背景
Java中有File类,用于封装文件/目录的各种信息,对目录/文件进行操作,但是我们不能获取到文件/目录中的内容。因此引入IO流,用于处理设备之间的数据的传输。
二、IO流的分类
按照方向:输入流、输出流
按照处理数据的单位:字节流(小)、字符流(大)
组合形成四种:输入字节流(InputStream)、输出字节流(OutputStream)
输入字符流(Reader)、输出字符流(Writer)
这四个就是抽象基类
按照功能:节点流、处理流
节点流:单独一个“流”处理数据
处理流:“流”套着“流”,组合使用(构造器嵌套)
三、字符流操作相关代码示例
1、FileReader读取文件内容
将桌面的test.txt文件的内容读取到程序里。
(1)一次读一个字符----->效率低下
public class FileTest {
public static void main(String[] args) throws IOException {
//创建一个File类对象
File f = new File("/Users/minh/Desktop/test.txt");
//创建一个FileReader的流的对象
FileReader fr = new FileReader(f);
//读取文件内容
//遍历方式1:
// int read = fr.read();
// while(read != -1){
// System.out.println((char)read);
// read = fr.read();
// }
//遍历方式2:
int read;
while ((read = fr.read()) != -1)
System.out.println((char)read);
//主动关闭流
fr.close();
}
}
(2)一次读多个字符----->提高效率
public class FileTest {
public static void main(String[] args) throws IOException {
//创建一个File类对象
File f = new File("/Users/minh/Desktop/test.txt");
//创建一个FileReader的流的对象
FileReader fr = new FileReader(f);
char[] ch = new char[5];//缓冲数组
int len = fr.read(ch);
while(len != -1){
//方式1:
// for(int i = 0; i < len; i++)
// System.out.print(ch[i]);
//方式2:
String str = new String(ch, 0, len);
System.out.print(str);
len = fr.read(ch);
}
//关闭流
fr.close();
}
}
2、FileWriter向文件写数据
将程序中的内容输出到文件中。
(1)一个字符一个字符输出
public class FileTest {
public static void main(String[] args) throws IOException {
//创建一个File类对象
File f = new File("/Users/minh/Desktop/output.txt");
//创建一个FileWriter的流的对象
FileWriter fw = new FileWriter(f);
//new FileWriter(f, false);
//new FileWriter(f, true);
String str = "hello牛奶hello麦片";
for(int i = 0; i < str.length(); i++){
fw.write(str.charAt(i));
}
//关闭流
fw.close();
}
}
如果目标文件不存在,那么会自动创建次文件
如果目标文件存在,new FileWriter(f)和new FileWriter(f, false)都相当于对源文件进行覆盖操作;new FileWriter(f, true)相当于对源文件进行追加操作。
(2)一次输出多个字符
File f = new File("/Users/minh/Desktop/output.txt");
FileWriter fw = new FileWriter(f,true);
String str = "hello牛奶hello麦片";
char[] chs = str.toCharArray();
fw.write(chs);
fw.close();
3、实现文件内容复制
public class FileTest {
public static void main(String[] args) throws IOException {
//源文件
File f1 = new File("/Users/minh/Desktop/test.txt");
//目标文件
File f2 = new File("/Users/minh/Desktop/output.txt");
//创建FileReader和FileWriter流对象
FileReader fr = new FileReader(f1);
FileWriter fw = new FileWriter(f2);
//数据传送
// //方式1:一个字符一个字符传送
// int read = fr.read();
// while(read != -1){
// fw.write(read);
// read = fr.read();
// }
// //方式2:利用缓冲数组-->一次读多个字符
// char[] chs = new char[5];
// int len = fr.read(chs);
// while(len != -1){
// fw.write(chs, 0, len);
// len = fr.read(chs);
// }
//方式3:
char[] chs = new char[5];
int len = fr.read(chs);
while(len != -1){
String str = new String(chs, 0, len);
fw.write(str);
len = fr.read(chs);
}
//关闭流,后用的流先关闭
fw.close();
fr.close();
}
}
4、注意事项
字符流操作不能用于操作非文本文件!
文本文件:(.txt、.c、.java、.cpp等)—>建议使用字符流操作
非文本文件:图片、视频、音频(.jpg、.avi、.mp3、.doc、.ppt、.xlsx等)—>建议使用字节点操作
5、利用try-catch-finally处理异常
public class FileTest {
public static void main(String[] args){
//源文件
File f1 = new File("/Users/minh/Desktop/test.txt");
//目标文件
File f2 = new File("/Users/minh/Desktop/output.txt");
//创建FileReader和FileWriter流对象
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader(f1);
fw = new FileWriter(f2);
char[] chs = new char[5];
int len = fr.read(chs);
while(len != -1){
String str = new String(chs, 0, len);
fw.write(str);
len = fr.read(chs);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(fw != null)
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
四、字节流操作相关代码示例
1、用字节流读取文本文件操作(不建议)
细节1:文件是utf-8进行存储的,英文字符底层占1个字节,但中文字符占3个字节;
细节2:read()读取一个字节,返回值是int类型,而不是byte类型,是为了避免-1时,无法确定是读入的字节还是文件结尾的情况
2、读图片文件
public class IOTest{
public static void main(String[]args) throws IOException {
File f = new File("/Users/minh/Desktop/pho.jpeg");
FileInputStream fis = new FileInputStream(f);
byte[] b = new byte[1024*5];
int len = fis.read(b);
while(len != -1){
len = fis.read(b);
}
fis.close();
}
}
3、非文本文件内容的复制
以图片文件为例:
public class IOTest{
public static void main(String[]args) throws IOException {
File f1 = new File("/Users/minh/Desktop/pho.jpeg");
File f2 = new File("/Users/minh/Desktop/out.jpeg");
FileInputStream fis = new FileInputStream(f1);
FileOutputStream fos = new FileOutputStream(f2);
byte[] b = new byte[1024*5];
int len = fis.read(b);
while(len != -1){
fos.write(b, 0, len);
len = fis.read(b);
}
fos.close();
fis.close();
}
}
五、缓冲字节流(–>处理流)
缓冲字节流的引入是为了减少访存的次数,提高效率。在程序中分别开辟一个输入缓冲区和一个输出缓冲区,用于暂存数据。源码默认大小为8192字节。处理非文本文件—>BufferedInputStream、BufferedOutputStream
代码示例(图片文件.jpeg):
public class IOBufferTest {
public static void main(String[] args) throws IOException {
File f1 = new File("/Users/minh/Desktop/pho.jpeg");
File f2 = new File("/Users/minh/Desktop/output.jpeg");
FileInputStream fis = new FileInputStream(f1);
FileOutputStream fos = new FileOutputStream(f2);
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] b = new byte[1024*8];
int len = bis.read(b);
while(len != -1){
bos.write(b, 0, len);
len = bis.read(b);
}
bos.close();
bis.close();
//如果处理流包裹着节点流,那么只要关闭处理流,里面的字节流也会随之被关闭。
// fos.close();
// fis.close();
}
}
六、缓冲字符流(–>处理流)
处理文本文件—>BufferedReader、BufferedWriter
代码示例(文本文件.txt):
public class IOBufferTest {
public static void main(String[] args) throws IOException {
File f1 = new File("/Users/minh/Desktop/test.txt");
File f2 = new File("/Users/minh/Desktop/output.txt");
FileReader fr = new FileReader(f1);
FileWriter fw = new FileWriter(f2);
BufferedReader br = new BufferedReader(fr);
BufferedWriter bw = new BufferedWriter(fw);
//方式1:
char[] chs = new char[30];
int len = br.read(chs);
while(len != -1){
bw.write(chs, 0, len);
len = br.read(chs);
}
// //方式2:读取String
// //每次读取文本文件中一行,返回字符串
// String str = br.readLine();
// while(str != null){
// bw.write(str);
// bw.newLine(); //在文本文件中每次写完一行需要换行
// str = br.readLine();
// }
bw.close();
br.close();
}
}
七、转换流(–>处理流)
转换流的作用是将字节流和字符流进行转换。
属于字符流。
InputStreamReader:字节输入流—>字符输入流
OutputStreamWriter:字符输出流—>字节输出流
代码示例:
public class IOChangeTest {
public static void main(String[] args) throws IOException {
File f1 = new File("/Users/minh/Desktop/test.txt");
File f2 = new File("/Users/minh/Desktop/output.txt");
FileInputStream fis = new FileInputStream(f1);
FileOutputStream fos = new FileOutputStream(f2);
//加入一个转换流,将字节流转换为字符流。
//此时需要指定一个编码,这个编码与文件本身编码格式一致。
//若不指定编码格式,则默认与程序的格式一致
InputStreamReader isr = new InputStreamReader(fis);
//InputStreamReader isr = new InputStreamReader(fis, "utf-8");
OutputStreamWriter osw = new OutputStreamWriter(fos, "gbk");
char[] chs = new char[30];
int len = isr.read(chs);
while(len != -1){
osw.write(chs, 0, len);
len = isr.read(chs);
}
osw.close();
isr.close();
}
}
注:在使用InputStreamReader和OutputStreamWriter时,需指定编码格式(即文件的编码格式),若不指定则默认与程序的编码格式一致。
八、System类
System的属性:
System.in:“标准”输入流—>默认情况下,从键盘输入(字节流)
System.out:“标准”输出流—>默认情况下,输出到控制台
1、System.in
public class SystemTest {
public static void main(String[] args) throws IOException {
// //标准输入流--->从键盘输入
// InputStream in = System.in;
// int n = in.read();
// System.out.println(n);
// //由上述代码可知,键盘输入实际上是System.in
// //而Scanner的作用相当于一个扫描器,扫描从System.in出来的数据
// Scanner sc = new Scanner(System.in);
// int i = sc.nextInt();
// System.out.println(i);
//引申--->Scanner也可以扫描其他的内容
Scanner sc = new Scanner(new FileInputStream(new File("/Users/minh/Desktop/test.txt")));
while(sc.hasNext()){
System.out.println(sc.next());
}
}
}
注:之前学的从键盘输入语句:Scanner sc = new Scanner(System.in);
其中真正起输入作用的是System.in,而Scanner仅是一个“扫描器”的作用,扫描从System.in读取的数据,因此其也可以用于扫描其他的内容。
2、System.out
输出流、打印流。
PrintStream out = System.out;
out.println("minh");
代码示例(将键盘输入内容输出到文件中):
public class SystemTest {
public static void main(String[] args) throws IOException {
//输入准备
InputStream in = System.in;
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
//输出准备
File f = new File("/Users/minh/Desktop/output.txt");
FileWriter fw = new FileWriter(f);
BufferedWriter bw = new BufferedWriter(fw);
//写入操作
String str = br.readLine();
while (!str.equals("exit")){
bw.write(str);
bw.newLine();//换行
str = br.readLine();
}
bw.close();
br.close();
}
}
九、数据流(–>处理流)
用来操作基本数据类型和字符串。
分为DataInputStream和DataOutputStream两类。
代码示例(利用DataOutputStream从程序向文件写数据):
public class DataTest {
public static void main(String[] args) throws IOException {
//向文件中写数据
DataOutputStream dos = new DataOutputStream(new FileOutputStream("\"/Users/minh/Desktop/output.txt\""));
dos.writeBoolean(true);
dos.writeDouble(9.8);
dos.writeUTF("minh");
dos.close();
}
}
代码示例(利用DataInputStream从文件读数据到程序中):
//从文件中读数据
DataInputStream dis = new DataInputStream(new FileInputStream(new File("/Users/minh/Desktop/output.txt")));
System.out.println(dis.readBoolean());
System.out.println(dis.readDouble());
System.out.println(dis.readUTF());
dis.close();
运行结果:
注:读的顺序需与写的顺序一致,否则就会报错!
十、对象流
用来存储和读取基本数据类型数据或对象的处理流。
它可以把Java中的对象写入到数据源中,也能把对象从数据源中还原。
1、序列化
代码示例(将一个对象(String类)写入文件):
public class ObjectTest {
public static void main(String[] args) throws IOException {
//将一个对象写入文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("/Users/minh/Desktop/output.txt")));
oos.writeObject("minh");
oos.close();
}
}
ObjectOutputStream类:把Java对象转换成与平台无关的二进制数据,从而可以在磁盘上永久保存该数据或者通过网络将这种二进制数据传输到另一个网络节点上。称为序列化
2、反序列化
代码示例(将上述文件保存的数据读入程序):
//将文件保存的数据读入程序
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("/Users/minh/Desktop/output.txt")));
System.out.println((String)ois.readObject());
ois.close();
ObjectInputStreame类:将上述二进制数据恢复成原来的Java对象。
称为反序列化
3、代码示例
代码示例:操作一个自定义类对象
一个自定义对象Person:
public class Person implements Serializable{
private String name;
private int age;
private String sex;
public Person() {
}
public Person(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
向文件中写入一个对象:
public class SerializableTest {
public static void main(String[] args) throws IOException {
Person p = new Person("minh", 10, "male");
//向文件中写入一个对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("/Users/minh/Desktop/output.txt")));
oos.writeObject(p);
oos.close();
}
}
注:若创建的对象没有实现Serializable接口,会爆出NotSerializableException错误,因此想要序列化所创建对象的类,必须实现该接口。
但查看源码发现该接口为一个空接口,相当于只起一个标识的作用,称为标识接口,表明只有继承这个接口的类才能序列化。
从文件中读取该对象:
//从文件中读取该对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("/Users/minh/Desktop/output.txt")));
System.out.println((Person)(ois.readObject()));
ois.close();
4、序列化版本标识符(serialVersionUID)
当对自定义对象类进行细微修改,导致文件中的数据和当前类对象不匹配,系统在读取文件数据时就会报错。
解决方案:给该类加一个“序列号”,即serialVersionUID
serialVersionUID:
凡是实现Serializable接口(标识接口)的类都会有一个标识序列化版本标识符的静态常量。
使用方法:private static final long serialVersionUID;
用其来表明类的不同版本间的兼容性。
5、序列化的细节
(1)被序列化的类的内部的所有属性,必须是可序列化的(基本数据类型都是可序列化的)
例如:若被序列化的类内部有一个内部类,那么该类也必须实现Serializable接口。
(2)static、transient修饰的属性不能被序列化。
因此,一些不希望被保存或被看见的属性就可以用这两个进行修饰。