韩顺平IO专题
思维导图:
1、前置知识
- 输入流:数据从外部(磁盘等)到程序(内存)的路径。
- 输出流:数据从程序(内存)到外部(磁盘等)的路径。
1.1、创建文件对象相关构造器和方法
注:在new File(path)之后,一定要使用createNewFile()
方法,否则文件不会被创建,只存在内存创建了一个File对象。
1.2、获取文件相关信息
1.2.1、常用的文件操作:
1.2.2、目录操作和文件删除
注:在Java编程中,目录也被当做文件
。
2、IO流原理和分类
2.1、原理
- I/O技术是用于处理数据传输。例如读写文件,网络通讯等。
- Java.io包下提供了各种“流”类和接口,用以获得不同种类的数据,并通过方法输入或者输出数据。
- 对于数据的输入/输出操作是以“流”的方式进行的。
2.2、分类
注:
一个字符占多少个字节和编码有关系
,不能明确。- 上面四个类都是抽象类,要使用他们的子类。
2.2.1、FileInputStream
-
结构图:
-
创造文件输入流构造方法:
一旦有了文件流对象
,就使用文件流对象对文件进行操作
。 -
读取操作:
//直接读
public int read() throws IOException
//指定每次读byte数组中的内容,不够就是实际读取的个数(推荐)
public int read(byte b[]) throws IOException
//指定在byte数组中读多少
public int read(byte b[], int off, int len) throws IOException
示例:
public void readFile02() {
String filePath = "e:\\hello.txt";
//字节数组
byte[] buf = new byte[8]; //一次读取8个字节.
int readLen = 0;
FileInputStream fileInputStream = null;
try {
//创建 FileInputStream 对象,用于读取 文件
fileInputStream = new FileInputStream(filePath);
//从该输入流读取最多b.length字节的数据到字节数组。 此方法将阻塞,直到某些输入可用。
//如果返回-1 , 表示读取完毕
//如果读取正常, 返回实际读取的字节数
while ((readLen = fileInputStream.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));//显示
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流,释放资源.
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2.2、FileOutputStream
- 构造方法:
注:
- 如果使用该类向文件中写入,文件不存在会自动创建。
- 如果想追加到原文件末尾,那么要写第二个参数(true),
默认是对源文件内容覆盖
。
- 相关方法:
public void write(int b) throws IOException
public void write(byte b[]) throws IOException
public void write(byte b[], int off, int len) throws IOException
如果要写入字符串,但是write方法只有写入byte数组的,所以将字符串转换为数组即可(getBytes()
)。
2.2.3、FileReader
文件字符输入流
- 构造器:
public FileReader(File file) throws FileNotFoundException
public FileReader(String fileName) throws FileNotFoundException
- 相关操作(使用父类定义的操作)
public int read() throws IOException
public int read(char cbuf[], int offset, int length) throws IOException
3. 如果在读取的时候,使用read(char[])
,那么在写出的时候,使用new String(char[],off, len)
写出。
2.2.4、FileWriter
文件字符输出流
- 构造器:
- 相关方法:
注:FileWriter使用之后,
必须要关闭或者刷新
,否则不会写入到指定文件。
2.2.5、文件的拷贝
- 思路:
- 创建文件输入流,读入到内存。
- 创建文件输出流,从内存读取到磁盘。
- 在读取的时候,是读一部分就写入到指定文件中(
write(byte[], off, len)
)。 - 最后记得关闭流。
2.3、节点流、处理流
2.3.1、节点流
- 上面提到了都是节点流,直接对数据操作的流就是节点流。
2.3.2、处理流
- 在已经存在的流之上,为程序提供更强大的读写功能。不会直接与数据源相连。
- 功能:
- 性能提高,主要以增加缓冲的方式来提高输入输出的效率。
- 操作的边界:处理流可能提供一系列边界方法来一次输入输出大量的数据,使用更加灵活方便。
- 以
BufferedReader
为例:
将Reader作为自己的一个属性,根据多态的思想,只要是Reader的子类,那么BufferedReader都可以进行处理。 BufferedWriter
同样是将Writer
作为自己的属性,只要是Writer的实现子类都可以进行处理。- 有处理流的时候,只需要关闭处理流即可,因为处理流里面包含了节点流(真正对数据进行操作的流)。
2.3.2.1、处理流的设计模式
- 这里韩老师自己手写模拟了一下。
步骤:
- 创建一个抽象类(
Reader_
),只用来定义方法。两个子类(FileReader_
、StringReader_
)继承并实现方法。这两个子类是模拟直接对数据源进行操作的- 此时写一个处理流(
BufferedReader_
),对功能进行扩展,因为扩展的是Read_
下的子类方法,但并没有指定是哪一个子类,所以直接使用Read_
作为自己的属性,通过注入可以确定到底是哪个子类。
-
- 装饰器模式。
2.3.2.2、BufferedReader
说明
bufferedReader.readLine()
是按行读取文件- 当返回null 时,表示文件读取完毕
- 在关闭的时候,追源码,可以看见in.close(),这个in就是处理流包装的节点流。
2.3.2.3、BufferedWriter
说明:
- new FileWriter(filePath, true) 表示以追加的方式写入。
- new FileWriter(filePath) , 表示以覆盖的方式写入。
- 换行是
newLine()
。
2.3.2.4、BufferedInputStream
- 里面将
IutputStream
作为自己的属性,只要是IutputStream
的实现子类,都可以传入作为参数,构建BufferedIutputStream
对象。 - 这是用来对字节流进行加强的,可以处理图片,视频等
二进制文件
。同样也可以操作文本文件,但是效率没有字符流快。 - 读取还是使用
字节数组
进行读取,每次先设置一个字节数组,只要读取长度不等于-1,就接着读。
2.3.2.5、BufferedOutputStream
- 里面将
outputStream
作为自己的属性,只要实现了这个抽象类的子类,都可以传入作为参数,构建BufferedOutputStream
对象。 - 同样是对二进制文件进行操作的。
- 写出的时候同样是
write(byte[], off, len)
,按照每次读取的实际长度进行写出。
2.3.2.6、ObjectInputStream
- 对象写出,实现序列化。
2.3.2.7、ObjectOutputStream
- 对象写入,实现反序列化。
注意点:
- 读写顺序要一致。
- 要求序列化或者反序列化对象,需要实现
Serialiable
接口。- 序列化的类中建议添加
private static final long serialVersionUID = 1L;
,可以提高版本兼容性。- 序列化对象的时候,默认将里面的所有属性都进行序列化,除了
static
或者transient
修饰的成员。
序列化对象的时候,要求里面属性
的类型也需要实现序列化接口。- 序列化具备
可继承性
,即:如果某类已经实现了序列化,那么它的所有子类也已经默认实现了序列化。- 序列化对象如果是自定义的类初始化的,那么这个类最好单独写。
2.3.3、序列化和反序列化
- 序列化就是在保存数据的时候,保存
数据的值
和类型
。序列化每次新增加了方法之后,需要重新执行,否则在反序列化的时候回报错。
- 反序列化就是在回复数据的时候,
回复
数据的值和数据类型。
2.3.4、BufferedWriter/BufferedReader拷贝
- 使用Reader和Writer是去操作字符文件,并不能去操作二进制的文件,否则就会产生文件损坏。
- 操作的时候,根据字符或者字节,创建对应的输入输出流,然后,然后每次只需要
readLine()
,只要不为空就继续读,每读一行就写一行,中间注意记得加上换行(newLine()
),否则就会写成一行。- 最后只需要关闭外层的流即可,内部流会自动关闭。
2.4、标准输入输出流
2.4.1、标准输入流
System.in
,编译类型:InputStream,运行类型:BufferedInputStream。- 表示的是:标准输入 键盘。
2.4.2、标准输出流
System.out
,编译类型:PrintStream,运行类型:PrintStream。- 表示的是:标准输出 显示器。
2.5、转换流
问题引出:字符集不一样,导致读取(例如:文本文件)的时候就会出现乱码。
解决:没有指定读取文件的编码方式,转换流就可以把字节流转为字符流
,字节流是可以指定编码方式的。
2.5.1、InputStreamReader
类图:
- 是
Reader的子类,是字符流
。 - 在构造器中,传入
字节流
以及指定编码方式
,就可以将字节流转换为字符流。
2.5.2、OutputStreamWriter
类图:
- 是
Writer的子类,是字符流
。 - 在构造器中,传入
字节流
以及指定编码方式
,就可以将字节流转换为字符流。
3、Properties
- 专门用来读写配置文件的集合类,是Hashtable的子类。
- 配置文件的格式:key=value
- 键值两边不需要空格,值也不要用引号。默认类型是String。
- 常见方法:
3.1、properties读文件
public class Properties02 {
public static void main(String[] args) throws IOException {
//使用Properties 类来读取mysql.properties 文件
//1. 创建Properties 对象
Properties properties = new Properties();
//2. 加载指定配置文件
properties.load(new FileReader("src\\mysql.properties"));
//3. 把k-v显示控制台
properties.list(System.out);
//4. 根据key 获取对应的值
String user = properties.getProperty("user");
String pwd = properties.getProperty("pwd");
System.out.println("用户名=" + user);
System.out.println("密码是=" + pwd);
}
}
3.2、properties修改文件
public class Properties03 {
public static void main(String[] args) throws IOException {
//使用Properties 类来创建 配置文件, 修改配置文件内容
Properties properties = new Properties();
//创建
//1.如果该文件没有key 就是创建
//2.如果该文件有key ,就是修改
/*
Properties 父类是 Hashtable , 底层就是Hashtable 核心方法
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;//如果key 存在,就替换
return old;
}
}
addEntry(hash, key, value, index);//如果是新k, 就addEntry
return null;
}
*/
properties.setProperty("charset", "utf8");
properties.setProperty("user", "汤姆");//注意保存时,是中文的 unicode码值
properties.setProperty("pwd", "888888");
//将k-v 存储文件中即可,第二个参数是注释,通常设置为null
properties.store(new FileOutputStream("src\\mysql2.properties"), null);
System.out.println("保存配置文件成功~");
}
}
保存是输出流(不管是字节还是字符)。