【Java基础|第三十三篇】转换流、对象流、数据流、随机访问流、properties类
本文介绍了Java IO流中的增强流部分,重点讲解了转换流、对象流、数据流和随机访问流的使用方法。转换流(InputStreamReader/OutputStreamWriter)用于解决不同编码导致的乱码问题,可与缓冲流结合提高效率。对象流(ObjectInputStream/ObjectOutputStream)实现对象的序列化与反序列化,支持基本数据类型和集合的持久化存储。数据流(DataI
目录
(九)File流与IO流
3、IO流(增强流部分)
(4)转换流:
<1>字符编码与字符集:
计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。
按照某种规则,将字符存储到计算机中,称为编码 。然后将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。
比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。但如果按照A规则存储,按照B规则解析,就会导致乱码现象。
字符编码 Character Encoding : 一套自然语言字符与二进制数(码点)之间的对应规则。
字符集 Charset :也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。
可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。
<2>字符转换时用的流:
当我们读取的文件的编码形式与程序中的编码形式不同时,就会出现乱码的问题。
OutputStreamWriter 将字节输出流转换为字符输出流,并指明编码
public class OutputStreamWriter extends Writer { //使用默认编码转换 public OutputStreamWriter(OutputStream in); //使用指定编码转换 public OutputStreamWriter(OutputStream in, String charsetName); //.... }
构造方法:由上面的源码可知,有两个构造方法,通过new创建,参数是字节输出流的对象或者是它子类的对象。
主要方法:与Writer的主要的方法一致。
InputStreamReader 将字节输入流转换为字符输入流,并指定编码
public class InputStreamReader extends Reader { //使用默认编码转换 public InputStreamReader(InputStream in); //使用指定编码转换 public InputStreamReader(InputStream in, String charsetName); //省略... }
构造方法:由上面的源码可知,有两个构造方法,通过new创建,参数是字节输入流的对象或者是它子类的对象。
主要方法:与Reader的主要方法一致
注意:(1)读取和写入要与文件的编码一致
(2)明确指定的编码,并保证它存在,否则报错。
(3)转换流一般与缓冲流结合使用
public class Test028_InputStreamReader {
public static void main(String[] args) throws IOException {
// 1.创建转换流对象,指定使用GKB编码
File file = new File("D:\\test\\File_GBK.txt");
InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "GBK");
// 2.读取文件内容并输出
char[] buff = new char[8];
int len;
while ((len = isr.read(buff)) != -1) {
System.out.print(new String(buff,0,len));
}
//3.关闭流 释放资源
isr.close();
}
}
案例展示:按照GBK编码读取 D:\\test\\File_GBK.txt 文件内容,然后写入UTF-8编码文件 D:\\test\\File_UTF8.txt 。注意拷贝效率,注意新文件中不要出现多余的空行。
public class Test028_Copy {
//优化转换流 读取,实现整行读取
// 可以对流 进行 反复增强
// FileInputStream --> InputStreamReader --> BufferedReader
public static void main(String[] args) throws Exception {
String file1 = "D:\\test\\File_GBK.txt";
String file2 = "D:\\test\\File_UTF8.txt";
InputStream is = new FileInputStream(file1);
InputStreamReader isr = new InputStreamReader(is,"gbk");
//准备缓冲字符流 进一步增强 性能
BufferedReader br = new BufferedReader(isr);
//简化写法,获取缓冲字符流对象(指定使用utf-8编码)
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file2), "utf-8"));
//2.借助缓冲流 进行 逐行读取
String line;
while((line = br.readLine()) != null) {
bw.write(line);
if(br.ready())
bw.newLine();
}
System.out.println("输出完成!");
//3.只需要关闭 最后的增强流对象即可
bw.close();
br.close();
}
}
(5)对象流:
<1>概述:
直接读写java对象的流,核心是实现对象的序列化与反序列化,解决“对象数据持久化的问题”
<2>序列化与反序列化:
对象和字节序列之间进行转换的机制
序列化:程序中,可以用一个字节序列来表示一个对象,该字节序列包含了对象的类型、对象中的数据等。如果这个字节序列写出到文件中,就相当于在文件中持久保存了这个对象的信息,实现类是ObjectOutputStream。
反序列化:相反的过程,从文件中将这个字节序列读取回来,在内存中重新生成这个对象,对象的类型、对象中的数据等,都和之前的那个对象保持一致。(注意,这时候的对象和之前的对象,内存地址可能是不同的),实现类是ObjectInputStream。
<3>ObjectOutputStream:实现序列化
public class ObjectOutputStream extends OutputStream implements ObjectOutput,
ObjectStreamConstants
{
//省略...
public ObjectOutputStream(OutputStream out) throws IOException;
//序列化方法
public final void writeObject(Object obj) throws IOException;
}
主要方法:源码中展示了构造方法与主要的将对象序列化的方法。该流还支持写入基本数据类型与字符串(writeInt/writeUTF(String s)),注意要抛出异常。
序列化要求:
即将被序列化的类必须实现 Serializable
未实现该接口的类,序列化时会抛NotSerializableException
。serialVersionUID 的作用
- 若类中未显式声明,JVM 会根据类结构(成员变量、方法等)自动生成一个版本号。
- 类结构微调(如新增无关成员变量)后,自动生成的版本号会变,导致旧数据反序列化失败。
- 建议显式声明(如
private static final long serialVersionUID = 1L;
),固定版本号,这样即使后期类结构发生变化,也可以成功序列化transient 关键字
- 修饰的成员变量不参与序列化,反序列化后为默认值(引用类型为 null,基本类型为 0/false 等)。
- 常用于敏感数据(如密码)或临时数据(无需持久化的数据)。
静态成员变量不序列化
序列化仅处理 “对象的实例数据”,静态变量属于类,不参与序列化。父子类序列化规则
- 父类实现 Serializable,子类自动可序列化(无需再实现)。
- 父类未实现 Serializable,子类实现时:父类需有无参构造器(反序列化时用于初始化父类部分),否则抛
InvalidClassException
。
案例展示:
//基础类
public class Student implements Serializable{
private String name;
private int age;
public Student() {}
public Student(String name, int age) {
this.name = name;
this.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;
}
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
//测试类
public class Test029_WriteObject {
public static void main(String[] args) throws IOException {
//把对象保存文件
ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("src/com/briup/chap11/test/stu.txt"));
Student stu = new Student("tom",20);
oos.writeObject(stu);
System.out.println("writeObject success!");
//操作完成后,关闭流
oos.close();
}
}
<4>ObjectInputStream: 反序列化
public class ObjectInputStream extends InputStream implements ObjectInput,
ObjectStreamConstants {
//省略...
public ObjectInputStream(InputStream in) throws IOException;
//反序列化方法
public final Object readObject()throws IOException, ClassNotFoundException;
}
主要方法:源码中展示了构造方法与主要的将对象反序列化的方法。该流还支持读取基本数据类型与字符串(readInt/readUTF(String s)),注意要抛出异常。
注意:(1)读取的字节必须是对象序列化之后得字节
(2)目标类的序列化id要与序列化时的版本号一致
案例展示:
public class Test029_ReadObject {
public static void main(String[] args) throws IOException,ClassNotFoundException {
//读取文件中对象
ObjectInputStream ois = new ObjectInputStream( new FileInputStream("src/com/briup/chap11/test/stu.txt"));
Student s1 = (Student) ois.readObject();
System.out.println("readObject: " + s1);
//操作完成后,关闭流
ois.close();
}
}
<5>集合序列化:
序列化多个对象时,先将所有对象添加到一个集合中,然后序列化集合对象;
反序列化时,从文件中读取单个集合对象,再从集合中获取所有对象。
案例展示:
public class Test029_WriteList {
public static void main(String[] args) throws Exception {
//1.实例化流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/com/briup/chap11/test/list.txt"));
//2.准备多个Student对象并加入List集合
Student s1 = new Student("tom",20);
Student s2 = new Student("zs",21);
Student s3 = new Student("jack",19);
List<Student> list = new ArrayList<>();
list.add(s1);
list.add(s2);
list.add(s3);
//3.将集合写入文件
oos.writeObject(list);
System.out.println("write list success!");
//4.操作完成后,关闭流
oos.close();
}
}
//运行结果:
write list success!
public class Test029_ReadList {
public static void main(String[] args) throws IOException,ClassNotFoundException {
ObjectInputStream oos = new ObjectInputStream(new FileInputStream("src/com/briup/chap11/test/list.txt"));
//2.读取集合对象
List<Student> list = (List<Student>) oos.readObject();
if(list == null) {
System.out.println("read null");
return;
}
System.out.println("read list size: " + list.size());
//3.遍历输出
for (Student stu : list) {
System.out.println(stu);
}
//4.关闭资源
oos.close();
}
}
//运行结果:
read list size: 3
Student{name='tom', age=20}
Student{name='zs', age=21}
Student{name='jack', age=19}
(6)数据流:
在 Java 中,数据流(Data Streams)提供了一种方便、高效和一致的方式来读写基本数据类型和字符串。虽然 Java 提供了字符流和字节流来处理输入和输出,但字符流主要用于处理文本数据,而字节流则用于处理二进制数据。而数据流则提供了一种专门用于读写基本数据类型和字符串的流。
数据流仅有两个核心类,均继承自FilterInputStream
/FilterOutputStream
,需包装字节流(如FileInputStream
/FileOutputStream
)使用:
-
DataInputStream:数据输入流,用于读取基本数据类型和字符串(从字节流中按特定格式解析数据)。
-
DataOutputStream:数据输出流,用于写入基本数据类型和字符串(将基本类型按统一格式转换为字节后写入)
(7)随机访问流:
java.io.RandomAccessFile 是JavaAPI中提供的对文件进行随机访问的流
核心特性与作用:
-
双向操作:同时实现
DataInput
和DataOutput
接口,可直接读写基本数据类型(int、double 等)、字符串(readUTF()
/writeUTF()
),兼具 “数据流” 功能。 -
随机定位:通过
seek(long pos)
方法跳转到文件任意字节位置(pos 为距离文件开头的偏移量),支持 “读一部分→跳转到其他位置写→再跳转回原位置读”。 -
直接操作文件:构造方法需传入文件路径和操作模式,无需依赖其他字节流 / 字符流,直接与文件交互。
-
支持文件截断:通过
setLength(long newLength)
可缩短或扩展文件长度(扩展部分填充空字节)。
public class RandomAccessFile implements DataOutput,DataInput, Closeable {
//省略...
//传入文件以及读写方式
public RandomAccessFile(File file, String mode) throws FileNotFoundException;
//跳过pos字节
public void seek(long pos) throws IOException;
}
主要方法:由上可知构造方法,可以指明读写的方式:
-
long getFilePointer()
:获取当前文件指针的位置(距离文件开头的字节数)。 -
void seek(long pos)
:将指针跳转到pos
字节处(pos 必须≥0,否则抛IllegalArgumentException
)。
public class Test0211_RandomAccessFile {
public static void main(String[] args) throws IOException {
// 1.实例化随机流对象,设为读写模式
File file = new File("src/com/briup/chap11/test/a.txt");
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
// 文件中要替换数据的位置
int replacePos = 6;
// 文件中要插入的内容
String replaceContent = "briup";
// 2.定位到要替换数据的位置
randomAccessFile.seek(replacePos);
// 在指定位置,写入需要替换的内容,覆盖原来此位置上的内容
randomAccessFile.write(replaceContent.getBytes());
System.out.println("替换成功!");
// 3.定位到文件的开始位置,读文件中的所有内容,并输出到控制台
randomAccessFile.seek(0);
byte[] buf = new byte[8];
int len = -1;
while ((len = randomAccessFile.read(buf)) != -1) {
System.out.print(new String(buf,0,len));
}
//4.关闭资源
randomAccessFile.close();
}
}
//运行结果:
hello briup123
4、propertie类:
在 Java 中, Properties 类是一个用于处理配置文件的工具类。配置文件通常以 .properties 扩展名,用于存储配置信息,如应用程序的设置、数据库连接参数等。
Properties 类继承自 Hashtable 类,因此它具有 Hashtable 的所有功能,同时还提供了一些特定于属性文件的方法
更多推荐
所有评论(0)