《Thinking in Java》第18章的内容是相当丰富精彩的,也在网络学习参考了很多前辈们的笔记,个人由于能力有限(毕竟和大神Bruce Eckel的能力相差甚远),将这一章的内容分三个部分来写,希望能够慢慢品味和领悟Java IO的精粹: 1. 起步:File类
令我很吊胃口的一件事情是,当我翻开圣经,想拜读Java IO的精髓时,Eckel告诉我,在学习真正用于流读写数据的类之前,让我们先学习如何处理文件目录问题(潜台词仿佛在说,对不起,菜鸟,你得从基础班开始!)
File类:Java中File既能代表一个特定文件的名称,又能代表一个目录下的一组文件(相当于Files)
我们要学会的是,如何从一个目录下筛选出我们想要的文件?可以通过目录过滤器FilenameFilter+正则表达式实现对文件的筛选:
import java.io.File; import java.io.FilenameFilter; import java.util.Arrays; import java.util.regex.Pattern; /**
* 目录过滤器
* 显示符合条件的File对象
* @author 15070229
*
*/ 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();
}
} 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);
}
}
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
这个用匿名内部类的方式来实现是更合适的,DirFilter在内部实现,使程序变的更小巧灵活:
* 通过内部类方式实现的目录列表器
*/ package c18; import java.io.File; import java.io.FilenameFilter; import java.util.Arrays; import java.util.regex.Pattern; public class DirList2 { public static FilenameFilter filter(final String regex) { //内部类 return new FilenameFilter() { private Pattern pattern = Pattern.compile(regex); @Override public boolean accept(File dir, String name) { return pattern.matcher(name).matches();
}
};
} 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);
}
}
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
针对一些文件集上的常用操作我们可以封装成一个工具类,比如:
/**
* 目录实用工具类
* 本地目录操作:local方法产生经过正则表达式筛选的本地目录的文件数组
* 目录树操作:walk方法产生给定目录下的由整个目录树中经过正则表达式筛选的文件构成的列表
*/ package c18; import java.io.File; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; public final class Directory { /**
* 根据正则表达式,筛选产生File数组
* @param dir
* @param regex
* @return */ public static File[] local(File dir, final String regex) { return dir.listFiles(new FilenameFilter() { private Pattern pattern = Pattern.compile(regex); @Override public boolean accept(File dir, String name) { return pattern.matcher(name).matches();
}
});
} // 方法重载 public static File[] local(String path, final String regex) { return local(new File(path), regex);
} /*
* TreeInfo的使命是收集返回的目录和文件信息
*/ public static class TreeInfo implements Iterable<File> { public List<File> files = new ArrayList<File>(); public List<File> dirs = new ArrayList<File>(); // public Iterator<File> iterator() { return files.iterator();
} void addAll(TreeInfo other) {
files.addAll(other.files);
dirs.addAll(other.dirs);
} public String toString() { return "dirs:" + PPrint.pformat(dirs) + "\n\nfiles: " + PPrint.pformat(files);
}
} /**
* 遍历目录
* @param start
* @param regex
* @return */ public static TreeInfo walk(String start, String regex) { return recurseDirs(new File(start), regex);
} public static TreeInfo walk(File start, String regex) { return recurseDirs(start, regex);
} public static TreeInfo walk(File start) { return recurseDirs(start, ".*");
} public static TreeInfo walk(String start) { return recurseDirs(new File(start), ".*");
} /**
* 递归遍历文件目录,收集更多的信息(区分普通文件和目录)
* @param startDir
* @param regex
* @return */ static TreeInfo recurseDirs(File startDir, String regex) {
TreeInfo result = new TreeInfo(); for(File item : startDir.listFiles()) { if (item.isDirectory()) { //持有目录 result.dirs.add(item);
} else if(item.getName().matches(regex)) //持有普通文件 result.files.add(item);
} return result;
} public static void main(String[] args) { // PPrint.pprint(Directory.walk(".").dirs); // for(File file : Directory.local(".", "T.*"))
System.out.println(file);
}
} /**
- 格式化打印机,打印格式如下:
- [
- .\.settings
- .\bin
- .\src
- ]
*/ 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(Arrays.asList(c));
}
}
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
Java IO中分别用到了装饰者模式和适配者模式。
装饰模式(Decorator)又称为包装模式(Wrapper),通过创建一个装饰(包装)对象,来装饰真实的对象。
Java I/O类库需要多种不同功能的组合,这正是使用装饰器模式的理由所在。为什么不使用继承而采用装饰器模式呢?如果说Java IO的各种组合是通过继承方式来实现的话,那么每一种组合都需要一个类,这样就会出现大量重复性的问题。而通过装饰器来实现组合,恰恰避免了这个问题。
对于字节流而言,FilterInputStream和FilterOutputStream是用来提供装饰器接口以控制特定输入\输出的两个类。需要注意的是,对于字符流而言,同样用到的是装饰者模式,但是有一点不同,Reader体系中的FilterRead类和InputStream体系中的FilterInputStream的功能不同,它不再是装饰者。class BufferedReader extends Reader BufferedInputStream:public class BufferedInputStream extends FilterInputStream
举一个例子如下所示,其中BufferedReader是装饰对象,FileReader是被装饰对象。
package c18; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class BufferedInputFile { public static String read(String filename) throws IOException{ //BufferedReader是装饰对象,FileReader是被装饰对象 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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
适配者模式又可以分为类适配方式(类似多继承)和对象适配方式。而Java IO中使用的是对象适配方式。我们以FileOutputStream为例,可以看到源码中,FileOutputStream继承了OutputStream类型,同时持有一个对FileDiscriptor对象的引用。这是一个将FileDiscriptor接口适配成OutputStream接口形式的对象形适配器模式。
public class FileOutputStream extends OutputStream { private FileDescriptor fd; public FileOutputStream(FileDescriptor fdObj) {
SecurityManager security = System.getSecurityManager(); if (fdObj == null) { throw new NullPointerException();
} if (security != null) {
security.checkWrite(fdObj);
}
fd = fdObj;
fd.incrementAndGetUseCount();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
本节主要介绍基于字节流的输入InputStream和输出OutputStream,以及用于实现装饰模式的FilterInputStream和FilterOutputStream。
InputStream的作用是表示从不同数据源产生输入的类:
下面列表展现了InputStream的派生类,所有的派生类都要联合装饰类FilterInputStream ByteArrayInputStream
/**
* Creates a <code>ByteArrayInputStream
* so that it uses <code>buf as its
* buffer array.
* The buffer array is not copied.
* The initial value of <code>pos
* is <code>0 and the initial value
* of <code>count is the length of
* <code>buf.
*
* @param buf the input buffer.
*/ public ByteArrayInputStream(byte buf[]) { this.buf = buf; this.pos = 0; this.count = buf.length;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
举个例子:
/**
* 格式化内存输出
*/ package c18; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.EOFException; import java.io.IOException; public class FormattedMemoryInput { public static void main(String[] args) throws IOException{ try { //DataInputStream面向字节的装饰类 DataInputStream in = new DataInputStream( new ByteArrayInputStream( //BufferedInputFile.read上例中已经实现了 BufferedInputFile.read("D:/workspace/java_learning/" + "Java_Learning/src/c18/DirList2.java").getBytes())); while (true) { //readByte按字节输出 System.out.println((char)in.readByte());
}
} catch (EOFException e) {
System.err.println("End of stream");
}
}
}
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
需要注意的是StringBufferInputStream已经过时了,JDK给出的过时原因如下:
This class does not properly convert characters into bytes. As of JDK 1.1, the preferred way to create a stream from a string is via the FileInputStream
- 作用:用于从文件中读取信息
- 构造器参数:表示文件路径的字符串、File对象、FileDescriptor对象
//String public FileInputStream(String name) throws FileNotFoundException { this(name != null ? new File(name) : null); } //File public FileInputStream(File file) throws FileNotFoundException { String name = (file != null ? file.getPath() : null); SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkRead(name); } if (name == null) { throw new NullPointerException(); } fd = new FileDescriptor(); fd.incrementAndGetUseCount(); open(name); } //FileDescriptor public FileInputStream(FileDescriptor fdObj) { SecurityManager security = System.getSecurityManager(); if (fdObj == null) { throw new NullPointerException(); } if (security != null) { security.checkRead(fdObj); } fd = fdObj; fd.incrementAndGetUseCount(); } 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
PipedInputStream
- 作用:产生写入PipedOutputStream中的数据,实现“管道化”
- 构造器参数:PipedOutputStream
public PipedInputStream(PipedOutputStream src, int pipeSize) throws IOException { initPipe(pipeSize); connect(src); } 1 2 3 4 5
管道流可以实现两个线程之间,二进制数据的传输。管道流就像一条管道,一端输入数据,别一端则输出数据。通常要分别用两个不同的线程来控制它们。(这里埋个伏笔,目前笔者对多线程掌握还不够成熟,等到后面学习Java并发中会继续提到PipedIntputStream/PipedOutputStream)
SequenceInputStream
- 作用:将两个或多个InputStream对象转换成单一InputStream
- 构造器参数:两个InputStream或一个容器Enumeration
public SequenceInputStream(Enumeration<? extends InputStream> e) { this.e = e; try { nextStream(); } catch (IOException ex) { // This should never happen throw new Error("panic"); } } public SequenceInputStream(InputStream s1, InputStream s2) { Vector v = new Vector(2); v.addElement(s1); v.addElement(s2); e = v.elements(); try { nextStream(); } catch (IOException ex) { // This should never happen throw new Error("panic"); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
举两个例子(源于网络):
import java.io.*; import java.util.*; class SequenceDemo1 { public static void main(String[] args)throws IOException { Vector<FileInputStream> v = new Vector<FileInputStream>(); v.add(new FileInputStream("c:\\1.txt")); v.add(new FileInputStream("c:\\2.txt")); v.add(new FileInputStream("c:\\3.txt")); Enumeration<FileInputStream> en = v.elements(); SequenceInputStream sis = new SequenceInputStream(en); FileOutputStream fos = new FileOutputStream("c:\\4.txt"); byte[] buf = new byte[1024]; int len = 0; while((len=sis.read(buf))!=-1) { fos.write(buf,0,len); } fos.close(); sis.close(); } } 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
import java.io.*; import java.util.*; class SequenceDemo2 { public static void main(String[] args)throws IOException { InputStream is1 = null; InputStream is2 = null; OutputStream os = null; SequenceInputStream sis = new null; is1 = new FileInputStream("d:"+File.separator+"a.txt"); is2 = new FileInputStream("d:"+File.separator+"b.txt"); os = new FileOutputStream("d:"+File.separator+"ab.txt"); sis = new SequenceInputStream(is1,is2); int temp = 0; while((temp)=sis.read()!=-1) { os.write(temp); } sis.close(); is1.close(); is2.close(); os.close(); } } 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
另外,还有FilterInputStream,它是抽象类,作为“装饰器”的接口,笔者将单独用一节来详述。
字节流输出OutputStream
OutputStream的作用是表示从不同数据源产生输入的类:
- 字节数组
- 文件
- 管道
下面是OutputStream的派生类。同样,所有的派生类都要联合装饰类FilterOutputStream ByteArrayOutputStream
与ByteArrayInputStream相对应,public ByteArrayOutputStream() { this(32); } public ByteArrayOutputStream(int size) { if (size < 0) { throw new IllegalArgumentException("Negative initial size: " + size); } buf = new byte[size]; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
FileOutputStream
与FileInputStream相对应, PipedOutputStream
- 作用:任何写入其中的信息都会自动作为相关PipedInputStream输出,实现“管道化”
- 构造器参数:PipedInputStream
装饰器FilterInputStream/FilterOutputStream
FilterInputStream/FilterOutputStream同样也是InputStream/OutputStream DataInputStream/DataOutputStream
- 作用:搭配使用,可以按照可移植的方式从流读取基本数据类型(int、char、long等)
- 构造器参数:InputStream/OutputStream
public DataInputStream(InputStream in) { super(in); } 1 2 3
public DataOutputStream(OutputStream out) { super(out); } 1 2 3
BufferedInputStream/BufferedOutputStream
- 作用:使用缓冲器输入输出
- 构造器参数:InputStream/OutputStream
举个例子:
/** * 使用缓冲区,读取二级制文件 */ package c18; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; 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()); } } 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
LineNumberInputStream
- 作用:跟踪输入流中的行号,可调用getLineNumber()和setLineNumber(int)
- 构造器参数:InputStream
该类已经被废弃了,推荐使用字符流的类来操作。
PushbackInputStream
- 作用:回推字节,读取字节,然后再将它们返回到流中。回推操作有unread()方法实现
- 构造器参数:InputStream
PushbackInputStream类实现了这一思想,提供了一种机制,可以“偷窥”来自输入流的内容而不对它们进行破坏。
//允许将size大小的字节回推回流 public PushbackInputStream(InputStream in, int size) { super(in); if (size <= 0) { throw new IllegalArgumentException("size <= 0"); } this.buf = new byte[size]; this.pos = size; } //每次允许回推一个字节 public PushbackInputStream(InputStream in) { this(in, 1); } 1 2 3 4 5 6 7 8 9 10 11 12 13
package c18; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.PushbackInputStream; public class PushbackInputStreamDemo { public static void main(String[] args) { byte[] arrByte = new byte[1024]; byte[] byteArray = new byte[]{'H', 'e', 'l', 'l', 'o',}; /* * new PushbackInputStream(is, 10)一次能回推10个字节的缓存 */ InputStream is = new ByteArrayInputStream(byteArray); PushbackInputStream pis = new PushbackInputStream(is, 10); try { for (int i = 0; i < byteArray.length; i++) { arrByte[i] = (byte) pis.read(); System.out.print((char) arrByte[i]); } //换行 System.out.println(); byte[] b = {'W', 'o', 'r', 'l', 'd'}; /* * unread()回推操作 * 将World回推到PushbackInputStream流中 * 下次read()会将这个字节再次读取出来 */ pis.unread(b); for (int i = 0; i < byteArray.length; i++) { arrByte[i] = (byte) pis.read(); System.out.print((char) arrByte[i]); } } catch (Exception ex) { ex.printStackTrace(); } } } 输出结果: Hello World 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
PrintStream
- 作用:PrintStream可以方便地输出各种类型的数据(而不仅限于byte型)的格式化表示形式。
- 构造器参数:InputStream
需要注意的是,与其他输出流不同, PrintStream 永远不会抛出 IOException ;它产生的IOException会被自身的函数所捕获并设置错误标记, 用户可以通过 checkError() 返回错误标记,从而查看PrintStream内部是否产生了IOException。 5. 基于字符流的IO操作
设计Reader和Writer继承层次结构主要是为了国际化的16位Unicode字符编码。下表展示了Reader/Writer/** * 文件读写工具 */ package c18; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; public class TextFile { 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(); } 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); } } }
- 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
需要注意以下几点:
1. 参考文献
感谢并致敬以下前辈的文章:
- 《Thinking in Java》 作者:Bruce Eckel
- Java之美[从菜鸟到高手演变]之Java中的IO
http://blog.csdn.net/zhangerqing/article/details/8466532
作者:终点- Java IO最详解
http://blog.csdn.net/yczz/article/details/38761237
作者:yczz- java中的IO操作总结(一)
http://www.cnblogs.com/nerxious/archive/2012/12/15/2818848.html
作者:Nerxious- java.io包中的字符流(下) 适配器模式和InputStreamReader/OutputStreamWriter
http://www.molotang.com/articles/782.html
作者: 三石·道- SequenceInputStream合并流
http://blog.csdn.net/xuefeng1009/article/details/6955707
作者:xuefeng1009- 探究java IO之PushbackInputStream类
http://my.oschina.net/fhd/blog/345011
作者:柳哥- JAVA中常用IO流类: PrintStream和PrintWriter
http://blog.csdn.net/caixiexin/article/details/6719627
作者:caixiexin- java io系列16之 PrintStream(打印输出流)详解
http://www.cnblogs.com/skywang12345/p/io_16.html
作者:skywang12345
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:http://blog.itpub.net/31541037/viewspace-2157019/