文章目录
Java I/O系统
1. File类
File既能代表一个特定文件的名称,又能代表一个目录下的一组文件的名称。如果它指的是一个文件集,可以对此集合调用list()方法,这个方法会返回一个字符数组。
目录列表器
假设我们想看一个目录列表,可以用两种方法来使用File对象。如果我们调用不带参数的list()方法,便可以获得此File对象包含的全部列表。然而,如果我们想获得一个受限的列表,则应该实现接口FilenameFilter,这个类存在的唯一原因就是将accept()方法,然后把这个方法提供给list()使用,使用list可以回调accept()。
import java.io.File;
import java.io.FilenameFilter;
import java.util.Arrays;
import java.util.regex.Pattern;
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();
}
}
File类不仅仅只代表存在的文件或目录。也可以用File对象来创建新的目录或尚不存在的整个目录路径。
2. 输入和输出
Java类库中的I/O类分成输入和输出两个部分,通过继承,任何自InputStream或Reader派生而来的类都含有名为read()的基本方法,用于读取单个字节或者字节数组;同样,任何自OutputStream或Writer派生而来的类都含有名为write()的基本方法,用于写单个字节或者字节数组。
InputStream的作用是用来表示那些从不同数据源产生输入的类,这些数据源包括:
- 字节数组
- String对象
- 文件
- ”管道“,工作方式与实际管道相似,从一端输入,从另一端输出
- 一个由其他种类的流组成的序列,以便我们可以将它们收集合并到一个流内
- 其他数据源
相关输入输出类型
InputStream | OutputStream |
---|---|
ByteArrayInputStream | ByteArrayOutputStream |
StringBufferInputStream | |
FileInputStream | FileOutputStream |
PipedInputStream | PipedOutputStream |
SequenceInputStream | |
FilterInputStream | FilterOutputStream |
其中,FilterInputStream和FilterOutputStream是用来提供装饰器类接口以控制特定输入流(InputStream)和输出流(OutputStream)的两个类,FilterInputStream和FilterOutputStream分别来自I/O类库中的基类InputStream和OutputStream派生而来,这两个类是装饰器的必要条件。
FilterInputStream类能够完成两件完全不同的事,DataInputStream允许读取不同的基本类型数据以及String对象,搭配对应的输出流。
其他FilterInputStream类则在内部修改InputStream的行为方式:是否缓冲,是否保留它所读过的行,以及是否把单一字符推回输入流等等。
FilterInputStream | FilterOutputStream |
---|---|
DataInputStream | DataOutputStream |
BufferedInputString | BufferedOutputString |
LineNumberInputStream | |
PushbackInputStream | |
PrintStream |
PrintStream最初的目的是为了以可视化格式打印所有的基本数据类型以及String对象。PrintStream有两个重要的方法,print()和println()。但它未完全国际化,不能以平台无关的方式处理换行动作。
3. Reader和Writer
InputStream和OutputStream是以面向字节形式的I/O提供既有价值的功能,而Reader和Writer则提供兼容Unicode与面向字符的功能。
InputStreamReader 和 OutputStreamWriter 可以将 InputStream 和 OutputStream 转变成 Reader 和 Writer。
Reader | Writer |
---|---|
FileReader | FileWriter |
StringReader | StringWriter |
CharArrayReader | CharArrayWriter |
PipedReader | PipedWriter |
装饰器
FilterReader | FilterWriter |
---|---|
BufferedReader | BufferedWriter |
LineNumberReader | |
PushbackReader | |
PrintWriter |
BufferedWriter并不是FilterWriter的子类,尽管FilterWriter是抽象类,但是没有任何的子类
PrintWriter的格式化接口实际上与PrintStream相同。它提供了一个既能接受Writer对象又能接受任何OutputStream对象的构造器。它还可以直接以一个路径为参数,打开文件。有一种PrintStream构造器还有一个选项,就是”自动执行清空“选项,如果构造器设置此选项,则在每个println()执行之后,便会自动清空。
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class BufferedInputFile {
public static String read(String filename)throws IOException{
BufferedReader in = new BufferedReader(new FileReader(new File(filename)));
// 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 Exception {
// System.out.print(read("src/IOsystem/BufferedInputFile.java"));
File path = new File("src/IOsystem/BufferedInputFile.javvva");
System.out.print(path.getAbsoluteFile());
}
}
4. 自我独立的类:RandomAccessFile
RandomAccessFile适用于由大小已知的记录组成的文件,所以可以使用seek()将记录从一处转移到另一处,然后读取或者修改记录。
这个类不支持装饰,所以不能将其与InputStream及OutputStream子类的任何组合起来。它的构造器有两个参数,第一个是文件名,第二个是对文件的操作方式,可指定以"只读"®方式或"读写"(rw)方式代开一个RandomAccessFile文件。
5. 标准I/O
Java提供了System.in、System.out和System.err。其中System.out和System.err已经事先被包装成了PrintStream对象,System.in却是一个没有被包装过的未经加工的InputStream,所以在读取System.in之前必须对其进行包装。
Java类提供了一些简单的静态方法调用,以允许对标准输入、输出和错误I/O流进行重定向:
setIn(InputStream)
setOut(PrintStream)
setErr(PrintStream)
6. 新I/O
新I/O速度的提高来自于所使用的结构更接近于操作系统执行I/O的方式:通道和缓冲器。其实我们并没有直接和通道加护,只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要么想缓冲器发送数据。
唯一直接与通道交互的缓冲器是ByteBuffer,也就是可以存储未加工字节的缓冲器。
旧的IO类库中的有3个类被修改了,用以生产FileChannel。这三个被修改的是FileInputStream、FileOutputStream以及用于既读既写的RandomAccessFile,这些都是字节操纵流,与nio性质一样。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
public class BufferToText {
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception{
FileChannel fc = new FileOutputStream("src/niosystem/data.txt").getChannel();
fc.write(ByteBuffer.wrap("s".getBytes()));
fc.close();
fc = new FileInputStream("src/niosystem/data.txt").getChannel();
ByteBuffer bf = ByteBuffer.allocate(BSIZE);
fc.read(bf);
bf.flip();
System.out.println(bf.asCharBuffer());
bf.rewind();
String encoding = System.getProperty("file.encoding");//得到默认字符集
System.out.println("Decoded using "+encoding+":"+ Charset.forName(encoding).decode(bf));
fc.close();
fc = new FileOutputStream("src/niosystem/data.txt").getChannel();
fc.write(ByteBuffer.wrap("s".getBytes("UTF-16BE")));
fc.close();
fc = new FileInputStream("src/niosystem/data.txt").getChannel();
bf.clear();
fc.read(bf);
bf.flip();
System.out.println(bf.asCharBuffer());
fc.close();
fc = new FileOutputStream("src/niosystem/data.txt").getChannel();
bf = ByteBuffer.allocate(100);
bf.asCharBuffer().put("s");
fc.write(bf);
fc.close();
fc = new FileInputStream("src/niosystem/data.txt").getChannel();
bf.clear();
fc.read(bf);
bf.flip();
System.out.println(bf.asCharBuffer());
}
}
getChannel()将会产生一个FileChannel,通道是一个相当基础的东西,可以向它传送用于读写的ByteBuffer。每次read()操作之后,都会将数据输入到缓冲器中,flip()则会准备缓冲器以便它的信息可以由write()提取,write()操作之后,信息仍在缓冲器中,接着clear()操作则对所有的内部指针重新安排,以便缓冲器在另一个read()操作期间能够做好接受数据的准备。
ByteBuffer可以看作是具有asCharBuffer()方法的CharBuffer,它还可以产生各种不同基本类型值的方法(asShortBuffer)。
缓冲器容纳的是普通的字节,为了把它们转换成字符,要么在输入它们的时候对其进行编码,要么在将其从缓冲器输出时对它们进行解码。
将数据从一个通道传输到另一个通道有3种方式:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
public class ChannelCopy {
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception{
FileChannel in = new FileInputStream("src/niosystem/some_text.txt").getChannel();
FileChannel out = new FileOutputStream("src/niosystem/data.txt").getChannel();
//1. 运用缓冲器将数据从一个通道传到另一个通道
// ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
// while (in.read(buffer)!=-1){//-1是一个分界符,表示到达输入的末尾
// buffer.flip();
// out.write(buffer);
// buffer.clear();
// }
//transferTo()和transferFrom()
// in.transferTo(0,in.size(),out);
out.transferFrom(in,0,in.size());
out.close();
in.close();
}
}
对于只读访问,必须显式地使用静态的allocate()方法来分配ByteBuffer
视图缓冲器
视图缓冲器(view buffer)可以让我们通过某个特定的基本数据类型的视图查看其底层的ByteBuffer。ByteBuffer依然是实际存储数据的地方,支持着前面的视图,因此,对视图的任何修改都会映射成为对ByteBuffer中数据的修改。
内存映射文件
内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件,有了内存映射文件,我们就可以假定整个文件都放在内存中,而且可以完全把它当做非常大的数组来访问。
对映射文件的部分加锁
7. 用GZIP进行简单压缩
压缩 | 解压缩 |
---|---|
DeflaterOutputStream(压缩基类) | InflaterOutputStream(压缩基类) |
CheckedOutputStream | CheckedInputStream |
ZipOutputStream | ZipInputStream |
GZIPOutputStream | GZIPInputStream |
压缩类库是属于InputStream和OutputStream继承层次结构的一部分。这样做是因为压缩类库是按字节方式而不是按字符方式处理的。
用GZIP进行简单压缩
import java.io.*;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class GZIPcompress {
public static void main(String[] args) throws IOException {
String fileName = "GZIPcompress.java";
String beforeFileName = "src/compress/";
BufferedReader in = new BufferedReader(new FileReader(beforeFileName+fileName));
BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(beforeFileName+"test.gz")));
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(beforeFileName+"test.gz"))));
String s;
while ((s=in2.readLine())!=null)
System.out.println(s);
}
}
用zip进行多文件保存
mport java.io.*;
import java.util.Enumeration;
import java.util.zip.*;
public class ZIPCompress {
public static void main(String[] args) throws IOException {
String[] filesName = {"GZIPcompress.java","ZIPCompress.java"};
String beforeFileName = "src/compress/";
FileOutputStream fout = new FileOutputStream(beforeFileName+"test.zip");
CheckedOutputStream csum = new CheckedOutputStream(fout,new Adler32());
ZipOutputStream zos = new ZipOutputStream(csum);
BufferedOutputStream out = new BufferedOutputStream(zos);
zos.setComment("A test of Java Zipping");
for(String arg:filesName){
System.out.println("writing file"+arg);
BufferedReader in = new BufferedReader(new FileReader(beforeFileName+arg));
zos.putNextEntry(new ZipEntry(arg));
int c;
while((c=in.read())!=-1)
out.write(c);
in.close();
out.flush();
}
out.close();
System.out.println("Checksum: "+csum.getChecksum().getValue());
System.out.println("read file");
FileInputStream fin = new FileInputStream(beforeFileName+"test.zip");
CheckedInputStream csumin = new CheckedInputStream(fin,new Adler32());
ZipInputStream in2 = new ZipInputStream(csumin);
BufferedInputStream bis = new BufferedInputStream(in2);
ZipEntry ze;
while((ze = in2.getNextEntry())!=null){
System.out.println("reading file "+ze);
int x;
while((x = bis.read())!=-1)
System.out.write(x);
}
bis.close();
ZipFile zf = new ZipFile(beforeFileName+"test.zip");
Enumeration e = zf.entries();
while(e.hasMoreElements()){
ZipEntry ze2 = (ZipEntry)e.nextElement();
System.out.println("file: "+ze2);
}
}
}
对于每一个要加入压缩档案的文件,都必须调用putNextEntry(),并将其传递给一个ZipEntry对象。
为了能够解压缩文件,ZipInputStream提供了一个getNextEntry()方法返回下一个ZipEntry。解文件还有一个更简便的方法,利用ZipFile对象读取文件。该对象有一个entries()方法用来向ZipEntries返回一个Enumeration(枚举)。
8. 对象序列化
对象序列化加入到语言中是为了支持两种主要特性。一是Java的远程方法调用(Remote Method Invocation,RMI),它使存活于其他计算机上的对象使用起来就像是存活于本机上一样。
使用一个Beans时,一般情况下是在设计阶段对它的状态信息进行配置。这种状态信息必须保存下来,并在程序启动时进行后期恢复。只要对象实现了Serializable接口,对象的序列化处理就会非常简单。
要序列化一个对象,首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内。这时,只需要调用writeObject()即可将对象序列化,并将其发送给OutputStream。
import java.io.*;
import java.util.Random;
public class Worm implements Serializable{
private static Random rand = new Random(47);
private Data[] dat = {
new Data(rand.nextInt(10)),
new Data(rand.nextInt(10)),
new Data(rand.nextInt(10))
};
private Worm next;
private char c;
public Worm(int i,char c){
System.out.println("constructor: "+i);
this.c = c;
if(--i>0){
next = new Worm(i,(char)(c+1));
}
}
public String toString(){
StringBuilder result = new StringBuilder(":");
result.append(c);
result.append("(");
for(Data data:dat){
result.append(data);
}
result.append(")");
if(next != null)
result.append(next);
return result.toString();
}
public static void main(String[] args) throws ClassNotFoundException, IOException {
Worm worm = new Worm(6,'a');
String path = "src/Serializable/";
System.out.println("worm = "+worm);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(path+"worm.txt"));
out.writeObject("Worm storage\n");
out.writeObject(worm);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream(path+"worm.txt"));
String s = (String)in.readObject();
Worm w2 = (Worm)in.readObject();
System.out.println(s+"w2 = "+w2);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream out2 = new ObjectOutputStream(bout);
out2.writeObject("Worm storage\n");
out2.writeObject(w2);
out2.flush();
out.close();
ObjectInputStream in2 = new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray()));
s = (String)in2.readObject();
Worm w3 = (Worm)in2.readObject();
System.out.println(s+"w3="+w3);
}
}
class Data implements Serializable{
private int n;
public Data(int n){
this.n = n;
}
public String toString(){
return Integer.toString(n);
}
}
序列化的控制
在某些情况下,可通过实现Externalizable接口代替Serializable来对序列化过程进行控制,这个接口添加了两个方法,writeExternal()和readExternal(),这两个方法会在序列化和反序列化还原的过程中被自动调用。
对于Externalizable对象的恢复,所有的普通默认构造器都会被调用,然后调用readExternal()。
具体看java编程思想 p577
transient关键字
有一种方法可防止对象的敏感部分被序列化,就是实现Externalizable。如果我们想在Serializable实现这个功能,这可以用transient关键字逐个字段地关闭序列化。这时候就需要手动实现有关键字的字段的序列化了。
9.XML
对象序列化的一个重要限制是它只是java的解决方案,只有java程序才能反序列化这种对象,一种更具互操作的解决方案是将数据转换成XML格式,这可以是其被各种各样的平台和语言使用。
10. Preferences
Perferences API与对象序列化相比,前者与对象持久性更密切,因为它可以自动存储和读取信息,不过它只能用于小的。受限的数据集合。