1.1、目录列表器
如果需要查看目录列表,可以通过file.list(FilenameFilter)方法来获取过滤后的文件目录列表(返回类型是String[])。例子:https://github.com/xu509/Java-practise/blob/master/src/test/file/directory/DirList2.java
1.2 目录使用工具
产生文件集的工具类 :https://github.com/xu509/Java-practise/blob/master/src/test/file/directory/Directory.java
处理文件类:https://github.com/xu509/Java-practise/blob/master/src/test/file/directory/ProcessFiles.java
1.3 目录的检查以及创建
常用File类方法:f.isFile(), f.isDirectory(),f.mkdirs();
2.1、InputStream类型
InputStream的作用是用来表示那些从不同数据源产生输入的类。数据源包括: 1、字节数组。2、String对象。3、文件。4、“管道”,即一端输入,另一端输出。5、一个由其他种类的流组成的序列。6、其他数据源,如Internet连接。
其中FilterInputStream为装饰器类提供基类。
2.2、OutputStream类型
该类别的类决定了输出所要去往的目标:字节数组、文件或管道。
FilterOutputStream为装饰器类提供基类。
3.1、通过FilterInputStream从InputStream读取数据
FilterInputStream类能够完成两件完全不同的事情。其中,DataInputStream:允许我们读取不同的基本类型数据以及String对象。其他FilterInputStream类则在内部修改InputStream的行为方式:BufferedInputStream :是否缓冲,可防治每次读取时都进行实际写操作。LineNumberInputStream :跟踪输入流的行号,可调用getLineNumber()和setLineNumber(int)。PushbackInputStream:具有“能弹出一个字节的缓冲区。”因此可以将督导的最后一个字符回退。
3.1、通过FilterInputStream从InputStream读取数据
以及是否把单一字符推回输入流等等。
3.2、通过FilterOutPutStream从OutputStream写入
DataOutputStream : 可以按照可移植的方式向流中写入基本类型数据。PrintStream: 用于格式化输出打印,具备方法print()和pringln().BufferedOutputStream:它对数据流使用缓冲技术,可调用flush()清空缓冲区。
4.1、数据的来源和去处
尽量尝试使用Reader和Writer,一旦程序代码无法成功编译,我们就会发现自己不得不使用面向字节的类库。
来源于去处:Java 1.0 类 相应的 Java 1.1 类 InputStream
OutputStream
FileInputStream
FileOutputStream
StringBufferInputStream(已弃用)
。。Reader
适配器:InputStreamReader
Writer
适配器:OutputStreamWriter
FileReader
FileWriter
StringReader
StringWriter
4.2、更改流的行为
过滤器:Java 1.0类 相应的Java 1.1类 FilterInputStream FilterReader FilterOutputStream FilterWriter(抽象类,没有子类) BufferedInputStream BufferedReader(有readLine()) BufferedOutputStream BufferedWriter DataInputStream DataInputStream,需要readline()时使用BufferedReader PrintStream PrintWriter LineNumberInputStream(已弃用) LineNumberReader StreamTokenizer StreamTokenizer(使用接受Reader的构造器) PushbackInputStream PushbackReader
4.3、未发生变化的类
DataOutputStream,File,RandomAccessFilemSequenceInputStream
RandomAccessFile适用于由大小已知的记录组成的文件,我们可以使用seek()将记录从一处转移到另一处,然后读取或者修改记录。
RandomAccessFile支持搜寻方法,并且只适用于文件。JDK1.4中,RandomAccessFile的大多数功能由nio存储映射文件所取代。
6.1、缓冲输入文件
使用BufferedReader的readLine来读取文件。public static String read(String filename) throws IOException { // Reading input by lines: BufferedReader in = new BufferedReader( new FileReader(filename)); String s; StringBuilder sb = new StringBuilder(); while ((s = in.readLine()) != null) sb.append(s + "\n"); in.close(); return sb.toString(); }
用 FileReader来创建输入流,再通过BufferedReader来装饰。例子:https://github.com/xu509/Java-practise/blob/master/src/test/file/excerise/BufferReaderExercise.java
6.2、从内存输入
使用BufferInputFile.read()读入的结果来创建StringReader。public class MemoryInput { public static void main(String[] args) throws IOException { StringReader in = new StringReader( BufferedInputFile.read("MemoryInput.java")); int c; while ((c = in.read()) != -1) System.out.print((char) c); } }
注意in.read()是以int形式返回下一字节,因此必须类型转换为char才能正确打印。
6.3、格式化的内存输入
要读取格式化的数据,可以使用DataInputStream,这是一个面向字节的I/O类(不是面向字符)。例子:try { DataInputStream in = new DataInputStream( new ByteArrayInputStream( BufferedInputFile.read( "file/example3.txt").getBytes())); while (true) System.out.print((char) in.readByte()); } catch (EOFException e) { System.err.println("End of stream"); }
通过捕获异常来检测输入末尾,但这是对异常特性的错误使用DataInputStream in = new DataInputStream( new BufferedInputStream( new FileInputStream("file/example3.txt"))); while (in.available() != 0) System.out.print((char) in.readByte());
6.4、基本的文件输出
FileWriter对象可以向文件写入数据。通常会用BufferedWriter将其包装起来用以缓冲输出。
例:为了格式化输出,用PrintWriter装饰BufferedWriter。static String file = "file/BasicFileOutput.out"; public static void main(String[] args) throws IOException { BufferedReader in = new BufferedReader( new StringReader( BufferedInputFile.read("src/io/BasicFileOutput.java"))); PrintWriter out = new PrintWriter( new BufferedWriter(new FileWriter(file))); int lineCount = 1; String s; while ((s = in.readLine()) != null) out.println(lineCount++ + ": " + s); out.close(); // Show the stored file: System.out.println(BufferedInputFile.read(file)); }
文本输出的快捷方式JAVA SE5在PrintWriter中添加了一个辅助构造器,使得不需要每次创建PrintWriter的时候都进行包装工作。旧 :PrintWriter out = new PrintWriter( new BufferedWriter(new FileWriter(file)));
新:
新旧效果相同,其他常见的写入任务都没有快捷方式。PrintWriter out = new PrintWriter(file);
使用缓冲(BufferedReader,BufferedWriter)和不使用的区别:
前者将一部分内存作为缓冲区,使每次操作在内存中操作,而不是直接操作文件,等文件关闭或者缓冲区满后才往文件中写。
如果需要大量的读写,使用缓冲会提高性能。如果需要立即生效,则不需要用缓冲。
6.5、存储和恢复数据
我们可以用 DataOutputStream写入数据,并用 DataInputStream恢复数据。另这2个流都是面向字节的,需用使用InputStream以及OutputStream。public class StoringAndRecoveringData { public static void main(String[] args) throws IOException { DataOutputStream out = new DataOutputStream( new BufferedOutputStream( new FileOutputStream("file/Data.txt"))); out.writeDouble(3.14159); out.writeUTF("That was pi"); out.writeDouble(1.41413); out.writeUTF("Square root of 2"); out.close(); DataInputStream in = new DataInputStream( new BufferedInputStream( new FileInputStream("file/Data.txt"))); System.out.println(in.readDouble()); // Only readUTF() will recover the // Java-UTF String properly: System.out.println(in.readUTF()); System.out.println(in.readDouble()); System.out.println(in.readUTF()); } }
根据API可以发现,DataOutputStream可以写入所有基于基本类型的数据。
6.6、读写随机访问文件
使用RandomAccessFile类似组合使用了DataInpuStream和DataOutputStream。static String file = "rtest.dat"; static void display() throws IOException { RandomAccessFile rf = new RandomAccessFile(file, "r"); for (int i = 1; i < 3; i++) System.out.println( "Value " + i + ": " + rf.readInt()); System.out.println(rf.readUTF()); rf.close(); } public static void main(String[] args) throws IOException { RandomAccessFile rf = new RandomAccessFile(file, "rw"); for (int i = 1; i < 3; i++) rf.writeInt(i); rf.writeUTF("The end of the file"); rf.close(); display(); rf = new RandomAccessFile(file, "rw"); rf.seek(2 * 2);//使用seek在文件中移动 查找精度类型,int2字节,第二个即是2*2 rf.writeInt(9);//修改第二个 rf.close(); display();
构造参数第二个自行选择:r - 只读,rw - 读写。
6.7、管道流
PipedInputStream、PipedOutputStream、PipedReader和PipedWriter的价值在于多线程,管道流用于任务之间的通信。
https://github.com/xu509/Java-practise/blob/master/src/net/mindview/util/TextFile.java
该工具类实现读取文件为String类型,写入str等简单功能。
7.1、读取二进制文件
public class BinaryFile { public static byte[] read(File bFile) throws IOException { BufferedInputStream bf = new BufferedInputStream( new FileInputStream(bFile)); try { byte[] data = new byte[bf.available()]; bf.read(data); return data; } finally { bf.close(); } } public static byte[] read(String bFile) throws IOException { return read(new File(bFile).getAbsoluteFile()); } }
练习:使用上述工具类,统计文件中字符出现的次数/ 统计字节出现的次数:https://github.com/xu509/Java-practise/blob/master/src/test/file/excerise/RecordKeyMap.java
标准I/O的意义在于:我们可以很容易地把程序串联起来,一个程序的标准输出可以成为另一个程序的标准输入。
8.1、从标准输入中读取
JAVA提供了 System.in、System.out和System.err 标准I/O模型。System.out和System.err已经事先包装成了printStream对象。System.in却是一个没有包装过的未经加工的InputStream。所以使用需要对其进行包装。包装步骤:其中InputStreamReader是适配器。BufferedReader stdin = new BufferedReader( new InputStreamReader(System.in));
8.2、将System.out转换成PrintWriter
System.out 是一个 PrintStream,也是OutputStream,PrintWriter有一个可以接受OutputStream的构造器。public class ChangeSystemOut { public static void main(String[] args) { PrintWriter out = new PrintWriter(System.out, true); out.println("Hello, world"); } }
8.3、标准I/O重定向
java 的 System 类提供了 setIn(InputStream),setOut(PrintStream),setErr(PrintStream) 来对标准输入、输出和错误I/O流进行重定向。例子:https://github.com/xu509/Java-practise/blob/master/src/io/Redirecting.java
例子中将标准输出和标准错误重定向到另一个文件,并且在最后恢复了标准输出。需注意的是,I/O重定向操纵的是字节流。
提供一个工具类让java内部执行其他操作系统的程序。
工具类:https://github.com/xu509/Java-practise/blob/master/src/net/mindview/util/OSExecute.java
自定义异常:https://github.com/xu509/Java-practise/blob/master/src/net/mindview/util/OSExecuteException.java
执行类:https://github.com/xu509/Java-practise/blob/master/src/io/OSExecuteDemo.java
JDK1.4后提供了新的I/O类库,其目的在于提高速度。
新I/O提出了通道和缓冲器的概念,我们可以把通道理解为一个包含煤层(数据)的矿藏,而缓冲器则是派送到矿藏的卡车。我们只和缓冲器交互,而通道也只和缓冲器交互。
ByteBuffer对象就是唯一与通道交互的缓冲器,可以存储未加工字节的缓冲器。ByteBuffer用于基础类型和字节形式的读取或输出,但无法输出或读取对象,包括字符串类型。
旧I/O类库中的FileInputStream,FileOutputStream以及RandomAccessFile都被修改,用以产生FileChannel(通道),这些都是字节操纵流,而Reader和Writer就不能用于产生通道;但是Channels类提供了实用方法,用于在通道中产生Reader和Writer。
上述三种流产生通道的例子:
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception {
// Write a file:
FileChannel fc =
new FileOutputStream("file/nio/data.txt").getChannel();
fc.write(ByteBuffer.wrap("Some text ".getBytes()));
fc.close();
// Add to the end of the file:
fc =
new RandomAccessFile("file/nio/data.txt", "rw").getChannel();
fc.position(fc.size()); // Move to the end
fc.write(ByteBuffer.wrap("Some more".getBytes()));
fc.close();
// Read the file:
fc = new FileInputStream("file/nio/data.txt").getChannel();
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
fc.read(buff);
buff.flip();//告知buff做好被读取字节的准备
while (buff.hasRemaining())
System.out.print((char) buff.get());
}
注意如果打算使用FileChannel在通知ByteBuffer读取后执行进一步的read()操作,每当进行read()后都必须使用flip()方法告知缓冲器做好被读取的准备,我们就必须调用clear()来为下一次的read()做好准备,如下:
public class ChannelCopy {
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception {
args = new String[2];
args[0] = "file/example3.txt";
args[1] = "file/nio/copy.out";
if (args.length != 2) {
System.out.println("arguments: sourcefile destfile");
System.exit(1);
}
FileChannel
in = new FileInputStream(args[0]).getChannel(),
out = new FileOutputStream(args[1]).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
while (in.read(buffer) != -1) {
buffer.flip(); // Prepare for writing
out.write(buffer);
buffer.clear(); // Prepare for reading
}
}
}
上面程序并不是处理此类问题最理想的方式,特殊方法transferTo()和transferFrom()允许我们将一个通道和另一个通道直接相连。
关键代码:
in.transferTo(0, in.size(), out);
ByteBuffer b = ByteBuffer.allocate(BSIZE);
while (in.read(b,in.size()) != -1) {
b.flip();
in.write(b,in.size()+1);
b.clear();
}
注意通道read和write的改变,以及2个连接方法的用法。
10.1、转变数据
使用CharBuffer()进行转换字符,然后通过asCharBuffer()可输出缓冲器。例子:https://github.com/xu509/Java-practise/blob/master/src/io/BufferToText.java对于获取的字节,要么在写入文件的时候就转换成字符,要么就在缓冲器输出的时候进行编码转换为字符。可以使用Charset类来实现功能。
如:(1)通过Charset.forName(encoding)获取Charset对象,然后对缓冲器进行解码。buff.rewind();//返回数据开始部分 String encoding = System.getProperty("file.encoding"); System.out.println("Decoded using " + encoding + ": " + Charset.forName(encoding).decode(buff));
(2)在写入时即使用编码。fc = new FileOutputStream("data2.txt").getChannel(); fc.write(ByteBuffer.wrap( "Some text".getBytes("UTF-16BE")));//输入时进行编码 fc.close();
(3)通过CharBuffer向ByteBuffer写入buff = ByteBuffer.allocate(24); // More than needed buff.asCharBuffer().put("Some text");//缓冲器输出时进行编码 fc.write(buff);
10.2、获取基本类型
ByteBuffer只保存字节类型,但可以从其所容纳的字节中产生不同的基本类型。完整例子:https://github.com/xu509/Java-practise/blob/master/src/io/GetData.java
获取char:bb.asCharBuffer().put("Howdy!"); char c; while ((c = bb.getChar()) != 0) printnb(c + " ");
获取short:bb.asShortBuffer().put((short) 471142); print(bb.getShort()); bb.rewind();
其中asCharBuffer()和asShortBuffer()都获取了该缓冲器上的视图。
10.3、视图缓冲器(View buffer)
视图缓冲器可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer。ByteBuffer依然是实际存储数据的地方,“支持”着前面的视图,因此,对视图的任何修改都会映射称为对ByteBuffer中数据的修改(即视图的put方法)。访问即修改底层ByteBuffer的例子:public class IntBufferDemo { private static final int BSIZE = 1024; public static void main(String[] args) { ByteBuffer bb = ByteBuffer.allocate(BSIZE); IntBuffer ib = bb.asIntBuffer(); // Store an array of int: ib.put(new int[]{11, 42, 47, 99, 143, 811, 1016}); // Absolute location read and write: System.out.println(ib.get(3)); ib.put(3, 1811); // Setting a new limit before rewinding the buffer. ib.flip(); while (ib.hasRemaining()) { int i = ib.get(); System.out.println(i); } } }
ByteBuffer通过不同的视图显示方式也不同例子 : https://github.com/xu509/Java-practise/blob/master/src/io/ViewBuffers.java
当数据为8字节数组 :不同的视图显示不同:new byte[]{0, 0, 0, 0, 0, 0, 0, 'a'}
Byte Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 5 -> 0, 6 -> 0, 7 -> 97, Float Buffer 0 -> 0.0, 1 -> 1.36E-43, Int Buffer 0 -> 0, 1 -> 97, Long Buffer 0 -> 97, Short Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 97, Double Buffer 0 -> 4.8E-322,
Char Buffer 0 -> ,1 -> , 2-> ,3 -> a
字节存放次序:
不同的机器可能会使用不同的字节排序方法来存储数据。“big endian”(高位优先)将最重要的字节放在地址最低的存储单元。“little endian”(低位优先) 则是将最重要的字节放在地址最高的存储器单元。当存储量大于一个字节时,就要考虑字节的顺序问题。ByteBuffer是以高位优先的形式存储数据的,并且数据在网上传送时也常常使用高位优先的形式。
我们可以使用带参数的ByteOrder.BIG_ENDIAN或ByteOrder.LITTLE_ENDIAN的order()方法改变排序方式。
完整: https://github.com/xu509/Java-practise/blob/master/src/io/Endians.javabb.order(ByteOrder.BIG_ENDIAN);
10.4、用缓冲器操纵数据
当需要将字节数组写入文件中时,使用ByteBuffer.wrap()方法将字节数组包装,使用getChannel方法在FileOutputStream上打开一个通道,接着将缓冲器的数据写入通道中。
10.5、缓冲器的细节
Buffer由数据与4个索引组成,这4个索引hi:mark(标记)、position(位置)、limit(界限)和 capacity(容量)。flip() : 将limit设置为position,position设置为0。此方法用于准备从缓冲区读取已经写入的数据。
remaining(): 返回(limit - position)。
hasRemaining(): 若介于position和limit之间的元素,则返回true()
研究方法对索引影响的例子 : https://github.com/xu509/Java-practise/blob/master/src/io/UsingBuffers.java10.6、内存映射文件
内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件。有了内存映射文件,我们就可以假定整个文件都放在内存中,而且可以完全把它当做非常大的数组来访问。public class LargeMappedFiles { static int length = 0x8FFFFFF; // 128 MB public static void main(String[] args) throws Exception { MappedByteBuffer out = new RandomAccessFile("file/example1.txt", "rw").getChannel() .map(FileChannel.MapMode.READ_WRITE, 0, length); for (int i = 0; i < length; i++) out.put((byte) 'x'); print("Finished writing"); for (int i = length / 2; i < length / 2 + 20; i++) printnb((char) out.get(i)); } }
如上图,为了能同时读写大文件,使用RandomAccessFile来获取通道,然后使用map()方法来产生MappedByteBuffer。MappedByteBuffer是由ByteBuffer继承而来,因此它具有ByteBuffer的所有方法。性能上使用映射文件访问要比旧的缓冲输入输出要高出不少。测试类: https://github.com/xu509/Java-practise/blob/master/src/io/MappedIO.java10.7、文件加锁
JDK1.4引入了文件加锁机制,允许我们同步访问某个作为共享资源的文件,通过通道的tryLock,或者lock方法,可以获得整个文件的锁,或部分的FileLock fl = fos.getChannel().tryLock();
fl.release();
可通过添加参数来对文件的一部分上锁tryLock(long position,long size,boolean shared) 或者 lock(...,...,...)第三个参数决定了锁是独占锁还是共享锁,第一第二个参数决定了文件的加锁区域,而没有参数的加锁方法会对整个文件进行加锁。独占锁或者共享锁的支持必须通过底层的操作系统提供,如果操作系统不支持共享锁,则会提供独占锁。可以通过FileLock.isShared()查询。
对映射文件的部分上锁对大文件的不同部分加锁:https://github.com/xu509/Java-practise/blob/master/src/io/LockingMappedFiles.java
Java I/O库提供读写压缩格式的数据流,使用它们进行封装以提供压缩功能,这些类不是从 Reader 和 Writer 派生出来,而是属于InputStream 和 OutputStream。
压缩类 | 功能 |
---|---|
CheckedInputStream | GetCheckSum() 为任何InputStream产生校验和(不仅是解压缩) |
CheckedOutputStream | GetCheckSum()为任何OutputStream产生校验和(不仅是压缩) |
DeflaterOutputStream | 压缩类的基类 |
ZipOutputStream | 一个DeflaterOutputStream,用于将数据压缩成Zip文件格式 |
GZIPOutputStream | 一个DeflaterOutputStreamm,用于将数据压缩成GZIP文件格式 |
InflaterInputStream | 解压缩类的基类 |
ZipInputStream | 一个InflaterInputStream,用于解压缩Zip文件格式的数据 |
GZIPInputStream | 一个InflaterInputStream,用于解压缩GZIP文件格式的数据 |
11.1 用GZIP进行简单压缩
如果对单个数据流(而不是互异数据)进行压缩,那么它可能是比较适合的选择BufferedReader in2 = new BufferedReader( new InputStreamReader(new GZIPInputStream( new FileInputStream("test.gz"))));
BufferedOutputStream out = new BufferedOutputStream( new GZIPOutputStream( new FileOutputStream("test.gz")));
11.2 用Zip进行多文件压缩
支持Zip格式的Java库更加全面。利用该库可以方便的保存多个文件,甚至一个独立的类,使得读取Zip文件更加方便。如下例,Checksum有2个类型,Adler32(更快)和CRC32(更准确)。
写入ZIP文件:对每一个要加入压缩档案的文件,都需要调用putNextEntry(),将其传递个一个ZipEntry对象。FileOutputStream f = new FileOutputStream("test.zip"); CheckedOutputStream csum = new CheckedOutputStream(f, new Adler32()); ZipOutputStream zos = new ZipOutputStream(csum); BufferedOutputStream out = new BufferedOutputStream(zos); zos.setComment("A test of Java Zipping"); // No corresponding getComment(), though. for (String arg : args) { print("Writing file " + arg); BufferedReader in = new BufferedReader(new FileReader(arg)); zos.putNextEntry(new ZipEntry(arg)); int c; while ((c = in.read()) != -1) out.write(c); in.close(); out.flush(); } out.close();
读取ZIP文件:FileInputStream fi = new FileInputStream("test.zip"); CheckedInputStream csumi = new CheckedInputStream(fi, new Adler32()); ZipInputStream in2 = new ZipInputStream(csumi); BufferedInputStream bis = new BufferedInputStream(in2); ZipEntry ze; while ((ze = in2.getNextEntry()) != null) { print("Reading file " + ze); int x; while ((x = bis.read()) != -1) System.out.write(x); } if (args.length == 1) print("Checksum: " + csumi.getChecksum().getValue()); bis.close();
更简单的方法:ZipFile zf = new ZipFile("test.zip"); Enumeration e = zf.entries(); while (e.hasMoreElements()) { ZipEntry ze2 = (ZipEntry) e.nextElement(); print("File: " + ze2); // ... and extract the data as before }
完整例子:https://github.com/xu509/Java-practise/blob/master/src/io/ZipCompress.java
11.3 Java档案文件
jar [options] destination [manifest] inputfile(s)
options 是一个字母集合(不需要输入任何“-”或其他任何标示符),一下字符在Unix和Linux系统的tar文件中也具有相同的意义.
c 创建一个新的或空的压缩文档 t 列出目录表 x 解压所有文件 x file 解压该文件 f 意指:“我打算指定一个文件名。” 如果没有用这个选项,
jar假设所有的输入都来自于标准输入。m 表示第一个参数将是用户自建的清单文件的名字 v 产生详细输出,描述jar所做的工作 O 只存储文件,不压缩文件(用来创建一个可放在类路径中的JAR文件) M 不自动创建文件清单
典型用法:jar cf myJarFile.jar *.class - 创建一个名为myJarFile.jar的JAR文件,该文件包含了当前目录中的所有类文件,以及自动产生清单文件jar cmf myJarFile.jar myManifestFile.mf *.class - 其他与上例相同,添加一个名为myManifestFile.mf的用户自建清单文件jar tf myJarFile.jar - 产生myJarFile.jar内所有文件的一个目录表jar tvf myJarFile.jar - 提供有关myJarFile.jar中的文件的更详细的信息
假定audio、classes和image是子目录,下面的命令将所有子目录合并到文件myApp.jar中,其中也包括了 "v"标志,当jar程序运行时,该标志可以提供更详细的信息 - jar cvf myApp.jar audio classes image
Java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。
利用对象的序列化可以实现轻量级持久性。
要序列化一个对象,首先需要创建一个OutputStream对象,然后使用ObjectOuptStream进行装饰。然后,只需要调用writeObject()即可将对象序列化,并将其发送给OutputStream。要反响进行该过程,需要将一个InputStream封装在ObjectInputStream内,然后调动readObject(),获得一个Object引用,然后对其向下转型则能直接设置他们。
输出序列化对象:
OutputStream outstream = new FileOutputStream(filepath);
ObjectOutputStream out = new ObjectOutputStream(outstream);
// out.writeObject("test write");
out.writeObject(a1);
out.close();
输入序列化对象:
ObjectInputStream in = new ObjectInputStream(new FileInputStream(filepath));
String s = (String) in.readObject();
Object obj = in.readObject();
in.close();
详细例子 : https://github.com/xu509/Java-practise/blob/master/src/io/Worm.java
12.1 寻找类
获取序列化后的文件,重新进行包装时需要原类的.class文件,否则无法包装。
12.2 序列化的控制
如果需要考虑特殊的安全问题,希望对象的一部分被序列化,或者一个对象被还原后,其子对象需要重新创建。在这些特殊情况下,可通过实现Externalizable接口代替Serializable接口,来对序列化过程进行控制。这个接口增添两个方法:writeExternal()、readExternal()。这两个方法会在序列化与反序列化还原的过程中被自动调用。
https://github.com/xu509/Java-practise/blob/master/src/io/Blips.java
如上例,Blip2因为构造方法非public所以无法反序列化还原。这是Externalizable和Serializable的不同,Serizalizable接口不依赖构造方法还原,而Externalizable还原时会调用所有的默认构造方法,然后再调用readExternal()。
https://github.com/xu509/Java-practise/blob/master/src/io/Blip3.java
Externalizable在反序列化时,需要向对象内所有的成员对象写入数据,否则还原后的对象中的成员对象数据为空。
public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(s); out.writeInt(i); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { s = (String) in.readObject(); i = in.readInt(); }
transient(瞬时)关键词
除了Externalizeable外,可以用transient在Serializable对象中逐个字段关闭序列化。例子: https://github.com/xu509/Java-practise/blob/master/src/io/Logon.javaprivate transient String password;
private Date date = new Date();
当对象被恢复时,password域会变成"null",date对象不会发生变化,序列化的时候是多少还原时就是多少。
Externalizable的替代方法
如果不希望实现Externalizable方法,我们可以实现Serializable接口,并且 添加名为writeObject()和readObject()方法。这样一旦对象被序列化或者反序列化还原,就会自动地分别调用这两个方法,这些方法必须有准确的方法特征签名:private void writeObject(ObjectOutputStream stream) throws IOException ; private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException;
https://github.com/xu509/Java-practise/blob/master/src/io/SerialCtl.java
private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(b); } private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); b = (String) stream.readObject(); }
版本控制
由于Java版本控制机制国语简单,因而对于序列化的支持,只适用于短期存储或应用之间的RMI。
12.3 使用”持久性“
我们将2个对象(他们都具有指向第三个对象的引用 )进行序列化,当我们恢复他们时,第三个对象会只出现一次吗?如果两个对象序列化成独立的文件,然后在代码的不同部分对它们进行反序列化还原,又会怎么样呢?https://github.com/xu509/Java-practise/blob/master/src/io/MyWorld.java
经过上述程序的试验,在同一个流内,2个对象引用了相同的地址。
https://github.com/xu509/Java-practise/blob/master/src/io/StoreCADState.java
在上面这个例子里,xPos,yPos,dimension都成功的序列化与反序列化了,但是static的color并没有成功的恢复。而Line类中的方法则的作用就是将静态变量的序列化和反序列化。/*序列化时记录static变量的值*/ public static void serializeStaticState(ObjectOutputStream os) throws IOException { os.writeInt(color); } /*序列化时记录static变量的值*/ public static void deserializeStaticState(ObjectInputStream os) throws IOException { color = os.readInt(); }
书中使用XOM类库来完成对XML的操作。
Preferences API 与对象序列化相比,与对象持久性更密切,因为它可以自动存储和读取信息。不过它只能用于小的、受限的数据集合——我们只能存储基本类型和字符串,并且每个字符串的存储长度不能超过8K。
顾名思义,Preferences API用于存储和读取用户的偏好,以及程序配置项的设置。
Preferences是一个键值集合(类似映射),存储在一个节点层次结构中。简单例子:
public class PreferencesDemo {
public static void main(String[] args) throws Exception {
Preferences prefs = Preferences
.userNodeForPackage(PreferencesDemo.class);
prefs.put("Location", "Oz");
prefs.put("Footwear", "Ruby Slippers");
prefs.putInt("Companions", 4);
prefs.putBoolean("Are there witches?", true);
int usageCount = prefs.getInt("UsageCount", 0);
usageCount++;
prefs.putInt("UsageCount", usageCount);
for (String key : prefs.keys())
print(key + ": " + prefs.get(key, null));
// You must always provide a default value:
print("How many companions does Dorothy have? " +
prefs.getInt("Companions", 0));
}
}
/* Output: (Sample)
Location: Oz
Footwear: Ruby Slippers
Companions: 4
Are there witches?: true
UsageCount: 53
How many companions does Dorothy have? 4
*///:~