提交 d1faf1f6 编写于 作者: 沉默王二's avatar 沉默王二 💬

Java IO

上级 139ea051
......@@ -160,6 +160,11 @@
- [文件的世界,一切皆字节流(Stream)](docs/io/stream.md)
- [Java字符流Reader和Writer的故事](docs/io/reader-writer.md)
- [Java缓冲流(Buffered):读写速度有了质的飞升](docs/io/buffer.md)
- [Java转换流,解决字符与字节之间编码、解码的乱码问题](docs/io/char-byte.md)
- [Java序列化流,字节和对象之间的序列化和反序列化](docs/io/serialize.md)
- [Java Serializable:明明就一个空的接口嘛](docs/io/Serializbale.md)
- [招银面试官:说说Java transient关键字](docs/io/transient.md)
- [Java打印流:PrintStream & PrintWriter](docs/io/print.md)
- [如何给女朋友解释什么是 BIO、NIO 和 AIO?](docs/io/BIONIOAIO.md)
......@@ -191,7 +196,6 @@
- [彻底讲明白的Java浅拷贝与深拷贝](docs/basic-extra-meal/deep-copy.md)
- [深入理解Java中的hashCode方法](docs/basic-extra-meal/hashcode.md)
- [一次性搞清楚equals和hashCode](docs/basic-extra-meal/equals-hashcode.md)
- [Java重写(Override)与重载(Overload)](docs/basic-extra-meal/override-overload.md)
- [Java重写(Overriding)时应当遵守的11条规则](docs/basic-extra-meal/Overriding.md)
- [Java到底是值传递还是引用传递?](docs/basic-extra-meal/pass-by-value.md)
- [Java不能实现真正泛型的原因是什么?](docs/basic-extra-meal/true-generic.md)
......
......@@ -153,6 +153,11 @@ export const sidebarConfig = sidebar({
"stream",
"reader-writer",
"buffer",
"char-byte",
"serialize",
"Serializbale",
"transient",
"print",
"BIONIOAIO",
],
},
......
......@@ -170,7 +170,16 @@ head:
### Java输入输出
- [Java IO学习整理](io/shangtou.md)
- [看完这篇,Java IO 不再混乱!](io/shangtou.md)
- [详解 File、Path、Paths、Files 四个类,Java操作文件不再难](io/file-path.md)
- [文件的世界,一切皆字节流(Stream)](io/stream.md)
- [Java字符流Reader和Writer的故事](io/reader-writer.md)
- [Java缓冲流(Buffered):读写速度有了质的飞升](io/buffer.md)
- [Java转换流,解决字符与字节之间编码、解码的乱码问题](io/char-byte.md)
- [Java序列化流,字节和对象之间的序列化和反序列化](io/serialize.md)
- [Java Serializable:明明就一个空的接口嘛](io/Serializbale.md)
- [招银面试官:说说Java transient关键字](io/transient.md)
- [Java打印流:PrintStream & PrintWriter](io/print.md)
- [如何给女朋友解释什么是 BIO、NIO 和 AIO?](io/BIONIOAIO.md)
......@@ -449,7 +458,7 @@ head:
- [大裁员下,程序员如何做“副业”?](xuexijianyi/chengxuyuan-fuye.md)
- [如何在繁重的工作中持续成长?](xuexijianyi/ruhzfzdgzzcxcz.md)
- [如何获得高并发的经验?](xuexijianyi/gaobingfa-jingyan-hsmcomputer.md)
- [怎么跟 HR 谈薪资?](docs/xuexijianyi/hr-xinzi.md)
- [怎么跟 HR 谈薪资?](xuexijianyi/hr-xinzi.md)
## 知识库搭建
......
---
title: 如何给女朋友解释清楚BIO、NIO和AIO?
shortTitle: BIO、NIO和AIO之间的区别
category:
- Java核心
tag:
- Java
- Java IO
description: Java程序员进阶之路,小白的零基础Java教程,BIO、NIO和AIO之间的区别
head:
- - meta
- name: keywords
content: Java,Java SE,Java 基础,Java 教程,Java 程序员进阶之路,Java 入门,Java IO,java BIO,java NIO,java AIO
---
# 如何给女朋友解释清楚BIO、NIO和AIO?
周末午后,在家里面进行电话面试,我问了面试者几个关于 IO 的问题,其中包括什么是 BIO、NIO 和 AIO?三者有什么区别?具体如何使用等问题,但是面试者回答的并不是很满意。于是我在面试评价中写道:"对 Java 的 IO 提醒理解不够深入"。恰好被女朋友看到了。
......@@ -292,6 +297,13 @@ public class WriteToFile {
>转载链接:[https://mp.weixin.qq.com/s/QQxrr5yP8X9YdFqIwXDoQQ](https://mp.weixin.qq.com/s/QQxrr5yP8X9YdFqIwXDoQQ)
>参考链接:[https://mp.weixin.qq.com/s/QQxrr5yP8X9YdFqIwXDoQQ](https://mp.weixin.qq.com/s/QQxrr5yP8X9YdFqIwXDoQQ)
---------
最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。
<img src="http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png">
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png)
---
title: Java Serializable:明明就一个空的接口嘛
shortTitle: Serializable 接口
category:
- Java核心
tag:
- Java IO
description: Java程序员进阶之路,小白的零基础Java教程,Java序列化流,字节和对象之间的序列化和反序列化
head:
- - meta
- name: keywords
content: Java,Java SE,Java 基础,Java 教程,Java 程序员进阶之路,Java 入门,Java Serializable
---
对于 Java 的序列化,我一直停留在最浅显的认知上——把那个要序列化的类实现 `Serializbale` 接口就可以了。我不愿意做更深入的研究,因为会用就行了嘛。
但随着时间的推移,见到 `Serializbale` 的次数越来越多,我便对它产生了浓厚的兴趣。是时候花点时间研究研究了。
## 01、先来点理论
Java 序列化是 JDK 1.1 时引入的一组开创性的特性,用于将 Java 对象转换为字节数组,便于存储或传输。此后,仍然可以将字节数组转换回 Java 对象原有的状态。
序列化的思想是“冻结”对象状态,然后写到磁盘或者在网络中传输;反序列化的思想是“解冻”对象状态,重新获得可用的 Java 对象。
再来看看序列化 `Serializbale` 接口的定义:
```java
public interface Serializable {
}
```
明明就一个空的接口嘛,竟然能够保证实现了它的“类的对象”被序列化和反序列化?
## 02、再来点实战
在回答上述问题之前,我们先来创建一个类(只有两个字段,和对应的 `getter/setter`),用于序列化和反序列化。
```java
class Wanger {
private String name;
private int 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;
}
}
```
再来创建一个测试类,通过 `ObjectOutputStream` 将“18 岁的王二”写入到文件当中,实际上就是一种序列化的过程;再通过 `ObjectInputStream` 将“18 岁的王二”从文件中读出来,实际上就是一种反序列化的过程。
```java
public class Test {
public static void main(String[] args) {
// 初始化
Wanger wanger = new Wanger();
wanger.setName("王二");
wanger.setAge(18);
System.out.println(wanger);
// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));){
oos.writeObject(wanger);
} catch (IOException e) {
e.printStackTrace();
}
// 从文件中读出对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));){
Wanger wanger1 = (Wanger) ois.readObject();
System.out.println(wanger1);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
```
不过,由于 `Wanger` 没有实现 `Serializbale` 接口,所以在运行测试类的时候会抛出异常,堆栈信息如下:
```
java.io.NotSerializableException: com.cmower.java_demo.xuliehua.Wanger
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.cmower.java_demo.xuliehua.Test.main(Test.java:21)
```
顺着堆栈信息,我们来看一下 `ObjectOutputStream``writeObject0()` 方法。其部分源码如下:
```
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
```
也就是说,`ObjectOutputStream` 在序列化的时候,会判断被序列化的对象是哪一种类型,字符串?数组?枚举?还是 `Serializable`,如果全都不是的话,抛出 `NotSerializableException`
假如 `Wanger` 实现了 `Serializable` 接口,就可以序列化和反序列化了。
```java
class Wanger implements Serializable{
private static final long serialVersionUID = -2095916884810199532L;
private String name;
private int age;
}
```
具体怎么序列化呢?
`ObjectOutputStream` 为例吧,它在序列化的时候会依次调用 `writeObject()``writeObject0()``writeOrdinaryObject()``writeSerialData()``invokeWriteObject()``defaultWriteFields()`
```java
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass();
desc.checkDefaultSerialize();
int primDataSize = desc.getPrimDataSize();
desc.getPrimFieldValues(obj, primVals);
bout.write(primVals, 0, primDataSize, false);
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
desc.getObjFieldValues(obj, objVals);
for (int i = 0; i < objVals.length; i++) {
try {
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
}
}
}
```
那怎么反序列化呢?
`ObjectInputStream` 为例,它在反序列化的时候会依次调用 `readObject()``readObject0()``readOrdinaryObject()``readSerialData()``defaultReadFields()`
```java
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass();
desc.checkDefaultSerialize();
int primDataSize = desc.getPrimDataSize();
desc.getPrimFieldValues(obj, primVals);
bout.write(primVals, 0, primDataSize, false);
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
desc.getObjFieldValues(obj, objVals);
for (int i = 0; i < objVals.length; i++) {
try {
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
}
}
}
```
我想看到这,你应该会恍然大悟的“哦”一声了。`Serializable` 接口之所以定义为空,是因为它只起到了一个标识的作用,告诉程序实现了它的对象是可以被序列化的,但真正序列化和反序列化的操作并不需要它来完成。
## 03、再来点注意事项
开门见山的说吧,`static``transient` 修饰的字段是不会被序列化的。
为什么呢?我们先来证明,再来解释原因。
首先,在 `Wanger` 类中增加两个字段。
```java
class Wanger implements Serializable {
private static final long serialVersionUID = -2095916884810199532L;
private String name;
private int age;
public static String pre = "沉默";
transient String meizi = "王三";
@Override
public String toString() {
return "Wanger{" + "name=" + name + ",age=" + age + ",pre=" + pre + ",meizi=" + meizi + "}";
}
}
```
其次,在测试类中打印序列化前和反序列化后的对象,并在序列化后和反序列化前改变 `static` 字段的值。具体代码如下:
```java
// 初始化
Wanger wanger = new Wanger();
wanger.setName("王二");
wanger.setAge(18);
System.out.println(wanger);
// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));){
oos.writeObject(wanger);
} catch (IOException e) {
e.printStackTrace();
}
// 改变 static 字段的值
Wanger.pre ="不沉默";
// 从文件中读出对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));){
Wanger wanger1 = (Wanger) ois.readObject();
System.out.println(wanger1);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
// Wanger{name=王二,age=18,pre=沉默,meizi=王三}
// Wanger{name=王二,age=18,pre=不沉默,meizi=null}
```
从结果的对比当中,我们可以发现:
1)序列化前,`pre` 的值为“沉默”,序列化后,`pre` 的值修改为“不沉默”,反序列化后,`pre` 的值为“不沉默”,而不是序列化前的状态“沉默”。
为什么呢?因为序列化保存的是对象的状态,而 `static` 修饰的字段属于类的状态,因此可以证明序列化并不保存 `static` 修饰的字段。
2)序列化前,`meizi` 的值为“王三”,反序列化后,`meizi` 的值为 `null`,而不是序列化前的状态“王三”。
为什么呢?`transient` 的中文字义为“临时的”(论英语的重要性),它可以阻止字段被序列化到文件中,在被反序列化后,`transient` 字段的值被设为初始值,比如 `int` 型的初始值为 0,对象型的初始值为 `null`
如果想要深究源码的话,你可以在 `ObjectStreamClass` 中发现下面这样的代码:
```java
private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
Field[] clFields = cl.getDeclaredFields();
ArrayList<ObjectStreamField> list = new ArrayList<>();
int mask = Modifier.STATIC | Modifier.TRANSIENT;
int size = list.size();
return (size == 0) ? NO_FIELDS :
list.toArray(new ObjectStreamField[size]);
}
```
看到 `Modifier.STATIC | Modifier.TRANSIENT`,是不是感觉更好了呢?
## 04、再来点干货
除了 `Serializable` 之外,Java 还提供了一个序列化接口 `Externalizable`(念起来有点拗口)。
两个接口有什么不一样的吗?试一试就知道了。
首先,把 `Wanger` 类实现的接口 `Serializable` 替换为 `Externalizable`
```java
class Wanger implements Externalizable {
private String name;
private int age;
public Wanger() {
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Wanger{" + "name=" + name + ",age=" + age + "}";
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
}
```
实现 `Externalizable` 接口的 `Wanger` 类和实现 `Serializable` 接口的 `Wanger` 类有一些不同:
1)新增了一个无参的构造方法。
使用 `Externalizable` 进行反序列化的时候,会调用被序列化类的无参构造方法去创建一个新的对象,然后再将被保存对象的字段值复制过去。否则的话,会抛出以下异常:
```
java.io.InvalidClassException: com.cmower.java_demo.xuliehua1.Wanger; no valid constructor
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:790)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1782)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
at com.cmower.java_demo.xuliehua1.Test.main(Test.java:27)
```
2)新增了两个方法 `writeExternal()``readExternal()`,实现 `Externalizable` 接口所必须的。
然后,我们再在测试类中打印序列化前和反序列化后的对象。
```java
// 初始化
Wanger wanger = new Wanger();
wanger.setName("王二");
wanger.setAge(18);
System.out.println(wanger);
// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));) {
oos.writeObject(wanger);
} catch (IOException e) {
e.printStackTrace();
}
// 从文件中读出对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));) {
Wanger wanger1 = (Wanger) ois.readObject();
System.out.println(wanger1);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
// Wanger{name=王二,age=18}
// Wanger{name=null,age=0}
```
从输出的结果看,反序列化后得到的对象字段都变成了默认值,也就是说,序列化之前的对象状态没有被“冻结”下来。
为什么呢?因为我们没有为 `Wanger` 类重写具体的 `writeExternal()``readExternal()` 方法。那该怎么重写呢?
```java
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
age = in.readInt();
}
```
1)调用 `ObjectOutput``writeObject()` 方法将字符串类型的 `name` 写入到输出流中;
2)调用 `ObjectOutput``writeInt()` 方法将整型的 `age` 写入到输出流中;
3)调用 `ObjectInput``readObject()` 方法将字符串类型的 `name` 读入到输入流中;
4)调用 `ObjectInput``readInt()` 方法将字符串类型的 `age` 读入到输入流中;
再运行一次测试了类,你会发现对象可以正常地序列化和反序列化了。
>序列化前:Wanger{name=王二,age=18}
序列化后:Wanger{name=王二,age=18}
## 05、再来点甜点
让我先问问你吧,你知道 ` private static final long serialVersionUID = -2095916884810199532L;` 这段代码的作用吗?
嗯......
`serialVersionUID` 被称为序列化 ID,它是决定 Java 对象能否反序列化成功的重要因子。在反序列化时,Java 虚拟机会把字节流中的 `serialVersionUID` 与被序列化类中的 `serialVersionUID` 进行比较,如果相同则可以进行反序列化,否则就会抛出序列化版本不一致的异常。
当一个类实现了 `Serializable` 接口后,IDE 就会提醒该类最好产生一个序列化 ID,就像下面这样:
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/io/Serializbale-7a9a05f6-a65c-46b0-b4d7-8b619297f351.jpg)
1)添加一个默认版本的序列化 ID:
```java
private static final long serialVersionUID = 1L
```
2)添加一个随机生成的不重复的序列化 ID。
```java
private static final long serialVersionUID = -2095916884810199532L;
```
3)添加 `@SuppressWarnings` 注解。
```java
@SuppressWarnings("serial")
```
怎么选择呢?
首先,我们采用第二种办法,在被序列化类中添加一个随机生成的序列化 ID。
```java
class Wanger implements Serializable {
private static final long serialVersionUID = -2095916884810199532L;
private String name;
private int age;
// 其他代码忽略
}
```
然后,序列化一个 `Wanger` 对象到文件中。
```java
// 初始化
Wanger wanger = new Wanger();
wanger.setName("王二");
wanger.setAge(18);
System.out.println(wanger);
// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));) {
oos.writeObject(wanger);
} catch (IOException e) {
e.printStackTrace();
}
```
这时候,我们悄悄地把 `Wanger` 类的序列化 ID 偷梁换柱一下,嘿嘿。
```java
// private static final long serialVersionUID = -2095916884810199532L;
private static final long serialVersionUID = -2095916884810199533L;
```
好了,准备反序列化吧。
```java
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));) {
Wanger wanger = (Wanger) ois.readObject();
System.out.println(wanger);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
```
哎呀,出错了。
```
java.io.InvalidClassException: local class incompatible: stream classdesc
serialVersionUID = -2095916884810199532,
local class serialVersionUID = -2095916884810199533
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
at com.cmower.java_demo.xuliehua1.Test.main(Test.java:27)
```
异常堆栈信息里面告诉我们,从持久化文件里面读取到的序列化 ID 和本地的序列化 ID 不一致,无法反序列化。
那假如我们采用第三种方法,为 `Wanger` 类添加个 `@SuppressWarnings("serial")` 注解呢?
```java
@SuppressWarnings("serial")
class Wanger implements Serializable {
// 省略其他代码
}
```
好了,再来一次反序列化吧。可惜依然报错。
```
java.io.InvalidClassException: local class incompatible: stream classdesc
serialVersionUID = -2095916884810199532,
local class serialVersionUID = -3818877437117647968
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
at com.cmower.java_demo.xuliehua1.Test.main(Test.java:27)
```
异常堆栈信息里面告诉我们,本地的序列化 ID 为 -3818877437117647968,和持久化文件里面读取到的序列化 ID 仍然不一致,无法反序列化。这说明什么呢?使用 `@SuppressWarnings("serial")` 注解时,该注解会为被序列化类自动生成一个随机的序列化 ID。
由此可以证明,Java 虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,还有一个非常重要的因素就是序列化 ID 是否一致。
也就是说,如果没有特殊需求,采用默认的序列化 ID(1L)就可以,这样可以确保代码一致时反序列化成功。
```java
class Wanger implements Serializable {
private static final long serialVersionUID = 1L;
// 省略其他代码
}
```
## 06、再来点总结
写这篇文章之前,我真没想到:“空空其身”的`Serializable` 竟然有这么多可以研究的内容!
写完这篇文章之后,我不由得想起理科状元曹林菁说说过的一句话:“在学习中再小的问题也不放过,每个知识点都要总结”——说得真真真真的对啊!
---------
最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png)
\ No newline at end of file
......@@ -198,7 +198,7 @@ private native int read0() throws IOException;
画幅图比较一下。
![](https://files.mdnice.com/user/3903/9508a24f-825a-4e41-a0cb-c9b9f338311b.png)
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/io/buffer-fcae80c2-04a5-4a1b-ab49-89a5ddabd38e.png)
再来看 BufferedOutputStream 的 read 方法:
......
---
title: Java转换流,解决字符与字节之间编码、解码的乱码问题
shortTitle: Java转换流(编码解码与乱码)
category:
- Java核心
tag:
- Java IO
description: Java程序员进阶之路,小白的零基础Java教程,Java转换流,解决字符与字节之间编码、解码的乱码问题
head:
- - meta
- name: keywords
content: Java,Java SE,Java 基础,Java 教程,Java 程序员进阶之路,Java 入门,Java IO,java 缓冲流,java InputStreamReader,java OutputStreamWriter,java 乱码,java 编码,java 解码
---
何谓转换流?为何由来?让我们暂时带着这两个问题来了解了解字符编码和字符集!
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/io/char-byte-86699b2c-4f24-492a-ba68-62c3be0f86bc.png)
## 字符编码与解码
众所周知,计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。
按照某种规则,将字符存储到计算机中,称为**编码** 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为**解码** 。比如说,按照`A`规则存储,同样按照`A`规则解析,那么就能显示正确的文本符号。反之,按照`A`规则存储,再按照`B`规则解析,就会导致乱码现象。
简单一点的说就是:
> 编码:字符(能看懂的)--字节(看不懂的)
>
> 解码:字节(看不懂的)-->字符(能看懂的)
代码解释则是
```java
String(byte[] bytes, String charsetName):通过指定的字符集解码字节数组
byte[] getBytes(String charsetName):使用指定的字符集合把字符串编码为字节数组
编码:把看得懂的变成看不懂的
String -- byte[]
解码:把看不懂的变成看得懂的
byte[] -- String
```
* **字符编码** `Character Encoding`: 就是一套自然语言的字符与二进制数之间的对应规则。
**编码表**则是生活中文字和计算机中二进制的对应规则
## 字符集
* **字符集** `Charset`:也叫**编码表**。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见的字符集有`ASCII`字符集、`GBK`字符集、`Unicode`字符集等。
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/io/char-byte-eafc0ac8-ce5b-4183-9a7e-9498e23b2d4e.png)
可见,当指定了**编码**,它所对应的**字符集**自然就指定了,所以**编码**才是我们最终要关心的。
* **ASCII字符集**
* ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
* 基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。ASCII的扩展字符集使用8位(bits)表示一个字符,共256字符,方便支持欧洲常用字符。
* **ISO-8859-1字符集**
* 拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。
* ISO-8859-1使用单字节编码,兼容ASCII编码。
* **GBxxx字符集**
* GB就是国标的意思,是为了显示中文而设计的一套字符集。
* **GB2312**:简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符。
* **GBK**:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。
* **GB18030**:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
* **Unicode字符集**
* Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。
* 它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。
* UTF-8编码,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。它使用一至四个字节为每个字符编码,编码规则:
1. 128个US-ASCII字符,只需一个字节编码。
2. 拉丁文等字符,需要二个字节编码。
3. 大部分常用字(含中文),使用三个字节编码。
4. 其他极少使用的Unicode辅助字符,使用四字节编码。
## 编码问题导致乱码
在java开发工具IDEA中,使用`FileReader` 读取项目中的文本文件。由于IDEA的设置,都是默认的`UTF-8`编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统默认的是GBK编码,就会出现乱码。
```java
public class ReaderDemo {
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("C:\\a.txt");
int read;
while ((read = fileReader.read()) != -1) {
System.out.print((char)read);
}
fileReader.close();
}
}
输出结果:���
```
那么如何读取GBK编码的文件呢? 这个时候就得讲讲转换流了!
> 从另一角度来讲:**字符流=字节流+编码表**
## InputStreamReader类--(字节流到字符流的桥梁)
转换流`java.io.InputStreamReader`,是`Reader`的子类,从字面意思可以看出它是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
### 构造方法
> `InputStreamReader(InputStream in)`: 创建一个使用默认字符集的字符流。
>
> `InputStreamReader(InputStream in, String charsetName)`: 创建一个指定字符集的字符流。
构造代码如下:
```java
InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");
```
### 使用转换流解决编码问题
```java
public class ReaderDemo2 {
public static void main(String[] args) throws IOException {
// 定义文件路径,文件为gbk编码
String FileName = "C:\\A.txt";
// 创建流对象,默认UTF8编码
InputStreamReader isr = new InputStreamReader(new FileInputStream(FileName));
// 创建流对象,指定GBK编码
InputStreamReader isr2 = new InputStreamReader(new FileInputStream(FileName) , "GBK");
// 定义变量,保存字符
int read;
// 使用默认编码字符流读取,乱码
while ((read = isr.read()) != -1) {
System.out.print((char)read); // �����ʺ
}
isr.close();
// 使用指定编码字符流读取,正常解析
while ((read = isr2.read()) != -1) {
System.out.print((char)read); // 沉默王二
}
isr2.close();
}
}
```
## 2.4 OutputStreamWriter类--(字符流到字节流的桥梁)
转换流`java.io.OutputStreamWriter` ,是Writer的子类,字面看容易混淆会误以为是转为字符流,其实不然,OutputStreamWriter为从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
### 构造方法
> `OutputStreamWriter(OutputStream in)`: 创建一个使用默认字符集的字符流。
>
> `OutputStreamWriter(OutputStream in, String charsetName)`: 创建一个指定字符集的字符流。
构造举例,代码如下:
```java
OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("a.txt"));
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("b.txt") , "GBK");
```
### 指定编码构造代码
```java
public class OutputDemo {
public static void main(String[] args) throws IOException {
// 定义文件路径
String FileName = "C:\\s.txt";
// 创建流对象,默认UTF8编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
// 写出数据
osw.write("沉默"); // 保存为6个字节
osw.close();
// 定义文件路径
String FileName2 = "D:\\A.txt";
// 创建流对象,指定GBK编码
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");
// 写出数据
osw2.write("王二");// 保存为4个字节
osw2.close();
}
}
```
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/io/char-byte-61d3a7e6-365e-41d3-8c4a-bc9e680c70a6.png)
为了达到**最高效率**,可以考虑在 `BufferedReader` 内包装 `InputStreamReader`
```java
BufferedReader in = new BufferedReader(new InputStreamReader(System.in))
```
>参考链接:[https://www.cnblogs.com/yichunguo/p/11775270.html](https://www.cnblogs.com/yichunguo/p/11775270.html),整理:沉默王二
---------
最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png)
\ No newline at end of file
......@@ -32,7 +32,7 @@ head:
在java中,一切皆是对象,File类也不例外,不论是哪个对象都应该从该对象的构造说起,所以我们来分析分析`File`类的构造方法。首先从API开始着手:
![](https://img-blog.csdnimg.cn/20191013095030887.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NTQzNTA4,size_16,color_FFFFFF,t_70)
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/io/file-path-37de6cfc-f82f-4459-bc7e-9b4bd7466d70.png)
比较常用的构造方法有三个:
......@@ -254,7 +254,7 @@ public class FileFor {
```
![](https://img-blog.csdnimg.cn/20191013114927925.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NTQzNTA4,size_16,color_FFFFFF,t_70)
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/io/file-path-0f1675a7-ee3b-4f28-9cca-00c4ba5a5759.png)
......
---
title: Java打印流:PrintStream & PrintWriter
shortTitle: Java打印流PrintStream
category:
- Java核心
tag:
- Java IO
description: Java程序员进阶之路,小白的零基础Java教程,Java打印流:PrintStream & PrintWriter
head:
- - meta
- name: keywords
content: Java,Java SE,Java 基础,Java 教程,Java 程序员进阶之路,Java 入门,Java IO,java 打印流,java PrintStream,java PrintWriter
---
## 何谓打印流
平时我们在控制台打印输出,是调用`print`方法和`println`方法完成的,各位用了这么久的输出语句肯定没想过这两个方法都来自于`java.io.PrintStream`类吧,哈哈。该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。
**打印流分类**
> 字节打印流PrintStream,字符打印流PrintWriter
**打印流特点**
> A:只操作目的地,不操作数据源
>
> B:可以操作任意类型的数据
>
> C:如果启用了自动刷新,在调用println()方法的时候,能够换行并刷新
>
> D:可以直接操作文件
这个时候有同学就要问了,哪些流可以直接操作文件呢?答案很简单,**如果该流的构造方法能够同时接收File和String类型的参数,一般都是可以直接操作文件的**
PrintStream是OutputStream的子类,PrintWriter是Writer的子类,两者处于对等的位置上,所以它们的API是非常相似的。二者区别无非一个是字节打印流,一个是字符打印流。
## 字节输出打印流PrintStream复制文本文件
```java
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
public class PrintStreamDemo {
public static void main(String[] args) throws IOException {
BufferedReader br=new BufferedReader(new FileReader("copy.txt"));
PrintStream ps=new PrintStream("printcopy.txt");
String line;
while((line=br.readLine())!=null) {
ps.println(line);
}
br.close();
ps.close();
}
}
```
## 字符输出打印流PrintWriter复制文本文件
```java
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 使用打印流复制文本文件
*/
public class PrintWriterDemo {
public static void main(String[] args) throws IOException {
BufferedReader br=new BufferedReader(new FileReader("aa.txt"));
PrintWriter pw=new PrintWriter("printcopyaa.txt");
String line;
while((line=br.readLine())!=null) {
pw.println(line);
}
br.close();
pw.close();
}
}
```
>参考链接:[https://www.cnblogs.com/yichunguo/p/11775270.html](https://www.cnblogs.com/yichunguo/p/11775270.html),整理:沉默王二
---------
最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png)
\ No newline at end of file
---
title: Java序列化流,字节和对象之间的序列化和反序列化
shortTitle: Java序列化流(序列化和反序列化)
category:
- Java核心
tag:
- Java IO
description: Java程序员进阶之路,小白的零基础Java教程,Java序列化流,字节和对象之间的序列化和反序列化
head:
- - meta
- name: keywords
content: Java,Java SE,Java 基础,Java 教程,Java 程序员进阶之路,Java 入门,Java IO,java 序列化流,java 序列化,java 反序列化,java ObjectOutputStream,java ObjectInputStream
---
序列化有什么好处呢?可以把对象写入文本文件或者在网络中传输。
如何实现序列化呢?让被序列化的对象所属类实现[Serializbale序列化接口](https://tobebetterjavaer.com/io/Serializbale.html)
接着我们来继续聊序列化和反序列化。
## 何谓序列化
Java 提供了一种对象**序列化**的机制。用一个字节序列可以表示一个对象,该字节序列包含该`对象的数据``对象的类型``对象中存储的属性`等信息。字节序列写出到文件之后,相当于文件中**持久保存**了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行**反序列化**`对象的数据``对象的类型``对象中存储的数据`信息,都可以用来在内存中创建对象。看图理解序列化:
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/io/serialize-8a1b6818-5f58-4057-b521-f8ba670d72a1.png)
## ObjectOutputStream类
`java.io.ObjectOutputStream` 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。
### 构造方法
`public ObjectOutputStream(OutputStream out)` : 创建一个指定OutputStream的ObjectOutputStream。
构造代码如下:
```java
FileOutputStream fileOut = new FileOutputStream("aa.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
```
### 序列化操作
1. 一个对象要想序列化,必须满足两个条件:
- 该类必须实现[`java.io.Serializable` 接口](https://tobebetterjavaer.com/io/Serializbale.html)`Serializable` 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出`NotSerializableException`
- 该类的所有属性都必须是可序列化的。如果有一个属性不需要可序列化,则该属性必须注明是瞬态的,使用[`transient` 关键字](https://tobebetterjavaer.com/io/transient.html)修饰。
```java
public class Employee implements java.io.Serializable {
public String name;
public String address;
public transient int age; // transient瞬态修饰成员,不会被序列化
public void addressCheck() {
System.out.println("Address check : " + name + " -- " + address);
}
}
```
2.写出对象方法
`public final void writeObject (Object obj)` : 将指定的对象写出。
```java
public class SerializeDemo{
public static void main(String [] args) {
Employee e = new Employee();
e.name = "zhangsan";
e.address = "beiqinglu";
e.age = 20;
try {
// 创建序列化流对象
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt"));
// 写出对象
out.writeObject(e);
// 释放资源
out.close();
fileOut.close();
System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
} catch(IOException i) {
i.printStackTrace();
}
}
}
输出结果
Serialized data is saved
```
## ObjectInputStream类
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
### 构造方法
`public ObjectInputStream(InputStream in)` : 创建一个指定InputStream的ObjectInputStream。
### 反序列化操作1
如果能找到一个对象的class文件,我们可以进行反序列化操作,调用`ObjectInputStream`读取对象的方法:
* `public final Object readObject ()` : 读取一个对象。
```java
public class DeserializeDemo {
public static void main(String [] args) {
Employee e = null;
try {
// 创建反序列化流
FileInputStream fileIn = new FileInputStream("employee.txt");
ObjectInputStream in = new ObjectInputStream(fileIn);
// 读取一个对象
e = (Employee) in.readObject();
// 释放资源
in.close();
fileIn.close();
}catch(IOException i) {
// 捕获其他异常
i.printStackTrace();
return;
}catch(ClassNotFoundException c) {
// 捕获类找不到异常
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
// 无异常,直接打印输出
System.out.println("Name: " + e.name); // zhangsan
System.out.println("Address: " + e.address); // beiqinglu
System.out.println("age: " + e.age); // 0
}
}
```
**JVM可以反序列化的对象,必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 `ClassNotFoundException` 异常。**
### 反序列化操作2
另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个`InvalidClassException`异常。发生这个异常的原因如下:
> 1、该类的序列版本号与从流中读取的类描述符的版本号不匹配
>
> 2、该类包含未知数据类型
>
> 2、该类没有可访问的无参数构造方法
`Serializable` 接口给需要序列化的类,提供了一个序列版本号。`serialVersionUID` 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
```java
public class Employee implements java.io.Serializable {
// 加入序列版本号
private static final long serialVersionUID = 1L;
public String name;
public String address;
// 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
public int eid;
public void addressCheck() {
System.out.println("Address check : " + name + " -- " + address);
}
}
```
## 序列化集合练习
1. 将存有多个自定义对象的集合序列化操作,保存到`list.txt`文件中。
2. 反序列化`list.txt` ,并遍历集合,打印对象信息。
### 案例分析
1. 把若干学生对象 ,保存到集合中。
2. 把集合序列化。
3. 反序列化读取时,只需要读取一次,转换为集合类型。
4. 遍历集合,可以打印所有的学生信息
### 案例代码实现
```java
public class SerTest {
public static void main(String[] args) throws Exception {
// 创建 学生对象
Student student = new Student("老王", "laow");
Student student2 = new Student("老张", "laoz");
Student student3 = new Student("老李", "laol");
ArrayList<Student> arrayList = new ArrayList<>();
arrayList.add(student);
arrayList.add(student2);
arrayList.add(student3);
// 序列化操作
// serializ(arrayList);
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("list.txt"));
// 读取对象,强转为ArrayList类型
ArrayList<Student> list = (ArrayList<Student>)ois.readObject();
for (int i = 0; i < list.size(); i++ ){
Student s = list.get(i);
System.out.println(s.getName()+"--"+ s.getPwd());
}
}
private static void serializ(ArrayList<Student> arrayList) throws Exception {
// 创建 序列化流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.txt"));
// 写出对象
oos.writeObject(arrayList);
// 释放资源
oos.close();
}
}
```
>参考链接:[https://www.cnblogs.com/yichunguo/p/11775270.html](https://www.cnblogs.com/yichunguo/p/11775270.html),整理:沉默王二
---------
最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png)
\ No newline at end of file
---
title: 招银面试官:说说Java transient关键字
shortTitle: Java transient关键字
category:
- Java核心
tag:
- Java IO
description: Java程序员进阶之路,小白的零基础Java教程,Java transient关键字
head:
- - meta
- name: keywords
content: Java,Java SE,Java 基础,Java 教程,Java 程序员进阶之路,Java 入门,Java transient
---
害,小二最熟的是 Java,但很多 Java 基础知识都不知道,比如 transient 关键字以前就没用到过,所以不知道它的作用是什么,今天去招银面试的时候,面试官问到了这个:说说 Java transient 关键字吧,结果小二直接懵逼了。
下面是他自己面试凉了以后回去做的总结,分享出来,大家一起涨下姿势~~~好了,废话不多说,下面开始:
## 1\. transient 的作用及使用方法
我们都知道一个对象只要实现了 Serilizable 接口,这个对象就可以被序列化,java 的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过程,只要这个类实现了 Serilizable 接口,这个类的所有属性和方法都会自动序列化。
然而在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上 transient 关键字。
换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
总之,java 的 transient 关键字为我们提供了便利,你只需要实现 Serilizable 接口,将不需要序列化的属性前添加关键字 transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
示例 code 如下:
```java
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* @description 使用transient关键字不序列化某个变量
* 注意读取的时候,读取数据的顺序一定要和存放数据的顺序保持一致
*
* @author Alexia
* @date 2013-10-15
*/
public class TransientTest {
public static void main(String[] args) {
User user = new User();
user.setUsername("沉默王二");
user.setPasswd("123456");
System.out.println("read before Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());
try {
ObjectOutputStream os = new ObjectOutputStream(
new FileOutputStream("C:/user.txt"));
os.writeObject(user); // 将User对象写进文件
os.flush();
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream(
"C:/user.txt"));
user = (User) is.readObject(); // 从流中读取User的数据
is.close();
System.out.println("\nread after Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User implements Serializable {
private static final long serialVersionUID = 8294180014912103005L;
private String username;
private transient String passwd;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
```
输出为:
```
read before Serializable:
username: Alexia
password: 123456 read after Serializable:
username: Alexia
password: null
```
密码字段为 null,说明反序列化时根本没有从文件中获取到信息。
## 2\. transient 使用小结
1)一旦变量被 transient 修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
2)transient 关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被 transient 关键字修饰的。变量如果是用户自定义类变量,则该类需要实现 Serializable 接口。
3)被 transient 关键字修饰的变量不能被序列化,一个静态变量不管是否被 transient 修饰,均不能被序列化。
第三点可能有些人很迷惑,因为发现在 User 类中的 username 字段前加上 static 关键字后,程序运行结果依然不变,即 static 类型的 username 也读出来为“Alexia”了,这不与第三点说的矛盾吗?
实际上是这样的:第三点确实没错(一个静态变量不管是否被 transient 修饰,均不能被序列化),反序列化后类中 static 型变量 username 的值为当前 JVM 中对应 static 变量的值,这个值是 JVM 中的,不是反序列化得出的,不相信?好吧,下面我来证明:
```java
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* @description 使用transient关键字不序列化某个变量
* 注意读取的时候,读取数据的顺序一定要和存放数据的顺序保持一致
*
* @author Alexia
* @date 2013-10-15
*/
public class TransientTest {
public static void main(String[] args) {
User user = new User();
user.setUsername("沉默王二");
user.setPasswd("123456");
System.out.println("read before Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());
try {
ObjectOutputStream os = new ObjectOutputStream(
new FileOutputStream("C:/user.txt"));
os.writeObject(user); // 将User对象写进文件
os.flush();
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
// 在反序列化之前改变username的值
User.username = "jmwang";
ObjectInputStream is = new ObjectInputStream(new FileInputStream(
"C:/user.txt"));
user = (User) is.readObject(); // 从流中读取User的数据
is.close();
System.out.println("\nread after Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User implements Serializable {
private static final long serialVersionUID = 8294180014912103005L;
public static String username;
private transient String passwd;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
```
运行结果为:
```
read before Serializable:
username: Alexia
password: 123456 read after Serializable:
username: jmwang
password: null
```
这说明反序列化后类中 static 型变量 username 的值为当前 JVM 中对应 static 变量的值,为修改后 jmwang,而不是序列化时的值 沉默王二。
## 3\. transient 使用细节——被 transient 关键字修饰的变量真的不能被序列化吗?
思考下面的例子:
```java
import java.io.Externalizable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
/**
* @descripiton Externalizable接口的使用
*
* @author Alexia
* @date 2013-10-15
*
*/
public class ExternalizableTest implements Externalizable {
private transient String content = "是的,我将会被序列化,不管我是否被transient关键字修饰";
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(content);
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
content = (String) in.readObject();
}
public static void main(String[] args) throws Exception {
ExternalizableTest et = new ExternalizableTest();
ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
new File("test")));
out.writeObject(et);
ObjectInput in = new ObjectInputStream(new FileInputStream(new File(
"test")));
et = (ExternalizableTest) in.readObject();
System.out.println(et.content);
out.close();
in.close();
}
}
```
content 变量会被序列化吗?好吧,我把答案都输出来了,是的,运行结果就是:
```
是的,我将会被序列化,不管我是否被transient关键字修饰
```
这是为什么呢,不是说类的变量被 transient 关键字修饰以后将不能序列化了吗?
我们知道在 Java 中,对象的序列化可以通过实现两种接口来实现,若实现的是 Serializable 接口,则所有的序列化将会自动进行,若实现的是 Externalizable 接口,则没有任何东西可以自动序列化,需要在 writeExternal 方法中进行手工指定所要序列化的变量,这与是否被 transient 修饰无关。
因此第二个例子输出的是变量 content 初始化的内容,而不是 null。
> 参考链接:[https://www.cnblogs.com/lanxuezaipiao/p/3369962.html](https://www.cnblogs.com/lanxuezaipiao/p/3369962.html),整理:沉默王二
---------
最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png)
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册