java序列化追加对象流,Thinking in java 繁琐知识点之 I/O流 、对象序列化

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内建支持,易于实现只需实现该接口即可             仅仅提供两个空方法实现该接口必须为两个空方法提供实现

无须任何代码支持   类中必须存在一个无参构造方法

性能略差  性能略高

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值