第9章 ?IO流?
9.1 ?Java流概述

?9.1.2 Reader和Writer继承结构图:

9.1.3 IO流的分类
有多种分类方式:
????????一种方式是按照流的方向进行分类:以内存作为参照物,
????????????????????????①往内存中去,叫做输入(Input)。或者叫做读(Read)。
????????????????????????②从内存中出来,叫做输出(Output)。或者叫做写(Write)。
????????另一种方式是按照读取数据方式不同进行分类:
????????????????????????①有的流是按照字节的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制位。
????????????????????????????????????????这种流是万能的,什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频文件等....
????????????????????????????????????????假设文件file1.txt,采用字节流的话是这样读的:a中国bc张三fe
????????????????????????????????????????????????????????第一次读:一个字节,正好读到'a'。
????????????????????????????????????????????????????????第二次读:一个字节,正好读到'中'字符的一半。
????????????????????????????????????????????????????????第三次读:一个字节,正好读到'中'字符的另外一半。
????????????????????????②有的流是按照字符的方式读取数据的,一次读取一个字符,
????????????????????????????????????????这种流是为了方便读取普通文本文件而存在的,这种流只能读取纯文本(word文档不属于纯文本)。
????????????????????????????????????????假设文件file1.txt,采用字符流的话是这样读的:?a中国bc张三fe
????????????????????????????????????????????????第一次读:'a'字符('a'字符在windows系统中占用1个字节。)
????????????????????????????????????????????????第二次读:'中'字符('中'字符在windows系统中占用2个字节。)
9.1.4 IO流的四大家族:
在java中只要类名以“Stream”结尾的都是字节流。以“Reader/Writer”结尾的都是字符流。
- java.io.InputStream ?字节输入流
- java.io.OutputStream 字节输出流
- java.io.Reader 字符输入流
- java.io.Writer 字符输出流
所有的流都实现了:java.io.Closeable接口,都是可关闭的,都有close()方法。流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费(占用)很多资源。养成好习惯,用完流一定要关闭。
所有的输出流都实现了:java.io.Flushable接口,都是可刷新的,都有flush()方法。养成一个好习惯,输出流在最终输出之后,一定要记得flush()刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)刷新的作用就是清空管道。注意:如果没有flush()可能会导致丢失数据。
9.1.5 java.io包下需要掌握的16个流:
文件专属:
- java.io.FileInputStream(掌握)
- java.io.FileOutputStream(掌握)
- java.io.FileReader
- java.io.FileWriter
转换流:(将字节流转换成字符流)
- java.io.InputStreamReader
- java.io.OutputStreamWriter
缓冲流专属:
- java.io.BufferedReader
- java.io.BufferedWriter
- java.io.BufferedInputStream
- java.io.BufferedOutputStream
数据流专属:
- java.io.DataInputStream
- java.io.DataOutputStream
标准输出流:
- java.io.PrintWriter
- java.io.PrintStream(掌握)
对象专属流:
- java.io.ObjectInputStream(掌握)
- java.io.ObjectOutputStream(掌握)
- abstract int read() 从输入流读取下一个数据字节。
- int read(byte[] b) 从输入流中读取一定数量的字节并将其存储在缓冲区数组 b 中。
- int read(byte[] b, int off, int len) 将输入流中最多 len 个数据字节读入字节数组。
9.1.7 OutputStream(字节输出流)
- void write(byte[] b) 将 b.length 个字节从指定的字节数组写入此输出流。
- void write(byte[] b, int off, int len) 将指定字节数组中从偏移量 off 开始的 len 个字节写入此输出流。
- abstract void write(int b) 将指定的字节写入此输出流。
9.1.8 Reader(字符输入流)
- int read() 读取单个字符。
- int read(char[] cbuf) 将字符读入数组。
- abstract int read(char[] cbuf, int off, int len) 将字符读入数组的某一部分。
9.1.9 Writer(字符输出流)
- Writer append(char c) 将指定字符追加到此 writer。
- void write(char[] cbuf) 写入字符数组。
- abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分。
- void write(int c) 写入单个字符。
9.2 ?文件流
FileInputStream类的其他常用方法:
- int available():返回流当中剩余没有读到的字节数量。
- long skip(long n):跳过几个字节不读。
FileInputStream fis = new FileInputStream("绝对/相对路径");
byte[] bytes = new byte[4];
int readCount = 0;
while((readCount = fis.read(bytes)) != -1) {
System.out.print(new String(bytes, 0, readCount)); //读了几个字节输出几个字节
}
?9.2.2 FileOutputStream(文件字节输出流)
输出的文件不存在的时候会自动创建,并且构造方法后的boolean值决定是追加写入(true)还是覆盖写入(false),默认为false(覆盖写入)。?
FileOutputStream fos = fos = new FileOutputStream("myfile", true);
byte[] bytes = {97, 98, 99, 100};
// 将byte数组全部写出!
fos.write(bytes); // abcd
// 将byte数组的一部分写出!
fos.write(bytes, 0, 2); // 再写出ab
String s = "我是一个中国人,我骄傲!!!";
// 将字符串转换成byte数组。
byte[] bs = s.getBytes();
fos.write(bs);
// 写完之后,最后一定要刷新
fos.flush();
9.2.3 FileReader(文件字符输入流)
FileReader reader = new FileReader("第10章_IO流/src/temp/example05");
char[] chars = new char[4]; // 一次读取4个字符
int readCount = 0;
while((readCount = reader.read(chars)) != -1) {
System.out.print(new String(chars,0,readCount));
}
9.2.4 FileWriter(文件字符输出流)
FileWriter out = new FileWriter("file", true);
char[] chars = {'我','是','中','国','人'};
out.write(chars);
out.write(chars, 2, 3);
out.write("我是一名java软件工程师!");
out.flush();
9.3 ?缓冲流
9.3.1 BufferedReader
带有缓冲区的字符输入流,使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组,自带缓冲。
构造方法:BufferedReader(Reader in) 创建使用默认大小的输入缓冲区的缓冲字符输入流。
当一个流的构造方法中需要-个流的时候,这个被传进来的流叫做:节点流。
外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。
例如:
FileReader reader = new FileReader("documentURL");
BufferedReader br = new BufferedReader(reader);
String s = null;
while((s = br.readLine()) != null){
System.out.println(s);
}
br.close();
关闭流的时候只需要关闭“包装流”(关闭最外层),里面的“节点流”会自动关闭。
String readLine() 读一行文字。 ?
9.3.2 BufferedWriter
void newLine() 写一行行分隔符。
BufferedWriter bw = new BufferedWriter(new FileWriter("第10章_IO流/src/temp/example06", true));
bw.write("打工人打工魂");
bw.flush();
bw.close();
9.4 ?转换流(将字节流转换成字符流)
public class BufferedReaderTest02 {
public static void main(String[] args) throws Exception{
/*// 字节流
FileInputStream in = new FileInputStream("Copy02.java");
// 通过转换流转换(InputStreamReader将字节流转换成字符流。)
InputStreamReader reader = new InputStreamReader(in);
// 这个构造方法只能传一个字符流。不能传字节流。
BufferedReader br = new BufferedReader(reader);*/
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("第10章_IO流/src/缓冲流/BufferedReaderTest01.java")));
String line = null;
while((line = br.readLine()) != null){
System.out.println(line);
}
// 关闭最外层
br.close();
}
}
9.4.2 OutputStreamWriter
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("f", true)));
9.5 ?数据流
9.5.1 DataOutputStream
java.io.DataOutputStream:数据专属的流。这个流可以将数据连同数据的类型一并写入文件。注意:这个文件不是普通文本文档(这个文件使用记事本打不开。)
public class DataOutputStreamTest {
public static void main(String[] args) throws Exception{
// 创建数据专属的字节输出流
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data"));
byte b = 100;
short s = 200;
dos.writeByte(b); // 把数据以及数据的类型一并写入到文件当中。
dos.writeShort(s);
dos.flush();
dos.close();
}
}
DataInputStream:数据字节输入流。DataOutputStream写的文件,只能使用DataInputStream去读。并且读的时候你需要提前知道写入的顺序。读的顺序需要和写的顺序一致。才可以正常取出数据。
public class DataInputStreamTest01 {
public static void main(String[] args) throws Exception{
DataInputStream dis = new DataInputStream(new FileInputStream("data"));
// 开始读
byte b = dis.readByte();
short s = dis.readShort();
System.out.println(b);
dis.close();
}
}
9.6 ?标准输出流
9.6.1 PrintStream
java.io.PrintStream:标准的字节输出流。默认输出到控制台。标准输出流不需要手动close()关闭,并且可以改变标准输出流方向。
public class PrintStreamTest {
public static void main(String[] args) throws Exception{
// 联合起来写
System.out.println("hello world!");
// 分开写
PrintStream ps = System.out;
ps.println("hello 这是输出在控制台上");
// 标准输出流不再指向控制台,指向“log”文件。
PrintStream printStream = new PrintStream(new FileOutputStream("第10章_IO流/src/temp/log"));
// 修改输出方向,将输出方向修改到"log"文件。
System.setOut(printStream);
// 再输出
System.out.println("此语句输出至\"第10章_IO流/src/temp/log\"中");
}
}
9.6.2 PrintWriter
打印流主要包含两个:PrintStream 和 PrintWriter,分别对应字节流和字符流。
public class PrintWriterTest01 {
public static void main(String[] args) {
try {
PrintWriter pw = new PrintWriter(new FileWriter("第10章_IO流/src/temp/log",true));
pw.println("PrintWriter是个标准字符输出流");
pw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
9.7 ?File类
File类和四大家族没有关系,所以File类不能完成文件的读和写。File对象代表文件和目录路径名的抽象表示形式。一个File对象有可能对应的是目录,也可能是文件。File只是一个路径名的抽象表示形式。
构造方法:File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
需要掌握File类中常用的方法:
- boolean createNewFile() 当且仅当具有该名称的文件尚不存在时,创建一个由该抽象路径名命名的新的空文件。
- boolean delete() 删除由此抽象路径名表示的文件或目录。
- boolean exists() 测试此抽象路径名表示的文件或目录是否存在。
- File getAbsoluteFile() 返回此抽象路径名的绝对形式。
- String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串。
- String getParent() 返回此抽象路径名的父 null的路径名字符串,如果此路径名未命名为父目录,则返回null。 ?
- File getParentFile() 返回此抽象路径名的父,或抽象路径名 null如果此路径名没有指定父目录。 ?
- String getPath() 将此抽象路径名转换为路径名字符串。 ?
- boolean mkdir() 创建由此抽象路径名命名的目录。 ?
- boolean mkdirs() 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。 ?
- boolean isDirectory() 测试此抽象路径名表示的文件是否为目录。 ?
- boolean isFile() 测试此抽象路径名表示的文件是否为普通文件。 ?
- long lastModified() 返回此抽象路径名表示的文件上次修改的时间。 ?
- long length() 返回由此抽象路径名表示的文件的长度。
- File[] listFiles() 返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件。
?9.8 ?对象专属流
9.8.1 序列化与反序列化

参与序列化和反序列化的对象,必须实现Serializable接口:
public interface Serializable {}
注意:通过源代码发现,Serializable接口只是一个标志接口:这个接口当中什么代码都没有。起到标识的作用,标志的作用,java虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。
????????Serializable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成一个序列化版本号。
1.transient
采用 transient 关键字修饰某一属性,序列化时会忽略该属性。
2.serialVersionUID
Java语言中是采用什么机制来区分类的?
- ?首先通过类名进行比对,如果类名不一样,肯定不是同一个类。
- ?如果类名一样,再怎么进行类的区别?靠序列化版本号进行区分。
这种自动生成的序列化版本号缺点是:一旦代码确定之后,不能进行后续的修改,因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候java虚拟机会认为这是一个全新的类。
最终结论:凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类,便不会报错
9.8.2 ObjectInputStream与ObjectOutputStream
定义一个User类(继承了Serializable接口):
public class User implements Serializable {
private int no;
// transient关键字表示游离的,不参与序列化。
private transient String name; // name不参与序列化操作!
public User(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"no=" + no +
", name='" + name +
'}';
}
}
一次序列化多个对象,可以将对象放置集合中,序列化集合:
public class ObjectOutputStreamTest02 {
public static void main(String[] args) throws Exception{
List<User> userList = new ArrayList<>();
userList.add(new User(1,"zhangsan"));
userList.add(new User(2, "lisi"));
userList.add(new User(3, "wangwu"));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("第10章_IO流/src/对象类/序列化与反序列化/users"));
// 序列化一个集合,这个集合对象中放了很多其他对象。
oos.writeObject(userList);
oos.flush();
oos.close();
}
}
反序列化集合:
public class ObjectInputStreamTest02 {
public static void main(String[] args) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("第10章_IO流/src/对象类/序列化与反序列化/users"));
//Object obj = ois.readObject();
//System.out.println(obj instanceof List); //true
List<User> userList = (List<User>)ois.readObject();
for(User user : userList){
System.out.println(user);
}
ois.close();
}
}
自动生成的序列号一经确认再修改时,会重新编译,生成新的序列化版本号,JVM认为这是一个全新的类便会报错,因此应在User类中声明一个固定的序列号版本号:
private static final long serialVersionUID = -111111111111111222L;
9.9 ?IO和Properties联合使用
IO+Properties的联合应用是非常好的一个设计理念: 以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取。将来只需要修改这个文件的内容,java代码不需要改动,不需要重新编译,服务器也不需要重启。就可以拿到动态的信息。
类似于以上机制的这种文件被称为配置文件。并且当配置文件中的内容格式是:“key1=value”的时候,我们把这种配置文件叫做属性配置文件。
Java规范中有要求:属性配置文件建议以.properties结尾,但这不是必须的。这种以.properties结尾的文件在java中被称为:属性配置文件。其中Properties是专门存放属性配置文件内容的一个类。配置文件最好“=”两边不要有空格。
public class IoPropertiesTest01 {
public static void main(String[] args) throws Exception{
/*
Properties是一个Map集合,key和value都是String类型。
想将userinfo文件中的数据加载到Properties对象当中。
*/
// 新建一个输入流对象
FileReader reader = new FileReader("chapter23/userinfo.properties");
// 新建一个Map集合
Properties pro = new Properties();
// 调用Properties对象的load方法将文件中的数据加载到Map集合中。
pro.load(reader); // 文件中的数据顺着管道加载到Map集合中,其中等号=左边做key,右边做value
String username = pro.getProperty("username");
System.out.println(username);
String password = pro.getProperty("password");
System.out.println(password);
}
}
|