Apache Commons IO是Apache基金会创建并维护的Java函数库。它提供了许多类使得开发者的常见任务变得简单,同时减少重复(boiler-plate)代码,这些代码可能遍布于每个独立的项目中,你却不得不重复的编写。这些类由经验丰富的开发者维护,对各种问题的边界条件考虑周到,并持续修复相关bug。
在下面的例子中,我们会向你演示一些不同功能的方法,这些功能都是在org.apache.commons.io包下。Apache Commons IO 是一个巨大工程,我们不会深入去剖析它的底层原理,但是会演示一些比较常用的例子,不管你是不是新手,相信这些例子都会对你有所帮助。
1. Apache Commons IO 示例
我们分别会用几段代码来演示下面的功能,每部分功能都代表Apache Commons IO所覆盖的一个单独领域,具体如下:
- 工具类
- 输入
- 输出
- 过滤器
- 比较器
- 文件监控器
为了更方便读者进行理解,我们会把创建的每个类的输出进行单独展示。并且会把示例中功能演示所用到的到文件放到工程目录(ExampleFolder目录)中。
注意:为了能使用org.apache.commons.io中的功能,你首先需要下载jar包(请点击这里),并且将jar包添加到Eclipse工程的编译路径下,右键点工程文件夹 -> Build Path -> Add external archives。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
class
ApacheCommonsExampleMain {
public
static
void
main(String[] args) {
UtilityExample.runExample();
FileMonitorExample.runExample();
FiltersExample.runExample();
InputExample.runExample();
OutputExample.runExample();
ComparatorExample.runExample();
}
}
|
这个main方法会运行所有的示例,你可以将其他行的代码注释来执行你想要的示例。
1.1 Utility Classes
FilenameUtils:
这个工具类是用来处理文件名(译者注:包含文件路径)的,他可以轻松解决不同操作系统文件名称规范不同的问题(比如windows和Unix)(在Unix系统以及Linux系统中文件分隔符是“/”,不支持”\“,windows中支持”\“以及”/“)。
FileSystemUtils:提供查看指定目录剩余空间的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
import
java.io.File;
import
java.io.IOException;
import
org.apache.commons.io.FileSystemUtils;
import
org.apache.commons.io.FileUtils;
import
org.apache.commons.io.FilenameUtils;
import
org.apache.commons.io.LineIterator;
import
org.apache.commons.io.IOCase;
public
final
class
UtilityExample {
// We are using the file exampleTxt.txt in the folder ExampleFolder,
// and we need to provide the full path to the Utility classes.
private
static
final
String EXAMPLE_TXT_PATH =
"C:UsersLilykosworkspaceApacheCommonsExampleExampleFolderexampleTxt.txt"
;
private
static
final
String PARENT_DIR =
"C:UsersLilykosworkspaceApacheCommonsExample"
;
public
static
void
runExample()
throws
IOException {
System.out.println(
"Utility Classes example..."
);
// FilenameUtils
System.out.println(
"Full path of exampleTxt: "
+
FilenameUtils.getFullPath(EXAMPLE_TXT_PATH));
System.out.println(
"Full name of exampleTxt: "
+
FilenameUtils.getName(EXAMPLE_TXT_PATH));
System.out.println(
"Extension of exampleTxt: "
+
FilenameUtils.getExtension(EXAMPLE_TXT_PATH));
System.out.println(
"Base name of exampleTxt: "
+
FilenameUtils.getBaseName(EXAMPLE_TXT_PATH));
// FileUtils
// We can create a new File object using FileUtils.getFile(String)
// and then use this object to get information from the file.
File exampleFile = FileUtils.getFile(EXAMPLE_TXT_PATH);
LineIterator iter = FileUtils.lineIterator(exampleFile);
System.out.println(
"Contents of exampleTxt..."
);
while
(iter.hasNext()) {
System.out.println(
"t"
+ iter.next());
}
iter.close();
// We can check if a file exists somewhere inside a certain directory.
File parent = FileUtils.getFile(PARENT_DIR);
System.out.println(
"Parent directory contains exampleTxt file: "
+
FileUtils.directoryContains(parent, exampleFile));
// IOCase
String str1 =
"This is a new String."
;
String str2 =
"This is another new String, yes!"
;
System.out.println(
"Ends with string (case sensitive): "
+
IOCase.SENSITIVE.checkEndsWith(str1,
"string."
));
System.out.println(
"Ends with string (case insensitive): "
+
IOCase.INSENSITIVE.checkEndsWith(str1,
"string."
));
System.out.println(
"String equality: "
+
IOCase.SENSITIVE.checkEquals(str1, str2));
// FileSystemUtils
System.out.println(
"Free disk space (in KB): "
+ FileSystemUtils.freeSpaceKb(
"C:"
));
System.out.println(
"Free disk space (in MB): "
+ FileSystemUtils.freeSpaceKb(
"C:"
) /
1024
);
}
}
|
输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Utility Classes example...
Full path of exampleTxt: C:UsersLilykosworkspaceApacheCommonsExampleExampleFolder
Full name of exampleTxt: exampleTxt.txt
Extension of exampleTxt: txt
Base name of exampleTxt: exampleTxt
Contents of exampleTxt...
This is an example text file.
We will use it
for
experimenting with Apache Commons IO.
Parent directory contains exampleTxt file:
true
Ends with string (
case
sensitive):
false
Ends with string (
case
insensitive):
true
String equality:
false
Free disk space (in KB):
32149292
Free disk space (in MB):
31395
|
1.2 文件监控器
org.apache.commons.io.monitor包下的类包含的方法可以获取文件的指定信息,不过更重要的是,它可以创建处理器(handler)来跟踪指定文件或目录的变化并且可以在文件或目录发生变化的时候进行一些操作。让我们来看看下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
import
java.io.File;
import
java.io.IOException;
import
org.apache.commons.io.FileDeleteStrategy;
import
org.apache.commons.io.FileUtils;
import
org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import
org.apache.commons.io.monitor.FileAlterationMonitor;
import
org.apache.commons.io.monitor.FileAlterationObserver;
import
org.apache.commons.io.monitor.FileEntry;
public
final
class
FileMonitorExample {
private
static
final
String
EXAMPLE_PATH =
"C:UsersLilykosworkspaceApacheCommonsExampleExampleFolderexampleFileEntry.txt"
;
private
static
final
String
PARENT_DIR =
"C:UsersLilykosworkspaceApacheCommonsExampleExampleFolder"
;
private
static
final
String
NEW_DIR =
"C:UsersLilykosworkspaceApacheCommonsExampleExampleFoldernewDir"
;
private
static
final
String
NEW_FILE =
"C:UsersLilykosworkspaceApacheCommonsExampleExampleFoldernewFile.txt"
;
public
static
void
runExample() {
System.out.println(
"File Monitor example..."
);
// FileEntry
// We can monitor changes and get information about files
// using the methods of this class.
FileEntry entry =
new
FileEntry(FileUtils.getFile(EXAMPLE_PATH));
System.out.println(
"File monitored: "
+ entry.getFile());
System.out.println(
"File name: "
+ entry.getName());
System.out.println(
"Is the file a directory?: "
+ entry.isDirectory());
// File Monitoring
// Create a new observer for the folder and add a listener
// that will handle the events in a specific directory and take action.
File parentDir = FileUtils.getFile(PARENT_DIR);
FileAlterationObserver observer =
new
FileAlterationObserver(parentDir);
observer.addListener(
new
FileAlterationListenerAdaptor() {
@Override
public
void
onFileCreate(File file) {
System.out.println(
"File created: "
+ file.getName());
}
@Override
public
void
onFileDelete(File file) {
System.out.println(
"File deleted: "
+ file.getName());
}
@Override
public
void
onDirectoryCreate(File dir) {
System.out.println(
"Directory created: "
+ dir.getName());
}
@Override
public
void
onDirectoryDelete(File dir) {
System.out.println(
"Directory deleted: "
+ dir.getName());
}
});
// Add a monior that will check for events every x ms,
// and attach all the different observers that we want.
FileAlterationMonitor monitor =
new
FileAlterationMonitor(
500
, observer);
try
{
monitor.start();
// After we attached the monitor, we can create some files and directories
// and see what happens!
File newDir =
new
File(NEW_DIR);
File newFile =
new
File(NEW_FILE);
newDir.mkdirs();
newFile.createNewFile();
Thread.sleep(
1000
);
FileDeleteStrategy.NORMAL.
delete
(newDir);
FileDeleteStrategy.NORMAL.
delete
(newFile);
Thread.sleep(
1000
);
monitor.stop();
}
catch
(IOException e) {
e.printStackTrace();
}
catch
(InterruptedException e) {
e.printStackTrace();
}
catch
(Exception e) {
e.printStackTrace();
}
}
}
|
输出
1
2
3
4
5
6
7
8
|
File Monitor example...
File monitored: C:UsersLilykosworkspaceApacheCommonsExampleExampleFolderexampleFileEntry.txt
File name: exampleFileEntry.txt
Is the file a directory?:
false
Directory created: newDir
File created: newFile.txt
Directory deleted: newDir
File deleted: newFile.tx
|
让我们来看看这里发生了什么,我们使用org.apache.commons.io.monitor包下的类创建了一个处理器来监听一些特定的事件(在上面的例子中就是我们对文件或目录所做的所有操作事件),为了获得这些信息,我们需要做以下几步操作:
1、创建一个File对象,这个对象指向我们需要监听变化的目录。
2、创建一个FileAlterationObserver对象,这个对象会观察这些变化。
3、通过调用addListener()方法,为observer对象添加一个 FileAlterationListenerAdaptor对象。你可以通过很多种方式来创建一个适配器,在我们的例子中我们使用内部类的方式进行创建并且只实现其中的一部分方法(只需要实现我们例子中需要用的方法即可)。
4、创建一个FileAlterationMonitor 对象,将已经创建好的observer对象添加其中并且传入时间间隔参数(单位是毫秒)。
1.3 过滤器
过滤器可以以组合的方式使用并且它的用途非常多样。它可以轻松的区分不同的文件并且找到满足我们条件的文件。我们可以组合不同的过滤器来执行文件的逻辑比较并且精确的获取我们所需要文件,而无需使用冗余的字符串比较来寻找我们的文件。
FiltersExample.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
import
java.io.File;
import
org.apache.commons.io.FileUtils;
import
org.apache.commons.io.IOCase;
import
org.apache.commons.io.filefilter.AndFileFilter;
import
org.apache.commons.io.filefilter.NameFileFilter;
import
org.apache.commons.io.filefilter.NotFileFilter;
import
org.apache.commons.io.filefilter.OrFileFilter;
import
org.apache.commons.io.filefilter.PrefixFileFilter;
import
org.apache.commons.io.filefilter.SuffixFileFilter;
import
org.apache.commons.io.filefilter.WildcardFileFilter;
public
final
class
FiltersExample {
private
static
final
String PARENT_DIR =
"C:UsersLilykosworkspaceApacheCommonsExampleExampleFolder"
;
public
static
void
runExample() {
System.out.println(
"File Filter example..."
);
// NameFileFilter
// Right now, in the parent directory we have 3 files:
// directory example
// file exampleEntry.txt
// file exampleTxt.txt
// Get all the files in the specified directory
// that are named "example".
File dir = FileUtils.getFile(PARENT_DIR);
String[] acceptedNames = {
"example"
,
"exampleTxt.txt"
};
for
(String file: dir.list(
new
NameFileFilter(acceptedNames, IOCase.INSENSITIVE))) {
System.out.println(
"File found, named: "
+ file);
}
//WildcardFileFilter
// We can use wildcards in order to get less specific results
// ? used for 1 missing char
// * used for multiple missing chars
for
(String file: dir.list(
new
WildcardFileFilter(
"*ample*"
))) {
System.out.println(
"Wildcard file found, named: "
+ file);
}
// PrefixFileFilter
// We can also use the equivalent of startsWith
// for filtering files.
for
(String file: dir.list(
new
PrefixFileFilter(
"example"
))) {
System.out.println(
"Prefix file found, named: "
+ file);
}
// SuffixFileFilter
// We can also use the equivalent of endsWith
// for filtering files.
for
(String file: dir.list(
new
SuffixFileFilter(
".txt"
))) {
System.out.println(
"Suffix file found, named: "
+ file);
}
// OrFileFilter
// We can use some filters of filters.
// in this case, we use a filter to apply a logical
// or between our filters.
for
(String file: dir.list(
new
OrFileFilter(
new
WildcardFileFilter(
"*ample*"
),
new
SuffixFileFilter(
".txt"
)))) {
System.out.println(
"Or file found, named: "
+ file);
}
// And this can become very detailed.
// Eg, get all the files that have "ample" in their name
// but they are not text files (so they have no ".txt" extension.
for
(String file: dir.list(
new
AndFileFilter(
// we will match 2 filters...
new
WildcardFileFilter(
"*ample*"
),
// ...the 1st is a wildcard...
new
NotFileFilter(
new
SuffixFileFilter(
".txt"
))))) {
// ...and the 2nd is NOT .txt.
System.out.println(
"And/Not file found, named: "
+ file);
}
}
}
|
输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
File Filter example...
File found, named: example
File found, named: exampleTxt.txt
Wildcard file found, named: example
Wildcard file found, named: exampleFileEntry.txt
Wildcard file found, named: exampleTxt.txt
Prefix file found, named: example
Prefix file found, named: exampleFileEntry.txt
Prefix file found, named: exampleTxt.txt
Suffix file found, named: exampleFileEntry.txt
Suffix file found, named: exampleTxt.txt
Or file found, named: example
Or file found, named: exampleFileEntry.txt
Or file found, named: exampleTxt.txt
And/Not file found, named: example
|
1.4 比较器
使用org.apache.commons.io.comparator
包下的类可以让你轻松的对文件或目录进行比较或者排序。你只需提供一个文件列表,选择不同的类就可以实现不同方式的文件比较。
ComparatorExample.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
import
java.io.File;
import
java.util.Date;
import
org.apache.commons.io.FileUtils;
import
org.apache.commons.io.IOCase;
import
org.apache.commons.io.comparator.LastModifiedFileComparator;
import
org.apache.commons.io.comparator.NameFileComparator;
import
org.apache.commons.io.comparator.SizeFileComparator;
public
final
class
ComparatorExample {
private
static
final
String PARENT_DIR =
"C:UsersLilykosworkspaceApacheCommonsExampleExampleFolder"
;
private
static
final
String FILE_1 =
"C:UsersLilykosworkspaceApacheCommonsExampleExampleFolderexample"
;
private
static
final
String FILE_2 =
"C:UsersLilykosworkspaceApacheCommonsExampleExampleFolderexampleTxt.txt"
;
public
static
void
runExample() {
System.out.println(
"Comparator example..."
);
//NameFileComparator
// Let's get a directory as a File object
// and sort all its files.
File parentDir = FileUtils.getFile(PARENT_DIR);
NameFileComparator comparator =
new
NameFileComparator(IOCase.SENSITIVE);
File[] sortedFiles = comparator.sort(parentDir.listFiles());
System.out.println(
"Sorted by name files in parent directory: "
);
for
(File file: sortedFiles) {
System.out.println(
"t"
+ file.getAbsolutePath());
}
// SizeFileComparator
// We can compare files based on their size.
// The boolean in the constructor is about the directories.
// true: directory's contents count to the size.
// false: directory is considered zero size.
SizeFileComparator sizeComparator =
new
SizeFileComparator(
true
);
File[] sizeFiles = sizeComparator.sort(parentDir.listFiles());
System.out.println(
"Sorted by size files in parent directory: "
);
for
(File file: sizeFiles) {
System.out.println(
"t"
+ file.getName() +
" with size (kb): "
+ file.length());
}
// LastModifiedFileComparator
// We can use this class to find which file was more recently modified.
LastModifiedFileComparator lastModified =
new
LastModifiedFileComparator();
File[] lastModifiedFiles = lastModified.sort(parentDir.listFiles());
System.out.println(
"Sorted by last modified files in parent directory: "
);
for
(File file: lastModifiedFiles) {
Date modified =
new
Date(file.lastModified());
System.out.println(
"t"
+ file.getName() +
" last modified on: "
+ modified);
}
// Or, we can also compare 2 specific files and find which one was last modified.
// returns > 0 if the first file was last modified.
// returns 0)
System.out.println(
"File "
+ file1.getName() +
" was modified last because..."
);
else
System.out.println(
"File "
+ file2.getName() +
"was modified last because..."
);
System.out.println(
"t"
+ file1.getName() +
" last modified on: "
+
new
Date(file1.lastModified()));
System.out.println(
"t"
+ file2.getName() +
" last modified on: "
+
new
Date(file2.lastModified()));
}
}
|
输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
Comparator example...
Sorted by name files in parent directory:
C:UsersLilykosworkspaceApacheCommonsExampleExampleFoldercomparator1.txt
C:UsersLilykosworkspaceApacheCommonsExampleExampleFoldercomperator2.txt
C:UsersLilykosworkspaceApacheCommonsExampleExampleFolderexample
C:UsersLilykosworkspaceApacheCommonsExampleExampleFolderexampleFileEntry.txt
C:UsersLilykosworkspaceApacheCommonsExampleExampleFolderexampleTxt.txt
Sorted by size files in parent directory:
example with size (kb):
0
exampleTxt.txt with size (kb):
87
exampleFileEntry.txt with size (kb):
503
comperator2.txt with size (kb):
1458
comparator1.txt with size (kb):
4436
Sorted by last modified files in parent directory:
exampleTxt.txt last modified on: Sun Oct
26
14
:
02
:
22
EET
2014
example last modified on: Sun Oct
26
23
:
42
:
55
EET
2014
comparator1.txt last modified on: Tue Oct
28
14
:
48
:
28
EET
2014
comperator2.txt last modified on: Tue Oct
28
14
:
48
:
52
EET
2014
exampleFileEntry.txt last modified on: Tue Oct
28
14
:
53
:
50
EET
2014
File example was modified last because...
example last modified on: Sun Oct
26
23
:
42
:
55
EET
2014
exampleTxt.txt last modified on: Sun Oct
26
14
:
02
:
22
EET
2014
|
让我们来看看这里用到了哪些类:
NameFileComparator:通过文件名来比较文件。
SizeFileComparator:通过文件大小来比较文件。
LastModifiedFileComparator:通过文件的最新修改时间来比较文件。
在这里你需要注意,比较可以在定的文件夹中(文件夹下的文件已经被sort()方法排序过了),也可以在两个指定的文件之间(通过使用compare()方法)。
1.5 输入
在org.apache.commons.io.input包下有许多InputStrem类的实现,我们来测试一个最实用的类,TeeInputStream,将InputStream以及OutputStream作为参数传入其中,自动实现将输入流的数据读取到输出流中。而且,通过传入第三个参数,一个boolean类型参数,可以在数据读取完毕之后自动关闭输入流和输出流。
InputExample.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
import
java.io.ByteArrayInputStream;
import
java.io.ByteArrayOutputStream;
import
java.io.File;
import
java.io.IOException;
import
org.apache.commons.io.FileUtils;
import
org.apache.commons.io.input.TeeInputStream;
import
org.apache.commons.io.input.XmlStreamReader;
public
final
class
InputExample {
private
static
final
String XML_PATH =
"C:UsersLilykosworkspaceApacheCommonsExampleInputOutputExampleFolderweb.xml"
;
private
static
final
String INPUT =
"This should go to the output."
;
public
static
void
runExample() {
System.out.println(
"Input example..."
);
XmlStreamReader xmlReader =
null
;
TeeInputStream tee =
null
;
try
{
// XmlStreamReader
// We can read an xml file and get its encoding.
File xml = FileUtils.getFile(XML_PATH);
xmlReader =
new
XmlStreamReader(xml);
System.out.println(
"XML encoding: "
+ xmlReader.getEncoding());
// TeeInputStream
// This very useful class copies an input stream to an output stream
// and closes both using only one close() method (by defining the 3rd
// constructor parameter as true).
ByteArrayInputStream in =
new
ByteArrayInputStream(INPUT.getBytes(
"US-ASCII"
));
ByteArrayOutputStream out =
new
ByteArrayOutputStream();
tee =
new
TeeInputStream(in, out,
true
);
tee.read(
new
byte
[INPUT.length()]);
System.out.println(
"Output stream: "
+ out.toString());
}
catch
(IOException e) {
e.printStackTrace();
}
finally
{
try
{ xmlReader.close(); }
catch
(IOException e) { e.printStackTrace(); }
try
{ tee.close(); }
catch
(IOException e) { e.printStackTrace(); }
}
}
}
|
输出
Input example... XML encoding: UTF-8 Output stream: This should go to the output.
1.6 输出
与org.apache.commons.io.input包中的类相似, org.apache.commons.io.output包中同样有OutputStream类的实现,他们可以在多种情况下使用,一个非常有意思的类就是 TeeOutputStream,它可以将输出流进行分流,换句话说我们可以用一个输入流将数据分别读入到两个不同的输出流。
OutputExample.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
import
java.io.ByteArrayInputStream;
import
java.io.ByteArrayOutputStream;
import
java.io.IOException;
import
org.apache.commons.io.input.TeeInputStream;
import
org.apache.commons.io.output.TeeOutputStream;
public
final
class
OutputExample {
private
static
final
String INPUT =
"This should go to the output."
;
public
static
void
runExample() {
System.out.println(
"Output example..."
);
TeeInputStream teeIn =
null
;
TeeOutputStream teeOut =
null
;
try
{
// TeeOutputStream
ByteArrayInputStream in =
new
ByteArrayInputStream(INPUT.getBytes(
"US-ASCII"
));
ByteArrayOutputStream out1 =
new
ByteArrayOutputStream();
ByteArrayOutputStream out2 =
new
ByteArrayOutputStream();
teeOut =
new
TeeOutputStream(out1, out2);
teeIn =
new
TeeInputStream(in, teeOut,
true
);
teeIn.read(
new
byte
[INPUT.length()]);
System.out.println(
"Output stream 1: "
+ out1.toString());
System.out.println(
"Output stream 2: "
+ out2.toString());
}
catch
(IOException e) {
e.printStackTrace();
}
finally
{
// No need to close teeOut. When teeIn closes, it will also close its
// Output stream (which is teeOut), which will in turn close the 2
// branches (out1, out2).
try
{ teeIn.close(); }
catch
(IOException e) { e.printStackTrace(); }
}
}
}
|
输出
1
2
3
|
Output example...
Output stream
1
: This should go to the output.
Output stream
2
: This should go to the output.
|
2. 下载完整的示例
你可以在这里下载源代码。
原文链接: Javacodegeeks 翻译: ImportNew.com - yewenhai
译文链接: http://www.importnew.com/13715.html