字节流
字符流可以操作任意文件。
1. 字节流的父类(抽象类)
InputStream
字节输入流
public int read(){}
public int read(byte[] b){}
public int read(byte[] b,int off,int len){}
OutputStream
字节输出流
public void write(int n){}
public void write(byte[] b){}
public void write(byte[] b,int off,int len){}
2. 字节流的子类
2.1. 文件字节流
- FileInputStream
public int read()
从输入流中读取一个字节数据,返回读到的字节数据,如果达到文件末尾,返回-1。public int read(byte[] b)
输入流中读取字节数组长度的字节数据存入数组中,返回实际读到的字节数;如果达到文件的尾部,则返回-1。
data.txt
abcdef
String path = "/Users/admin/Downloads/data.txt";
FileInputStream fis = new FileInputStream(path);
// read()读取单个字节
// int data = fis.read();
// System.out.println((char) data); // a
// 如read() == -1,则表示已经读完,否则返回当前字节
// while ((data = fis.read()) != -1) {
// System.out.print((char) data); // bcdef
// }
// 一次性读取多个字节并输出
int cnt = 0;
byte[] data = new byte[1024];
// 如read(byte[] byte) == -1,则表示已经读完,否则返回读取的数量
while ((cnt = fis.read(data)) != -1) {
// 根据读取的数量截取String长度并进行输出
System.out.print(new String(data, 0, cnt)); // abcdef
}
fis.close();
- FileOutputStream
public void write(int b)
将指定字节写入输出流。public void write(bute[] b)
一次写多个字节,将b数组中所有字节,写入输出流。
String path = "/Users/admin/Downloads/data.txt";
FileOutputStream fos = new FileOutputStream(path);//默认覆盖
// new FileOutputStream(path, true); // 第二个参数表示追加
// 将一个字节输入到文件中
// fos.write(97); // a
// fos.write('b'); // b
// 将多个字节输入到文件中
byte[] data = new String("helloworld").getBytes(StandardCharsets.UTF_8);
fos.write(data); // helloworld
fos.close();
案例
String inputPath = "/Users/admin/Downloads/1.png";
String outputPath = "/Users/admin/Downloads/2.png";
FileInputStream fis = new FileInputStream(inputPath);
FileOutputStream fos = new FileOutputStream(outputPath);
byte[] data = new byte[1024];
int count = 0;
while ((count = fis.read(data)) != -1) {
fos.write(data, 0, count);
}
fis.close();
fos.close();
2.2. 字节缓冲流
- 缓冲流:BufferedInputStream/BufferedOutputStream
- 提高IO效率,减少访问磁盘的次数;
- 数据存储在缓冲区中。flush可以将缓存区的内容写入文件,也可以直接close。
- 缓冲区默认大小为8192 bytes,即8kb。
BufferedInputStream
String path = "/Users/admin/Downloads/data.txt";
// 使用该输入流每次会从硬盘读入
FileInputStream fis = new FileInputStream(path);
// 缓冲流需要一个底层流
// 缓冲流每次从缓冲区读取
BufferedInputStream bis = new BufferedInputStream(fis);
// 读取
// int data;
// while ((data = bis.read()) != -1) {
// System.out.print((char) data);
// }
// 我们也可以自己创建一个缓冲区;
// 每次读取从自己创建的缓冲区中读取。
int count;
byte[] buf = new byte[1024];
while ((count = bis.read(buf)) != -1) {
System.out.println(new String(buf, 0, count));
}
bis.close(); // 缓冲流在关闭的同时也会关闭底层流,所以底层流可以不用手动关闭
BufferedOutputStream
String path = "/Users/admin/Downloads/data.txt";
FileOutputStream fos = new FileOutputStream(path);
BufferedOutputStream bos = new BufferedOutputStream(fos);
for (int i = 0; i < 10; i++) {
bos.write("Hello world\n".getBytes()); // 调用此方法会写入缓冲区中,并不会写入磁盘
bos.flush(); // 将缓冲区的内容写入磁盘
}
bos.close();
2.3. 对象流
- 对象流:ObjectOutputStream/ObjectInputStream
- 增加了缓冲区功能。
- 增强了读写8种基本数据类型和字符串功能。
- 增强了读写对象的功能:
readObject()
从流中读取一个对象。writeObject(Object obj)
向流中写入一个对象。
序列化和反序列化
使用流传输对象的过程称为序列化、反序列化。
- 注:序列化的类必要要实现Serializable接口
其中,Serializable为标志接口,仅作为标识。
public interface Serializable {
}
Student类
package demo.pojo;
import java.io.Serializable;
/**
* @author admin
* @date 2021/8/15 6:54 下午
*/
public class Student implements Serializable {
@Serial
private static final long serialVersionUID = 5697270298722545258L;
String name;
int age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
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;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
}
使用ObjectOutputStream实现对象的序列化
String path = "/Users/admin/Downloads/student.bin";
FileOutputStream fos = new FileOutputStream(path);
ObjectOutputStream oos = new ObjectOutputStream(fos);
Student student = new Student("张三", 15);
oos.writeObject(student);
oos.flush();
oos.close();
注:如果执行上述代码后抛出java.io.NotSerializableException
,意思是类不能被序列化,需要实现Serializable接口。
student.bin打开如下:
使用ObjectInputStream实现反序列化(读取重构对象)
String path = "/Users/admin/Downloads/student.bin";
FileInputStream fis = new FileInputStream(path);
ObjectInputStream ois = new ObjectInputStream(fis);
Object o = ois.readObject();
System.out.println(o);
if (o instanceof Student student){
System.out.println(student.getName());
System.out.println(student.getAge());
}
ois.close();
Student{name='张三', age=15}
张三
15
序列化和反序列化注意事项
- 序列化类必须实现Serializable接口,前文已经说过。
- 序列化类中的对象属性也要求实现Serializable接口。也就是说如果Student类中有一个Grad类型的属性
private Grad info;
那么Grad这个类也要实现Serializable接口。 - 序列化类中可以添加序列化版本号ID,以保证序列化的类和被序列化的类是同一个类。在上面的代码中我并没有添加序列号版本,虽然IDE没有报错,但是会显示一个警告,提示我添加序列化版本号(串行版本标识)。我们可以在Student类中添加:
private static final long serialVersionUID = 66666L;
- 使用transient(短暂的)修饰属性,可以避免该属性被序列化。用它来修饰age:
private transient int age;
把tang这个对象序列化后再反序列化,这个对象的age属性就变成了0。
- 静态属性不能被序列化。
- 可以利用集合来序列化多个对象:
ArrayList<Student> arrayList=new ArrayList<Student>();
arrayList.add(s1);
arrayList.add(s2);
arrayList.add(s3);
objectOutputStream.writeObject(arrayList);
ArrayList<Student> list=(ArrayList<Student>)objectInputStream.readObject();
3. 编码方式
IOS-8859-1
收录除ASCII外,还包括西欧、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。采用1个字节来表示,最多只能表示256个字符。
UTF-8
针对Unicode码表的可变长度字符编码。国际上使用的编码,也称为“万国码”,收录了几乎所有国家的常用字符。采用1至3个字节来表示一个字符。
GB2312
简体中文,采用1个或2个字节来表示字符,95年之前所采用的编码。
GBK
简体中文的扩充,GB2312的升级版本。
BIG5
台湾,繁体中文。
当编码方式和解码方式不一致时,会出现乱码。