在编程中,数据存储是重中之重,在之前的学习中所有程序中的数据都是临时存放在内存中的,不能做到有效存储和长久保存,而本次的文件流就暂时解决了数据不能长久存储的问题(虽然后面会学习数据库存储,但文件流存储也很常用)。
须知无论是读取文件还是写入文件等操作,在计算机中都是以流的形式进行操作的,就像水的流动一样,以内存为自身来看,内存向外写就是写操作,从文件读取到内存就是读操作。输入流和输出流的区分:以内存为参照,如果数据向内存流动,则是输入流,反之是输出流。
IO流中分为字节流和字符流,详细分类如图:
字符流和字节流的主要区别在于每次读取数据的大小不同,字节流读取的时候,读到一个字节就返回一个字节,字节流可以处理所有类型数据,如:图片,MP3,AVI视频文件,而字符流只能处理字符数据,每次读取一个字符大小。但是只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。字节输入流类包括:FileInputStream、BufferedInputStream和DataInputStream;字节输出流类包括:FileOutputStream、BufferedOutputStream和DataOutputStream。字符流输入类:FileReader:用来读取字符文件的便捷类;字符流输出类:FileWriter:用来写入字符文件的便捷类,可用于写入字符流。由此看出:结尾由Stream的流是字节流,而由Reader或Writer的流是字符流。具体流的使用场景如下图:
流分类 | 使用分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
抽象基类 | InputStream | OutputStream | Reader | Writer | |
节点流 | 访问文件 | FileInputStream | FileOutStream | FileReader | FileWriter |
访问数值 | ByteArrayInputStream | ByteArrayOutStream | CharArrayReader | CharArrayWriter | |
访问管道 | PipedInputStream | PipedOutStream | PipedReader | PipedWriter | |
访问字符串 | StringReader | StringWriter | |||
处理流 | 缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | |||
对象流 | ObjectInputStream | ObjectOutputStream | |||
抽象基类(过滤) | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter | |
打印流 | PrintStream | PrintWriter | |||
推回输入流 | PushbackInputStream | PushbackReader | |||
特殊流 | DataInputStream | DataOutputStream |
接下来对文件字节流的读写的FileInputStream和FileOutPutStream和文件字符流FileWriter和FileReader进行详细的解读。FileInputStream流被称为文件字节输入流,意思指对文件数据以字节的形式进行读取操作,FileInputStream()的构造函数需要传入一个文件对象来指定读取的具体文件,FileInputStre中读取文件的方法为read(),可以传入一个自定义大小的byte[]数组,结束标志是读取到的字节数为-1。
FileOutputStream流是指文件字节输出流,专用于输出原始字节流如图像数据等,其继承OutputStream类,拥有输出流的基本特性。FileOutputStream()的构造函数同样需要传入一个文件对象,FileOutputStream中写入文件的方法为write(),也能传入一个byte[]数组。注意:写入数据后需要使用close()关闭流。
FileWriter:用来写入字符文件的便捷类。此类的构造方法可以设置默认字符编码和默认字节缓冲区大小。FileWriter被称为文件字符输出流,FileWriter也需要传入一个文件对象,不过与字节输出流不同的是字符流的write()方法可以直接传入字符串,为了保证能够在文件中看到写入的数据需要使用flush()刷新或者直接关闭流来实现,两者的区别在于使用flush()后还可以使用该流,仍能对流进行操作,而使用close()之后不能再对该流进行操作。
FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。FileReader被称为文件字符输入流,FileReader也需要传入一个文件对象,不过与字节输入流不同的是字符流的read()方法可以直接传入字符数组,使用完流后一定要用close()释放资源。
这里在说一下文件流操作中最容易出现的中文乱码问题,1:存入和取出时的编码方式不同就会产生中文乱码,例如设置一个对象的存入编码方式为GBK编码,而IDEA集成开发环境默认是UTF-8的话读取出来的数据就会是乱码显示。2:使用字节流读取含有中文的文件时会产生中文乱码,英文字母和数字不会产生乱码因为字节流读取是按照一个字节一个字节来读取的,英文字母和数字只占用一个字节,就像分割一样,读到一个就拿出,而中文在UTF-8下是由3个字节组成,所以这样一个中文本该是3个字节组成一个汉字,而读取的时候拆分成了3个小份,单独显示就是看不懂的乱码了。测试代码如下:
public class IoDemo8 {
public static void main(String[] args) {
String s = "abc里";
byte[] bytes = s.getBytes();
String s1 = null;
try {
// 如果存入和取出时的编码方式不同就会产生中文乱码
s1 = new String(bytes,"GBK");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println(s1);
try {
PrintWriter printWriter = new PrintWriter("test.txt");
printWriter.print("haha拜");
printWriter.flush();
printWriter.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
// 使用字节流读取会产生中文乱码
FileInputStream fileInputStream = new FileInputStream("test.txt");
int flag = 0;
while((flag = fileInputStream.read()) !=-1) {
System.out.println((char)flag);
}
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
最后在简述一下如何使用对象流实现对象序列化和反序列化。对象的序列化指将一个Java对象写入IO流中,与此对应的是,对象的反序列化则指从IO流中恢复该Java对象如果需要让某个对象支持序列化机制,则必须让它的类是可序列化的。为了让某个类是可序列化的,必须实现Serializable接口,实现该接口并不需要重写任何方法,它只是作为一个标记接口,标记该类是可序列化的。(唯一需要注意的是可以为该类建立唯一序列号来区分,如果在序列化类中重新添加属性并使用该属性,再次反序列化的时候就会报错,因为已经对类进行修改和重新使用,系统会自动把默认的序列号修改,导致你重新反序列化的时候因为找不到原来序列号版本的文件数据而失败。)只需要了解出错的原理,以后出现问题知道解决方向就能很快搞定。对象序列化及反序列化的代码应用,建一个Student学生列,实现Serializable接口,定义一些常用属性如学号,姓名等,在建一个.txt的文本文件来存储序列化信息,然后就使用ObjectOutputStream对象流进行序列化,用ObjectInputStream进行反序列化。参考代码如下:
public class Student implements Serializable {
// 建立唯一序列号
// public static final long serialVersionUID = 1L;
private String sno;
private String name;
// private String tel;
public Student() {
}
public Student(String sno,String name) {
this.sno = sno;
this.name = name;
}
public String getSno() {
return sno;
}
public void setSno(String sno) {
this.sno = sno;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"sno='" + sno + '\'' +
", name='" + name + '\'' +
'}';
}
}
public class IoDemo2 {
public static void main(String[] args) throws Exception {
List<Student> list = new ArrayList<>();
list.add(new Student("s001","zs"));
list.add(new Student("s002","li"));
list.add(new Student("s003","wy"));
// 序列化
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("demo.txt"));
outputStream.writeObject(list);
outputStream.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("demo.txt"));
List<Student> list1 = (List<Student>) ois.readObject();
for (Student student : list1) {
System.out.println(student);
}
ois.close();
}
}
运行结果:
至此文件流的基本知识及应用也阐述完了,知识学完,也喝一口鸡汤暖心提神,代码之路,bug无穷,要么出彩,要么出局!