复习
1.线程的同步与通信有几种方式?
2.创建线程有几种方式?
3.什么是方法重写与方法重载?
4.线程与进程的区别?
5.如何最高效的遍历Map?
6.线程的状态与生命周期?
7.HashTable与HashMap的区别?
8.LinkedList与ArrayList的区别?
9.集合框架的继承体系?
10.什么是并发与并行?
前文链接
1.偏头痛杨的Java入门教学系列之认识Java篇
2.偏头痛杨的Java入门教学系列之变量&数据类型篇
3.偏头痛杨的Java入门教学系列之表达式&运算符&关键字&标识符&表达式篇
4.偏头痛杨的Java入门教学系列之初级面向对象篇
5.偏头痛杨的Java入门教学系列之流程控制语句篇
6.偏头痛杨的Java入门教学系列之数组篇
7.偏头痛杨的Java入门教学系列之进阶面向对象篇
8.偏头痛杨的Java入门教学系列之异常篇
9.偏头痛杨的Java入门教学系列之初级集合框架篇
10.偏头痛杨的Java入门教学系列之初级多线程篇
前戏
有很多同学单纯的认为IO不就是一个读文件和写文件吗,不重要,只要简单的复制粘贴就OK,
会用个File以及什么流就算"熟练掌握 "了。
使用场景也就上传文件才用的到,仅此而已。
呵呵呵呵,那就大错特错了,IO的使用范围很广,最能体现IO价值的就是网络上的数据传递,
尤其是进入互联网时代后,各种常见的分布式架构,都少不了IO的体现。
并且很多大厂的面试题中都会体现出对IO的重视,包括衍生出来的NIO、序列化等等。
因此学好IO,变成了一件很重要的事情。
IO基本概念
IO可以简单的理解成INPUT和OUT,代表输入输出的意思。输入就是读,输出就是写。
IO可以读写硬盘、光盘、内存、键盘、网络等资源上的数据。
流
IO中的流就相当于现实生活中的水流一样,一打开自来水的龙头开关,水就从一头流向另一头。
可以理解成每个按顺序排列的水滴就是需要传输的字节。
把有序数据理解成流,流负责传输数据,以便于输入输出。数据是流动的,是有方向的流动。
流的分类
按数据的走向可以分为:输入流,输出流。
按数据的单位可以分为:字节流、字符流。
按装饰模式可以分为:节点流(底层)、处理流(上层)。
输入流与输出流
输入流:只能从中读取数据,而不能向其写入数据。一般用于将数据从网络、硬盘读取到内存中。
输出流:只能向其写入数据,而不能从中读取数据。一般用于将数据从内存中写入到网络、硬盘。
输入流主要由InputStream和Reader作为父类。
输出流主要由OutputStream和Writer作为父类。
他们都是抽象的,因此无法直接创建对象。
字节流与字符流
字节流与字符流的用法几乎完全一样,区别在于所操作的单位不同,字节流操作8位的字节,
而字符流操作16位的字符。
字节流主要由InputStream和OutputStream作为父类。
字符流主要由Reader和Writer作为父类。
节点流与处理流
处理流(上层):
对一个已存在的流进行连接或封装,通过封装后的流来实现数据读写功能。
节点流(底层):
向特定的节点写入&读取数据的流。程序连接到实际的数据源,和实际的输入输出节点连接。
使用处理流进行输入输出时,程序不会和实际的输入输出节点连接,对底层的处理流做了一层封装。
程序可以采用相同的输入输出代码来访问不同的数据源。(涉及到装饰器模式)
处理流使得java程序无需理会输入输出的节点是磁盘、网络还是其他,
只要将这些节点流包装成处理流,
就可以使用相同的输入输出代码来读写不同的输入输出设备的数据。
节点流用于和底层的物理存储节点直接关联,不同的物理节点获取节点流的方式可能存在差异。
程序可以把不同的物理节点流包装成统一的处理流,
允许程序使用统一的输入输出代码来读写不同的物理存储节点资源。
常用的输入输出流体系
io流按功能分成许多类,每个功能又提供字节流&字符流,
字节流与字符流又分别提供了输入流与输出流。
如果输入输出的是文本内容可以使用字符流,如果输入输出的是二进制内容,可以使用字节流。
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
抽象父类 | InputStream | OutputStream | Reader | Writer |
访问文件 (节点流 ) | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 (节点流 ) | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 (节点流) | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 (节点流 ) | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
过滤器流 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
打印流 | PrintStream | PrintReader | ||
推回输入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
字节流
字节流是IO最原始的方式,因为计算机处理数据总是以一个byte为基本单位的,
字节流就是每次读取的单位为byte。字节流是所有流的基础,也是其他高级流的前提。
字节流可以处理所有类型的数据,包括:音乐、图片、文字、视频、各种文件等等。
多数以"Stream"结尾的类都是字节流。
字符流只能处理文本,读写的单位是字符。多数以"Writer"与"Reader"结尾的类都是字节流。
java的基础字节流的类为:InputStream,OutputStream。
通过他们俩可以衍生出许多子类,常见的有:
FileInputStream,FileOutputStream,ObjectInputStream,ObjectOutputStream,BufferedInputStream,BufferedOutputStream等。
byte是计算机最基本的单位,所以字节流可以应付几乎所有的流的处理,只不过,在处理具体数据格式的时候,效率没有具体的实现类高,如字符格式,对象格式等。主要操作对象是byte数组,
通过read()和wirte()方法把byte数组中的数据写入或读出。
使用字节流复制文件
public class FileInputOutStreamDemo1 {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("enya恩雅-only time.mp3");
fos = new FileOutputStream("new-enya恩雅-only time.mp3");
byte[] temp = new byte[1024];
while(fis.read(temp)!=-1) {
fos.write(temp);
}
temp = new byte[1024];
//将字符串写入到文件
fos = new FileOutputStream("aaa.txt");
fos.write("我爱你亲爱的姑娘".getBytes());//直接覆盖,而不是追加。如果想追加怎么办?
fis = new FileInputStream("aaa.txt");
fis.read(temp);
System.out.println(new String(temp));
} catch (Exception e) {
e.printStackTrace();
} finally {
if(fis!=null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos!=null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("结束");
}
}
可以理解成把输入数据想象成存储在一个水管当中,
输入流的read()方法从水管中读取一个或多个水滴,
水滴的单位是字节或字符,当使用数组作为读取方法参数时,read(byte []),
这个数组相当于一个竹筒,
使用竹筒去水管中取水,程序重复这个取水的过程,直到read(byte [])返回-1为止。
字符流
针对文本文件,使用字符流来写入和读出字符数据。无需再使用字节流进行包装,
字符流是由字节流包装而来,
它包括:StringReader,StringWriter,BufferedReader,BufferedWriter。
对于前者,他们的使用方法和字节流类似,主要还是read()和wirte(),
而后者多加了一个readLine()方法,用于读取文章类型的文本。
FileReader、FileWriter,节点流,会直接和指定文件相关联。
使用字符流复制文本文件
public class FileWriterDemo1 {
public static void main(String[] args) {
FileReader fr = null;
FileWriter fw = null;
try {
char[] temp = new char[1024];
// 将字符串写入到文件
fw = new FileWriter("aaa.txt");
fw.write("我爱你亲爱的姑娘55555");// 直接覆盖,而不是追加。如果想追加怎么办?
fr = new FileReader("aaa.txt");
fr.read(temp);
System.out.println(new String(temp));
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("结束");
}
}
字符流对象在创建时,一般需要提供一个输入or输出流。
例如在创建BufferedReader或者BufferedWriter对象时,
需要提供InputStreamReader或OutputStreamWriter对象。
对于特定支付格式的文本内容,还需要在创建的时候提供字符格式类型作为构造参数。
小例子:字符输入流
File f = new File("D:\\VSS存放目录\\KOOF\\3-开发阶段\\3.3-数据库","koof30.sql");
FileReader fi = new FileReader(f);
BufferedReader in = new BufferedReader(fi);
String s ;
String total = "";
while((s=in.readLine())!=null){
total += s+"\n";
}
处理流(上层)与节点流(底层)
处理流隐藏底层设备上节点流的差异,并对外提供更加方便的输入输出方法,
让我们只关心上层流的操作,有点像应用层的感觉。
可以使用处理流来包装节点流,通过处理流执行输入输出功能,
节点流则负责与底层的IO设别、文件交互。
处理流的构造参数不是一个物理节点,而是已经存在的节点流,而节点流的构造参数都是物理节点。
上面的例子都是使用节点流(FileInputStream、FileOutputStream、FileReader、FileWriter)。
在使用节点流过程中比较繁琐,因此我们可以使用处理流。
public class PrintStreamDemo1 {
public static void main(String[] args) {
PrintStream ps = null;
try {
ps = new PrintStream(new FileOutputStream("bbb.txt"));
ps.print("太阳当空照花儿对我笑");
ps.println("小鸟说早早早你为什么背上小书包");
ps.println(new PrintStreamDemo1());
System.out.println("结束");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ps != null) {
ps.close();
}
}
}
}
缓冲流
缓冲流先将数据读写到缓冲区,提高输入输出效率。
BufferedReader可以一次读一行文本,以换行符作为标志,
如果没有遇到换行符则阻塞(此处的阻塞是指数据没读完之前)。
public class BufferedDemo1 {
public static void main(String[] args) {
// jdk1.7支持的自动关闭资源的try写法
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("123.mp3"));
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("enya恩雅-only time.mp3"));
BufferedReader br = new BufferedReader(new FileReader("456.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("789.txt"))) {
String tempStr = null;
while ((tempStr = br.readLine()) != null) {
// 如遇乱码问题可以:
// 1.将文本文件保存到UTF-8的编码集
// 2.使用转换流并设置编码集而并非FileReader文件流
System.out.println(tempStr);
//BufferedWriter不会自动换行,可以使用PrintWriter自动换行。
bw.write(tempStr);
}
byte[] temp = new byte[1024];
while (bis.read(temp) > 0) {
bos.write(temp);
}
System.out.println("结束");
} catch (Exception e) {
e.printStackTrace();
}
}
}
RandomAccessFile
RandomAccessFile是传统io中功能丰富的文件访问类,同时支持读写、随机访问,
程序可以跳到文件的任意位置进行读写。如果只希望访问文件的部分内容,
而不是从头读到尾,以及追加原有文件,在原有文件后面输出内容,可使用RandomAccessFile。
创建RandomAccessFile对象需要指定一个mode参数。
r:以只读方式打开指定文件,此时想写入或文件不存在则会抛出异常。
rw:以读写方式打开指定文件,如果文件不存在则尝试创建该文件。
rws:以读写方式打开指定文件,对文件内容的每个更新都同步写入到底层存储设备。
public class RandomAccessFileDemo1 {
public static void main(String[] args) {
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile("aaa.txt","rw");
//获取指针位置,初始位置为0
System.out.println(raf.getFilePointer());
raf.write("江山如此多娇,引无数英雄尽折腰。".getBytes());
//移动指针位置
raf.seek(6);
System.out.println(raf.getFilePointer());
//追加内容
raf.seek(raf.length());
raf.write("昔秦皇汉武略输文采,唐宗宋祖稍逊风骚。".getBytes());
//从头到尾读
raf.seek(0);
byte[] temp = new byte[1024];
while(raf.read(temp)>0) {
System.out.println(new String(temp));
}
System.out.println("结束");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
RandomAccessFile将指针移动到文件中间位置后输出,则新输出的内容会覆盖原有内容。
如果想不覆盖,需要先把要插入内容后面的内容读入缓冲区或写入临时文件,
先把要插入内容写入文件后,在将缓冲区或临时文件的内容追加到文件后面。
File
java用File类来表示目录和文件(用isDirectory()或isFile()方法判断),
我们可以通过File类来操作文件与目录。
并且在创建File对象的时候,并不检查该目录或文件是否存在,只作为一种代表。
File能新建、删除、重命名文件和目录,File不能访问文件内容本身,要想访问需要使用IO流。
public class FileDemo1 {
public static void main(String[] args) throws Exception {
File f1 = new File("enya恩雅-only time.mp3");//相对路径文件名
File f2 = new File("D:\\yangWorkSpaces\\yangDemoWorkSpace\\javademo");//绝对路径
/**
* 访问文件名&路径相关方法
*/
System.out.println(f1.getName());//返回文件名
System.out.println(f2.getName());//返回路径名,只返回最后一级子路径名
System.out.println(f2.getPath());//返回路径名,在File的构造里怎么写的就返回什么
System.out.println(f1.getAbsolutePath());//返回绝对路径
System.out.println(f1.getAbsoluteFile());//返回绝对路径对应的File对象
System.out.println(f2.getParentFile());//返回父目录对应的File对象
System.out.println(f2.getParent());//返回父目录对象
System.out.println(f1.renameTo(new File("abc.mp3")));//重命名文件&目录,成功返回true,否则返回false
/**
* 文件检查相关方法
*/
System.out.println(f1.exists());//判断文件&目录是否存在
System.out.println(f1.canWrite());//判断文件&目录是否可写
System.out.println(f1.canRead());//判断文件&目录是否可读
System.out.println(f1.isFile());//判断文件&目录是否是文件
System.out.println(f1.isDirectory());//判断文件&目录是否是目录
System.out.println(f2.isAbsolute());//判断文件&目录是否是绝对路径
/**
* 获取常规文件信息
*/
System.out.println(f1.lastModified());//返回文件&目录的最后修改时间,long类型
System.out.println(f1.length());//返回文件&目录的长度
/**
* 文件操作相关方法
*/
//当File对象不存在时创建对象,创建成功返回true,否则返回false
System.out.println(new File("4.mp3").createNewFile());
System.out.println(new File("4.mp3").delete());//删除文件&目录,删除成功返回true,否则返回false
//创建临时空文件,在前缀与后缀中间会有一串随机数组成临时文件名
System.out.println(File.createTempFile("aaa", ".txt"));
System.out.println(new File("4.mp3").createNewFile());
//在jvm退出后删除文件
new File("4.mp3").deleteOnExit();
//删除文件,成功返回true,失败返回false
System.out.println(new File("4.mp3").delete());
/**
* 目录操作相关方法
*/
System.out.println(new File("D:\\aaa\\bb").mkdir());//创建目录,成功返回true,错误返回false
System.out.println(Arrays.asList(new File("D:\\aaa").list()));//返回子文件名&目录名的字符串数组
System.out.println(Arrays.asList(new File("D:\\aaa").listFiles()));//返回子文件名&目录名的文件数组
System.out.println(Arrays.asList(File.listRoots()));//列出系统根路径,例如[C:\, D:\]
/**
* 文件过滤
*/
File f = new File("D:\\yangWorkSpaces\\yangDemoWorkSpace\\javase\\src\\main\\java\\com\\mingyisoft\\javase\\thread\\threadsafe\\demo1");
System.out.println(Arrays.asList(f.list(new FilenameFilter(){
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".java") || new File(name).isDirectory();
}
})));
}
}
序列化
把java对象存储到磁盘或网络传输java对象。序列化机制使得对象可以脱离程序的运行而独立存在。
其他程序或节点获取到这个二进制流,无论是从硬盘上还是网络上,
都可以将二进制流恢复成原来的java对象。
序列化:把对象转换为字节序列的过程。
反序列化:把字节序列恢复为对象的过程。
注意事项:
~方法、静态变量、瞬态变量不会被序列化。
~使用transient关键字修饰实例变量可使该变量无法被序列化。
~反序列化对象时必须有序列化对象的class文件。
~必须按照写入的顺序读取数据。
使用Serializable接口实现序列化机制
必须让类实现java.io.Serializable接口表示该类的对象支持序列化机制,该接口没有需要实现的方法,
他仅仅告诉java底层该类的对象是可以进行序列化的,
并且序列化版本ID由serialVersionUID变量提供。
反序列化无需通过构造器来初始化java对象。
如果使用序列化机制写入多个java对象,使用反序列化恢复java对象时必须按写入的顺序读取,
否则抛异常。
如果被序列化的类有父类,则父类也需要实现Serializable接口,否则父类的属性无法被序列化。
如果被序列化的类内有嵌套类对象,则嵌套类也需要实现Serializable接口,否则抛异常。
java的io提供了ObjectOutputStream,ObjectInputStream用作对象的序列化和反序列化。
public class SerializableDemo1 {
public static void main(String[] args) {
Teacher t = new Teacher();
t.setName("alex");
t.setAge(18);
Student s1 = new Student();
s1.setName("张三555");
s1.setSex('5');
s1.setYear(5);
s1.description = "xxx";
//嵌套对象
s1.setTeacher(t);
Student s2 = new Student();
s2.setName("李四555");
s2.setSex('6');
s2.setYear(6);
s2.description = "666";
//嵌套对象
s2.setTeacher(t);
try {
// Student对象序列化过程,使用ObjectOutputStream
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.ser"));
oos.writeObject(s1);
oos.writeObject(s2);
oos.writeObject(t);
s1.setName("王二麻子");
oos.writeObject(s1);
Student2 s3 = new Student2();
s3.setYear(33);
s3.setGpa(33);
oos.writeObject(s3);
oos.flush();
oos.close();
// Student对象反序列化过程,使用ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.ser"));
Student ss1 = (Student) ois.readObject();
System.out.println("name = " + ss1.getName());
System.out.println("sex = " + ss1.getSex());
System.out.println("year = " + ss1.getYear());
System.out.println("gpa = " + ss1.getGpa());
System.out.println("description = " + ss1.description);
System.out.println("t.name = " + ss1.getTeacher().getName());
System.out.println("t.age = " + ss1.getTeacher().getAge());
Student ss2 = (Student) ois.readObject();
System.out.println("name = " + ss2.getName());
System.out.println("sex = " + ss2.getSex());
System.out.println("year = " + ss2.getYear());
System.out.println("gpa = " + ss2.getGpa());
System.out.println("description = " + ss1.description);
System.out.println("t.name = " + ss2.getTeacher().getName());
System.out.println("t.age = " + ss2.getTeacher().getAge());
Teacher tt = (Teacher) ois.readObject();
System.out.println(tt.getName()+" "+tt.getAge());
/**
* 所有采用序列化机制的java对象都有一个序列化编号(不同于序列化版本号),
* 当程序尝试序列化一个对象时,先检查当前对象是否已经被序列化过,只有从未序列化(本次jvm进程)过的对象,
* 系统才会将该对象转换成字节序列,如果当前对象已经被序列化过,则自动使用其序列化编号而不是重新
*/
System.out.println(ss1.getTeacher()==ss2.getTeacher());
System.out.println(ss1.getTeacher()==tt);
System.out.println(ss2.getTeacher()==tt);
Student ss3 = (Student) ois.readObject();
/**
* 即使属性改变,也不会序列化成新属性。内存里只有1个s1对象,并且s1的属性不能再被改变。
*/
System.out.println("应该是王二麻子吗->"+ss3.getName());
/**
* 自定义序列化,返回集合
*/
List returnList = (List) ois.readObject();
for (Object obj : returnList) {
System.out.println(obj);
}
ois.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Student extends Person implements Serializable{
private String name;
/**
* 不进行序列化transient关键字,transient只能修饰属性
*/
private transient char sex;
private int year;
private double gpa;
private Teacher teacher;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public double getGpa() {
return gpa;
}
public void setGpa(double gpa) {
this.gpa = gpa;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
}
class Teacher implements Serializable{
/**
*
*/
private static final long serialVersionUID = -1053051757102136827L;
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
class Student2 extends Person implements Serializable{
private String name;
/**
* 不进行序列化transient关键字,transient只能修饰属性
*/
private transient char sex;
private int year;
private double gpa;
private Teacher teacher;
private Object writeReplace() {
List<Object> returnList = new ArrayList<Object>();
returnList.add(year);
returnList.add(gpa);
return returnList;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public double getGpa() {
return gpa;
}
public void setGpa(double gpa) {
this.gpa = gpa;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
}
class Person implements Serializable{
protected String description;
}
使用Serializable接口实现自定义序列化
通过java提供的自定义序列化机制可以让程序员来控制如何序列化&不序列化各个属性。
为了实现自定义序列化机制,类中需要提供若干方法。自定义序列化可以用来做加密处理,
例如被黑客截取到流中的信息也是加密过的。
class Teacher implements Serializable{
/**
*
*/
private static final long serialVersionUID = -1053051757102136827L;
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
private void writeObject(ObjectOutputStream out) throws IOException {
System.out.println("自定义写出");
//反转一下名字
out.writeObject(new StringBuffer(name).reverse());
out.writeInt(age);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
System.out.println("自定义读入");
//反转一下名字
this.name = (((StringBuffer)in.readObject()).reverse()).toString();
this.age = in.readInt();
}
}
class Student2 extends Person implements Serializable{
private String name;
/**
* 不进行序列化transient关键字,transient只能修饰属性
*/
private transient char sex;
private int year;
private double gpa;
private Teacher teacher;
private Object writeReplace() {
List<Object> returnList = new ArrayList<Object>();
returnList.add(year);
returnList.add(gpa);
return returnList;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public double getGpa() {
return gpa;
}
public void setGpa(double gpa) {
this.gpa = gpa;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
}
使用Externalizable接口实现序列化机制
对实现了Serializable接口的类,其序列化与反序列化采用默认的序列化方式,
Externalizable接口继承了Serializable接口,实现了Externalizable接口的类,
由程序员完全控制序列化与反序列化行为,更加灵活。
public class ExternalizableDemo1 {
public static void main(String[] args) {
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("xxx.aaa"));
Car c1 = new Car("alex", 12);
Car c2 = new Car("jack", 15);
oos.writeObject(c1);
oos.writeObject(c2);
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("xxx.aaa"));
Car c11 = (Car) ois.readObject();
Car c22 = (Car) ois.readObject();
System.out.println(c11);
System.out.println(c22);
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Car implements Externalizable {
private String name;
private Integer age;
public Car() {
}
public Car(String name, Integer age) {
this.name = name;
this.age = age;
}
public String toString() {
return this.name + this.age;
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(new StringBuffer(name).reverse());
out.writeInt(age);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.name = (((StringBuffer) in.readObject()).reverse()).toString();
this.age = in.readInt();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
序列化版本号
变量serialVersionUID是一个静态的long型的常量,用于在序列化和反序列化过程中,
起到一个辨别类的序列化版本作用,如果两个类名完全相同,
则通过serialVersionUID来判断该类是否符合要求,如果不行则抛异常。
当一个类升级后(对原有类进行修改并编译成class文件),只要serialVersionUID保持不变,
序列化机制会把他们当成同一个序列化版本。
如果不显式定义则jvm会自动计算创建serialVersionUID,
而修改后的类与修改前的类计算结果往往不同,而造成版本不兼容问题。
我们应该显式的定义个serialVersionUID,
即使在某个对象被序列化之后,它所对应的class文件被修改了,
该对象也依然可以被正确的反序列化。
序列化的时机
在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。
比如最常见的是tomcat中的Session对象,当有 10万用户并发访问,
就有可能出现10万个Session对象,内存可能吃不消,
于是tomcat就会把一些seesion先序列化到硬盘中,等要用了,
再把保存在硬盘中的对象还原到内存中。
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,
都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,
才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
通过浏览器发送了一个普通的HTTP请求,该请求携带了一个JSON格式的参数,
在服务端需要将该JSON参数转换为普通的Java对象,这个转换过程称为序列化。
再比如,在服务端获取了数据,此时该数据是一个普通的Java对象,
然后需要将这个Java对象转换为JSON字符串,并将其返回到浏览器中进行渲染,
这个转换过程称为反序列化。
总结
其实发现IO并不只是单纯的保存文件而已,IO在很多场景下都会出现,学好IO后下面就衍生出NIO,
以及后面的AIO。