Java IO流
文章目录
一、基础概念
1、输入输出流的概念:
输入流: 从外部源(如磁盘、网络等)读取数据到内存。比如从文件读取字节数据到一个字节数组。
输出流: 从内存将数据写入到外部目标(如磁盘、网络等)。比如把字节数组的数据输出到一个磁盘文件。**缓存区概念:**在输入输出过程中,数据通常会先经过一个缓存区,以减少与外部设备的交互次数,提高效率。
2、字符流 vs 字节流
字节流: 处理原始字节数据,如 FileInputStream 和 FileOutputStream。
字符流: 处理字符数据,通常涉及字符编码转换,如 FileReader 和 FileWriter。有 write 和 read 方法的不一定是字符流,字节流也有 write 和 read 方法。例如,FileInputStream 和 FileOutputStream 也提供了 read 和 write 方法。
3、缓冲高效流:
通过在内存中维护一个缓冲区来减少与外部设备的交互次数,提高 I/O 操作的效率。
BufferedInputStream 和 BufferedOutputStream:分别用于字节输入流和字节输出流的缓冲。
BufferedReader 和 BufferedWriter:分别用于字符输入流和字符输出流的缓冲。
4、转换流:
用于在字节流和字符流之间进行转换,处理字符编码问题。
InputStreamReader:将字节输入流转换为字符输入流。
OutputStreamWriter:将字符输出流转换为字节输出流。
5、序列化与反序列化:
序列化:将对象转换为字节流,以便存储或传输。
反序列化:将字节流转换回对象。
ObjectOutputStream 和 ObjectInputStream:分别用于对象的序列化和反序列化。
二、字节流
2.1 字节输出流
包含的方法:
- public void close()关闭输出流并释放与此流相关联的任何系统资源。
- public void flush():刷新此输出流并强制任何缓冲的输出字节被写出。
- public void write(byte[] b):将b.length的字节从指定的字节数组写入此输出流
- public void write(byte[] b,int off,int len):从指定的字节数组写入 len 字节,从偏移量 off 开始输出到此输出流
- public abstract void write(int b):将指定的字节写入此输出流。 (一个字节)
- close()关闭流对象,但是先刷新一次缓冲区,关闭之后,流对象不可以继续再使用了。
- flush()仅仅是刷新缓冲区(一般写字符时要用,因为字符是先进入的缓冲区),流对象还可以继续使用。
关于flush方法
1)自动刷新:
FileOutputStream 会在每次调用 write 方法时立即将数据写入到文件中,因为它没有内置的缓冲机制。因此,即使你不调用 flush 方法,数据也会立即写入到文件中。
2)关闭流时的刷新:
当你调用 close 方法时,FileOutputStream 会自动调用 flush 方法(但一般是在1-10M左右刷一次,而不是说每write()一次就flush()一次,那样也会使效率变低),确保所有未写入的数据都被写入到文件中,然后再关闭流。
什么时候需要显式调用 flush 方法?
1)确保数据立即写入:
如果你需要确保数据立即写入到文件中,而不是等待流被关闭,可以显式调用 flush 方法。例如,在日志记录或实时数据处理中,你可能希望数据立即写入到文件中,以防止数据丢失。
2)使用缓冲流时:
当使用 BufferedOutputStream 时,数据会先写入到内存中的缓冲区,而不是立即写入到文件中。为了确保缓冲区中的数据被写入到文件中,你需要显式调用 flush 方法。
示例
public static void outStream() throws IOException {
byte[] bytes = {97, 98, 97, 99, 101, 102};
FileOutputStream fos = new FileOutputStream("F:\\javaIO流Out类测试文件.txt");
//这里会把字节数组写入输出流指定的文件
fos.write(bytes);
// fos.flush();
fos.close();
}
还可以定义字节录入位置和长度
public static void partOutsTream() throws IOException {
byte[] bytes = {97, 98, 97, 99, 101, 102};
FileOutputStream fos = new FileOutputStream("F:\\javaIO流Out类测试文件.txt");
//定义字节录入位置和长度
fos.write(bytes, 0, 2);
fos.close();
}
2.2 字节输入流
常用方法:
- int read()从该输入流读取一个字节的数据。 读取完成后最后会返回一个-1
- int read(byte[] b) 从该输入流读取最多 b.length个字节的数据为字节数组。
- void close()关闭此文件输入流并释放与流相关联的任何系统资源。
- 还有其他的…
public static void FileInputStreamTest() throws IOException {
int i = 0;
FileInputStream fis = new FileInputStream("F:\\javaIO流Out类测试文件.txt");
//判断文件是否为空,若不空,则读取。这里其实是读取到的一个个字节 ACSLL 码值到了变量i,然后打印
while ((i = fis.read()) != -1) {
System.out.print(i + ",");
}
fis.close();
}
一次读取多个字节并转成字符串
public static void FileInputStreamTest2() throws IOException {
int i = 0;
FileInputStream fis = new FileInputStream("F:\\javaIO流Out类测试文件.txt");
//定义字节数组,大小是2的倍数最好
byte[] bytes = new byte[1024];
//调用read方法取字节存储到字节数组中,并将有效字节数返回并赋值给i
while ((i = fis.read(bytes)) != -1) {
//将字节数组中的值取出来用String的构造函数转换成字符串,取值范围是0到i(i为有效值)
System.out.print(new String(bytes, 0, i));
}
fis.close();
}
然后还可以输入输出流结合实现文件赋值
public static void inputAndOutputStream() throws IOException {
//首先输入到字节数组,即用输入类
FileInputStream fis = new FileInputStream("F:\\javaIO流Out类测试文件.txt");
//其次输出到磁盘,即用输出类
FileOutputStream fos = new FileOutputStream("F:\\javaIO流Out类测试文件2.txt");
int i;
byte[] bytes = new byte[1024];
//i返回的是有效文件个数
while ((i = fis.read(bytes)) != -1) {
fos.write(bytes, 0, i);
}
//先关文件输出流
fos.close();
fis.close();
System.out.println("--------");
}
三、字符流
3.1 、字符输入流
输入流Reader 子类 FileReader 继承自InputStreamReader,InputStreamReader继承自Reader
包含的方法:
- int read() 读一个字符
- int read(char[] cbuf) 将字符读入数组。
- abstract int read(char[] cbuf, int off, int len) 将字符读入数组的一部分。
- abstract void close() 关闭流并释放与之相关联的任何系统资源。
示例
public static void fileReaderTest() throws IOException {
FileReader fr = new FileReader("F:\\字符测试文件.txt");
int len;
while ((len = fr.read()) != -1) {
System.out.println((char) len);
}
System.out.println("-------------");
}
字符输入流 ,转字符串
public static void fileReaderTranlateTest() throws IOException {
FileReader fr = new FileReader("F:\\字符测试文件.txt");
int len;
char[] chars = new char[1024];
while ((len = fr.read(chars)) != -1) {
System.out.println(new String(chars, 0, len));
}
}
3.2、字符输出流
Writer 子类FileWriter 继承 OutputStreamWriter 继承 Writer
包含的方法:
- abstract void close() 关闭流,先刷新。
- abstract void flush() 刷新流。
- void write(char[] cbuf) 写入一个字符数组。
- abstract void write(char[] cbuf, int off, int len) 写入字符数组的一部分。
- void write(int c) 写一个字符。
- void write(String str) 写一个字符串。
- void write(String str, int off, int len) 写一个字符串的一部分。
示例
public static void fileWriteTest() throws IOException {
FileWriter fw = new FileWriter("F:\\字符测试文件.txt");
fw.write("知识点");
fw.flush();//刷新缓冲区数据到文件中
fw.close();
}
四、缓冲流
4.1、字节输出缓冲流
BufferedOutputStream(OutputStream out) 继承 OutputStream
构造方法:
- BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流
- BufferedOutputStream(OutputStream out,int size) 创建一个新的缓冲输出流,以将具有指定缓冲区大小的
数据写入指定的底层输出流
示例
public static void bufferOutPutStreamTest() throws IOException {
FileOutputStream fos = new FileOutputStream("F:\\缓冲流测试.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write("测试缓冲流".getBytes());
bos.flush();
bos.close();
}
4.2、字节输入缓冲流
BufferedInputStream继承自InputStream
构造方法:
- BufferedInputStream(InputStream in) 创建一个BufferedInputStream并保存其参数,输入流 in ,供以后使用。
- BufferedInputStream(InputStream in, int size) 创建BufferedInputStream具有指定缓冲区大小,并保存其参数,输入流 in ,供以后使用。
示例
public static void bufferInPutStreamTest() throws IOException {
FileInputStream fis = new FileInputStream("F:\\缓冲流测试.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
int len;
byte[] bytes = new byte[1024];
System.out.println("---------------");
while ((len = bis.read(bytes)) != -1) {
System.out.println(new String(bytes));
}
bis.close();
}
4.3字符缓冲输出流
BufferedWriter继承自Write
构造方法:
- BufferedWriter(Writer out) 创建使用默认大小的输出缓冲区的缓冲字符输出流。
- BufferedWriter(Writer out, int sz) 创建一个新的缓冲字符输出流,使用给定大小的输出缓冲区。
- 成员方法(特有): void newLine() 写一行行分隔符。
public static void bufferedWriterTest() throws IOException {
FileWriter fw = new FileWriter("F:\\缓冲流测试文件.txt");
BufferedWriter bw = new BufferedWriter(fw);
bw.write("测试缓冲字符流");
bw.newLine();
bw.write("shd");
bw.flush();
bw.close();
}
4.4、字符缓冲输入流
BufferedReader继承自Reader
构造方法:
- BufferedReader(Reader in) 创建使用默认大小的输入缓冲区的缓冲字符输入流。
- BufferedReader(Reader in, int sz) 创建使用指定大小的输入缓冲区的缓冲字符输入流。
- 特有的成员方法:String readLine() 读一行文字。以换行符(‘\n’),回传(‘\r’)或者回车后直接跟着换行(\r\n)作为结束行的标志,如果读取到的这一行无数据了,会返回null值,其他时候返回的是读取的数据(但是不会读取行的终止符合,即如果本来有换行,读取后是不会读取换行符)
public static void bufferedReaderTest() throws IOException {
BufferedReader br = new BufferedReader(new FileReader("F:\\缓冲流测试文件.txt"));
//读的是一行,所以只有一字符串为类型接收数据
String str;
while ((str = br.readLine()) != null) {
System.out.println(str);
}
HashMap a = new HashMap();
}
五、转换流
作用: 转换流(Conversion Streams)是用于处理字符编码转换的一类流,它们主要用于将字节流转换为字符流,或者将字符流转换为字节流。这在处理不同编码格式的文本文件时特别有用。Java中主要的转换流包括InputStreamReader和OutputStreamWriter。
在Java中,IO流提供了读写数据的功能。转换流(Conversion Streams)是用于处理字符编码转换的一类流,它们主要用于将字节流转换为字符流,或者将字符流转换为字节流。这在处理不同编码格式的文本文件时特别有用。Java中主要的转换流包括InputStreamReader
和OutputStreamWriter
。
5.1、InputStreamReader
InputStreamReader
是一个桥接器,它将一个字节输入流转换为一个字符输入流。它使用指定的字符集(如UTF-8, GBK等)来解码字节。如果没有明确指定字符集,那么将使用平台默认的字符集。
使用示例:
import java.io.*;
public class InputStreamReaderExample {
public static void main(String[] args) {
StringBuilder contentBuilder = new StringBuilder();
try (InputStream is = new FileInputStream("input.txt");
InputStreamReader isr = new InputStreamReader(is, "UTF-8")) { // 指定字符集为UTF-8
int c;
while ((c = isr.read()) != -1) {
contentBuilder.append((char) c);
}
} catch (IOException e) {
e.printStackTrace();
}
// 将内容转换为字符串
String content = contentBuilder.toString();
// 打印或进一步处理内容
System.out.println(content); // 如果需要打印
}
}
5.2、OutputStreamWriter
OutputStreamWriter
同样是一个桥接器,但它的作用与InputStreamReader
相反,它将一个字符输出流转换为一个字节输出流。它使用指定的字符集来编码字符。
使用示例:
import java.io.*;
public class OutputStreamWriterExample {
public static void main(String[] args) {
try (OutputStream os = new FileOutputStream("output.txt");
OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8")) { // 指定字符集为UTF-8
String content = "Hello, World!";
osw.write(content);
osw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
六、序列化与反序列化
在Java中,序列化(Serialization)和反序列化(Deserialization)是将对象的状态转换为字节流以便于存储或传输,以及从字节流恢复对象状态的过程。这是Java提供的一种强大的机制,用于在网络通信、文件存储等方面实现对象的持久化。
6.1、序列化(Serialization)
序列化是指将对象的状态信息转换为可以存储或传输的形式的过程。在Java中,可以通过实现 Serializable
接口来使一个类的对象能够被序列化。
实现 Serializable 接口
要使一个类的对象可以被序列化,该类需要实现 Serializable
接口。这个接口是一个标记接口,没有任何方法需要实现,但它告诉Java虚拟机(JVM)该类的对象可以被序列化。
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 1L; // 可选,但推荐使用
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getters and Setters
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;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
序列化对象
使用 ObjectOutputStream
类可以将对象序列化并写入到一个输出流中,例如文件或网络连接。
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
public class SerializationExample {
public static void main(String[] args) {
Person person = new Person("Alice", 30);
//编译并运行 SerializationExample 类,将 Person 对象序列化到 person.ser 文件中。
try (FileOutputStream fos = new FileOutputStream("person.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos)) {
oos.writeObject(person);
System.out.println("Object has been serialized.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
6.2、反序列化(Deserialization)
反序列化是指将字节流恢复为对象的过程。在Java中,可以通过 ObjectInputStream
类从输入流中读取字节流并恢复对象。
反序列化对象
使用 ObjectInputStream
类可以从输入流中读取字节流并恢复对象。
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.IOException;
public class DeserializationExample {
public static void main(String[] args) {
Person person = null;
//从文件读取序列化得对象
try (FileInputStream fis = new FileInputStream("person.ser");
ObjectInputStream ois = new ObjectInputStream(fis)) {
person = (Person) ois.readObject();
System.out.println("Object has been deserialized: " + person);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
注意事项
serialVersionUID
: 建议为每个实现了Serializable
接口的类定义一个serialVersionUID
。这个字段用于版本控制,如果类的结构发生变化,可以通过修改serialVersionUID
来防止反序列化时出现兼容性问题。- 瞬态字段: 使用
transient
关键字可以标记某些字段不被序列化。这些字段在序列化时会被忽略,反序列化时会恢复为默认值(例如数字类型为0,引用类型为null)。 - 安全性: 序列化和反序列化可能会带来安全风险,特别是当处理不受信任的数据时。应确保只从可信来源读取序列化的数据,并考虑使用其他安全措施。