Java笔记——IO流

文件

文件是保存数据的地方

文件流:文件在程序中是以流的形式来操作的,流是指数据源(文件)和程序(内存)之间经历的路径

输入流:文件(磁盘) \(\rightarrow\) Java程序(内存)

输出流:Java程序(内存) \(\rightarrow\) 文件(磁盘)

常用文件操作

  • 创建文件对象相关构造器和方法
1
2
3
4
5
new File(String pathname) // 根据路径构建一个File对象
new File(File parent, String child) // 根据父目录文件 + 子路径构建
new File(String parent, String child) // 根据父目录 + 子路径构建

file.createNewFile(); // 写到磁盘
  • 获取文件相关信息
1
2
3
4
5
6
7
getName
getAbsolutePath
getParent
length() // 一个汉字3个字节
exists
isFile
isDirectory
  • 目录的操作和文件删除
1
2
3
//mkdir创建一级目录、mkdirs创建多级目录、delete删除空目录或文件
返回boolean
在java编程中,目录也被当做文件

IO流原理及流的分类

io流概览
  • Java IO 流原理

    1. I/O技术是非常实用的技术,用于处理数据传输。如读写文件,网络通讯等。
    2. Java 程序中,对于数据的输入、输出操作以“流(stream)”的方式进行。
    3. java.io 包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过方法输入或输出数据
  • 流的分类

    • 操作数据单位不同分为:字节流(8bit)二进制文件,字符流(按字符)文本文件 (一个汉字3个字节)
    • 数据流的流向不同分为:输入流,输出流
    • 按流的角色的不同分为:节点流,处理流/包装流
(抽象基类) 字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer

Java 的 IO 流共涉及40多个类,实际上非常规则,都是如上4个抽象基类派生的,由这四个类派生出来的子类名称都是以其父类名作为子类名前缀。

InputStream 字节输入流

1
2
3
4
5
6
InputStream抽象类是所有 类字节输入流 的超类

InputStream常用子类:
- FileInputStream:文件输入流
- BufferedInputStream:缓存字节输入流 (直接父类FilterInputStream)
- ObjectInputStream:对象字节输入流

FileInputStream

1
2
3
4
5
6
7
8
9
构造器
FileInputStream(File file)
FileInputStream(FileDescriptor fdObj)
FileInputStream(String name) // 指定路径

方法
read() 从此输入流中读取一个数据字节
read(byte[] b) 从此输入流中将最多b.length个字节的数据读入一个byte数组中
close() 关闭此文件输入流并释放于此流有关的所有系统资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void readFile01() {
int readDate = 0;
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream("./hhh.txt");
while ((readDate = fileInputStream.read()) != -1) {
System.out.print((char)readDate);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileInputStream.close(); // 关闭流
} catch (IOException e) {
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void readFile02() {
int readLen = 0;
byte[] b = new byte[9]; // 一次读9个字节
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream("./hhh.txt");
while ((readLen = fileInputStream.read(b)) != -1) {
System.out.print(new String(b, 0, readLen)); // char[]转String输出
}

} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

FileOutputStream

1
2
3
4
5
6
7
8
构造器
FileOutputStream(File file)
FileOutputStream(String name, boolean append) // 追加

方法
write(byte[] b)
write(byte[] b, int off, int len) // off表示偏移量,即启示位置
write(int b) // 将指定的字节写入此文件输出流
1
2
String str = "hello, world!";
fileOutputStream.write(str.getBytes());

文件拷贝 = 输入流 + 输出流

FileReader 和 FileWriter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FileReader 和 FileWriter 是字符流,即按照字符来操作io

FileReader方法 (直接父类InputStreamReader, 父类的父类Reader)
new FileReader(File/String)
read() // 每次读取一个字符,返回该字符,如果读到文件末尾返回-1
read(char[]) // 批量读取多个字符到数组,返回读取的字符数
new String(char[]) // 将char[] 转成String
new String(char[], off, len)

FileWriter方法 (直接父类OutputStreamWriter, 父类的父类Writer)
new FileWriter(File/String) // 覆盖写入
new FileWriter(File/String, true) // 追加写入
write(int/char[]/string)
String.toCharArray() // 将String转换成char[]
注意:FileWriter使用后必须close或flush, 否则写入不到指定文件!

节点流和处理流

  1. 节点流可以从一个特定的数据源读取数据,如 FileReaderFileWriter
  2. 处理流也叫包装流,是连接在已存在的流(节点流或处理流)之上,为程序提供更为强大的读取功能,如 BufferedReaderBufferedWriter

BufferedReaderReader 的子类,有属性 private Reader in; ,即可以封装一个节点流,该节点流可以是任意的,只要是 Reader 的子类;同理 BufferedWriter 也一样。

节点流和处理流

区别与联系

  1. 节点流是底层流/低级流,直接跟数据源相接。
  2. 处理流包装节点流既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。
  3. 处理流对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连

处理流的功能主要体现在:

  1. 性能的提高:主要以增加缓冲的方式来提高输入输出的效率
  2. 操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.company.file;

public class BufferedReader_ {
public static void main(String[] args) {

BufferReader_ bufferReader_ = new BufferReader_(new FileReader_());
// bufferReader_.readFiles(4);
bufferReader_.readFile();
BufferReader_ bufferReader_1 = new BufferReader_(new StringReader_());
// bufferReader_1.readStrings(3);
}
}

abstract class Reader_ {
public void fileRead() {}
public void stringRead() {}
}

class FileReader_ extends Reader_{
public void fileRead() {
System.out.println("读取文件~~~");
}
}

class StringReader_ extends Reader_ {
public void stringRead() {
System.out.println("读取字符串~~~");
}
}

class BufferReader_ extends Reader_ {
private Reader_ reader_;

public BufferReader_(Reader_ reader_) { // 构造器
this.reader_ = reader_;
}

public void readFile() { // 封装一层
reader_.fileRead();
}

public void readFiles(int num) { // 多次读取文件,或者加缓冲byte[]
while (num-- != 0) {
reader_.fileRead(); // 动态绑定
}
}

public void readStrings(int num) {
while (num -- != 0) {
reader_.stringRead();
}
}
}

处理流 BufferedReader 和 BufferedWriter

BufferedReaderBufferedWriter 属于字符流,是按照字符来读取数据的,关闭时,只需要关闭外层流即可。不要去操作二进制文件(音频, 视频, pdf, doc),可能造成文件损坏。

1
2
3
4
5
6
7
8
9
10
11
12
13
BufferedReader bufferedReader = new BufferedReader(new FileReader());
String line;
while ((line = bufferedReader.readLine()) != null) // 按行读取,当返回null,表示文件读取完毕
System.out.println(line);
// 关闭流,只需要关闭Bufferedreader, 因为底层会自动去关闭节点流FileReader
bufferedReader.close();

BufferedWriter bw = new BufferedWriter(new FileWriter("./ok.txt"));
bw.write("hello, world!");
bw.newLine();
bw.write("hello, java!");
bw.newLine();
bw.close();

处理流 BufferedInputStream 和 BufferedOutputStream

BufferedInputtStream字节流,在创建 BufferedInputStream 时,会创建一个内部缓存区数组。 BufferedOutputStream 也是字节流,实现缓冲的输出流,可以将多个字节写入底层输出流中,而不必对每个字节写入调用底层系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 字节流拷贝
public class BufferedCopy {
public static void main(String[] args) throws IOException {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
int readLen = 0;
byte[] b = new byte[1024];
try {
bis = new BufferedInputStream(new FileInputStream("./demo.png"));
bos = new BufferedOutputStream(new FileOutputStream("./tmp/demo1.png"));
while ((readLen = bis.read(b)) != -1) {
bos.write(b, 0, readLen);
}

} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (bis != null) {
bis.close();
}
if (bos != null) {
bos.close();
}
}
}
}

对象处理流 ObjectInputStream和ObjectOutputStream

能够将基本数据类型或者对象进行序列化和反序列化操作,ObjectInputStream 提供反序列化功能,ObjectOutputStream 提供序列化功能。

  1. 序列化就是在保存数据时,保存数据的值和数据类型
  2. 反序列化就是在恢复数据时,恢复数据的值和数据类型
  3. 需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:
1
2
Serializable // 这是一个标记接口
Externalizable // 继承自Serializable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
writeInt()
writeBoolean()
writeChar()
writeDouble()
writeUTF("你好")
oos.writeObject(new Dog("kimi"));

// 读取(反序列化)顺序要和保存(序列化)数据顺序一致
...
// dog的编译类型是Object, 运行类型是Dog
Object dog = ois.readObject();
// 如果希望调用Dog的方法,需要向下转型(要保证Dog类的定义可被引用)
Dog dog2 = (Dog)dog;
dog2.getName();

注意事项:

  1. 读写顺序要一致
  2. 要求序列化或反序列化对象,需要实现 Serializable
  3. 序列化的类中建议添加 SerialVersionUID,为了提高版本的兼容性
  4. 序列化对象时,默认将里面所有属性都进行序列化,但除了 statictransient 修饰的成员
  5. 序列化对象时,要求里面属性的类型也需要实现序列化接口
  6. 序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化

标准输入输出流

编译类型 运行类型 默认设备
System.in InputStream BufferedInputStream 键盘
System.out PrintStream PrintStream 显示器

转换流 InputStreamReader和OutputStreamWriter

字节流转成字符流:解决文件乱码问题

1
2
InputStreamReader(InputStream, Charset) //指定编码方式
OutputStreamReader(OutputStream, Charset)
  1. InputStreamReader: Reader 的子类,可以将 InputStream (字节流)包 装成Reader(字符流)
  2. OutputStreamWriter: Writer 的子类,实现将 OutputStream (字节流) 包装成 Writer(字符流)
  3. 当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
  4. 可以在使用时指定编码格式(比如utf 8, gbk,gb2312, IS08859-1等)
1
2
3
4
5
6
7
8
9
10
11
InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk"); // 转换流
BufferedReader br = new BufferedReader(isr); // 包装一次,让效率更高
String s = br.readLine();
System.out.println("读取内容: " + s);
br.close();

String filePath = "./tmmp.txt";
String charSet = "utf-8";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charSet));
osw.write("你好");
osw.close();

打印流 PrintStream和PrintWriter

打印流只有输出流,没有输入流

字节流 PrintStream 的父类 OutputStream,字符流 PrintWriter 的父类 Writer

1
2
3
4
5
6
7
PrintStream out = System.out; // 默认输出位置显示器
out.print("hello"); // print的底层是write
// out.write("hello".getByte());
out.close();

System.setOut(new PrintStream("./tmp.txt")); //重定向输出设备到文件
System.out.println("hello!"); // 写入内容
1
2
3
4
// PrintWriter pw = new PrintWriter(System.out);
PrintWriter pw = new PrintWriter(new FileWriter("./tmp.txt"));
pw.print("hello, world");
pw.close(); // 没关闭无法刷新写入

Properties 类

配置文件的读取和写入

1
2
3
4
5
6
7
8
1. 专门用于读写配置文件的集合类 (Hashtable的子类)
格式:键=值 (键值对不需要有空格,值不需要用引号引起来,默认类型是String)
2. 常见方法
load: 加载配置文件的键值对到Properties对象
list: 将数据显示到指定设备/流对象
getProperty(key): 根据键获取值
setProperty(key, value): 设置键值对到Properties对象
store: 将Properties中的键值对存储到配置文件 (中文unicode)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1. 创建Properties对象
Properties properties = new Peoperties();
// 2. 加载指定配置文件
properties.load(new FileReader("./sql.properties"));
// 3. 把k-v显示控制台
properties.list(System.out);
// 4. 根据k获取v
properties.getProperty(xxx);

// 5. 创建键值对
Properties properties1 = new Peoperties();
properties1.setProperty("user", "kuhn");
// 6. 保存
properties1.store(new FileOutputStream("./mysql.properties"), "注解");

Java笔记——IO流
https://nessaj7.github.io/2022/07/10/e50dfc354168.html
作者
kuhn
发布于
2022年7月10日
许可协议