Thinking in java 琐碎知识点之 I/O流 、对象序列化
Java I/O流 、对象序列化
1、File类
此类的实例可能表示(也可能不表示)实际文件系统对象,如文件或目录。
File类可以新建、删除和重命名文件和目录,但是File不能访问文件本身的内容,这要使用IO流。
File对象的createNewFile()方法在磁盘上创建真实的文件
例程:FileTest.java
import java.io.*;
public class FileTest {
public static void main(String[] args) throws IOException {
File file=new File("test1");//此相对路径即表示相对JVM的路径
File file2=new File(".");//以当前路径来创建一个File对象
File file3=new File("F:\\workplace\\IO\\jaa.txt");//系统不一定就存在jaa.txt这个文件
System.out.println("file.getName()\t"+file.getName());
System.out.println("file2.getName()\t"+file2.getName());
System.out.println("file3.getName()\t"+file3.getName());
System.out.println("file.getParent()\t"+file.getParent());
System.out.println("file2.getParent()\t"+file2.getParent());
System.out.println("file3.getParent()\t"+file3.getParent());
System.out.println("file.getAbsolutePath()\t"+file.getAbsolutePath());
System.out.println("file.getAbsoluteFile()\t"+file.getAbsoluteFile());
System.out.println("file.getAbsoluteFile().getParent()\t"+file.getAbsoluteFile().getParent());
System.out.println("file2.getAbsolutePath()\t"+file2.getAbsolutePath());
System.out.println("file3.getAbsolutePath()\t"+file3.getAbsolutePath());
File file4=new File("F://FileTest//test1.doc");
System.out.println("file4.exists()\t"+file4.exists());
//在系统中创建一个文件,注意test1.doc前面的目录一定要是真实存在的,否则执行createNewFile方法会报错
file4.createNewFile();
System.out.println("file4.exists()\t"+file4.exists());
File file5=new File("F:\\zpc");
System.out.println("file5.mkdir()"+file5.mkdir());//在系统中创建一个File对象所对应的目录
File file6=new File("F:\\workplace");
String fileList[]=file6.list();
System.out.println("=======F:\\workplace目录下的所有文件和路径如下=======");
for(String s:fileList){
System.out.println(s);
}
//File的静态方法listRoots列出所有的磁盘根路径
File[] roots=File.listRoots();
System.out.println("======系统所有根路径=======");
for(File f:roots){
System.out.println(f);
}
//在F:\\zpc目录下创建一个临时文件,并指定当JVM退出时删除该文件
File temFile=File.createTempFile("zpca", ".txt",file5);
temFile.deleteOnExit();
//通过挂起当前线程5秒,会看到临时文件被创建5秒后由于程序执行完毕,JVM退出,该文件又自动删除
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
文件过滤器:
import java.io.*;
public class FileNameFilterTest {
public static void main(String[] args) {
File file=new File("F:\\workplace\\collection\\src");
String fileList[]=file.list(new MyFilenameFilter());
for(String s:fileList){
System.out.println(s);
}
}
}
class MyFilenameFilter implements FilenameFilter{
public boolean accept(File dir, String name) {
return name.endsWith(".java")||new File(name).isDirectory();
}
}
2. io流分类
读取一个文件,输入流
写出一个文件,输出流
字符流:专门用于读写文本文件的(记事本可以正常打开),字符流操作的最小数据单元是16位的字符 查询本机上的编码表(GBK)
问题?word excel是不是文本文件
常见的文本文件: .txt .java html xml sql
字节流:操作的最小单元是8位的字节,最小的存储单位1个字节
1个字节8个二进制位
任意文件
问题?能操作文件夹吗 (不能)
3、Java中的io继承体系
继承体系,类与类之间继承关系,子类中的共性提取成的父类
父类中定义的功能,是这个体系中的最共性的内容
学习一个继承体系的时候,找父类去看,建立子类对象
字符流:
输出流,写入文件的抽象基类(体系中的最高层的父类) Writer
输入流,读取文件的抽象基类 Reader
字节流:
输出流,写入文件的抽象基类 OutputStream
输入流,读取文件的抽象基类 InputStream
4. 字符流的输入流(读文件)使用
字符流读取文件
查阅API文档找到了Reader
read()方法读取单个字符,返回int值?
返回的int值,是读取到的字符的ASCII码值
read()方法,每执行一次,自动的向后读取一个字符
读取到文件末尾的时候,得到-1的值
找到Reader类的子类 FileReader
FileReader(String fileName) 传递字符串文件名
read(字符数组)
返回int值
数组中存储的就是文件中的字符
int返回值,读到末尾就是-1
int返回数组中,读取到的字符的有效个数
好处:可以提高读取的效率
注意问题:
出现异常:XXXX拒绝访问
A.操作,确认是不是操作的文件
B.登录windows的账户是不是管理员,不是管理员登录的,不能操作c盘下的文件
其他盘符是可以的
例程:字符流读取文件
import java.io.*;
//Reader是字符流读的抽象基类(输入流,读取文件的抽象基类)
public class FileReaderDemo {
public static void main(String[] args) {
FileReader fr1 = null;
FileReader fr2 = null;
try {
fr1 = new FileReader("F:\\f.txt");
int len = 0;// read方法返回读取的单个字符的ASCII码(可以转换成字符输出)
while ((len = fr1.read()) != -1) {
System.out.print((char) len);
}
System.out.println("\n******将字符读入缓冲数组再输出*****");
// 定义字符数组
char[] buf = new char[512];// 将512*2字节字符读入缓冲数组
fr2 = new FileReader("F:\\Thinking.txt");
while ((len = fr2.read(buf)) != -1) {//返回值len表示读取的有效字符的长度
System.out.print(new String(buf,0,len));//将字符数组包装成String
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fr1 != null) {
fr1.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
throw new RuntimeException("文件关闭失败");
}
try {
if (fr2 != null) {
fr2.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
例程:自己实现的一行一行读取的方法
import java.io.*;
class MyReadLine {
// 自己实现一行一行读取
private Reader r;
public MyReadLine(Reader r) {
this.r = r;
}
public String myReaderLine() throws IOException {
// 定义一个字符缓冲区,读一个字符就存储到这里
StringBuilder sb = new StringBuilder();
int len = 0;
while ((len = r.read()) != -1) {
if (len == '\r') {
continue;
}
if (len == '\n') {
return sb.toString();
} else {
sb.append((char) len);// 读到的是有效字符,存到缓冲区
}
}
//看看缓冲区是否还有内容,有可能内容不是以回车符结尾的
if (sb.length() != 0) {
return sb.toString();
} else
return null;
}
public void MyClose() {
try {
if (r != null)
r.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class TestMyReadLine {
public static void main(String[] args) throws IOException {
MyReadLine my = null;
try {
my = new MyReadLine(new FileReader("F:\\Thinking.txt"));
String line = null;
while ((line = my.myReaderLine()) != null) {
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (my != null) {
my.MyClose();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
5.字符流的输出流(写文件)使用
字符流写文件
查阅API文档,找到了使用的子类,FileWriter
FileWriter(String fileName)
根据给定的文件名构造一个 FileWriter 对象。
void write(String str) 是FileWriter类的父类的方法
记住:Java中字符流写数据,不会直接写到目的文件中,写到内存中
想将数据写到目的文件中,刷新,刷到目的文本中
flush()刷新
记住:流对象中的功能,调用了Windows系统中的功能来完成
释放掉操作系统中的资源,简称:关闭流
close方法,关闭流之前,关闭的时候,先要刷新流中的数据
但是,如果写文件的数据量很大,写一句刷一句才好
看到了父类中的write方法
记住:IO操作,需要关闭资源,关闭资源的时候,开了几个流,就要关闭几个流
单独的进行关闭资源,单独写try catch,保证每一个流都会被关闭
例程:Writer类的使用
import java.io.*;
//Writer是字符流写的抽象基类(输出流,写入文件的抽象基类)
public class FileWriterDemo {
public static void main(String[] args) {
FileWriter fw1 = null;
FileWriter fw2 = null;// 关流的时候要分别处理异常
try {
fw1 = new FileWriter("F:\\filewriter.txt");
fw1.write("作品z");
char[] ch={'q','w','e','你'};
fw2 = new FileWriter("E:\\fileWriter.txt");
fw2.write(ch,1,ch.length-1);
//第二个参数表示开始写入字符处的偏移量(从第几个字符开始写),第三个参数表示写多少长度的数据
fw2.write(ch,0,ch.length-3);
fw2.write(ch,0,ch.length);
fw2.write(ch);//Writer能一次写入一个字符数组大小的内容;Reader能一次读出一个字符数组(相当于缓冲)大小的内容
fw1.flush();// 字符流必须手动刷新才能真的写入文件
fw2.flush();
} catch (IOException e) {
throw new RuntimeException("文件写入失败");
} finally {
try {
if (fw1 != null) // 健壮性判断,流有可能为空(流对象没有建立成功)
{
fw1.close();// 调用close方法时会自动刷新,但是不要都等到这一步才刷
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//开了几个流关闭几个,单独关闭(否则第一个关的时候抛了异常,第二个就没有机会关了)
try {
if (fw2 != null)
fw2.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
6. 复制文本文件
读取源文件 FileRreader
写入到目的文件 FileWriter
两种文件复制方式,分别计算时间
读一个字符,写一个字符
第一个数组,写一个数组
利用缓冲区复制文件
第一行,写一行的操作
例程:复制文本文件
import java.io.*;
public class CopyText {
// 读取一个数组,写一个数组
public static void copyText1() {
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("F:\\Thinking.txt");
fw = new FileWriter("F:\\Thinking2.txt");
char[] buff = new char[1024];
int len = 0;
while ((len = fr.read(buff)) != -1) {
fw.write(buff, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fr != null)
fr.close();
} catch (IOException e) {
throw new RuntimeException("文件读取关闭失败");
}
try {
if (fw != null)
fw.close();
} catch (IOException e) {
throw new RuntimeException("文件写入关闭失败");
}
}
}
// 读取一个字符,写一个字符
public static void copyText2() {
FileReader fr = null;
FileWriter fw = null;
try {
fw = new FileWriter("F:\\Thinking2.txt");
fr = new FileReader("F:\\Thinking.txt");
int len = 0;
while ((len = fr.read()) != -1) {
fw.write((char) len);
}
} catch (Exception e) {
throw new RuntimeException("文件复制失败");
} finally {
try {
if (fr != null)
fr.close();
} catch (IOException e) {
throw new RuntimeException("文件读取关闭失败");
}
try {
if (fw != null){
fw.close();
}
} catch (IOException e) {
throw new RuntimeException("文件写入关闭失败");
}
}
}
//读取一行写一行的方式,利用的缓冲区对象
public static void copyText3(){
FileReader fr=null;
FileWriter fw=null;
BufferedReader bfr=null;
BufferedWriter bfw=null;
try {
fr=new FileReader("F:\\Thinking.txt");
fw=new FileWriter("F:\\Thinking3.txt");
bfr=new BufferedReader(fr);
bfw=new BufferedWriter(fw);
String sLine=null;
while((sLine=bfr.readLine())!=null){
bfw.write(sLine);
//bfw.write("\r\n");
bfw.newLine();
}
} catch (IOException e) {
throw new RuntimeException("文件复制失败");
}finally{
try {
if(bfr!=null);
bfr.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(bfw!=null)
bfw.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
copyText1();
//copyText2();
//copyText3();
long cost = System.currentTimeMillis() - start;
System.out.println("耗时:" + cost+"ms");
}
}
7. 字符流的缓冲区对象(也叫处理流、包装流)
利用数组提升了文件的读写速度,运行效率提升了
我们想到了效率问题,Java工程师(Oracle),也想到了提升效率
写好了字符流的缓冲区对象,目的提供流的写,读的效率
写入流的缓冲区对象BufferedWriter
BufferedWriter(Writer out)
参数Writer类型的参数,传递的参数是Writer类的子类对象
缓冲区,提供流的写的效率,哪个流的效率 FileWriter
void newLine() 写一个换行,具有跨平台
不用newLine()方法,也可以实现换行 \r\n
\r\n Windows下的换行符号
\n Linux下的换行符号
字符缓冲流示例
BufferedWriter将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
import java.io.*;
public class BufferedWriterDemo {
public static void main(String[] args)throws IOException {
//字符输出流对象
FileWriter fw=new FileWriter("F:\\f.txt");
//建立缓冲区对象,提高输出流的效率
BufferedWriter bfw=new BufferedWriter(fw);
bfw.write("哈w哈");
bfw.write("呵w呵");
bfw.newLine();//写一行,BufferedReader可以读一行
bfw.write("嘻w嘻");
bfw.append("append");//追加写
bfw.flush();
bfw.close();
}
}
读取流的缓冲区对象BufferedReader
BufferedReader(Reader in)
参数Reader类型参数,传递的是Reader类的子类对象
缓冲区,提供流的读的效率,哪个流的效率FileRreader
读取一行的方法 readLine(),文件末尾返回null
不是末尾返回字符串 String
字符缓冲流示例
BufferedReader 从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
import java.io.*;
public class BufferedReaderDemo {
public static void main(String[] args)throws IOException {
//字符输出流对象
FileReader fr=new FileReader("F:\\Thinking.txt");
//建立缓冲区对象,提高输出流的效率
BufferedReader bfw=new BufferedReader(fr);
//读一行,返回字符串,末位返回null
String line=null;
while((line=bfw.readLine())!=null){
System.out.println(line);
}
bfw.close();
}
}8、字节流
例程1:
import java.io.*;
public class InputStreamDemo {
public static void main(String[] args)throws IOException {
FileInputStream fis=new FileInputStream("F:\\Thinking.txt");
byte[] bytes=new byte[1024];//1kb
int len=0;
len=fis.read(bytes);//len表示有效个数
System.out.println(new String(bytes));
System.out.println(len);
}
}
例程2:
import java.io.*;
public class OutPutStreamDemo {
public static void main(String[] args) throws IOException{
FileOutputStream fos=new FileOutputStream("F:\\f.txt");
//写字节数组
byte[] bytes={97,98,99};
fos.write(bytes,0,bytes.length);//第二个参数是下标,第三个是写入的个数
fos.write("\r\n".getBytes());//换行
//写字符数组,只能将字符串转成字节数组,用String类方法getBytes
fos.write(97);//写入了a字符
fos.write("zpc".getBytes());
fos.close();
}
}
例程3:
import java.io.*;
public class CopyFile {
public static void main(String[] args) throws IOException {
FileInputStream fis=new FileInputStream("E:\\KuGou\\屠洪刚-孔雀东南飞.mp3");
FileOutputStream fos=new FileOutputStream("I:\\孔雀东南飞.mp3");
int len=0;
byte[] bytes=new byte[1024];
while((len=fis.read(bytes))!=-1){
fos.write(bytes,0,len);
}
fos.close();
fis.close();
}
}
例程4:
import java.io.*;
/*
* 字节流读取键盘的输入
* 用户一行一行的写,但是read方法每次只能读取一个字节,不方便
* 能否一次读取一个行呢?可以使用转换流
* 字节流转换成字符流后可以使用字符缓冲流按行读取
*/
public class KeyInDemo1 {
public static void main(String[] args) throws IOException {
InputStream in = System.in;// 返回字节输入流对象
// int len=0;
// len=in.read();//read方法每次只能读取一个字节
// System.out.println(len);
// 使用转换流将字节输入流转成字符输入流
InputStreamReader isr = new InputStreamReader(in);
//为了达到最高效率,可要考虑在 BufferedReader内包装 InputStreamReader
BufferedReader bfr = new BufferedReader(isr);
String line = null;
while ((line = bfr.readLine()) != null) {
// 使用了while循环,则要自定义结束循环的语句
if ("exit".equals(line)) {
System.out.println("程序终止!");
break;
}
System.out.println(line);
}
}
}
例程5:
import java.io.*;
public class KeyInDemo2 {
public static void main(String[] args) throws IOException {
InputStream in = System.in;// 返回字节输入流对象
//将字节输入流转换成字符输入流
InputStreamReader isr = new InputStreamReader(in);
// 为了达到最高效率,可要考虑在 BufferedReader内包装 InputStreamReader
BufferedReader bfr = new BufferedReader(isr);
OutputStream out=new FileOutputStream("F:\\test.txt");
//将字节输出流转化为字符输出流
OutputStreamWriter osw=new OutputStreamWriter(out);
BufferedWriter bfw=new BufferedWriter(osw);
String line = null;
while ((line = bfr.readLine()) != null) {
// 使用了while循环,则要自定义结束循环的语句
if ("exit".equals(line)) {
System.out.println("程序终止!");
break;
}
//一行一行的写
bfw.write(line);
bfw.newLine();
bfw.flush();
}
}
}
9. 处理流PrintStream流的用法
import java.io.*;
/*
* PrintStream类的输出功能非常强大,通常需要输出文本内容时,
* 应该将输出流包装成PrintStream后再输出
* 对应的由于BufferedReader具有readLine方法,可以方便地一次读取一行内容,
* 所以经常把读取文本的输入流包装成BufferedReader,用以方便读取输入流的文本内容
*/
public class PrintStreamTest {
public static void main(String[] args) {
PrintStream pst=null;//称作处理流或包装流
try {
FileOutputStream fos=new FileOutputStream("F:\\Thinking.txt");//直接和物理文件打交道的流也称节点流
//以PrintStream来包装FileOutputStream输出流
pst=new PrintStream(fos);
//使用printStream来实现输出
pst.println("普通字\r\n符串");
pst.println(new PrintStreamTest());
} catch (FileNotFoundException e) {
e.printStackTrace(pst);
}
finally{
if(pst!=null){
pst.close();}
}
}
}
10、重定向标准输入输出流
Java的标准输入输出分别通过System.in和System.out来代表,默认情况下它们分别代表键盘和显示器
System类提供了重定向标准输入输出的方法
例程:RedirectIn.java
import java.io.*;
import java.util.Scanner;
public class RedirectIn {
public static void main(String[] args) {
FileInputStream fis = null;
try {
// 将标准输入重定向到fis输入流
fis = new FileInputStream("F:\\Thinking.txt");
System.setIn(fis);
//使用流接受标准输入
//BufferedReader bf = new BufferedReader(new InputStreamReader(
//System.in));
//String len = "";
//while ((len = bf.readLine()) != null) {
//System.out.println(len);
//}
//使用Scanner
Scanner sn=new Scanner(System.in);
System.out.println("=====使用Scanner=====");
while(sn.hasNext()){
System.out.println(sn.next());
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (fis != null)
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
例程:RedirectOut.java
import java.io.*;
public class RedirectOut {
public static void main(String[] args) {
// TODO Auto-generated method stub
PrintStream p=null;
try {
p=new PrintStream(new FileOutputStream("F:\\out.txt"));
p.println("p.println()");
System.out.println("重定向输出流到F:\\out.txt");
System.setOut(p);
System.out.println("此行不显示在屏幕上(不在控制台输出),直接写入了文件");
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
if(p!=null){
p.close();
}
}
}
}
Scanner和IO流接(BufferedReader)受键盘输入的比较(BufferedReader输入都被当成String对象,BufferedReader不能读取基本类型输入项)
例程:InputTest.java
public class InputTest {
public static void main(String[] args) throws IOException {
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
//int len=br.read();
//System.out.println((char)len);
//String len2=br.readLine();
//System.out.println(len2);
//Scanner sn=new Scanner(System.in);
//System.out.println(sn.nextLine());
//System.out.println(sn.nextInt());
String input = "1 fish 3 fish red fish blue fish";
Scanner s = new Scanner(input).useDelimiter("\\s*fish\\s*");//自定义分隔符为fish
System.out.println(s.nextInt());
System.out.println(s.nextInt());
System.out.println(s.next());
System.out.println(s.next());
input="324 42314 fish zpc";
s=new Scanner(input);
System.out.println(s.nextInt());
System.out.println(s.nextInt());
System.out.println(s.next());
// System.out.println(s.nextByte(0));
s.close();
}
}
//Scanner也可以指定读取某个文件
public class TestScanner {
public static void main(String[] args){
Scanner sn=null;
//sn.useDelimiter("\n");//如果增加这一行将只把回车符作为分隔符
try {
sn=new Scanner(new File("F:\\Thinking.txt"));
while(sn.hasNext()){
System.out.println(sn.nextLine());
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
11、RandomAccessFile对象
package Serialize;
/*
* 功能:读一个文件,复制自身追加到此文件,操作完成后文件应该变成双倍大小
* RandomAccessFile对象不能向文件的指定位置插入内容,这会覆盖原来的内容
* 如果要在指定位置插入内容,程序需要先把插入点后面的内容都入缓冲区,
* 等把需要插入的数据写入文件后,在将缓冲区的内容追加到文件后面,详见InsertContent.java
*/
import java.io.*;
public class RandomAccessFileTest {
public static void main(String[] args) {
RandomAccessFile raf = null;
// 以只读方式打开一个RandomAccessFile对象
try {
raf = new RandomAccessFile("F:\\掀起你的盖头来.mp3", "rw");
System.out.println("RandomAccessFile对象的文件指针初始位置:"
+ raf.getFilePointer());
System.out.println("raf.length():"+raf.length());
//读文件
byte[] b = new byte[1024];
int hasRead = 0;
int readTotal=0;
long FileTotalLen= raf.length();
while (((hasRead = raf.read(b)) != -1)&&readTotal<=FileTotalLen) {
readTotal+=hasRead;
//System.out.println(new String(b, 0, hasRead));
raf.seek(raf.length());
raf.write(b);
raf.seek(readTotal);
}
System.out.println("raf.length():"+raf.length());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (raf != null) {
raf.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//InsertContent.java
import java.io.*;
/*
* 功能:向一个文件的指定位置插入内容,而不覆盖原有的内容
*/
public class InsertContent {
public static void main(String[] args) {
insert("F:\\thinking.txt",200,"===========我是鸟鹏!==========");
}
public static void insert(String fileName, long pos, String content) {
InputStream is = null;
OutputStream os = null;
RandomAccessFile raf = null;
// 创建一个临时文件来保存插入点后面的数据
try {
File temp = File.createTempFile("tem", null);
temp.deleteOnExit();
raf = new RandomAccessFile(fileName, "rw");
os=new FileOutputStream(temp);
byte[] buf=new byte[1024];
int hasRead=0;
raf.seek(pos);
while((hasRead=raf.read(buf))!=-1){
os.write(buf,0,hasRead);
}
raf.seek(pos);
raf.write(content.getBytes());
//下面的代码实现插入数据的功能
is=new FileInputStream(temp);
while((hasRead=is.read(buf))!=-1){
raf.write(buf,0,hasRead);
}
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(raf!=null)
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
12、对象序列化
在Java中如果需要将某个对象保存到磁盘或者通过网络传输,那么这个类应该实现Serializable标记接口或者Externalizable接口之一
序列化的步骤:
a、创建一个ObjectOutputstream处理流(必须建立在其它结点的基础上)对象
b、调用ObjectOutputstream对象的writeObject方法输出可序列化对象
从二进制流中恢复Java对象的反序列化步骤(按照实际写入的顺序读取):
a、创建一个ObjectInputStream处理流(必须建立在其它结点的基础上)对象
b、调用ObjectInputstream对象的readObject方法输出可序列化对象(该方法返回的是Object类型的Java对象)
(反序列化恢复Java对象时必须要提供java对象所属类的class文件)
注:如果一个可序列化对象有多个父类,则该父类要么是可序列化的,要么有无参的构造器,因为反序列化机制要恢复其关联的父类实例
而恢复这些父类实例有两种方式:使用序列化机制、使用父类无参的构造器
采用Java序列化机制时,只有当第一次调用writeObject输出某个对象时才会将该对象转换成字节序列写到ObjectOutputStream
在后面程序中如果该对象的属性发生了改变,即再次调用writeObject方法输出该对象时,改变后的属性不会被输出
如果父类没有实现Serializable接口,则其必须有默认的构造函数(即没有参数的构造函数)
但是若把父类标记为可以串行化,则在反串行化的时候,其默认构造函数不会被调用。
这是因为Java 对串行化的对象进行反串行化的时候,直接从流里获取其对象数据来生成一个对象实例,而不是通过其构造函数来完成。
例程:
import java.io.Serializable;
public class Person implements Serializable {
private String name ;
private int age;
public Person(){
System.out.println("Person的无参构造器");
}
public Person(String name ,int age){
System.out.println("Person的有参构造器");
this.age=age;
this.name=name;
}
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;
}
}
import java.io.Serializable;
public class Teacher implements Serializable {
private String name;
private Person student;//Teacher类持有的Person类应该是可序列化的
public Teacher(String name,Person student){
System.out.println("Teacher的有参构造器");
this.student=student;
this.name=name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getStudent() {
return student;
}
public void setStudent(Person student) {
this.student = student;
}
}
import java.io.*;
public class SerializeMutable {
public static void main(String[] args) {
ObjectOutputStream oos=null;
ObjectInputStream ois=null;
try {
oos=new ObjectOutputStream(new FileOutputStream("F:\\objecttest.txt"));
Person p=new Person("zpc", 24);
oos.writeObject(p);
p.setName("niao鹏");
//第二次写同样的对象时只是输出序列化编号
oos.writeObject(p);
ois=new ObjectInputStream(new FileInputStream("F:\\objecttest.txt"));
Person p1=(Person) ois.readObject();
//实际得到的是和p1同样的对象
Person p2=(Person) ois.readObject();
System.out.println("p1=p2? "+(p1==p2));
System.out.println("p2.getName():"+p2.getName());
System.out.println("===================================");
Person perstu=new Person("云翔", 15);
Teacher t1=new Teacher("马老师",perstu);
Teacher t2=new Teacher("周老师",perstu);
oos.writeObject(perstu);
oos.writeObject(t1);
oos.writeObject(t2);
Person pperstu1=(Person)ois.readObject();
Teacher tt1=(Teacher)ois.readObject();
Teacher tt2=(Teacher)ois.readObject();
System.out.println("tt1.getStudent()==p? "+(tt1.getStudent()==pperstu1));
System.out.println("tt2.getStudent()==p? "+(tt2.getStudent()==pperstu1));
System.out.println(tt2.getStudent().getName());
oos.writeObject(perstu);
Person pperstu2=(Person)ois.readObject();
System.out.println("pperstu2==pperstu1? "+(pperstu2==pperstu1));
System.out.println(pperstu1.getName());
System.out.println(pperstu2.getName());
//下面的语句报错,再次证明了写入、读出对象要一致
//Person pperstu3=(Person)ois.readObject();
//System.out.println(pperstu3.getName());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}catch(ClassNotFoundException e){
e.printStackTrace();
}
}
}
13、自定义序列化
在属性前面加上transient关键字,可以指定Java序列化时无需理会该属性值,由于transient修饰的属性将被完全隔离在序列化机制外,
这会导致在反序列化恢复Java对象时无法取得该属性值。
实现自定义序列化要重写类的如下方法:
private void writeObject(java.io.ObjectOutputStream out) throws IOException;
private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException;
还有种更彻底的序列化机制可以在序列化某对象时替换该对象,此种情况下应为序列化类提供如下特殊方法:
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
Java的序列化机制保证在序列化某个对象之前,先调用对象的writeReplace()方法,如果该方法返回另一个Java对象,
则系统转为序列化另一个对象。
小结:系统在序列化某个对象之前,先会调用该对象的如下两个方法:
writeReplace和writeObject,系统先调用被序列化对象的writeReplace方法,如果返回另一个Java对象,则再次调用该java对象的writeReplace方法....
直到该方法不在返回一个对象为止,程序最后调用该对象的writeObject方法来保存该对象的状态
与writeReplace方法相对的有一个方法可以替代原来反序列化的对象
即:private Object readResolve() throws ObjectStreamException;
这个方法会紧接着readObject之后被立即调用,该方法的返回值会代替原来反序列化的对象,而原来readObject反序列化的对象会被立即丢弃
(此方法在序列化单例类、早期枚举类时很有用)
14、另一种序列化机制:Java类实现Externalizable接口
两种序列化机制的区别:
实现Serializable接口 实现Externalizable接口
系统自动存储必要信息
程序员决定需要存储哪些信息
Java内建支持,易于实现只需实现该接口即可 仅仅提供两个空方法实现该接口必须为两个空方法提供实现
无须任何代码支持 类中必须存在一个无参构造方法
性能略差 性能略高