Java基础教程之IO操作 · 下
- 🔹本节学习目标
- 1️⃣ System类对 IO 的支持
- 1.1 错误输出:System.err
- 1.2 信息输出:System.out
- 1.3 系统输入:System. in
- 2️⃣ 字符缓冲流:BufferedReader
- 3️⃣ 扫描流:Scanner
- 4️⃣ 对象序列化
- 4.1 序列化接口:Serializable
- 4.2 实现序列化与反序列化
- 4.3 transient 关键字
- 🌾 总结
🔹本节学习目标
- 了解System类对 IO 的支持:错误输出(System.err)、信息输出(System.out)和系统输入(System.in);
- 掌握缓冲字符流、打印流及扫描流的使用;
- 掌握对象序列化流的应用场景及使用,掌握接口(Serializable)及transient关键字的作用;
1️⃣ System类对 IO 的支持
System 类是现在为止使用最多的一个类,所有的信息输出都会使用到 “System.out.println()
”或 “System.out.print()
”两个方法完成。实际上 System 类中也专门提供了与 I0 有关的3个对象常量,如下所示。
常量 | 类型 | 描述 |
---|---|---|
public static final PrintStream err | 常量 | 显示器上错误输出 |
public static final PrintStream out | 常量 | 显示器上信息输出 |
public static final InputStream in | 常量 | 键盘数据输入 |
可以发现, err
与 out
两个对象的类型都属于 PrintStream
类型, in
对象类型属于 InputStream
, 而最早使用的"System.out.println()
" 代码从本质上来讲就是调用了 System
类中的out
常量,由于此常量类型为 PrintStream
, 所以可以继续调用 PrintStream
类中的 print()
或 println()
方法,也就证明 Java 的任何输出操作实际上都是 I0操作。
1.1 错误输出:System.err
System.err
是 PrintStream
类对象,此对象专门负责进行错误信息的输出操作。
// 范例 1: 错误输出
package com.xiaoshan.demo;
public class TestDemo {
public static void main(String[] args) throws Exception {
try{
Integer.parseInt("abc"); //此处一定会发生异常
} catch (Exception e){
System.err.println(e); //错误输出
}
}
}
程序执行结果:
java.lang.NumberFormatException: For input string: "abc"
本程序试图将字符串 “abc
” 转换为 int
型数据,而由于字符串不是由数字所组成,所以此处一定会产生异常,而本程序在异常处理中利用System.err
实现了错误信息的打印。
从实际的效果来看,如果本程序中使用了System.out
, 其信息内容是完全一样的, 那么为什么非要用System.err
而不使用System.out
呢?
System.err
和 System.out
都属于PrintStream
类型的对象,所以两者的功能肯定完全相同,但是在Java最初设计时,希望某些错误信息不被使用者看见,所以就定义了 System.err
,而System.err
与 System.out
默认情况下都是在屏幕上输出(可以使用 System类中的"setErr(PrintStream err)
"或"setOut(PrintStream err)
"改变输出位置,但一般不会改变out
的输出位置),所以区别就不这么大了。
另外如果是在IDE工具中显示,会发现System.err
输出错误信息使用的是红色字体, 而System.out
输出错误信息使用的是黑色字体。
1.2 信息输出:System.out
Sysetm.out
是进行屏幕输出的操作对象,是一个 PrintStream
类的实例化对象,并且由JVM 负责该对象的实例化,由于 PrintStream
也是 OutputStream
的子 类,所以可以利用 System.out
为 OutputStream
类进行实例化。
从实际上讲,下边范例的代码意义并不大,因为 OutputStream
与 PrintStream
相比,明显输出时使用 PrintStream
更加方便 (因为它提供了各种 print()
、println()
方法), 而本程序的意义只是在于验证不同的子类为 OutputStream
的位置也不同这一对象多态性的概念应用。
// 范例 2: 利用 OutputStream 实现屏幕输出
package com.xiaoshan.demo;
import java.io.OutputStream;
public class TestDemo{
public static void main(String[] args) throws Exception{
// OutputStream 就为屏幕输出
OutputStream out = System.out;
out.write("https://lst66.blog.csdn.net".getBytes());
}
}
程序执行结果:
https://lst66.blog.csdn.net
由于System.out
属于PrintStream
类的实例,所以可以直接利用其为 OutputStream
对象实例化, 此时调用 write()
方法输出时就变为了屏幕显示。
在JDK 1.8提供的函数式接口里面,有一个消费型的函数式接口,此接口中的方法不返回结果,但是会接收参数。此时就可以利用方法引用的概念,利用System.out.println()
实现 Consumer
接口的实例化。
// 范例 3: 消费型函数式接口与方法引用
package com.xiaoshan.demo;
import java.util.function.Consumer;
public class TestDemo {
public static void main(String[] args) throws Exception{
Consumer<String> con = System.out::println; //方法引用
con.accept("更多文章请访问:https://lst66.blog.csdn.net"); // 输 出
}
}
程序执行结果:
更多文章请访问:https://lst66.blog.csdn.net
程序采用方法引用的概念,将 System.out
对象中的 println()
方法引用到 Consumer
接口中,而后利用accept()
方法就可以实现屏幕信息输出。
1.3 系统输入:System. in
在许多编程语言中为了方便用户的交互操作,都会直接提供一种键盘输入数据的操作功能,但是在 Java 中并没有提供这样可以直接使用的键盘输入操作,而要想实现此类操作必须采用 IO 处理的形式完成,操作的核心就是利用 System.in
( 此为 InputStream
类实例化对象)完成。
// 范例 4: 实现键盘的数据输入
package com.xiaoshan.demo;
import java.io.InputStream;
public class TestDemo {
public static void main(String[] args) throws Exception{//此处直接抛出
//为了方便读者理解,本处将System.in使用InputStream接收,但实际上不需要此操作
InputStream input = System.in; //System.in为InputStream类实例
//开辟空间接收数据
//信息提示,此处没有换行
//读取数据并返回长度
byte data[] = new byte[1024];
System.out.print("请输入数据:");
int len = input.read(data);
System.out.println("输入数据为:"+ new String(data, O, len));
}
}
程序执行结果:
请输入数据:更多文章请访问:https://lst66.blog.csdn.net
输入数据为:更多文章请访问:https://lst66.blog.csdn.net
本程序利用 System.in
实现了键盘数据的读取操作,与文件读取不同的地方在于,本程序的文件内容已经是固定好的,所以读取时会直接进行加载 (如下图(a) 所示)。通过键盘读取时,由于内容还未输入,所以会出现一个等待用户输入的操作界面,用户输入完成按回车后才会开始读取数据(如下图(b) 所示)。
虽然上边范例的代码实现了键盘输入的操作功能,但是此时就会出现一个问题:本处读取的数据为1024个字节,如果读取的内容超过了1024个字节,就会出现漏读的问题 ,也即超过数组长度的部分不会被接收。
在上边范例程序中,只是读取了一次,所以当出现内容超过预定义数组长度后就会出现漏读的问题。但是也可以利用内存流将其进行改进,例如:可以将所有读取的数据暂时保存在内存输出流中,并且ByteArraryOutputStream
类中提供了 “toByteArray()
” 方法可以取得全部的字节数据,这样结合循环读取就可以将内容全部读取。此处提供一种思路,不再编写复杂代码演示, 有兴趣的朋友可以自行实现。
而除了上述这种方式,要想解决此时的漏读问题,也可以不使用数组接收,而是采用循环的方式进行读取,并且将每次读取进来的数据利用 StringBuffer
类对象保存。
// 范例 5: 改进输入操作设计
package com.xiaoshan.demo;
import java.io.InputStream;
public class TestDemo{
public static void main(String[] args) throws Exception{
InputStream input = System.in;
StringBuffer buf = new StringBuffer(); //接收输入数据
System.out.print("请输入数据:"); //提示信息
int temp = 0; //接收每次读取数据长度
while((temp = input.read()) != -1){ //判断是否有输入数据
if (temp == '\n'){ //判断是否为回车符
break; //停止接收
}
buf.append((char) temp); //保存读取数据
}
System.out.println("输入数据为:" + buf); //输出内容
}
}
程序执行结果(输入英文时):
请输入数据:www.baidu.com
输入数据为:www.baidu.com
程序执行结果(输入中文):
请输入数据:我是小山
输入数据为:我是å°å±±
此程序由于使用了 StringBuffer
类对象保存数据,所以没有数据长度的限制,但是此时的代码在输入英文数据时没有任何问题,而输入中文数据时却会出现乱码。造成乱码的原因也很简单,这是一个中文的编码拆分成了两半(每次读取一个字节) 而造成的编码出错。要想解决此类问题,就可以利用字符缓冲输入流完成。
2️⃣ 字符缓冲流:BufferedReader
为了可以进行完整数据的输入操作,最好的做法是采用缓冲区的方式对输入的数据进行暂存,而后程序可以利用输入流一次性读取内容,如下图所示,这样就可以避免输入中文时的读取错乱问题。
如果要使用缓冲区进行数据操作,java.io
包提供了以下两种操作流。
- 字符缓冲区流:
BufferedReader
、BufferedWriter
; - 字节缓冲区流:
BufferedInputStream
、BufferedOutputStream
。
以上给出的4个操作类中,最为重要的就是BufferedReader
类,此类是Reader
的子类,属于字符缓冲输入流,而如果要处理中文数据,字符流是最方便的。 BufferedReader
类的常用方法如下所示。
方法 | 类型 | 描述 |
---|---|---|
public BufferedReader(Reader in) | 构造方法 | 设置字符输入流 |
public String readLine() throws IOException | 普通方法 | 读取一行数据,默认以“\n”为分隔符 |
如果要使用 BufferedReader
类来处理 System.in
的操作就会出现一个问题, BufferedReader
是 Reader
的子类,并且构造方法中也要接收 Reader
类型,而 System.in
是 InputStream
类型,所以此处必须将 InputStream
类型转换为 Reader
类型,那么就可以利用 InputStreamReader
类来实现这一转换操作。字节输入流转字符缓冲输入流结构如下图所示。
// 范例 6: 键盘数据输入的标准格式
package com.xiaoshan.demo;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class TestDemo {
public static void main(String[] args) throws Exception{
//System.in 是InputStream 类对象,BufferedReader 的构造方法里面需要接收 Reader 类对象
//利用InputStreamReader 将字节流对象变为字符流对象
BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));
System.out.print("请输入数据:");
String str = buf.readLine(); //以回车作为换行
System.out.println("输入的内容:"+str);
}
}
程序执行结果:
请输入数据:小山code
输入的内容:小山code
此程序将 System.in
字节输入流对象转换为了 BufferedReader
类对象,可以利用 readLine()
方法直接读取键盘输入的数据,并且可以很好地进行中文处理。
那么既然System.in
属于 InputStream
类型对象,并且对于字节流也提供了一个 BufferedInputStream
的字节缓冲输入流对象,为什么不使用这个类,而非要使用 BufferedReader
类呢?
原因是字符流方便处理中文,并且支持String
返回。打开 BufferedInputStream
类的方法可以发现,在该类中并没有类似 readLine()
功能的方法,也就是说只有 BufferedReader
类可以直接将一行输入数据以字符串的形式返回,这样用户可以进行更多操作,例如:数据类型转换、正则验证、字符串 操作等。
// 范例 7: 判断输入内容
package com.xiaoshan.demo;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class TestDemo {
public static void main(String[] args)throws Exception{
BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));
boolean flag = true; //编写一个循环的逻辑
while (flag){ //判断标志位
System.out.print("请输入年龄:"); //提示信息
String str = buf.readLine(); //以回车作为换行
if (str.matches("\\d{1,3}")){ //输入数据由数字组成
System.out.println("年龄是:"+ Integer.parseInt(str));
flag = false; //退出循环
}else {
System.out.println("年龄输入错误,应该由数字所组成。");
}
}
}
}
程序执行结果:
请输入年龄:https://lst66.blog.csdn.net/
年龄输入错误,应该由数字所组成。
请输入年龄:27
年龄是:27
此程序实现了一个简单的逻辑,要求用户在输入年龄时必须输入数字,由于BufferedReader
读取数据时使用的readLine()
方法返回的是String
类型,所以可以利用正则进行判断。当判断通过时会利用包装类将字符串转换为 int
型数据,同时会退出while
循环;当判断失败时会提示用户错误信息,并且等待用户重新输入。本程序流程如下图所示。
这个程序实际上是所有实际项目开发的一个缩影,在程序中由于输入数据的返回类型是 String
, 就方便利用正则进行判断 ,或者进行数据类型的转换。所以只要是输入数据一般都会采用 String
类型,像在 JSP 技术中接收表单参数使用的 request.getParameter()
方法实际上返回的也是 String
类型的对象 。
字符缓冲流除了可以接收输入信息外,也可以利用缓冲区进行文件的读取,此时只需要在实例化
BufferedReader
类对象时传递 FileReader
类(实际上也可以使用 FileInputStream
, 但是依然需要 InputStreamReader
转换)。
// 范例 8: 读取文件
package com.xiaoshan.demo;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
public class TestDemo {
public static void main(String[] args) throws Exception{
File file = new File("D:" + File.separator + "xiaoshan.txt");
//使用文件输入流实例化 BufferedReader类对象
BufferedReader buf = new BufferedReader(new FileReader(file));
String str = null; //接收输入数据
//读取数据并判断是否存在 //输出读取内容
while ((str = buf.readLine()) != null){
System.out.println(str);
}
buf.close();
}
}
运行结果:
姓名:小山, 年龄:19, 成绩:59.96
此程序使用 BufferedReader
读取输入的数据信息,以“\n
”作为读取数据的分隔符,并在读取时采用循环的模式,将每一行数据读取后直接进行输出,利用这样的处理方式要比直接使用 InputStream
读取更加简单。
3️⃣ 扫描流:Scanner
从 JDK 1.5 开始增加了一个 java.util.Scanner
的程序类,利用这个类可以方便地实现数据的输入操作。 Scanner
类并没有定义在 java.io
包中,而是定义在了 java.util
包中,所以此类是一个工具类,此类的继承结构如下图所示。
在JDK 1.5之前如果要进行数据的输入操作,使用 java.io.BufferedReader
类是最方便的,但是 BufferedReader
类会存在以下两个问题。
(1)它读取数据时只能按照字符串返回:public String readLine() throws IOException
;
(2)所有的分隔符都是固定的。
为此,从JDK 1.5开始增加了新的Scanner
类,而Scanner
类增加后对于键盘数据输入的实现也会变得更加简单,这一点大家可以观察到。
在Scanner
类的定义中可以发现其 实现了Iterator
接口,它在整个Java开发中是一个重要的接口,在 Iterator
接口里面主要定义了两个抽象方法:hasNext()
(判断是否有数据)、next()
(取得数据),同时这两个方法也会出现在Scanner
类中,并且Scanner
类提供了具体数据类型的判断与取得操作。
通过Scanner
类的继承关系可以发现, Scanner
不仅实现了 Iterator
(迭代)接口与 Closeable
接口,而且
Scanner
类的构造方法还可以接收 InputStream
或 File
等类型以实现输入流操作。Scanner 类中定义的常用方法如下所示。
方法 | 类型 | 描述 |
---|---|---|
public Scanner(InputStream source) | 构造 | 接收InputStream输入流对象,此为输入源 |
public boolean hasNext() | 普通 | 判断是否有数据输入 |
public String next() | 普通 | 取出输入数据,以String形式返回 |
public boolean hasNextXxx() | 普通 | 判断是否有指定类型数据存在 |
public 数据类型 nextXxx() | 普通 | 取出指定数据类型的数据 |
public Scanner useDelimiter(String pattern) | 普通 | 设置读取的分隔符 |
// 范例 9: 利用 Scanner 实现键盘数据输入
package com.xiaoshan.demo;
import java.util.Scanner;
public class TestDemo {
public static void main(String[] args) throws Exception{
Scanner scan = new Scanner(System.in); //准备接收键盘输入数据
System.out.print("请输入内容:"); //提示信息
if (scan.hasNext()){ //是否有输入数据
System.out.println("输入内容:"+ scan.next()); //存在内容则输出
}
scan.close();
}
}
程序执行结果:
请输入内容:小山的博客,https://lst66.blog.csdn.net
输入内容:小山的博客,https://lst66.blog.csdn.net
程序实现了键盘输入数据的操作,通过对比可以发现,利用 Scanner
实现的键盘输入数据操作代码要比 BufferedReader
更加简单。
实际上上边范例的代码属于Scanner
使用的标准形式,即先使用 hasNextXxx()
进行判断,有数据之后再进行输入。对于字符串的操作中是否有 hasNextXxx()
方法判断意义不大 (可以直接使用 next()
, 但是这样做不标准), 因为即使此时代码不输入任何字符串也表示接收(因为不为NULL, 是一个空字符串), 但是如果是具体的数据类型输入就有意义了。
// 范例 10: 输入一个数字—— double
package com.xiaoshan.demo;
import java.util.Scanner;
public class TestDemo {
public static void main(String[] args) throws Exception{
Scanner scan = new Scanner(System.in); //准备接收键盘输入数据
System.out.print("请输入成绩:");
if(scan.hasNextDouble()){ //表示输入的是一个小数
double score = scan.nextDouble(); //省略了转型
System.out.println("输入内容:"+ score);
}else{ //表示输入的不是一个小数
System.out.println("输入的不是数字,错误!");
}
scan.close();
}
}
程序执行结果(输入错误):
请输入成绩:xiaoshan
输入的不是数字,错误!
程序执行结果(输入正确):
请输入成绩:100.0
输入内容:100.0
本程序在输入数据时使用了 Scanner
类的 hasNextDouble()
方法来判断输入的数据是否是 double
数值,如果是则利用 nextDouble()
直接将输入的字符串转化为 double
型数据后返回。
Scanner
类中除了支持各种常用的数据类型外,也可以在输入数据时使用正则表达式来进行格式验证。
// 范例 11: 正则验证
package com.xiaoshan.demo;
import java.util.Scanner;
public class TestDemo{
public static void main(String[] args) throws Exception {
Scanner scan = new Scanner(System.in); //准备接收键盘输入数据
System.out.print("请输入生日:"); //提示文字
if (scan.hasNext("\\d{4}-\\d{2}-\\d{2}")){ //正则验证
String bir = scan.next("\\d{4}-\\d{2}-\d{2}"); //接收数据
System.out.println("输入内容:"+ bir);
}else{ //数据格式错误
System.out println("输入的生日格式错误!");
}
scan.close();
}
}
程序执行结果:
请输入生日:1997-12-15
输入内容:1997-12-15
此程序利用正则验证了输入的字符串数据,如果数据符合正则规则,则进行接收,如果不符合则提示信息错误。
在 Scanner
类的构造里面由于接收的类型是 InputStream
, 所以依然可以设置一个文件的数据流。
考虑到文件本身会存在多行内容,所以需要考虑读取的分隔符问题(默认是空字符为分隔符,例如:空格或换行), 这样在读取前就必须使用 “useDelimiter()
” 方法来设置分隔符。
// 范例 12: 读取文件
package com.xiaoshan.demo;
import java.io.File;
import java.io.FilelnputStream;
import java.util.Scanner;
public class TestDemo {
public static void main(String[] args) throws Exception{
Scanner scan = new Scanner(new FilelnputStream(new File("D:" + File.separator + "xiaoshan.txt"))); //设置读取的文件输入流
scan.useDelimiter("\n"); //设置读取的分隔符
while (scan.hasNext()){ //循环读取
System.out.println(scan.next()); //直接输出读取数据
}
scan.close();
}
}
此程序利用 Scanner
实现了文件数据的读取操作,在读取前可以使用 useDelimiter()
方法设置指定的读取分隔符,随后就可以利用 while
循环来读取数据。
通过一系列分析,大家应该已经清楚了 InputStream
、OutputStream
的不足,同时也应该发现利用 PrintStream
(或PrintWriter
) 可以加强程序输出数据的操作支持, Scanner
(或 BufferedReader
) 可以加强程序输入数据的操作支持。所以在日后的开发 中,只要操作的是文本数据 (不是二进制数据), 输出时都使用打印流,输入时都使用扫描流(或者使用字符缓冲区输入流)。
4️⃣ 对象序列化
Java 允许用户在程序运行中进行对象的创建,但是这些创建的对象都只保存在内存中,所以这些对象的生命周期都不会超过 JVM 进程。但是在很多时候可能需要在 JVM 进程结束后对象依然可以被保存下来,或者在不同的 JVM 进程中要进行对象传输,那么在这样的情况下就可以采用对象序列化的方式进行处理。
4.1 序列化接口:Serializable
对象序列化的本质实际上就是将内存中所保存的对象数据转换为二进制数据流进行传输的操作。但 是并不是所有类的对象都可以直接进行序列化操作,要被序列化的对象所在的类一定要实现 java.io.Serializable
接口。而通过文档观察可以发现,序列化接口里面并没有任何操作方法存在,因为它是一个标识接口,表示一种能力。
// 范例 13: 定义一个可以被序列化对象的类
package com.xiaoshan.demo;
import java.io.Serializable;
@SuppressWarnings("serial") //压制序列化版本号警告信息
class Book implements Serializable{ //此类对象可以被序列化
private String title;
private double price;
public Book(String title, double price){
this.title = title;
this.price = price;
}
@Override
public String toString(){
return "书名:"+ this.title + ",价格:" + this.price;
}
}
4.2 实现序列化与反序列化
实现了Serializable
接口后并不意味着对象可以实现序列化操作。实际上在对象序列化与反序列化的操作中,还需要以下两个类的支持。
- 序列化操作类:
java.io.ObjectOutputStream
,将对象序列化为指定格式的二进制数据; - 反序列化操作类:
java.io.ObjectInputStream
,将序列化的二进制对象信息转换回对象内容。
ObjectOutputStream
类的常用方法如下所示:
方法 | 类型 | 描述 |
---|---|---|
public ObjectOutputStream(OutputStream out) throws IOException | 构造 | 指定对象序列化的输出流 |
public final void writeObject(Object obj) throws IOException | 普通 | 序列化对象 |
ObjectInputStream
类的常用方法如下所示:
方法 | 类型 | 描述 |
---|---|---|
public ObjectInputStream(InputStream in) throws IOException | 构造 | 指定对象反序列化的输入流 |
public final Object readObject() throws IOException,ClassNotFoundException | 普通 | 从输入流中读取对象 |
通过 ObjectOutputStream
与 ObjectInputStream
类的方法定义可以发现,序列化对象时
(writeObject()
) 接收的参数统一为 Object
, 而反序列化对象时 (readObject()
) 返回的类型也为 Object
, 所以这两个类可以序列化或反序列化Java 中的所有数据类型(Object
可以接收所有引用类型, 将基本类型自动装箱为包装类后接收)。
// 范例 14: 实现序列化对象操作——ObjectOutputStream
package com.xiaoshan.demo;
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class TestDemo {
public static void main(String[] args) throws Exception {
ser();
}
public static void ser() throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream( new File("D:" + File.separator + "book.ser")));
oos.writeObject(new Book("Java开发实战经典",79.8));//序列化对象
oos.close();
}
}
此程序实现了 Book
类对象序列化的操作,在实例化 ObjectOutputStream
类对象时需要设置一个 OutputStream
类对象,此时设置的为 FileOutputStream
子类,表示对象将被序列化到文件中。book.ser
文件保存内容如图所示。
下面演示反序列化操作。
// 范例 15: 实现反序列化操作——ObjectInputStream
package com.xiaoshan.demo;
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class TestDemo {
public static void main(String[] args) throws Exception {
dser();
}
public static void dser() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FilelnputStream(new File("D:"+ File.separator + "book.ser")));
Object obj= ois.readObject(); //反序列化对象
Book book =(Book) obj; //转型
System.out.println(book);
ois.close();
}
}
程序执行结果:
书名:Java开发实战经典,价格:79.8
此程序首先利用 ObjectInputStream
类通过指定 InputStream
子类对象确定对象读取的输入流为文件,然后利用 readObject()
方法可以将被序列化的对象反序列化回来。由于返回的对象类型都是 Object
, 所以如果有需要可以利用向下转型的操作,将返回对象转化为具体的子类类型。
4.3 transient 关键字
Java 中对象最有意义的内容就是对象的属性信息,所以在默认情况下,如果要进行对象的序列化操作,所序列化的一定是对象的属性信息,并且该对象中的所有属性信息都将被序列化。如果某些属性的内容不需要被保存,就可以通过 transient
关键字来定义。
// 范例 16: 定义不需要序列化的属性
package com.xiaoshan.demo;
import java.io.Serializable;
@SuppressWarnings("'serial")
class Book implements Serializable { //此类对象可以被序列化
private transient String title; //此属性无法被序列化
private double price;
public Book(String title, double price){
this.title = title;
this.price = price;
}
@Override
public String toString(){
return "书名:"+this.title+",价格:"+ this.price;
}
}
此程序中 Book
类的 title
属性上使用了 transient
关键字进行定义,这样当进行对象序列化操作时, 此属性的内容将不能被保存。
既然 java.io.Serializable
接口中没有定义任何抽象方法,那么是不是意味着,开发中所有的类都实现 Serializable
接口会比较好呢?
在实际的开发中,并不是所有的类都要求去实现序列化接口,只有需要传输对象所在的类时才需要实现Serializable
接口,而这样的类最主要的就是简单Java类。由于简单Java 类的实际定义与数据表结构相似,所以在很多情况下,很少会使用 transient
关键字。
🌾 总结
在本文中,我们详细探讨了Java中System
类对IO操作的支持,以及字符缓冲流、扫描流和对象序列化流的应用。
首先,我们了解到System
类提供了三个重要的I/O流:错误输出(System.err
)、信息输出(System.out
)和系统输入(System.in
)。通过这些流,我们可以方便地处理错误信息、输出结果和用户输入。
接下来,我们研究了字符缓冲流(BufferedReader
),它提供了高效读取文本数据的能力。通过使用缓冲机制,字符缓冲流可以减少磁盘或网络读取的次数,从而提高性能。
我们还讨论了扫描流(Scanner
),它提供了一种简便的方式来解析基本类型和字符串。通过Scanner
类,我们可以方便地从输入源(如键盘或文件)中读取数据,并以各种类型的形式进行处理。
最后,我们探索了对象序列化流,它提供了一种将对象转换为字节流并写入到文件中的机制。我们介绍了序列化接口(Serializable
)的作用以及如何实现对象的序列化与反序列化。同时,我们了解到transient
关键字的用途,它可以让对象的某些字段在序列化过程中被忽略。
通过掌握System
类对IO的支持以及字符缓冲流、扫描流和对象序列化流的应用,我们可以更有效地处理输入输出操作,并实现数据的读取、写入和持久化。这些技术在日常开发中非常实用,特别是在涉及文本处理、数据交互和对象存储等方面。
《【Java基础教程】(四十五)IO篇 · 中:转换流、内存流和打印流(探索装饰设计模式与PrintStream类的进阶),文件操作案例实践、字符编码问题~》