File类
File可以表示一个文件的名字也可以表示某个目录下一堆文件的名字,如果是表示多个文件,那么可以使用list方法来获取他们的set集合,返回的是一系列的字符串。下面我们就来看下如何使用这个类以及相关的FilenameFilter接口。如果想要获取某个目录下的所有文件列表那么直接使用默认的list方法,如果需要筛选特定的文件,那么就可以使用文件过滤器,如下:
//
: io/DirList.java
//
Display a directory listing using regular expressions.
//
{Args: “D.*.java”}
import java.util.regex.*
;
import java.io.*
;
import java.util.*
;
public
class
DirList {
public
static
void
main(String[] args) {
File path =
new File(“.”
);
String[] list;
if(args.length == 0
)
list =
path.list();
else
list = path.list(
new DirFilter(args[0
]));
Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
for
(String dirItem : list)
System.out.println(dirItem);
}
}
class DirFilter
implements
FilenameFilter {
private
Pattern pattern;
public
DirFilter(String regex) {
pattern =
Pattern.compile(regex);
}
public
boolean
accept(File dir, String name) {
return
pattern.matcher(name).matches();
}
}
/*
Output:
DirectoryDemo.java
DirList.java
DirList2.java
DirList3.java
*///
:~
然后我们可以使用匿名内部类来修改上面的代码,需要注意的是为了是的匿名内部类能够使用外部的对象,参数应该设置为final修饰:
//
: io/DirList2.java
//
Uses anonymous inner classes.
//
{Args: “D.*.java”}
import java.util.regex.*
;
import java.io.*
;
import java.util.*
;
public
class
DirList2 {
public
static FilenameFilter filter(
final
String regex) {
//
Creation of anonymous inner class:
return
new
FilenameFilter() {
private Pattern pattern =
Pattern.compile(regex);
public
boolean
accept(File dir, String name) {
return
pattern.matcher(name).matches();
}
};
//
End of anonymous inner class
}
public
static
void
main(String[] args) {
File path =
new File(“.”
);
String[] list;
if(args.length == 0
)
list =
path.list();
else
list = path.list(filter(args[0
]));
Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
for
(String dirItem : list)
System.out.println(dirItem);
}
}
/*
Output:
DirectoryDemo.java
DirList.java
DirList2.java
DirList3.java
*///
:~
然后我们还可以更进一步,将匿名内部累放到参数里面:
//
: io/DirList3.java
//
Building the anonymous inner class “in-place.”
//
{Args: “D.*.java”}
import java.util.regex.*
;
import java.io.*
;
import java.util.*
;
public
class
DirList3 {
public
static
void main(
final
String[] args) {
File path =
new File(“.”
);
String[] list;
if(args.length == 0
)
list =
path.list();
else
list = path.list(
new
FilenameFilter() {
private Pattern pattern = Pattern.compile(args[0
]);
public
boolean
accept(File dir, String name) {
return
pattern.matcher(name).matches();
}
});
Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
for
(String dirItem : list)
System.out.println(dirItem);
}
}
/*
Output:
DirectoryDemo.java
DirList.java
DirList2.java
DirList3.java
*///
:~
我们经常需要用到获取某个路径下的所有文件或这是某个目录树下的所有文件,下面的代码提供了两个方法,第一个local方法用来获取本地路径的File对象集合,第二个方法walk用来获取制定目录树下的所有文件和目录:
//
: net/mindview/util/Directory.java
//
Produce a sequence of File objects that match a
//
regular expression in either a local directory,
//
or by walking a directory tree.
package
net.mindview.util;
import java.util.regex.*
;
import java.io.*
;
import java.util.*
;
public
final
class
Directory {
public
static File[] local(File dir,
final
String regex) {
return dir.listFiles(
new
FilenameFilter() {
private Pattern pattern =
Pattern.compile(regex);
public
boolean
accept(File dir, String name) {
return pattern.matcher(
new
File(name).getName()).matches();
}
});
}
public
static File[] local(String path,
final String regex) {
//
Overloaded
return local(
new
File(path), regex);
}
//
A two-tuple for returning a pair of objects:
public
static
class TreeInfo
implements Iterable
{
public List files =
new ArrayList
();
public List dirs =
new ArrayList
();
//
The default iterable element is the file list:
public Iterator
iterator() {
return
files.iterator();
}
void
addAll(TreeInfo other) {
files.addAll(other.files);
dirs.addAll(other.dirs);
}
public
String toString() {
return “dirs: ” + PPrint.pformat(dirs) + “nnfiles: ” +
PPrint.pformat(files);
}
}
public
static TreeInfo walk(String start, String regex) {
//
Begin recursion
return recurseDirs(
new
File(start), regex);
}
public
static TreeInfo walk(File start, String regex) {
//
Overloaded
return
recurseDirs(start, regex);
}
public
static TreeInfo walk(File start) {
//
Everything
return recurseDirs(start, “.*”
);
}
public
static
TreeInfo walk(String start) {
return recurseDirs(
new File(start), “.*”
);
}
static
TreeInfo recurseDirs(File startDir, String regex){
TreeInfo result =
new
TreeInfo();
for
(File item : startDir.listFiles()) {
if
(item.isDirectory()) {
result.dirs.add(item);
result.addAll(recurseDirs(item, regex));
}
else
//
Regular file
if
(item.getName().matches(regex))
result.files.add(item);
}
return
result;
}
//
Simple validation test:
public
static
void
main(String[] args) {
if(args.length == 0
)
System.out.println(walk(“.”
));
else
for
(String arg : args)
System.out.println(walk(arg));
}
}
//
/:~
TreeInfo。toString方法使用默认的方法使得对于容量较大的容器打印出来的内容不容易阅读,下面创建一个一种格式化工具,增加了换行和缩进:
//
: net/mindview/util/PPrint.java
//
Pretty-printer for collections
package
net.mindview.util;
import java.util.*
;
public
class
PPrint {
public
static String pformat(Collection>
c) {
if(c.size() == 0)
return “[]“
;
StringBuilder result =
new StringBuilder(“["
);
for
(Object elem : c) {
if(c.size() != 1
)
result.append("n "
);
result.append(elem);
}
if(c.size() != 1
)
result.append("n"
);
result.append("]“
);
return
result.toString();
}
public
static
void pprint(Collection>
c) {
System.out.println(pformat(c));
}
public
static
void
pprint(Object[] c) {
System.out.println(pformat(Arrays.asList(c)));
}
}
//
/:~
下面就是一个我们定义的Directory的一个使用范例:
//
: io/DirectoryDemo.java
//
Sample use of Directory utilities.
import java.io.*
;
652
Thinking in Java Bruce Eckel
import net.mindview.util.*
;
import
static net.mindview.util.Print.*
;
public
class
DirectoryDemo {
public
static
void
main(String[] args) {
//
All directories:
PPrint.pprint(Directory.walk(“.”
).dirs);
//
All files beginning with ‘T’
for(File file : Directory.local(“.”, “T.*”
))
print(file);
print(“———————-”
);
//
All Java files beginning with ‘T’:
for(File file : Directory.walk(“.”, “T.*\.java”
))
print(file);
print(“======================”
);
//
Class files containing “Z” or “z”:
for(File file : Directory.walk(“.”,”.*[Zz].*\.class”
))
print(file);
}
}
/*
Output: (Sample)
[.xfiles]
.TestEOF.class
.TestEOF.java
.TransferTo.class
.TransferTo.java
———————-
.TestEOF.java
.TransferTo.java
.xfilesThawAlien.java
======================
.FreezeAlien.class
.GZIPcompress.class
.ZipCompress.class
*///
:~
我们还可以更进一步,根据传递的Strategy对象来确定如何处理目录下的文件和子目录:
//
: net/mindview/util/ProcessFiles.java
package
net.mindview.util;
import java.io.*
;
public
class
ProcessFiles {
public
interface
Strategy {
void
process(File file);
}
private
Strategy strategy;
private
String ext;
public
ProcessFiles(Strategy strategy, String ext) {
this.strategy =
strategy;
this.ext =
ext;
}
public
void
start(String[] args) {
try
{
if(args.length == 0
)
processDirectoryTree(
new File(“.”
));
else
for
(String arg : args) {
File fileArg =
new
File(arg);
if
(fileArg.isDirectory())
processDirectoryTree(fileArg);
else
{
//
Allow user to leave off extension:
if(!arg.endsWith(“.” +
ext))
arg += “.” +
ext;
strategy.process(
new
File(arg).getCanonicalFile());
}
}
}
catch
(IOException e) {
throw
new
RuntimeException(e);
}
}
public
void processDirectoryTree(File root)
throws
IOException {
for(File file : Directory.walk(root.getAbsolutePath(), “.*\.” +
ext))
strategy.process(file.getCanonicalFile());
}
//
Demonstration of how to use it:
public
static
void
main(String[] args) {
new ProcessFiles(
new
ProcessFiles.Strategy() {
public
void
process(File file) {
System.out.println(file);
}
}, “java”
).start(args);
}
}
/*
(Execute to see output)
*///
:~
File类不仅仅可以代表既有的文件和目录,你还可以使用File对象来创建新的文件和目录,还可以获取他们的属性和删除文件,下面的例子展示了这些方法的用法:
//
: io/MakeDirectories.java
//
Demonstrates the use of the File class to
//
create directories and manipulate files.
//
{Args: MakeDirectoriesTest}
import java.io.*
;
public
class
MakeDirectories {
private
static
void
usage() {
System.err.println(“Usage:MakeDirectories path1 …n” + “Creates each pathn” + “Usage:MakeDirectories -d path1 …n” + “Deletes each pathn” +
“Usage:MakeDirectories -r path1 path2n” + “Renames from path1 to path2″
);
System.exit(1
);
}
private
static
void
fileData(File f) {
System.out.println(“Absolute path: ” + f.getAbsolutePath() + “n Can read: ” + f.canRead() + “n Can write: ” + f.canWrite() + “n getName: ” + f.getName() +
“n getParent: ” + f.getParent() + “n getPath: ” + f.getPath() + “n length: ” + f.length() + “n lastModified: ” +
f.lastModified());
if
(f.isFile())
System.out.println(“It’s a file”
);
else
if
(f.isDirectory())
System.out.println(“It’s a directory”
);
}
public
static
void
main(String[] args) {
if(args.length < 1
) usage();
if(args[0].equals(“-r”
)) {
if(args.length != 3
) usage();
old =
new File(args[1
]),
rname =
new File(args[2
]);
old.renameTo(rname);
fileData(old);
fileData(rname);
return;
//
Exit main
}
int count = 0
;
boolean del =
false
;
if(args[0].equals(“-d”
)) {
count++
;
del =
true
;
}
count–
;
while(++count <
args.length) {
File f =
new
File(args[count]);
if
(f.exists()) {
System.out.println(f + ” exists”
);
if
(del) {
System.out.println(“deleting…” +
f);
f.delete();
}
}
else {
//
Doesn’t exist
if(!
del) {
f.mkdirs();
System.out.println(“created ” +
f);
}
}
fileData(f);
}
}
}
/*
Output: (80% match)
created MakeDirectoriesTest
Absolute path: d:aaa-TIJ4codeioMakeDirectoriesTest
Can read: true
Can write: true
getName: MakeDirectoriesTest
getParent: null
getPath: MakeDirectoriesTest
length: 0
lastModified: 1101690308831
It’s a directory
*///
:~
Java类库中用于IO的对象分成输入和输出,通过集成,所有派生自InputStream和Reader类的对象都有read方法用来读取字节或者字节数组;所有派生自OutputStream或Writer的对象都有write方法用来写入字节或者字节数组。但是通常你不会使用这些方法,他们的存在是为了让别的类可以使用,这些类提供了更加有用的接口,因此你很少创建一个类就满足流对象的要求,而是通过组合多个对象来满足我们的需求。Java IO中最容易让人疑惑的就是需要通过多个对象来生成一个流。Java中所遇与输入有关的类都继承自InputStream,所有与输出有关的类都继承自OutputStream。
IO 中的装饰者
Java IO库中需要多种不同特性的组合,因此非常适合使用装饰者模式,之所以提供了filter类是因为filter类是所有装饰者的基类。装饰者必须同他装饰的对象拥有相同的接口,但是装饰者还可以扩展filter类中的接口。装饰者模式虽然给程序带来了很大的灵活性,但是也使得代码变得复杂,Java IO之所以用起来有些复杂就是因为你必须创建多个对象来获得我们想要的IO对象。
为InputStream和OutputStream提供装饰者接口的是FilterInputStream和FilterOutputStream, FilterInputStream和FilterOutputStream都是从IO基类InputStream和OutputStream继承而来的,这样就可以为所有被装饰的对象提供同样的接口。
FilterInputStream类完成了两个重要的事情,通过DataOutputStream你可以将数据从一个地方通过流移动到另外一个地方,其余的FilterInputStream类从内部更改了InputStream的行为,例如BufferedInputStream对应了是有使用缓存,LineNumberInputStream对应了是否跟踪了读取的行数,PushbackInputStream对应了是否可以向后读取字符。后两个类是用来支持Java编译器的,因此通常是用不到的。
与DataInputStream相对应的是DataOutputStream,将数据从对象读到流中从而使得对应的DataInputStream可以在任何时候任何机器上读取。PrintStream将数据了行和字符串对象进行打印,在PrintStream中最重要的方法是print和println,对不同类型的打印进行了重载。PrintStream也很容易出现问题,因为他捕获了所有的IOExceptions,因此你必须显式的调用checkError,如果有错误则反悔true,另外PrintStream没有很好的处理不同平台的处理换行的不同方式。这个问题可以使用PrintWriter解决。BufferedOutputStream对流进行缓存,这样不用每次书写到流的数据就被马上写入到物理设备上。我们几乎总是会使用到这个类。
Reaers和Writers
当你看到Reader和Writer的时候,你有可能会觉得他们会替代InputStream和OutputStream,但是实际上他们各自有各自的使用场合。InputStream和OutputStream提供了基于字节的IO操作,Reader和Writer提供了基于Unicode编码的字符IO操作,另外,有些时候你必须将InputStream转化为Reader或者将OutputStream转换为Writer,这个时候可以使用InputStreamReader和OutStreamWriter。还有就是Reader和Writer最重要的一点,老式的IO流操作只支持八位的流不支持十六位的Unicode编码,因为Unicode编码是通用的编码,因此Reader和Writer被添加用来支持Unicode的IO操作,另外,新添加的类库效率更高。
几乎所有原先的Java IO 流类都有对应的Reader和Writer来执行相应的Unicode操作。但是有些情况下还是应该采用InputStream和OutputStream,尤其是java.util.zip类库是基于字节的而不是基于字符的。因此最小心的做法是使用Reader和Writer的时候使用try语句块。因为有些情况下我们使用Reader和Writer的时候不能通过编译必须使用基于字节的操作。
对于InputStream和OutputStream,数据流通过装饰者来适应不同的需求,这些装饰者是FilterInputStream和FilterOutputStream的派生类,Reader和Writer也使用了类似的做法。不同的是BufferedOutputStream死FilterOutputStream的子类,但是BufferedWriter不是FilterWriter的子类。另外,DataInputStream不提供readLine方法,但是BufferedReader中有,除了这一点,DataInputStream还是一个很好的选择。为了使得过渡到PrintWriter容易些,他的构造器可以接收OutputStream和Writer对象,PrinteWriter的格式化接口同PrintStream基本相同。在Java SE5中,PrintWriter增加了新的构造器用来简化文件的创建,另外还可以在构造器中设置自动清除缓存,在每次println之后执行清理。
但是在Java 1.0 到Java 1.1中还是有些类是没有变化的,他们是DataOutputStream,File,RandomAccessFile和SequenceInputStream,因为DataOutputStream没有变化,因此在存储和操作传输格式的数据的时候还是使用InputStream和OutputStream层次。
RandomAccessFile
RandomAccessFile通过seek方法用来在文件内容中定位,然后读取或者改变记录,记录不用必须是一样的大小,你值需要确定插入文件中的位置和内容的大小。RandomeAccesFile不是InputStream和OutputStream的继承结构中的类。虽然他们同DataInputStream和DataOutputStream一样都继承自DataInput和DataOutput接口,但是他们之间除此之外并没有其余的相似点。他没有使用任何现有的InputStream和OutputStream,完全是一个不相关的类,因为他有着与其他IO类型完全不同的操作,可以在文件中向前和向后移动。
另外,RandomAccessFile与DataInputStream一样需要同DataOutputStream配合使用,通过getFilePoint找到在文件中的位置,seek方法移动到文件中的其他地方,leng用来决定文件的最大容量。另外构造器中需要第二个参数来指定是记性只读操作还是进行读写操作。seek方法只在RandomAccessFile中才有,且进队文件作用,BufferedInputStream也允许你使用mark方法来标记位置,也可以使用reset来返回标记的位置,但是这两个方法毕竟作用有限。
I/O流的使用
虽然IO数据流的类可以通过多种不同的组合进行使用,但是实际应用中你可能仅需要几种典型的组合,下面的例子就是一些基本的IO操作。
为了打开一个文件进行字符输入,可以使用FileInputReader,使用字符串或者File对象作为文件名,为了效率,可以使用缓存,将结果的引用传递个BufferedReader构造器,因为BufferredReader提供了readLine方法,因此最终你是从这里进行读取数据,当readLine反悔Null的时候,就说明你读到了文件的结尾:
//
: io/BufferedInputFile.java
import java.io.*
;
public
class
BufferedInputFile {
//
Throw exceptions to console:
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();
}
public
static
void main(String[] args)
throws
IOException {
System.out.print(read(“BufferedInputFile.java”
));
}
}
/*
(Execute to see output)
*///
:~
下面使用BufferedInputFile.read方法来创建StringReader,然后使用read每次读取一个字符并且输出:
//
: io/MemoryInput.java
import java.io.*
;
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);
}
}
/*
(Execute to see output)
*///
:~
为了读取格式化数据,你可以使用DataInputStream,他是基于字节的IO类,你必须使用所有InputStream相关的类而不是Reader类,而且你也可以使用InputStream读取任何文件,如下是读取字符串:
//
: io/FormattedMemoryInput.java
import java.io.*
;
public
class
FormattedMemoryInput {
public
static
void main(String[] args)
throws
IOException {
try
{
DataInputStream in =
new DataInputStream(
new ByteArrayInputStream(BufferedInputFile.read(“FormattedMemoryInput.java”
).getBytes()));
while(
true
)
System.out.print((
char
)in.readByte());
}
catch
(EOFException e) {
System.err.println(“End of stream”
);
}
}
}
/*
(Execute to see output)
*///
:~
如果使用readByte每次读取一个字节,那么任何字节都是有效的结果,因此返回的结果不能用来检测是否到了流的结尾,可以使用available方法来查看还有多少有效的字符,如下:
//
: io/TestEOF.java
//
Testing for end of file while reading a byte at a time.
import java.io.*
;
public
class
TestEOF {
public
static
void main(String[] args)
throws
IOException {
DataInputStream in =
new DataInputStream(
new BufferedInputStream(
new FileInputStream(“TestEOF.java”
)));
while(in.available() != 0
)
System.out.print((
char
)in.readByte());
}
}
/*
(Execute to see output)
*///
:~
注意这里的available根据不同的读写对象而不同,字面的理解是可以流畅读取的字节数,对于一个文件则是指整个文件,对于其他的流则可能不是这样的,因此需要谨慎使用。
FileWriter对象用来将数据写入文件,使用缓存可以提高效率,下面的例子中,是哦那个PrintWriter进行格式化,通过这个方法写入的文件可以像一般文本文件一样读取:
//
: io/BasicFileOutput.java
import java.io.*
;
public
class
BasicFileOutput {
static String file = “BasicFileOutput.out”
;
public
static
void main(String[] args)
throws
IOException {
BufferedReader in =
new BufferedReader(
new StringReader(BufferedInputFile.read(“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));
}
}
/*
(Execute to see output)
*///
:~
当最后readLine结束的时候会返回null,然后显式关闭out,因为如果没有调用close,那么因为缓存没有写入磁盘的关系,所获得文件将会不完整。另外Java SE 5中提供了PrintWriter的简单的构造器写法,这样就不用书写那么多代码来创建文本文件了,如下:
//
: io/FileOutputShortcut.java
import java.io.*
;
public
class
FileOutputShortcut {
static String file = “FileOutputShortcut.out”
;
public
static
void main(String[] args)
throws
IOException {
BufferedReader in =
new BufferedReader(
new StringReader(BufferedInputFile.read(“FileOutputShortcut.java”
)));
//
Here’s the shortcut:
PrintWriter out =
new
PrintWriter(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));
}
}
/*
(Execute to see output)
*///
:~
PrintWriter格式化数据这样更利于人们阅读,但是如果需要从其他的流中恢复数据那么就需要使用DataOutputStream来写数据,DataInputStream来恢复数据。当然这些流可以是任意格式,但是下面的例子使用了文件,对读取和写入都进行了缓存。DataOutputStream和DataInputStream都是基于字节的,因此需要InputStreams和OutputStreams:
//
: io/StoringAndRecoveringData.java
import java.io.*
;
public
class
StoringAndRecoveringData {
public
static
void main(String[] args)
throws
IOException {
DataOutputStream out =
new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(“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(“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());
}
}
/*
Output:
3.14159
That was pi
1.41413
Square root of 2
*///
:~
如果使用DataOutputStream写数据,那么Java保证了可以准确的使用DataInputStream对数据进行恢复,而不管读写的平台如何,当使用DataOutputStream来写入字符串的时候能够被DataInputStream恢复的唯一可靠的方法是使用UTF-8编码,也就是使用writeUTF和readUTF。UTF-8对于ASCII字符使用一个字节,对于非ASCII字符使用两个或三个字节,另外字符串的长度存储在UTF-8编码的前两个字节中。但是writeUTF和readUTF采用了针对Java的UTF-8编码,也就是如果想要通过非Java程序读取通过writeUTF方法写入的文件,那么需要做一些额外的工作才行。
使用RandomAccessFile同使用DataInputStream和DataOutputStream类似,另外你还可以使用seek来在文件中进行移动并且更改内容,在使用RandomAccessFile的时候,逆序清楚文件的结构这样才可以正确的操作,RandomAccessFile有特定的方法来进行读写和UTF-8操作:
//
: io/UsingRandomAccessFile.java
import java.io.*
;
public
class
UsingRandomAccessFile {
static String file = “rtest.dat”
;
static
void display()
throws
IOException {
RandomAccessFile rf =
new RandomAccessFile(file, “r”
);
for(
int i = 0; i < 7; i++
)
System.out.println(“Value ” + i + “: ” +
rf.readDouble());
System.out.println(rf.readUTF());
rf.close();
}
public
static
void main(String[] args)
throws
IOException {
RandomAccessFile rf =
new RandomAccessFile(file, “rw”
);
for(
int i = 0; i < 7; i++
)
rf.writeDouble(i*1.414
);
rf.writeUTF(“The end of the file”
);
rf.close();
display();
rf =
new RandomAccessFile(file, “rw”
);
rf.seek(5*8
);
rf.writeDouble(47.0001
);
rf.close();
display();
}
}
/*
Output:
Value 0: 0.0
Value 1: 1.414
Value 2: 2.828
Value 3: 4.242
Value 4: 5.656
Value 5: 7.069999999999999
Value 6: 8.484
The end of the file
Value 0: 0.0
Value 1: 1.414
Value 2: 2.828
Value 3: 4.242
Value 4: 5.656
Value 5: 47.0001
Value 6: 8.484
The end of the file
*///
:~
另外一个我们常用的功能就是读写文件,问题是Java的IO库需要你书写一定量的代码才能实现这个功能,没有提供基本的公用函数,更加棘手的是装饰者模式使得更加难以记住如何打开文件。因此最好是我们可以在类库中添加读写文件的公用类,可以帮助我们解决这些问题,从而减少每次操作的冗余代码。下面是一个前面用到过的TextFile类用来读写文件,包含了静态的读取和写入文件,还可以创建一个TextFile对象将文件内容存储到ArrayList中:
//
: net/mindview/util/TextFile.java
//
Static functions for reading and writing text files as
//
a single string, and treating a file as an ArrayList.
package
net.mindview.util;
import java.io.*
;
import java.util.*
;
public
class TextFile
extends ArrayList
{
//
Read a file as a single string:
public
static
String read(String fileName) {
StringBuilder sb =
new
StringBuilder();
try
{
BufferedReader in=
new BufferedReader(
new FileReader(
new
File(fileName).getAbsoluteFile()));
try
{
String s;
while((s = in.readLine()) !=
null
) {
sb.append(s);
sb.append(“n”
);
}
}
finally
{
in.close();
}
}
catch
(IOException e) {
throw
new
RuntimeException(e);
}
return
sb.toString();
}
//
Write a single file in one method call:
public
static
void
write(String fileName, String text) {
try
{
PrintWriter out =
new PrintWriter(
new
File(fileName).getAbsoluteFile());
try
{
out.print(text);
}
finally
{
out.close();
}
}
catch
(IOException e) {
throw
new
RuntimeException(e);
}
}
//
Read a file, split by any regular expression:
public
TextFile(String fileName, String splitter) {
super
(Arrays.asList(read(fileName).split(splitter)));
//
Regular expression split() often leaves an empty
//
String at the first position:
if(get(0).equals(“”)) remove(0
);
}
//
Normally read by lines:
public
TextFile(String fileName) {
this(fileName, “n”
);
}
public
void
write(String fileName) {
try
{
PrintWriter out =
new PrintWriter(
new
File(fileName).getAbsoluteFile());
try
{
for(String item :
this
)
out.println(item);
}
finally
{
out.close();
}
}
catch
(IOException e) {
throw
new
RuntimeException(e);
}
}
//
Simple test:
public
static
void
main(String[] args) {
String file = read(“TextFile.java”
);
write(“test.txt”
, file);
TextFile text =
new TextFile(“test.txt”
);
text.write(“test2.txt”
);
//
Break into unique sorted list of words:
TreeSet words =
new TreeSet(
new TextFile(“TextFile.java”, “\W+”
));
//
Display the capitalized words:
System.out.println(words.headSet(“a”
));
}
}
/*
Output:
[0, ArrayList, Arrays, Break, BufferedReader, BufferedWriter, Clean, Display, File, FileReader, FileWriter, IOException, Normally, Output, PrintWriter, Read, Regular, RuntimeException, Simple, Static, String, StringBuilder, System, TextFile, Tools, TreeSet, W, Write]
*///
:~
下面是对一个二进制文件的操作:
//
: net/mindview/util/BinaryFile.java
//
Utility for reading files in binary form.
package
net.mindview.util;
import java.io.*
;
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());
}
}
//
/:~
Java的标准IO标准中有Syste.in,System.out和System.err。System.err和System.out都是已经封装好的,但是System.int是没有封装的,也就是你可以直接使用System.ot和System.err,但是System.in必须在使用前进行封装,通常需要使用readLine一次读取一行,把System.in封装到BufferedReader,使用InputSystemStreamReader将System.in封装到Reader中:
//
: io/Echo.java
//
How to read from standard input.
//
{RunByHand}
import java.io.*
;
public
class
Echo {
public
static
void main(String[] args)
throws
IOException {
BufferedReader stdin =
new BufferedReader(
new
InputStreamReader(System.in));
String s;
while((s = stdin.readLine()) !=
null && s.length()!= 0
)
System.out.println(s);
//
An empty line or Ctrl-Z terminates the program
}
}
//
/:~
System.out也是PrintStream,是一种OutputStream,PrintWriter的构造器接收OutputStream作为参数,可以通过构造器将System.out转化为PrintWriter:
//
: io/ChangeSystemOut.java
//
Turn System.out into a PrintWriter.
import java.io.*
;
public
class
ChangeSystemOut {
public
static
void
main(String[] args) {
PrintWriter out =
new PrintWriter(System.out,
true
);
out.println(“Hello, world”
);
}
}
/*
Output:
Hello, world
*///
:~
Java中的System类允许你对标注输入进行重丁香,可以调用静态方法setIn(InputStream), setOut(PrintStream), setErr(PrintStream),重定向输出在创建大量的输出到屏幕上的时候非常有用,重定向输入在命令行程序中非常有用:
//
: io/Redirecting.java
//
Demonstrates standard I/O redirection.
import java.io.*
;
public
class
Redirecting {
public
static
void main(String[] args)
throws
IOException {
PrintStream console =
System.out;
BufferedInputStream in =
new BufferedInputStream(
new FileInputStream(“Redirecting.java”
));
PrintStream out =
new PrintStream(
new BufferedOutputStream(
new FileOutputStream(“test.out”
)));
System.setIn(in);
System.setOut(out);
System.setErr(out);
BufferedReader br =
new BufferedReader(
new
InputStreamReader(System.in));
String s;
while((s = br.readLine()) !=
null
)
System.out.println(s);
out.close();
//
Remember this!
System.setOut(console);
}
}
//
/:~
还有些情况下我们需要通过Java执行另外一个程序,然后控制程序的输入和输出,Java提供了实现这样功能的函数,一个典型的情况是执行一段程序然后将结果输出到控制台,为了执行程序相OSExecute.command方法传递执行程序的命令行字符串,然后命令行江北传递给java.lang.ProcessBuilder构造器,最后ProcessBuilder对象开始启动:
//
: net/mindview/util/OSExecute.java
//
Run an operating system command
I/O 677
//
and send the output to the console.
package
net.mindview.util;
import java.io.*
;
public
class
OSExecute {
public
static
void
command(String command) {
boolean err =
false
;
try
{
Process process =
new ProcessBuilder(command.split(” “
)).start();
BufferedReader results =
new BufferedReader(
new
InputStreamReader(process.getInputStream()));
String s;
while((s = results.readLine())!=
null
)
System.out.println(s);
BufferedReader errors =
new BufferedReader(
new
InputStreamReader(process.getErrorStream()));
//
Report errors and return nonzero value
//
to calling process if there are problems:
while((s = errors.readLine())!=
null
) {
System.err.println(s);
err =
true
;
}
}
catch
(Exception e) {
//
Compensate for Windows 2000, which throws an
//
exception for the default command line:
if(!command.startsWith(“CMD /C”
))
command(“CMD /C ” +
command);
else
throw
new
RuntimeException(e);
}
if
(err)
throw
new OSExecuteException(“Errors executing ” +
command);
}
}
//
/:~
//
: io/OSExecuteDemo.java
//
Demonstrates standard I/O redirection.
import net.mindview.util.*
;
public
class
OSExecuteDemo {
public
static
void
main(String[] args) {
OSExecute.command(“javap OSExecuteDemo”
);
}
}
/*
Output:
Compiled from “OSExecuteDemo.java”
public class OSExecuteDemo extends java.lang.Object{
678 Thinking in Java Bruce Eckel
public OSExecuteDemo();
public static void main(java.lang.String[]);
}
*///
:~
NEW I/O
JDK 1.4中添加了java.nio.*包只有一个目的,那就是速度。之前的IO包也使用了nio进行重写从而提高了效率,即使没有显式的使用nio代码也一样可以享受到高效率的代码。效率提高是因为nio使用了一种更加贴近于计算机操作系统的IO结构,隧道和缓存。我们不直接与隧道进行交互而是与缓存进行交互,由隧道进行缓存的传输。隧道可以将数据写入缓存也可以将数据清空缓存。唯一可以直接与隧道交互的类是ByteBuffer,基于字节的,从JDK的文档可以了解到java.nioByteBuffer是相当底层的,通过传入分配的空间来创建一个对象,然后通过调用方法进行压入和读取数据,可以通过字节格式也可以通过初始值数据格式。但是没有办法压入或读取独享,字符串也不可以。各种操作都相当的贴近底层,因为通过更加贴近操作系统可以提高效率。
在旧的Java IO中更改了三个类从而可以产生FileChannel:FileInputStream,FileOutputStream和用于读写的RandomAccessFile。但是他们都是基于字节的。Reader和Writer字符模型的类不生成隧道,但是java.nio.channels.Channels类有方法来生成Readers和Writers:
//
: io/GetChannel.java
//
Getting channels from streams
import java.nio.*
;
import java.nio.channels.*
;
import java.io.*
;
public
class
GetChannel {
private
static
final
int BSIZE = 1024
;
public
static
void main(String[] args)
throws
Exception {
//
Write a file:
FileChannel fc =
new FileOutputStream(“data.txt”
).getChannel();
fc.write(ByteBuffer.wrap(“Some text “
.getBytes()));
fc.close();
//
Add to the end of the file:
fc =
new RandomAccessFile(“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(“data.txt”
).getChannel();
ByteBuffer buff =
ByteBuffer.allocate(BSIZE);
fc.read(buff);
buff.flip();
while
(buff.hasRemaining())
System.out.print((
char
)buff.get());
}
}
/*
Output:
Some text Some more
*///
:~
当你调用read来通知FileChannel来存储字节到ByteBuffer的时候,你必须调用flip方法来告诉缓存来准被释放数据,如果想要使用read继续读取数据,那么每次读取的时候都应该调用clear来进行准备,如下:
//
: io/ChannelCopy.java
//
Copying a file using channels and buffers
//
{Args: ChannelCopy.java test.txt}
import java.nio.*
;
import java.nio.channels.*
;
import java.io.*
;
public
class
ChannelCopy {
private
static
final
int BSIZE = 1024
;
public
static
void main(String[] args)
throws
Exception {
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
}
}
}
//
/:~
我们创建了两个FileChannel,一个用来读取一个用来写入,放FileChannel.read反悔-1的时候表示到了输入的结尾,每次read之后将数据压入到缓存,filp用来设置缓存到准备状态这样可以将数据开始写入,在write方法之后,信息还是在缓存内,通过clear来充值所有的内部指针从而使得可以进行另外一次read。但是上面的程序并不是理想的操作方式,下面我们创建transferTo和transferFrom来进行隧道的连接:
//
: io/TransferTo.java
//
Using transferTo() between channels
//
{Args: TransferTo.java TransferTo.txt}
import java.nio.channels.*
;
import java.io.*
;
public
class
TransferTo {
public
static
void main(String[] args)
throws
Exception {
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();
in.transferTo(0
, in.size(), out);
//
Or:
//
out.transferFrom(in, 0, in.size());
}
}
//
/:~
如果查看GetChannel.java,我们会发现为了将信息写入文件,我们是将拿出来的字节转换为字符,如果查看java.nio.CharBuffer类,可以看到他有yieldtoString方法来直接将缓存中的数据返回字符串,因为ByteBuffer可以当作CharBuffer看带,因为有asCharBuffer方法,为什么不那样使用呢?请看下面的代码为何行不通:
//
: io/BufferToText.java
//
Converting text to and from ByteBuffers
import java.nio.*
;
import java.nio.channels.*
;
import java.nio.charset.*
;
import java.io.*
;
public
class
BufferToText {
private
static
final
int BSIZE = 1024
;
public
static
void main(String[] args)
throws
Exception {
FileChannel fc =
new FileOutputStream(“data2.txt”
).getChannel();
fc.write(ByteBuffer.wrap(“Some text”
.getBytes()));
fc.close();
fc =
new FileInputStream(“data2.txt”
).getChannel();
ByteBuffer buff =
ByteBuffer.allocate(BSIZE);
fc.read(buff);
buff.flip();
//
Doesn’t work:
System.out.println(buff.asCharBuffer());
//
Decode using this system’s default Charset:
buff.rewind();
String encoding = System.getProperty(“file.encoding”
);
System.out.println(“Decoded using ” + encoding + “: ” +
Charset.forName(encoding).decode(buff));
//
Or, we could encode with something that will print:
fc =
new FileOutputStream(“data2.txt”
).getChannel();
fc.write(ByteBuffer.wrap(“Some text”.getBytes(“UTF-16BE”
)));
fc.close();
//
Now try reading again:
fc =
new FileInputStream(“data2.txt”
).getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
//
Use a CharBuffer to write through:
fc =
new FileOutputStream(“data2.txt”
).getChannel();
buff = ByteBuffer.allocate(24);
//
More than needed
buff.asCharBuffer().put(“Some text”
);
fc.write(buff);
fc.close();
//
Read and display:
fc =
new FileInputStream(“data2.txt”
).getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
}
}
/*
Output:
????
Decoded using Cp1252: Some text
Some text
Some text
*///
:~
缓存中存放的是字节数据,当压入的时候需要进行编码,取出的时候进行解码,可以通过使用java.nio.charset.Charset类提供的编码工具来进行:
//
: io/AvailableCharSets.java
//
Displays Charsets and aliases
import java.nio.charset.*
;
import java.util.*
;
import
static net.mindview.util.Print.*
;
public
class
AvailableCharSets {
public
static
void
main(String[] args) {
SortedMap charSets =
Charset.availableCharsets();
Iterator it =
charSets.keySet().iterator();
while
(it.hasNext()) {
String csName =
it.next();
printnb(csName);
Iterator aliases =
charSets.get(csName).aliases().iterator();
if
(aliases.hasNext())
printnb(“: “
);
while
(aliases.hasNext()) {
printnb(aliases.next());
if
(aliases.hasNext())
printnb(“, “
);
}
print();
}
}
}
/*
Output:
Big5: csBig5
Big5-HKSCS: big5-hkscs, big5hk, big5-hkscs:unicode3.0, big5hkscs, Big5_HKSCS
EUC-JP: eucjis, x-eucjp, csEUCPkdFmtjapanese, eucjp, Extended_UNIX_Code_Packed_Format_for_Japanese, x-euc-jp, euc_jp
EUC-KR: ksc5601, 5601, ksc5601_1987, ksc_5601, ksc5601-1987, euc_kr, ks_c_5601-1987, euckr, csEUCKR
GB18030: gb18030-2000
GB2312: gb2312-1980, gb2312, EUC_CN, gb2312-80, euc-cn, euccn, x-EUC-CN
GBK: windows-936, CP936
…
*///
:~
虽然ByteBufffer承载的是字节数据,但是也提供了方法可以用来将字节转换成不同类型的初始化数据,如下:
//
: io/GetData.java
//
Getting different representations from a ByteBuffer
import java.nio.*
;
import
static net.mindview.util.Print.*
;
public
class
GetData {
private
static
final
int BSIZE = 1024
;
public
static
void
main(String[] args) {
ByteBuffer bb =
ByteBuffer.allocate(BSIZE);
//
Allocation automatically zeroes the ByteBuffer:
int i = 0
;
while(i++ <
bb.limit())
if(bb.get() != 0
)
print(“nonzero”
);
print(“i = ” +
i);
bb.rewind();
//
Store and read a char array:
bb.asCharBuffer().put(“Howdy!”
);
char
c;
while((c = bb.getChar()) != 0
)
printnb(c + ” “
);
print();
bb.rewind();
//
Store and read a short:
bb.asShortBuffer().put((
short)471142
);
print(bb.getShort());
bb.rewind();
//
Store and read an int:
bb.asIntBuffer().put(99471142
);
print(bb.getInt());
bb.rewind();
//
Store and read a long:
bb.asLongBuffer().put(99471142
);
print(bb.getLong());
bb.rewind();
//
Store and read a float:
bb.asFloatBuffer().put(99471142
);
print(bb.getFloat());
bb.rewind();
//
Store and read a double:
bb.asDoubleBuffer().put(99471142
);
print(bb.getDouble());
bb.rewind();
}
}
/*
Output:
i = 1025
H o w d y !
12390
99471142
99471142
9.9471144E7
9.9471142E7
684 Thinking in Java Bruce Eckel
*///
:~
缓存师徒可以允许你通过特定初始值类型的窗口查看底层的ByteBuffer信息,还可以允许你读取ByteBuffer中的初始值类型的值。
//
: io/IntBufferDemo.java
//
Manipulating ints in a ByteBuffer with an IntBuffer
import java.nio.*
;
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);
}
}
}
/*
Output:
99
11
42
47
1811
143
811
1016
*///
:~
一旦ByteBuffer是通过int或者其他的初始值类型的缓存师徒填充后,这个ByteBuffer可以直接写入隧道,我们可以直接从隧道读取,使用缓存师徒转换到特定的初始值类型,如下:
//
: io/ViewBuffers.java
import java.nio.*
;
import
static net.mindview.util.Print.*
;
public
class
ViewBuffers {
public
static
void
main(String[] args) {
ByteBuffer bb = ByteBuffer.wrap(
new
byte[]{ 0, 0, 0, 0, 0, 0, 0
, ‘a’ });
bb.rewind();
printnb(“Byte Buffer “
);
while
(bb.hasRemaining())
printnb(bb.position()+ ” -> ” + bb.get() + “, “
);
print();
CharBuffer cb =
((ByteBuffer)bb.rewind()).asCharBuffer();
printnb(“Char Buffer “
);
while
(cb.hasRemaining())
printnb(cb.position() + ” -> ” + cb.get() + “, “
);
print();
FloatBuffer fb =
((ByteBuffer)bb.rewind()).asFloatBuffer();
printnb(“Float Buffer “
);
while
(fb.hasRemaining())
printnb(fb.position()+ ” -> ” + fb.get() + “, “
);
print();
IntBuffer ib =
((ByteBuffer)bb.rewind()).asIntBuffer();
printnb(“Int Buffer “
);
while
(ib.hasRemaining())
printnb(ib.position()+ ” -> ” + ib.get() + “, “
);
print();
LongBuffer lb =
((ByteBuffer)bb.rewind()).asLongBuffer();
printnb(“Long Buffer “
);
while
(lb.hasRemaining())
printnb(lb.position()+ ” -> ” + lb.get() + “, “
);
print();
ShortBuffer sb =
((ByteBuffer)bb.rewind()).asShortBuffer();
printnb(“Short Buffer “
);
while
(sb.hasRemaining())
printnb(sb.position()+ ” -> ” + sb.get() + “, “
);
print();
DoubleBuffer db =
((ByteBuffer)bb.rewind()).asDoubleBuffer();
printnb(“Double Buffer “
);
while
(db.hasRemaining())
printnb(db.position()+ ” -> ” + db.get() + “, “
);
}
}
/*
Output:
Byte Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 5 -> 0, 6 -> 0, 7 -> 97,
Char Buffer 0 -> , 1 -> , 2 -> , 3 -> a,
Float Buffer 0 -> 0.0, 1 -> 1.36E-43,
Int Buffer 0 -> 0, 1 -> 97,
Long Buffer 0 -> 97,
686 Thinking in Java Bruce Eckel
Short Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 97,
Double Buffer 0 -> 4.8E-322,
*///
:~
ByteBuffer通过封装八个字节数组,然后通过缓存师徒显式不同的初始值类型,如下面的图片所示:

不同的激起对待字节的排序方法是不同的,大尾数据表示权重高的字节放在地位的内存地址,小尾表示权重高的字节放在高位的内存地址。当存储大于一个字节的数据类型的时候,我们需要考虑字节排序,ByteBuffer的字节排序是使用大尾方式,而且在网络上传输数据的时候噎死使用大尾排序,你可以使用order方法来更改ByteBuffer的字节排序方法,可选的参数有ByteOrderBIG_ENDIAN或者是ByteOrderLITTLE_ENDIAN。如下面的字节0000000001100001,如果按照short进行读取则结果是97,如果按照大尾读取,则结果是24832。下面是一个例子:
//
: io/Endians.java
//
Endian differences and data storage.
import java.nio.*
;
import java.util.*
;
import
static net.mindview.util.Print.*
;
public
class
Endians {
public
static
void
main(String[] args) {
ByteBuffer bb = ByteBuffer.wrap(
new
byte[12
]);
bb.asCharBuffer().put(“abcdef”
);
print(Arrays.toString(bb.array()));
bb.rewind();
bb.order(ByteOrder.BIG_ENDIAN);
bb.asCharBuffer().put(“abcdef”
);
print(Arrays.toString(bb.array()));
bb.rewind();
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.asCharBuffer().put(“abcdef”
);
print(Arrays.toString(bb.array()));
}
}
/*
Output:
[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]
[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]
[97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0]
*///
:~
下面是一个nio类中的关系图,可以方便我们用来转换数据:

注意这里ByteBuffer是移入和移除隧道的唯一放哪广发,只能创建初始值类型的缓存或者使用as从ByteBuffer中获取,也就是你不能转换初始值类型的缓存到ByteBuffer,但是因为你可以通过试图缓存移动初始值数据进入和移出BuyteBuffer,因此这几乎不算是什么限制了。
缓存中一些获取和修改数据的有效的方法,如下面的例子:
//
: io/UsingBuffers.java
import java.nio.*
;
import
static net.mindview.util.Print.*
;
public
class
UsingBuffers {
private
static
void
symmetricScramble(CharBuffer buffer){
while
(buffer.hasRemaining()) {
buffer.mark();
char c1 =
buffer.get();
char c2 =
buffer.get();
buffer.reset();
buffer.put(c2).put(c1);
}
}
public
static
void
main(String[] args) {
char[] data = “UsingBuffers”
.toCharArray();
ByteBuffer bb = ByteBuffer.allocate(data.length * 2
);
CharBuffer cb =
bb.asCharBuffer();
cb.put(data);
print(cb.rewind());
symmetricScramble(cb);
print(cb.rewind());
symmetricScramble(cb);
print(cb.rewind());
}
}
/*
Output:
UsingBuffers
sUniBgfuefsr
690 Thinking in Java Bruce Eckel
UsingBuffers
*///
:~
虽然我们可以直接调用字符数组的wrap方法来生成一个CharBuffer,但是实际上是创建了一个ByteBuffer, CharBuffer仅仅是用来作为接口对ByteBuffer进行操作。当通过symmetricScramble方法进入缓存的时候,postion表示缓存的第一个元素,capacity和limit指向最后一个元素。在symmetricScramble方法中while循环进行历遍知道position到达limit的位置,当调用get和put函数的时候更改了position在缓存中的指向。你还可以调用get和put方法并且传入索引参数,用来指定get和put发生的位置,但是这并不会改变缓存中的position。当控制进入while循环的时候mark的值通过mark方法进行设定到开头,通过两个get方法将前两个字符保存到c1和c2,然后position指向了第三个字符的位置,为了执行交换,需要在position=0的地方写入c2,在position=1的地方写入c1,我们可以调用put方法来实现也可以将position设置到mark的位置,也就是reset所做的事情;两个put方法写入c2和c1,在下一个循环的时候mark设置到当前position的位置。整个过程持续到历遍完整个buffer,在最后position设置到buffer的尾部,如果想要带你buffer,自由在position和limit之间的内容会被打印,因此如果你想要显式整个缓存内容,你必须将positio设置到buffer的开头,可以使用rewind方法。
内存映射文件可以允许创建和修改对于内存来说非常大的文件,通过内存映射文件,我们可以不用将整个文件放入内存中,而是像操作大容量数组一样来进行操作,这样可以极大简化我们需要书写的代码:
//
: io/LargeMappedFiles.java
//
Creating a very large file using mapping.
//
{RunByHand}
import java.nio.*
;
import java.nio.channels.*
;
import java.io.*
;
import
static net.mindview.util.Print.*
;
public
class
LargeMappedFiles {
static
int length = 0x8FFFFFF;
//
128 MB
public
static
void main(String[] args)
throws
Exception {
MappedByteBuffer out =
new RandomAccessFile(“test.dat”, “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 + 6; i++
)
printnb((
char
)out.get(i));
}
}
//
/:~
虽然老式的流式的IO通过nio进行了效率的改进,而且映射文件看起来也是非常快,下面的程序做了一个性能比较:
//
: io/MappedIO.java
import java.nio.*
;
import java.nio.channels.*
;
import java.io.*
;
public
class
MappedIO {
private
static
int numOfInts = 4000000
;
private
static
int numOfUbuffInts = 200000
;
private
abstract
static
class
Tester {
private
String name;
public Tester(String name) {
this.name =
name; }
public
void
runTest() {
System.out.print(name + “: “
);
try
{
long start =
System.nanoTime();
test();
double duration = System.nanoTime() -
start;
System.out.format(“%.2fn”, duration/1.0e9
);
}
catch
(IOException e) {
throw
new
RuntimeException(e);
}
}
public
abstract
void test()
throws
IOException;
}
private
static Tester[] tests =
{
new Tester(“Stream Write”
) {
public
void test()
throws
IOException {
DataOutputStream dos =
new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(
new File(“temp.tmp”
))));
for(
int i = 0; i < numOfInts; i++
)
dos.writeInt(i);
dos.close();
}
},
new Tester(“Mapped Write”
) {
public
void test()
throws
IOException {
FileChannel fc =
new RandomAccessFile(“temp.tmp”, “rw”
).getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0
, fc.size()).asIntBuffer();
for(
int i = 0; i < numOfInts; i++
)
ib.put(i);
fc.close();
}
},
new Tester(“Stream Read”
) {
public
void test()
throws
IOException {
DataInputStream dis =
new DataInputStream(
new BufferedInputStream(
new FileInputStream(“temp.tmp”
)));
for(
int i = 0; i < numOfInts; i++
)
dis.readInt();
dis.close();
}
},
new Tester(“Mapped Read”
) {
public
void test()
throws
IOException {
FileChannel fc =
new FileInputStream(
new File(“temp.tmp”
)).getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_ONLY, 0
, fc.size()).asIntBuffer();
while
(ib.hasRemaining())
ib.get();
fc.close();
}
},
new Tester(“Stream Read/Write”
) {
public
void test()
throws
IOException {
RandomAccessFile raf =
new RandomAccessFile(
new File(“temp.tmp”), “rw”
);
raf.writeInt(1
);
for(
int i = 0; i < numOfUbuffInts; i++
) {
raf.seek(raf.length() - 4
);
raf.writeInt(raf.readInt());
}
raf.close();
}
},
new Tester(“Mapped Read/Write”
) {
public
void test()
throws
IOException {
FileChannel fc =
new RandomAccessFile(
new File(“temp.tmp”), “rw”
).getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0
, fc.size()).asIntBuffer();
ib.put(0
);
for(
int i = 1; i < numOfUbuffInts; i++
)
ib.put(ib.get(i - 1
));
fc.close();
}
}
};
public
static
void
main(String[] args) {
for
(Tester test : tests)
test.runTest();
}
}
/*
Output: (90% match)
Stream Write: 0.56
Mapped Write: 0.12
Stream Read: 0.80
Mapped Read: 0.07
Stream Read/Write: 5.32
Mapped Read/Write: 0.02
*///
:~
从测试结果中可以看到虽然映射文件的创建很消耗资源,但是总体的性能优势还是非常明显的。
文件锁定可以允许你同步文件操作,使其作为一种共享资源。两个线程可以是不同的JVM内,或者一个在JVM内一个是操作系统的进程,文件锁定对于其他操作系统的进程是可见的,因为Java文件锁定直接映射到本地操作系统的锁定机制,如下:
//
: io/FileLocking.java
import java.nio.channels.*
;
import java.util.concurrent.*
;
import java.io.*
;
public
class
FileLocking {
public
static
void main(String[] args)
throws
Exception {
FileOutputStream fos=
new FileOutputStream(“file.txt”
);
FileLock fl =
fos.getChannel().tryLock();
if(fl !=
null
) {
System.out.println(“Locked File”
);
TimeUnit.MILLISECONDS.sleep(100
);
fl.release();
System.out.println(“Released Lock”
);
}
fos.close();
}
}
/*
Output:
Locked File
Released Lock
*///
:~
可以通过FileLock来锁定整个文件,通过调用tryLock或者lock方法来对FileChannel进行锁定,如果失败则直接从方法中反悔,lock的释放可以通过FileLock.release,还可以锁定文件的一部分tryLock(long position, long size, boolean shared); 或者lock(long position, long size, boolean shared),第三个参数表示锁是不是共享锁。虽然没有参数的锁定方法可以适应文件大小的改变,但是固定内容大小的锁定不跟随文件大小的改变而改变,如果一个锁锁定了position到position+size的内容,而期间文件在position+size的后面增减了内容,那么超过position+size的内容是没有被锁定的,而没有参数的锁定则锁定整个文件,即使文件的内容动态增加。
操作系统底层可能提供了共享锁或者排他锁,如果操作系统不支持共享锁,那么在请求共享锁的时候将会返回一个排他锁,锁的类型可以通过FileLock.isShared进行查询。
文件映射通常用来较大的文件,那么我们可以锁定文件的部分内容,那么其他的进程就可以修改其他没有锁定的部分,这个在数据库等的设计中死经常用到的,如下面的一个例子:
//
: io/LockingMappedFiles.java
//
Locking portions of a mapped file.
//
{RunByHand}
import java.nio.*
;
import java.nio.channels.*
;
import java.io.*
;
public
class
LockingMappedFiles {
static
final
int LENGTH = 0x8FFFFFF;
//
128 MB
static
FileChannel fc;
public
static
void main(String[] args)
throws
Exception {
fc =
new RandomAccessFile(“test.dat”, “rw”
).getChannel();
MappedByteBuffer out = fc.map(FileChannel.MapMode.READ_WRITE, 0
, LENGTH);
for(
int i = 0; i < LENGTH; i++
)
out.put((
byte
)’x’);
new LockAndModify(out, 0, 0 + LENGTH/3
);
new LockAndModify(out, LENGTH/2, LENGTH/2 + LENGTH/4
);
}
private
static
class LockAndModify
extends
Thread {
private
ByteBuffer buff;
private
int
start, end;
LockAndModify(ByteBuffer mbb,
int start,
int
end) {
this.start =
start;
this.end =
end;
mbb.limit(end);
mbb.position(start);
buff =
mbb.slice();
start();
}
public
void
run() {
try
{
//
Exclusive lock with no overlap:
FileLock fl = fc.lock(start, end,
false
);
System.out.println(“Locked: “+ start +” to “+
end);
//
Perform modification:
while(buff.position() < buff.limit() – 1
)
buff.put((
byte)(buff.get() + 1
));
fl.release();
System.out.println(“Released: “+start+” to “+
end);
}
catch
(IOException e) {
throw
new
RuntimeException(e);
}
}
}
}
//
/:~
压缩
Java IO中包含了一些支持使用压缩格式来进行文件读写流的类,这些类不是从Reader和Writer继承而来的,而是InputStream和OutputStream继承中的一部分,因为这些压缩类库是基于字节而不是字符的,但是有些时候我们不得不混合使用两种数据流。
GZIP接口非常简单,适用于单个数据流数据的压缩,如下:
//
: io/GZIPcompress.java
//
{Args: GZIPcompress.java}
import java.util.zip.*
;
import java.io.*
;
public
class
GZIPcompress {
public
static
void main(String[] args)
throws
IOException {
if(args.length == 0
) {
System.out.println(“Usage: nGZIPcompress filen” + “tUses GZIP compression to compress ” + “the file to test.gz”
);
System.exit(1
);
}
BufferedReader in =
new BufferedReader(
new FileReader(args[0
]));
BufferedOutputStream out =
new BufferedOutputStream(
new GZIPOutputStream(
new FileOutputStream(“test.gz”
)));
System.out.println(“Writing file”
);
int
c;
while((c = in.read()) != -1
)
out.write(c);
in.close();
out.close();
System.out.println(“Reading file”
);
BufferedReader in2 =
new BufferedReader(
new InputStreamReader(
new GZIPInputStream(
new FileInputStream(“test.gz”
))));
String s;
while((s = in2.readLine()) !=
null
)
System.out.println(s);
}
}
/*
(Execute to see output)
*///
:~
支持Zip格式的类库提供了更多的扩展性,你可以轻易存储多个文件,还有另外的类可以使得阅读Zip文件非常便捷。类库使用了标准的Zip格式,因此可以与所有的Zip工具无缝对接使用,下面的例子与上面的例子类似,但是可以接收任意多的命令行参数,另外还展示了Checksum类来计算压缩文件,有两个Checksum类型Adler32和CRC32:
//
: io/ZipCompress.java
//
Uses Zip compression to compress any
//
number of files given on the command line.
//
{Args: ZipCompress.java}
import java.util.zip.*
;
import java.io.*
;
import java.util.*
;
import
static net.mindview.util.Print.*
;
public
class
ZipCompress {
public
static
void main(String[] args)
throws
IOException {
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();
//
Checksum valid only after the file has been closed!
print(“Checksum: ” +
csum.getChecksum().getValue());
//
Now extract the files:
print(“Reading file”
);
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();
//
Alternative way to open and read Zip files:
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
}
/*
if(args.length == 1)
*/
}
}
/*
(Execute to see output)
*///
:~
对象序列化
对象通常是在我们需要的时候存在,当我们不需要或者程序终止运行的时候不存在,但是有些情况下我们想要让我们的对象在程序关闭的情况下依然保存一些信息,这样当下次程序启动的时候可以直接使用这些信息,当然我么可以选择读写文件或者使用数据库,但是本着面向对象的原则,有些时候我们还是希望对对象进行持久化。Java的序列化对任何继承了Serializeable接口的对象都可以转化为字节序列然后在稍后在恢复成出事的状态,而且是可以通过网络跨平台的。
序列化是一种轻量级的对象持久技术,对象持久技术就是说对象的生命周期不收程序是否运行的影响,通过将对象序列化然后在恢复,可以使得对象的生命周期在程序运行之间进行延续。之所以称之为轻量级是因为他不是说使用类似于持久化的关键字然后操作系统就会为你处理好所有的事情,而是需要你手动书写很多的代码。如果你需要更加严格的持久化机制可以考虑使用一些工具,例如Hibernate。
序列化对象非常需要创建一些OutputStream对象,然后封装到ObjectOutputStream对象中,这里我们值需要调用writeObject方法,然后你的对象就被序列化并且发送到OutputStream中了,为了执行反向过程,你需要封装InputStream到ObjectInputStream中,然后调用readObject方法,返回的是上塑造型后的Object对象,你可以直接转换为你需要的对象。序列化还有一个非常人性化的地方那个就是不仅仅保存了被序列化的对象的信息,还保存了对象中包含和涉及到的所有对象的信息,如下:
//
: io/Worm.java
//
Demonstrates object serialization.
import java.io.*
;
import java.util.*
;
import
static net.mindview.util.Print.*
;
class Data
implements
Serializable {
private
int
n;
public Data(
int n) {
this.n =
n; }
public String toString() {
return
Integer.toString(n); }
}
public
class Worm
implements
Serializable {
private
static Random rand =
new Random(47
);
private Data[] d = {
new Data(rand.nextInt(10)),
new Data(rand.nextInt(10)),
new Data(rand.nextInt(10
))};
private
Worm next;
private
char
c;
//
Value of i == number of segments
public Worm(
int i,
char
x) {
print(“Worm constructor: ” +
i);
c =
x;
if(–i > 0
)
next =
new Worm(i, (
char)(x + 1
));
}
public
Worm() {
print(“Default constructor”
);
}
public
String toString() {
StringBuilder result =
new StringBuilder(“:”
);
result.append(c);
result.append(“(“
);
for
(Data dat : d)
result.append(dat);
result.append(“)”
);
if(next !=
null
)
result.append(next);
return
result.toString();
}
public
static
void main(String[] args)
throws
ClassNotFoundException, IOException {
Worm w =
new Worm(6
, ‘a’);
print(“w = ” +
w);
ObjectOutputStream out =
new ObjectOutputStream(
new FileOutputStream(“worm.out”
));
out.writeObject(“Worm storagen”
);
out.writeObject(w);
out.close();
//
Also flushes output
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream(“worm.out”
));
String s =
(String)in.readObject();
Worm w2 =
(Worm)in.readObject();
print(s + “w2 = ” +
w2);
ByteArrayOutputStream bout =
new
ByteArrayOutputStream();
ObjectOutputStream out2 =
new
ObjectOutputStream(bout);
out2.writeObject(“Worm storagen”
);
out2.writeObject(w);
out2.flush();
ObjectInputStream in2 =
new ObjectInputStream(
new
ByteArrayInputStream(bout.toByteArray()));
s =
(String)in2.readObject();
Worm w3 =
(Worm)in2.readObject();
print(s + “w3 = ” +
w3);
}
}
/*
Output:
Worm constructor: 6
Worm constructor: 5
Worm constructor: 4
Worm constructor: 3
Worm constructor: 2
Worm constructor: 1
w = :a(853):b(119):c(802):d(788):e(199):f(881)
Worm storage
w2 = :a(853):b(119):c(802):d(788):e(199):f(881)
Worm storage
w3 = :a(853):b(119):c(802):d(788):e(199):f(881)
*///
:~
那么仅仅依靠序列化文件的内容就可以反序列化出对象吗?下面我们实验一下,首先在目录下创建如下文件:
//
: io/Alien.java
//
A serializable class.
import java.io.*
;
public
class Alien
implements Serializable {}
//
/:~
然后在同一个目录下创建Alien对象的序列化代码:
//
: io/FreezeAlien.java
//
Create a serialized output file.
import java.io.*
;
public
class
FreezeAlien {
public
static
void main(String[] args)
throws
Exception {
ObjectOutput out =
new ObjectOutputStream(
new FileOutputStream(“X.file”
));
Alien quellek =
new
Alien();
out.writeObject(quellek);
}
}
//
/:~
当上面的程序运行后在同一个目录下创建了一个X.file文件,然后在同一个目录下创建一个子目录xfiles,在里面添加下面的代码文件:
//
: io/xfiles/ThawAlien.java
//
Try to recover a serialized file without the
//
class of object that’s stored in that file.
//
{RunByHand}
import java.io.*
;
public
class
ThawAlien {
public
static
void main(String[] args)
throws
Exception {
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream(
new File(“..”, “X.file”
)));
Object mystery =
in.readObject();
System.out.println(mystery.getClass());
}
}
/*
Output:
class Alien
*///
:~
我们可以看到虽然打开文件并且反序列化Alien对象的时候,JVM找不到Alien.class,也就是说JVM必须在有相关.class文件的时候才能进行反序列化。
下面我们考虑一种情况,如果类中有些信息我们不想要进行序列化呢,例如处于安全的考虑,或者某些内容需要重新初始化,那么我们如何进行控制呢?Externalizable接口结成了Serializable接口,并且添加了writeExternal和readExternal方法,这样就可以自动对你的对象的序列化和反序列化进行控制了,如下面的例子:
//
: io/Blips.java
//
Simple use of Externalizable & a pitfall.
import java.io.*
;
import
static net.mindview.util.Print.*
;
class Blip1
implements
Externalizable {
public
Blip1() {
print(“Blip1 Constructor”
);
}
public
void writeExternal(ObjectOutput out)
throws
IOException {
print(“Blip1.writeExternal”
);
}
public
void readExternal(ObjectInput in)
throws
IOException, ClassNotFoundException {
print(“Blip1.readExternal”
);
}
}
class Blip2
implements
Externalizable {
Blip2() {
print(“Blip2 Constructor”
);
}
public
void writeExternal(ObjectOutput out)
throws
IOException {
print(“Blip2.writeExternal”
);
}
public
void readExternal(ObjectInput in)
throws
IOException, ClassNotFoundException {
print(“Blip2.readExternal”
);
}
}
public
class
Blips {
public
static
void main(String[] args)
throws
IOException, ClassNotFoundException {
print(“Constructing objects:”
);
Blip1 b1 =
new
Blip1();
Blip2 b2 =
new
Blip2();
ObjectOutputStream o =
new ObjectOutputStream(
new FileOutputStream(“Blips.out”
));
print(“Saving objects:”
);
o.writeObject(b1);
o.writeObject(b2);
o.close();
//
Now get them back:
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream(“Blips.out”
));
print(“Recovering b1:”
);
b1 =
(Blip1)in.readObject();
//
OOPS! Throws an exception:
//
! print(“Recovering b2:”);
//
! b2 = (Blip2)in.readObject();
}
}
/*
Output:
Constructing objects:
Blip1 Constructor
Blip2 Constructor
Saving objects:
Blip1.writeExternal
Blip2.writeExternal
Recovering b1:
Blip1 Constructor
Blip1.readExternal
*///
:~
与从Serializable对象反序列化不同的是,Serializable对象是完全从存储的字节中恢复,不需要构造函数,但是Externalizable对象所有正常的默认构造函数都会发生,包括域定义的时候的初始化,然后调研那个readExternal方法,我们需要注意的时候所有的默认构造函数总是会被调用,用来反序列化Externalizable对象。下面的代码显示了序列化和范序列化Externalizable对象所必须完成的事情:
//
: io/Blip3.java
//
Reconstructing an externalizable object.
import java.io.*
;
import
static net.mindview.util.Print.*
;
public
class Blip3
implements
Externalizable {
private
int
i;
private String s;
//
No initialization
public
Blip3() {
print(“Blip3 Constructor”
);
//
s, i not initialized
}
public Blip3(String x,
int
a) {
print(“Blip3(String x, int a)”
);
s =
x;
i =
a;
//
s & i initialized only in non-default constructor.
}
public String toString() {
return s +
i; }
public
void writeExternal(ObjectOutput out)
throws
IOException {
print(“Blip3.writeExternal”
);
//
You must do this:
out.writeObject(s);
out.writeInt(i);
}
public
void readExternal(ObjectInput in)
throws
IOException, ClassNotFoundException {
print(“Blip3.readExternal”
);
//
You must do this:
s =
(String)in.readObject();
i =
in.readInt();
}
public
static
void main(String[] args)
throws
IOException, ClassNotFoundException {
print(“Constructing objects:”
);
Blip3 b3 =
new Blip3(“A String “, 47
);
print(b3);
ObjectOutputStream o =
new ObjectOutputStream(
new FileOutputStream(“Blip3.out”
));
print(“Saving object:”
);
o.writeObject(b3);
o.close();
//
Now get it back:
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream(“Blip3.out”
));
print(“Recovering b3:”
);
b3 =
(Blip3)in.readObject();
print(b3);
}
}
/*
Output:
Constructing objects:
Blip3(String x, int a)
A String 47
Saving object:
Blip3.writeExternal
Recovering b3:
Blip3 Constructor
Blip3.readExternal
A String 47
*///
:~
属性i和s只在第二个构造函数中进行了初始化,因此如果在readExternal中没有对s和i进行初始化,那么s将会为nll而i则为0。如果你的类继承自另外一个Externalizable对象,那么你还需要在序列化和反序列化的时候调用基类的writeExternal和readExternal方法。
如果类中有些对象包含了敏感信息,不希望别人通过反序列化文件或者拦截网络通信信息来获取的话,一种方法是通过上面的Externalizable来手动选择哪些属性进行序列化和反序列化,对于Serializable对象,所有的属性包括私有的都会进行自动的序列化和反序列化,这个时候可以使用transient关键字来标识需要在序列化和反序列化过程中忽略的字段,如下:
//
: io/Logon.java
//
Demonstrates the “transient” keyword.
import java.util.concurrent.*
;
import java.io.*
;
import java.util.*
;
import
static net.mindview.util.Print.*
;
public
class Logon
implements
Serializable {
private Date date =
new
Date();
private
String username;
private
transient
String password;
public
Logon(String name, String pwd) {
username =
name;
password =
pwd;
}
public
String toString() {
return “logon info: n username: ” + username + “n date: ” + date + “n password: ” +
password;
}
public
static
void main(String[] args)
throws
Exception {
Logon a =
new Logon(“Hulk”, “myLittlePony”
);
print(“logon a = ” +
a);
ObjectOutputStream o =
new ObjectOutputStream(
new FileOutputStream(“Logon.out”
));
o.writeObject(a);
o.close();
TimeUnit.SECONDS.sleep(1);
//
Delay
//
Now get them back:
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream(“Logon.out”
));
print(“Recovering object at ” +
new
Date());
a =
(Logon)in.readObject();
print(“logon a = ” +
a);
}
}
/*
Output: (Sample)
logon a = logon info:
username: Hulk
date: Sat Nov 19 15:03:26 MST 2005
password: myLittlePony
Recovering object at Sat Nov 19 15:03:28 MST 2005
logon a = logon info:
username: Hulk
date: Sat Nov 19 15:03:26 MST 2005
password: null
*///
:~
如果以为某种原因不想使用Externalizable接口,那么可以继承Serializable接口,容纳后添加writeObject和readObject方法,那么这两个方法将会取代默认的序列化方法在序列化的时候被调用,但是这两个方法必须是下面的格式:
private
void writeObject(ObjectOutputStream stream)
throws
IOException;
private
void readObject(ObjectlnputStream stream)
throws IOException, ClassNotFoundException
这里设计比较奇怪的就是他们都是私有的,按照道理来说只能在类的内部进行调用,但是实际上他么并没有在类的内部进行调用,而是被ObjectOutputStream和ObjectInputStream所调用,至于他们是如何调用的,我也不知道了。可能的设计一个该市唉调用ObjectOutputStream.writeObject的时候使用反序列化来查看传递的对象是否包含了writeObject方法,如果有则调用,如果没有则使用默认的方法。另外在writeObject中可以调用defaultWriteObject这个默认的序列化方法,也可以使用自定义的方法,对于readObject方法也是一样的,看下面的例子:
//
: io/SerialCtl.java
//
Controlling serialization by adding your own
//
writeObject() and readObject() methods.
import java.io.*
;
public
class SerialCtl
implements
Serializable {
private
String a;
private
transient
String b;
public
SerialCtl(String aa, String bb) {
a = “Not Transient: ” +
aa;
b = “Transient: ” +
bb;
}
public String toString() {
return a + “n” +
b; }
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();
}
public
static
void main(String[] args)
throws
IOException, ClassNotFoundException {
SerialCtl sc =
new SerialCtl(“Test1″, “Test2″
);
System.out.println(“Before:n” +
sc);
ByteArrayOutputStream buf=
new
ByteArrayOutputStream();
ObjectOutputStream o =
new
ObjectOutputStream(buf);
o.writeObject(sc);
//
Now get it back:
ObjectInputStream in =
new ObjectInputStream(
new
ByteArrayInputStream(buf.toByteArray()));
SerialCtl sc2 =
(SerialCtl)in.readObject();
System.out.println(“After:n” +
sc2);
}
}
/*
Output:
Before:
Not Transient: Test1
Transient: Test2
After:
Not Transient: Test1
Transient: Test2
*///
:~
下面我们的问题是如果我们序列化的两个对象都有对第三个对象的引用会如何?当反序列化的时候我们是得到第一个被引用的第三个对象吗?如果我饿美女序列化两个对象到不同的文件,然后在代码的不同的地方进行反序列化又是什么结果?看下面的测试代码:
//
: io/MyWorld.java
import java.io.*
;
import java.util.*
;
import
static net.mindview.util.Print.*
;
class House
implements
Serializable {}
class Animal
implements
Serializable {
private
String name;
private
House preferredHouse;
Animal(String nm, House h) {
name =
nm;
preferredHouse =
h;
}
public
String toString() {
return name + “[" +
super.toString() + "], ” + preferredHouse + “n”
;
}
}
public
class
MyWorld {
public
static
void main(String[] args)
throws
IOException, ClassNotFoundException {
House house =
new
House();
List animals =
new ArrayList
();
animals.add(
new Animal(“Bosco the dog”
, house));
animals.add(
new Animal(“Ralph the hamster”
, house));
animals.add(
new Animal(“Molly the cat”
, house));
print(“animals: ” +
animals);
ByteArrayOutputStream buf1 =
new
ByteArrayOutputStream();
ObjectOutputStream o1 =
new
ObjectOutputStream(buf1);
o1.writeObject(animals);
o1.writeObject(animals);
//
Write a 2nd set
//
Write to a different stream:
ByteArrayOutputStream buf2 =
new
ByteArrayOutputStream();
ObjectOutputStream o2 =
new
ObjectOutputStream(buf2);
o2.writeObject(animals);
//
Now get them back:
ObjectInputStream in1 =
new ObjectInputStream(
new
ByteArrayInputStream(buf1.toByteArray()));
ObjectInputStream in2 =
new ObjectInputStream(
new
ByteArrayInputStream(buf2.toByteArray()));
List animals1 =
(List)in1.readObject(),
animals2 =
(List)in1.readObject(),
animals3 =
(List)in2.readObject();
print(“animals1: ” +
animals1);
print(“animals2: ” +
animals2);
print(“animals3: ” +
animals3);
}
}
/*
Output: (Sample)
animals: [Bosco the dog[Animal@addbf1], House@42e816
, Ralph the hamster[Animal@9304b1], House@42e816
, Molly the cat[Animal@190d11], House@42e816
]
animals1: [Bosco the dog[Animal@de6f34], House@156ee8e
, Ralph the hamster[Animal@47b480], House@156ee8e
, Molly the cat[Animal@19b49e6], House@156ee8e
]
714 Thinking in Java Bruce Eckel
animals2: [Bosco the dog[Animal@de6f34], House@156ee8e
, Ralph the hamster[Animal@47b480], House@156ee8e
, Molly the cat[Animal@19b49e6], House@156ee8e
]
animals3: [Bosco the dog[Animal@10d448], House@e0e1c6
, Ralph the hamster[Animal@6ca1c], House@e0e1c6
, Molly the cat[Animal@1bf216a], House@e0e1c6
]
*///
:~
我们可以看到当将所有的东西都序列化到一个单一流中的时候,你会反序列化出相同的对象组织结构,不会有重复的对象。你可以在序列化的过程中改变对象的状态,但是对象的序列化的状态将会是你开始序列化他们时候的状态。安全的做法是将对象的序列化作为原子事务进行处理。下面的代码是一个计算机辅助设计系统,用来演示刚才的结论:
//
: io/StoreCADState.java
//
Saving the state of a pretend CAD system.
import java.io.*
;
import java.util.*
;
abstract
class Shape
implements
Serializable {
public
static
final
int RED = 1, BLUE = 2, GREEN = 3
;
private
int
xPos, yPos, dimension;
private
static Random rand =
new Random(47
);
private
static
int counter = 0
;
public
abstract
void setColor(
int
newColor);
public
abstract
int
getColor();
public Shape(
int xVal,
int yVal,
int
dim) {
xPos =
xVal;
yPos =
yVal;
dimension =
dim;
}
public
String toString() {
return getClass() + “color[" + getColor() + "] xPos[" + xPos + "] yPos[" + yPos + "] dim[" + dimension + "]n”
;
}
public
static
Shape randomFactory() {
int xVal = rand.nextInt(100
);
int yVal = rand.nextInt(100
);
int dim = rand.nextInt(100
);
switch(counter++ % 3
) {
default
:
case 0:
return
new
Circle(xVal, yVal, dim);
case 1:
return
new
Square(xVal, yVal, dim);
case 2:
return
new
Line(xVal, yVal, dim);
}
}
}
class Circle
extends
Shape {
private
static
int color =
RED;
public Circle(
int xVal,
int yVal,
int
dim) {
super
(xVal, yVal, dim);
}
public
void setColor(
int newColor) { color =
newColor; }
public
int getColor() {
return
color; }
}
class Square
extends
Shape {
private
static
int
color;
public Square(
int xVal,
int yVal,
int
dim) {
super
(xVal, yVal, dim);
color =
RED;
}
public
void setColor(
int newColor) { color =
newColor; }
public
int getColor() {
return
color; }
}
class Line
extends
Shape {
private
static
int color =
RED;
public
static
void serializeStaticState(ObjectOutputStream os)
throws
IOException { os.writeInt(color); }
public
static
void deserializeStaticState(ObjectInputStream os)
throws IOException { color =
os.readInt(); }
public Line(
int xVal,
int yVal,
int
dim) {
super
(xVal, yVal, dim);
}
public
void setColor(
int newColor) { color =
newColor; }
public
int getColor() {
return
color; }
}
public
class
StoreCADState {
public
static
void main(String[] args)
throws
Exception {
Listextends Shape>> shapeTypes =
new ArrayListextends Shape>>
();
//
Add references to the class objects:
shapeTypes.add(Circle.
class
);
shapeTypes.add(Square.
class
);
shapeTypes.add(Line.
class
);
List shapes =
new ArrayList
();
//
Make some shapes:
for(
int i = 0; i < 10; i++
)
shapes.add(Shape.randomFactory());
//
Set all the static colors to GREEN:
for(
int i = 0; i < 10; i++
)
((Shape)shapes.get(i)).setColor(Shape.GREEN);
//
Save the state vector:
ObjectOutputStream out =
new ObjectOutputStream(
new FileOutputStream(“CADState.out”
));
out.writeObject(shapeTypes);
Line.serializeStaticState(out);
out.writeObject(shapes);
//
Display the shapes:
System.out.println(shapes);
}
}
/*
Output:
[class Circlecolor[3] xPos[58] yPos[55] dim[93]
, class Squarecolor[3] xPos[61] yPos[61] dim[29]
, class Linecolor[3] xPos[68] yPos[0] dim[22]
, class Circlecolor[3] xPos[7] yPos[88] dim[28]
, class Squarecolor[3] xPos[51] yPos[89] dim[9]
, class Linecolor[3] xPos[78] yPos[98] dim[61]
, class Circlecolor[3] xPos[20] yPos[58] dim[16]
, class Squarecolor[3] xPos[40] yPos[11] dim[22]
, class Linecolor[3] xPos[4] yPos[83] dim[6]
, class Circlecolor[3] xPos[75] yPos[10] dim[42]
]
*///
:~
//
: io/RecoverCADState.java
//
Restoring the state of the pretend CAD system.
//
{RunFirst: StoreCADState}
import java.io.*
;
import java.util.*
;
public
class
RecoverCADState {
@SuppressWarnings(“unchecked”
)
public
static
void main(String[] args)
throws
Exception {
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream(“CADState.out”
));
//
Read in the same order they were written:
Listextends Shape>> shapeTypes = (Listextends Shape>>
)in.readObject();
Line.deserializeStaticState(in);
List shapes = (List
)in.readObject();
System.out.println(shapes);
}
}
/*
Output:
[class Circlecolor[1] xPos[58] yPos[55] dim[93]
, class Squarecolor[0] xPos[61] yPos[61] dim[29]
, class Linecolor[3] xPos[68] yPos[0] dim[22]
, class Circlecolor[1] xPos[7] yPos[88] dim[28]
, class Squarecolor[0] xPos[51] yPos[89] dim[9]
, class Linecolor[3] xPos[78] yPos[98] dim[61]
, class Circlecolor[1] xPos[20] yPos[58] dim[16]
, class Squarecolor[0] xPos[40] yPos[11] dim[22]
, class Linecolor[3] xPos[4] yPos[83] dim[6]
, class Circlecolor[1] xPos[75] yPos[10] dim[42]
]
*///
:~
从上面的代码中我们可以看到虽然类是Serializable,暗示并没有符合我们的预期,如果想要序列化静态字段,我们必须手动进行。这也就是serializeStaticState和deserializeStaticState静态方法的必要性。你肯以看到她们呢在序列化和反序列化的过程中被显式调用,因此为了使得上面的程序能够正常运行,我们需要做以下几点:添加serializeStaticState和deserializeStaticState方法到shape,移除ArrayList shipeTypes和所有与他相关的代码;在shape中添加调用新的序列化和反序列化静态方法。
XML
因为XML的跨平台性和广泛使用,我们也可以将数据转换为XML文件,java中提供了XML编程的类库,如javax.xml.*库,但是这里我们使用开源的XOM库因为他的使用更加简单,而且还添加了XML正确性的验证功能。下面的例子有一个Person对象包含了firstname和lastname,我们将他序列化到XML:
//
: xml/Person.java
//
Use the XOM library to write and read XML
//
{Requires: nu.xom.Node; You must install
//
the XOM library from
http://www.xom.nu
}
import nu.xom.*
;
import java.io.*
;
import java.util.*
;
public
class
Person {
private
String first, last;
public
Person(String first, String last) {
this.first =
first;
this.last =
last;
}
//
Produce an XML Element from this Person object:
public
Element getXML() {
Element person =
new Element(“person”
);
Element firstName =
new Element(“first”
);
firstName.appendChild(first);
Element lastName =
new Element(“last”
);
lastName.appendChild(last);
person.appendChild(firstName);
person.appendChild(lastName);
return
person;
}
//
Constructor to restore a Person from an XML Element:
public
Person(Element person) {
first= person.getFirstChildElement(“first”
).getValue();
last = person.getFirstChildElement(“last”
).getValue();
}
public String toString() {
return first + ” ” +
last; }
//
Make it human-readable:
public
static
void format(OutputStream os, Document doc)
throws
Exception {
Serializer serializer=
new Serializer(os,”ISO-8859-1″
);
serializer.setIndent(4
);
serializer.setMaxLength(60
);
serializer.write(doc);
serializer.flush();
}
public
static
void main(String[] args)
throws
Exception {
List people = Arrays.asList(
new Person(“Dr. Bunsen”, “Honeydew”),
new Person(“Gonzo”, “The Great”),
new Person(“Phillip J.”, “Fry”
));
System.out.println(people);
Element root =
new Element(“people”
);
for
(Person p : people)
root.appendChild(p.getXML());
Document doc =
new
Document(root);
format(System.out, doc);
format(
new BufferedOutputStream(
new FileOutputStream(“People.xml”
)), doc);
}
}
/*
Output:
[Dr. Bunsen Honeydew, Gonzo The Great, Phillip J. Fry]
Dr. Bunsen
Honeydew
Gonzo
The Great
Phillip J.
Fry
*///
:~
反序列化的过程一样非常简单:
//
: xml/People.java
//
{Requires: nu.xom.Node; You must install
//
the XOM library from
http://www.xom.nu
}
//
{RunFirst: Person}
import nu.xom.*
;
import java.util.*
;
public
class People
extends ArrayList
{
public People(String fileName)
throws
Exception {
Document doc =
new
Builder().build(fileName);
Elements elements =
doc.getRootElement().getChildElements();
for(
int i = 0; i < elements.size(); i++
)
add(
new
Person(elements.get(i)));
}
public
static
void main(String[] args)
throws
Exception {
People p =
new People(“People.xml”
);
System.out.println(p);
}
}
/*
Output:
[Dr. Bunsen Honeydew, Gonzo The Great, Phillip J. Fry]
*///
:~
配置文件
Preference API是一种更加趋近于持久化的方法,因为他自动进行信息的存储和恢复,但只只能用于初始值类型和字符串,而且字符串的长度不能好过8K,通常这种方法用来存储用户对程序的参数设定。配置信息都是键值对,按照分层的方式存储,如下:
//
: io/PreferencesDemo.java
import java.util.prefs.*
;
import
static net.mindview.util.Print.*
;
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
*///
:~
在使用Preference API的时候并没有创建新的文件,而是使用了操作系统的资源来完成存储的任务,具体的机制根据操作系统的不同而不同,例如在Windows中是使用注册表。关键的是你不需要处理底层的细节,系统会自动完成你需要的功能。