I/O系统
1、File类
File即能代表一个特定文件的名称,又能代表一个目录下的一组文件的名称。
(1)目录列表器
调用不带参数的list()方法,可以获得此File对象包含的全部列表。在list()方法中加上FilenameFilter参数,可以获得此File对象包含的受限列表。
FilenameFilter接口主要定义了accept()方法,提供给list()来回调accept()。list()方法会为此目录对象下的每个文件名调用accept(),来判断该文件是否包含在内。accept()会使用一个正则表达式的matcher对象,来查看此正则表达式regex是否匹配这个文件的名字,并返回布尔值表示。
1 public class Test { 2 public static void main(String[] args) { 3 File path = new File("."); 4 String[] list; 5 if(args.length == 0) 6 list = path.list(); 7 else 8 list = path.list(new FilenameFilter() { 9 private Pattern pattern = Pattern.compile(args[0]); 10 public boolean accept(File dir, String name) { 11 return pattern.matcher(name).matches(); 12 } 13 }); 14 Arrays.sort(list, String.CASE_INSENSITIVE_ORDER); 15 for(String dirItem : list) 16 System.out.println(dirItem); 17 } 18 }
(2)目录实用工具
Directory工具类可以通过使用local()方法来产生由本地目录中的文件构成的File对象数组,或者通过使用walk()方法产生给定目录下的由整个目录树中所有文件构成的List<File>。
local()方法使用被称为listFile()的File.list()的变体来产生File数组。walk()方法将开始目录的名字转换为File对象,然后调用recurseDirs(),该方法将递归地遍历目录,并在每次递归中都收集更多的信息。
src/net/mindView/utils/Directory.java · sumAll/Java编程思想 - 码云 - 开源中国 (gitee.com)
2、输入和输出
“流”代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象。
通过继承,任何自InputStream或Reader派生而来的类都含有名为read()的基本方法,用于读取单个字节或者字节数组。任何自OutputStream或Writer派生而来的类都含有名为write()的基本方法,用于写单个字节或者字节数组。
(1)InputStream类型
InputStream的作用是用来表示那些从不同数据源产生输入的类。
类 | 功能 | 构造器参数 |
---|---|---|
ByteArrayInputStream | 允许将内存的缓冲区当作InputStream使用 | 缓冲区,字节将从中取出 作为一种数据源;将其与FilterInputStream 对象相连以提供有用接口 |
StringBufferInputStream | 将String转换成InputStream | 字符串。底层实现实际使用StringBuffer 作为一种数据源;将其与FilterInputStream 对象相连以提供有用接口 |
FileInputStream | 用于从文件中读取信息 | 字符串,表示文件名、文件或FileDescriptor 对象 作为一种数据源;将其与FilterInputStream 对象相连以提供有用接口 |
PipedInputStream | 产生用于写入相关PipInputStream的数据。 实现"管道化" 概念。 | PipedOutputStream 作为多线程中的数据源;将其FilterInputStream 对象相连以提供有用接口 |
SequenceInputStream | 将两个或多个InputStream对象转换成 单一InputStream | 两个InputSream对象或一个容纳inputStream 对象的容器Enumeration 作为一种数据源;将其与FilterInputStream 对象相连以提供有用接口 |
FilterInputStream | 抽象类,作为"装饰器"的接口。其中, "装饰器"为其他的InputStream类提供有用 功能 |
(2)OutputStream类型
类 | 功能 | 构造器参数 |
---|---|---|
ByteArrayOutputStream | 在内存中创建缓冲区。所有送往“流”的数据都要放置在此缓冲区。 | 缓冲区初始化尺寸(可选的) 用于指定数据的目的地:将其与FilterOutputStream对象相连以提供有用接口 |
FileOutputStream | 用于将信息写至文件。 | 字符串,表示文件名、文件或FileDescriptor对象 指定数据的目的地:将其与FilterOutputStream对象相连以提供有用接口 |
PipedOutputStream | 任何写入其中的信息都会自动作为相关PipedOutputStream的输出。实现“管道化”概念。 | PipedOutputStream 指定用于多线程的数据的目的地:将其与FilterOutputStream对象相连以提供有用接口 |
FilterOutputStream | 抽象类,作为"装饰器"的接口。其中, "装饰器"为其他的OutputStream类提供有用 功能 |
3、添加属性和有用的接口
装饰器模式(Decorator Pattern),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式比生成子类更灵活。 ----《大话设计模式》
FilterInputStream和FilterOutputStream是用来提供装饰器类接口以控制特定输入流(InputStream)和输出流(OutputStream)的两个类。
(1)FilterInputStream类型
类 | 功能 | 构造器参数 |
---|---|---|
DataInputStream | 与DataOutputStream搭配使用,因此我们可以按照可移植方式从流读取基本数据类型(int、char、long等) | InputStream包含用于读取基本类型数据的全部接口 |
BufferedInputStream | 使用它可以防止每次读取时都得进行实际写操作。代表“使用缓冲器” | InputStream,可以指定缓冲区大小(可选的)本质上不提供接口,只不过是向进程中添加缓冲区所必需的。与接口对象搭配 |
LineNumberInputStream | 跟踪输入流中的行号;可调用getLineNUmber()和setLineNumber(int) | InputStream仅增加了行号,因此可能要与接口对象搭配使用 |
PushbackInputStream | 具有“能弹出一个字节的缓冲区”。因此可以将读到的最后一个字符回退 | InputStream通常作为编译器的扫描器,之所以包含在内是因为Java编译器的需要,我们可能永远不会用到 |
(2)FilterOutputStream类型
类 | 功能 | 构造器参数 |
---|---|---|
DataOutputStream | 与DataInputStream搭配使用,因此可以按照可移植方式向流中写入基本类型数据(char、int、long等) | OutputStream包含用于写入基本类型数据的全部接口 |
PrintStream | 用于产生格式化输出。其中DataOutputStream处理数据的存储,PrintStream处理显示 | OutputStream,可以用boolean值指示是否在每次换行时清空缓冲区(可选的)应该是对OutputStream对象的“final”封装。可能会经常使用到它 |
BufferedOutputStream | 使用它以避免每次发送数据时都要进行实际的写操作。代表“使用缓冲区”。可以调用flush()清空缓冲区 | OutputStream,可以指定缓冲区大小(可选的)本质上并不提供接口,只不过是向进程中添加缓冲区所必需的。与接口对象搭配 |
4、Reader和Writer
Reader和Writer提供兼容Unicode与面向字符的I/O功能。设计Reader和Writer继承层次结构主要是为了国际化,为了在所有的I/O操作中都支持Unicode。
5、RandomAccessFile
RandomAccessFile不是InputStream或者OutputStream继承层次结构中的一部分。它实现了DataInput和DataOutput接口。它支持搜寻方法,并且只适用于由大小已知的记录组成的文件。第二个参数用来指示我们只是“随机读”(r)还是“既读又写”(rw)。它并不支持写文件。
方法 | 作用 |
---|---|
getFilePointer() | 用于查找当前所处的文件位置 |
seek() | 用于在文件内移至新的位置 |
length() | 用于判断文件的最大尺寸 |
6、I/O流的典型使用方式
(1)缓冲输入文件
BufferedReader可以对文件进行缓冲,提高写入速度。
1 public class BufferedInputFile { 2 public static String 3 read(String fileName) throws IOException{ 4 BufferedReader in = new BufferedReader(new FileReader(fileName)); 5 String s; 6 StringBuilder sb = new StringBuilder(); 7 while((s = in.readLine())!=null){ 8 sb.append(s + "\n"); 9 } 10 in.close(); 11 return sb.toString(); 12 } 13 }
(2)从内存输入
1 public class MemoryInput { 2 public static void main(String[] args) throws IOException { 3 StringReader in = new StringReader(BufferedInputFile.read("MemoryInput.java")); 4 int c; 5 while((c = in.read()) != -1) 6 System.out.println((char) c); 7 } 8 }
(3)格式化的内存输入
通过DataInputStream读取格式化数据,它是一个面向字节的I/O类(不是面向字符的)。因此我们必须使用InputStream类而不是Reader类。
1 public class Test { 2 public static void main(String[] args) throws IOException { 3 //创建方式1 4 DataInputStream in = new DataInputStream( 5 new BufferedInputStream( 6 new FileInputStream("TestEOF.java")) 7 ); 8 //创建方式2 9 DataInputStream in2 = new DataInputStream( 10 new ByteArrayInputStream( 11 BufferedInputFile.read("TestEOF").getBytes() 12 ) 13 ); 14 //输出方式1 15 while(in.available() != 0){ 16 System.out.println((char)in.readByte()); 17 } 18 //输出方式2 19 try{ 20 while (true){ 21 System.out.println((char)in2.readByte()); 22 } 23 }catch (EOFException e){ 24 System.out.println("End of stream"); 25 } 26 } 27 }
(4)基本的文件输出
FileWriter对象可以向文件写入数据。
1 public class FileOutput { 2 static String file = "FileOutput.out"; 3 public static void main(String[] args) throws IOException { 4 BufferedReader in = new BufferedReader( 5 new StringReader( 6 BufferedInputFile.read("FileOutput.java")) 7 ); 8 PrintWriter out = new PrintWriter(file); 9 int lineCount = 1; 10 String s; 11 while((s = in.readLine()) != null){ 12 out.println(lineCount++ + ":" + s); 13 } 14 out.close(); 15 System.out.println(BufferedInputFile.read(file)); 16 } 17 }
(5)存储和恢复数据
当我们使用DataOutputStream时,写字符串并且让DataInputStream能够恢复它的唯一可靠的做法就是使用UTF-8编码,在这个示例中是用writeUTF()和readUTF()来实现的。UTF-8是一种多字节格式,其编码长度根据实际使用的字符集会有所变化。字符串长度存储在UTF-8字符串的前两个字节中。
1 public class Test { 2 public static void main(String[] args) throws IOException { 3 DataOutputStream out = new DataOutputStream( 4 new BufferedOutputStream( 5 new FileOutputStream("Data.txt")) 6 ); 7 out.writeDouble(3.14159); 8 out.writeUTF("That was pi"); 9 out.writeDouble(1.52363); 10 out.close(); 11 DataInputStream in = new DataInputStream( 12 new BufferedInputStream( 13 new FileInputStream("Data.txt")) 14 ); 15 System.out.println(in.readDouble()); 16 System.out.println(in.readUTF()); 17 System.out.println(in.readDouble()); 18 } 19 }
(6)读写随机访问文件
在使用RandomAccessFile时,你必须知道文件排版,这样才能正确地操作它。seek()方法通过字节查找位置,例如有6个double数据,需要定位到第5个double数据的位置,可以使用seek(5*8),8是double的字节长。
1 public class UsingRandomAccessFile { 2 static String file = "rtest.dat"; 3 static void display() throws IOException{ 4 RandomAccessFile rf = new RandomAccessFile(file,"r"); 5 for(int i=0;i<7;i++){ 6 System.out.println("Value " + i + ":" + rf.readDouble()); 7 } 8 System.out.println(rf.readUTF()); 9 rf.close(); 10 } 11 public static void main(String[] args) throws IOException { 12 RandomAccessFile rf = new RandomAccessFile(file,"rw"); 13 for(int i=0;i<7;i++){ 14 rf.writeDouble(i*1.326); 15 } 16 rf.writeUTF("The end of the file"); 17 rf.close(); 18 display(); 19 rf = new RandomAccessFile(file,"rw"); 20 rf.seek(5*8); 21 rf.writeDouble(62.001); 22 rf.close(); 23 display(); 24 } 25 }
7、标准I/O
标准I/O的意义在于:我们可以很容易地把程序串联起来,一个程序的标准输出可以成为另一程序的标准输入。
(1)从标准输入中读取
按照标准I/O模型,Java提供了System.in、System.out和System.err。
1 public class Test { 2 public static void main(String[] args) throws IOException { 3 BufferedReader stdin = new BufferedReader( 4 new InputStreamReader(System.in)); 5 String s; 6 while((s = stdin.readLine()) != null && s.length() != 0) 7 System.out.println(s.toUpperCase()); 8 } 9 }
(2)将System.out转换成PrintWriter
System.out是一个PrintStream,而PrintStream是一个OutputStream。PrintWriter有一个可以接受OutputStream作为参数的构造器。因此,只要需要,就可以使用那个构造器把System.out转换成PrintWriter。
1 public class Test { 2 public static void main(String[] args) throws IOException { 3 PrintWriter out = new PrintWriter(System.out,true); 4 out.println("Hello,world"); 5 } 6 }
(3)标准I/O重定向
Java的System类提供了一些简单的静态方法调用,以允许我们对标准输入、输出和错误I/O流进行重定向:
①setIn(InputStream)
②setOut(PrintStream)
③setErr(PrintStream)
1 public class Redirecting { 2 public static void main(String[] args) throws IOException { 3 PrintStream console = System.out; 4 BufferedInputStream in = new BufferedInputStream( 5 new FileInputStream("Redirecting.java") 6 ); 7 PrintStream out= new PrintStream( 8 new BufferedOutputStream( 9 new FileOutputStream("test.out")) 10 ); 11 System.setIn(in); 12 System.setOut(out); 13 System.setErr(out); 14 BufferedReader br = new BufferedReader( 15 new InputStreamReader(System.in) 16 ); 17 String s; 18 while((s = br.readLine()) != null){ 19 System.out.println(s); 20 } 21 out.close(); 22 System.setOut(console); 23 } 24 }
8、新I/O
JDK 1.4的java.nio.*包中引入了新的javaI/O类库,其目的在于提高速度。速度的提高来自于所使用的结构更接近于操作系统执行I/O的方式:通道和缓冲器。唯一直接与通道交互的缓冲器是ByteBuffer,也就是说可以存储未加工字节的缓冲器。
1 public class ChannelCopy { 2 private static final int BSIZE = 1024; 3 public static void main(String[] args) throws IOException { 4 if(args.length !=2) 5 System.exit(1); 6 FileChannel 7 in = new FileInputStream(args[0]).getChannel(), 8 out = new FileOutputStream(args[1]).getChannel(); 9 ByteBuffer buffer = ByteBuffer.allocate(BSIZE);//表示只读访问 10 //方式1 11 while (in.read(buffer) != -1){ 12 buffer.flip();//为写做准备 13 out.write(buffer); 14 buffer.clear();//为读做准备 15 } 16 //方式2 17 in.transferTo(0,in.size(),out); 18 //or 19 //out.transferFrom(in,0,in.size()); 20 } 21 }
(1)转换数据
使用原始的方式是每次只读取一个字节的数据,然后将每个byte类型强制转换成char类型。由于ByteBuffer可以看作是具有asCharBuffer()方法的CharBuffer,因此可以直接转换成CharBuffer再输入输出。
1 public class BufferToText { 2 private static final int BSIZE = 1024; 3 public static void main(String[] args) throws IOException { 4 FileChannel fc = 5 new FileOutputStream("data2.txt").getChannel(); 6 fc.write(ByteBuffer.wrap("Some text".getBytes())); 7 fc.close(); 8 fc = new FileInputStream("data2.txt").getChannel(); 9 ByteBuffer buff = ByteBuffer.allocate(BSIZE); 10 fc.read(buff);buff.flip(); 11 buff.rewind(); 12 //方式1 13 String encoding = System.getProperty("file.encoding");//产生代表默认字符集名称的字符串 14 System.out.println(Charset.forName(encoding).decode(buff));//用于产生Charset对象,对字符串进行解码 15 16 //方式2 17 fc = new FileOutputStream("data2.txt").getChannel(); 18 fc.write(ByteBuffer.wrap("Some text".getBytes("UTF-16BE"))); 19 fc.close(); 20 fc = new FileInputStream("data2.txt").getChannel(); 21 buff.clear();fc.read(buff);buff.flip(); 22 System.out.println(buff.asCharBuffer()); 23 24 //方式3 25 fc = new FileOutputStream("data2.txt").getChannel(); 26 buff = ByteBuffer.allocate(24);//分配了24个字节,9个字符只需18个字节,分配的比需要的多 27 buff.asCharBuffer().put("Some text"); 28 fc.write(buff); 29 fc.close(); 30 fc = new FileInputStream("data2.txt").getChannel(); 31 buff.clear();fc.read(buff);buff.flip(); 32 System.out.println(buff.asCharBuffer()); 33 } 34 }
(2)获取基本类型
1 public class Test { 2 private static final int BSIZE = 1024; 3 public static void main(String[] args) throws IOException { 4 ByteBuffer bb = ByteBuffer.allocate(BSIZE); 5 6 bb.rewind(); 7 bb.asCharBuffer().put("Howdy"); 8 char c = bb.getChar(); 9 10 bb.rewind(); 11 bb.asShortBuffer().put((short)693); 12 short b = bb.getShort(); 13 14 bb.rewind(); 15 bb.asIntBuffer().put(6); 16 int j = bb.getInt(); 17 } 18 }
(3)视图缓冲器
视图缓冲器可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer。ByteBuffer依然是实际存储数据的地方,但对视图的任何修改都会映射成为对ByteBuffer中数据的修改。
可以在一个ByteBuffer上建立不同的视图缓冲器。
1 ByteBuffer bb = ByteBuffer.wrap(new byte[]{0,0,0,0,0,0,0,'a'}); 2 CharBuffer cb = ((ByteBuffer)bb.rewind()).asCharBuffer(); 3 ShortBuffer sb = ((ByteBuffer)bb.rewind()).asShortBuffer(); 4 IntBuffer ib = ((ByteBuffer)bb.rewind()).asIntBuffer(); 5 LongBuffer lb = ((ByteBuffer)bb.rewind()).asLongBuffer(); 6 FloatBuffer fb = ((ByteBuffer)bb.rewind()).asFloatBuffer(); 7 DoubleBuffer db = ((ByteBuffer)bb.rewind()).asDoubleBuffer();
字节存放次序有两种方式:“big endian”(高位优先,将最重要的字节存放在地址最低的存储器单元)和“little endian”(低位优先,将最重要的字节放在地址最高的存储器单元)。ByteBuffer默认是以高位优先的形式存储数据的,但可以通过带有参数ByteOrder.BIG_ENDIAN或ByteOrder.LITTLE_ENDIAN的order()方法改变ByteBuffer的字节排序方式。
例如:0000,0000,0110,0001
高位优先形式得到97(0000,0000,0110,0001)
低位优先形式得到24832(0110,0001,0000,0000)
(4)用缓冲器操纵数据
把一个字节数组写到文件中去,那么就应该使用ByteBuffer.wrap()方法把字节数组包装起来,然后用getChannel()方法在FileOutputStream上打开一个通道,接着将来自于ByteBuffer的数据写到FileChannel中。
ByteBuffer是将数据移进移出通道的唯一方式。不能把基本类型的缓冲器转换成ByteBuffer。
(5)缓冲器的细节
方法 | 作用 |
---|---|
capacity() | 返回缓冲区容量 |
clear() | 清空缓冲区,将position设置为0 |
flip() | 将limit设置为position,position设置为0 |
limit() | 返回limit值 |
limit(int lim) | 设置limit值 |
mark() | 将mark设置为position |
position() | 返回position值 |
position(int pos) | 设置position值 |
remaining() | 返回(limit-position) |
hasRemaining() | 若有介于position和limit之间的元素,则返回true |
reset() | 把position值设置为mark的值 |
(6)内存映射文件
内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件。
1 public class LargeMappedFiles { 2 private static final int BSIZE = 1024; 3 public static void main(String[] args) throws IOException { 4 int length = 0x8FFFFFF;//128M 5 MappedByteBuffer out = 6 new RandomAccessFile("test.dat","rw").getChannel(). 7 map(FileChannel.MapMode.READ_WRITE,0,length); 8 for(int i=0;i<length;i++) 9 out.put((byte) 'x'); 10 System.out.println("Finished writing"); 11 for(int i=length/2;i<length/2+6;i++) 12 System.out.print((char)out.get(i)); 13 } 14 }
(7)文件加锁
文件加锁机制允许我们同步访问某个作为共享资源的文件。文件锁对其他操作系统进程是可见的,因为java的文件加锁直接映射到了本地操作系统的加锁工具。
1 public class Test { 2 private static final int BSIZE = 1024; 3 public static void main(String[] args) throws IOException, InterruptedException { 4 FileOutputStream fos = new FileOutputStream("file.txt"); 5 FileLock fl = fos.getChannel().tryLock();//只能在通道上获得锁 6 if(fl != null){ 7 System.out.println("Locked File"); 8 TimeUnit.MILLISECONDS.sleep(100); 9 fl.release(); 10 System.out.println("Released Lock"); 11 } 12 fos.close(); 13 } 14 }
可以使用如下方法对文件的一部分上锁:
tryLock(long position, long size, boolean shared)
或者
lock(long position, long size, boolean shared)
其中,加锁的区域由size-position决定,第三个参数指定是否是共享锁。
9、压缩
Java I/O类库中的类支持读写压缩格式的数据流。压缩类库是按字节方式处理的。
压缩类 | 功能 |
---|---|
CheckedInputStream | GetCheckSum()为任何InputStream产生校验和(不仅是解压缩) |
CheckedOutputStream | GetCheckSum()为任何OutputStream产生校验和(不仅是压缩) |
DeflaterOutputStream | 压缩类的基类 |
ZipOutputStream | 一个DeflaterOutputStream,用于将数据压缩成Zip文件格式 |
GZIPOutputStream | 一个DeflaterOutputStream,用于将数据压缩成GZIP文件格式 |
InflaterInputStream | 解压缩类的基类 |
ZipInputStream | 一个InflaterInputStream,用于解压缩Zip文件格式的数据 |
GZIPInputStream | 一个InflaterInputStream,用于解压缩GZIP文件格式的数据 |
(1)用GZIP进行简单压缩
1 class GZIPcompress{ 2 public static void main(String[] args) throws IOException { 3 BufferedReader in= new BufferedReader(new FileReader(args[0])); 4 BufferedOutputStream out = new BufferedOutputStream( 5 new GZIPOutputStream( 6 new FileOutputStream("test.gz")) 7 ); 8 System.out.println("Writing file"); 9 int c; 10 while((c=in.read()) != -1){ 11 out.write(c); 12 } 13 System.out.println("Reading file"); 14 BufferedReader in2 = new BufferedReader( 15 new InputStreamReader( 16 new GZIPInputStream( 17 new FileInputStream("test.gz"))) 18 ); 19 String s; 20 while((s=in2.readLine()) != null){ 21 System.out.println(s); 22 } 23 } 24 }
(2)用Zip进行多文件保存
通过Checksum类来计算和校验文件的校验和的方法,有两种类型:Adler32(它快一些)和CRC32(慢一些,但更准确)。
1 class Zipcompress{ 2 public static void main(String[] args) throws IOException { 3 //保存 4 FileOutputStream f = new FileOutputStream("test.zip"); 5 CheckedOutputStream csum = new CheckedOutputStream(f,new Adler32()); 6 ZipOutputStream zos = new ZipOutputStream(csum); 7 BufferedOutputStream out = 8 new BufferedOutputStream(zos); 9 for (String arg : args){ 10 BufferedReader in = 11 new BufferedReader(new FileReader(arg)); 12 zos.putNextEntry(new ZipEntry(arg));//开始编写新的 ZIP 文件条目,并将流定位到条目数据的开头 13 int c; 14 while((c = in.read()) != -1){ 15 out.write(c); 16 } 17 in.close(); 18 out.flush();//将缓冲区中的数据强制写出 19 } 20 out.close(); 21 22 //读取 23 FileInputStream fi = new FileInputStream("test.zip"); 24 CheckedInputStream csumi = new CheckedInputStream(fi,new Adler32()); 25 ZipInputStream in2 = new ZipInputStream(csumi); 26 BufferedInputStream bis = new BufferedInputStream(in2); 27 ZipEntry ze; 28 while((ze = in2.getNextEntry()) != null){ 29 int x; 30 while ((x=bis.read()) != -1) 31 System.out.write(x); 32 } 33 } 34 }
10、对象序列化
Java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。利用它可以实现轻量级持久性(持久性意味着一个对象的生存周期并不取决于程序是否正在执行;它可以生存于程序的调用之间)。
1 Thing1 t1 = new Thing1(1);//实现了Serializable接口 2 ObjectOutputStream out = new ObjectOutputStream( 3 new FileOutputStream("thing1.out")); 4 out.writeObject(t1); 5 out.close(); 6 ObjectInputStream in = new ObjectInputStream( 7 new FileInputStream("thing1.out")); 8 Thing1 t2 = (Thing1)in.readObject();
(1)序列化的控制
如果不希望对象的某一部分被序列化,或者一个对象被还原以后,某子对象需要重新创建,从而不必将该对象序列化。在这种特殊情况下,可通过实现 Externalizable接口—代替实现Serializable接口—来对序列化过程进行控制。
Externalizable接口添加了两个方法:writeExternal()和readExternal(),会在序列化和反序列化还原的过程中被自动调用。
在反序列化还原的过程中,所有普通的默认构造器都会被调用,因此构造器必须是public。
(2)transient(瞬时)关键字
对于实现了Serializable接口的对象,可以用transient(瞬时)关键字逐个字段地关闭序列化,它的意思是“不用麻烦你保存或恢复数据—我自己会处理的”。
1 private transient String password;
(3)Externalizable的替代方法
可以在实现Serializable接口的类中添加名为writeObject()和readObject()的方法。此时会自动调用这两个方法,而不是默认的序列化机制。
在下面的例子中,非transient字段由defaultWriteObject()方法保存,而transient字段必须在程序中明确保存和恢复。
1 class SerialCtl implements Serializable{ 2 private String a; 3 private transient String b; 4 public SerialCtl(String aa,String bb){ 5 a = "Not transient:" + aa; 6 b = "Transient:" + bb; 7 } 8 9 @Override 10 public String toString() { 11 return a + "\n" + b; 12 } 13 14 private void writeObject(ObjectOutputStream stream) throws IOException{ 15 stream.defaultWriteObject(); 16 stream.writeObject(b); 17 } 18 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 19 stream.defaultReadObject(); 20 b = (String) stream.readObject(); 21 } 22 23 public static void main(String[] args) throws IOException, ClassNotFoundException { 24 SerialCtl sc = new SerialCtl("Test1","Test2"); 25 System.out.println("Before:\n" + sc); 26 ByteArrayOutputStream buf = new ByteArrayOutputStream(); 27 ObjectOutputStream o = new ObjectOutputStream(buf); 28 o.writeObject(sc); 29 ObjectInputStream in = new ObjectInputStream( 30 new ByteArrayInputStream(buf.toByteArray()) 31 ); 32 SerialCtl sc2 = (SerialCtl) in.readObject(); 33 System.out.println("After:\n" + sc2); 34 } 35 } 36 37 /*Output: 38 Before: 39 Not transient:Test1 40 Transient:Test2 41 After: 42 Not transient:Test1 43 Transient:Test2 44 */
参考于《Java编程思想》,第525~589页