Java I/O系统
File类
File 类这个名字有一定的误导性,我们可能认为它指代的是文件,实际上它只是一个文件路径 (FilePath )。下面是一些关于这个类的用法。
目录列表器
如果我们想获得文件目录下的文件列表,我们可以通过File 类的list() 方法。另外还有关于过滤的方法(不是简单的判断后缀名)。下面是一个例子:
import java.io.*;
import java.util.*;
import java.util.regex.Pattern;
public class DirList {
public static void main (String args[]) {
File path = new File("." );
String[] list = path.list(new DirFilter("^[\\S]+txt$" ));
File[] filelist1 = path.listFiles(new FilenameFilter() {
public boolean accept (File dir, String name) {
return name.endsWith("txt" );
}
});
File[] filelist2 = path.listFiles(new FileFilter() {
public boolean accept (File pathname) {
System.out.println(pathname.getName() + ": " + pathname.length());
return pathname.length() > 1000 ;
}
});
Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
System.out.println(Arrays.toString(list));
System.out.println(filelist1.length);
System.out.println(filelist2.length);
}
}
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类对于下面几章的学习非常重要,需要记住一些常用的字段和方法。
字段
类型 字段名 解释 static String pathSeparator 路径分隔符,(根据操作系统而异)windows为分号“; ”。在 UNIX 系统上,此字段为“: ”。为了方便它被表示一个字符串。 static char pathSeparatorChar 路径分隔符,(根据操作系统而异)windows为分号“; ”。在 UNIX 系统上,此字段为“: ”。 static String separator 名称分隔符,(根据操作系统而异)windows为反斜杠“\ ”。在 UNIX 系统上,此字段的值为“/ ”。为了方便它被表示一个字符串。 static char separatorChar 名称分隔符,(根据操作系统而异)windows为反斜杠“\ ”。在 UNIX 系统上,此字段的值为“/ ”。
构造方法
定义 解释 File(File parent, String child) 根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。 File(String pathname) 通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。 File(String parent, String child) 根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。 File(URI uri) 通过将给定的 file: URI 转换为一个抽象路径名来创建一个新的 File 实例。
常用实例方法
返回类型 定义 解释 boolean canExecute() 是否可执行,文件夹也可以执行。这个在unix系统中有所应用,下面两个也是。 boolean canRead() 可读 boolean canWrite() 可写 boolean createNewFile() 当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。 boolean delete() 删除此抽象路径名表示的文件或目录。 void deleteOnExit() 在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。(存放虚拟机运行时的临时文件) boolean exists() 测试此抽象路径名表示的文件或目录是否存在。 File getAbsoluteFile() 返回此抽象路径名的绝对路径名形式。与new的时候传进去的路径有关 String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串。 File getCanonicalFile() 返回此抽象路径名的规范形式。与new的时候传进去的路径无关 String getCanonicalPath() 返回此抽象路径名的规范路径名字符串。 String getName() 返回由此抽象路径名表示的文件或目录的名称。 String getParent() 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。 File getParentFile() 返回此抽象路径名父目录的抽象路径名;如果此路径名没有指定父目录,则返回 null。 String getPath() 将此抽象路径名转换为一个路径名字符串。(与new的时候相同) boolean isDirectory() 测试此抽象路径名表示的文件是否是一个目录。 boolean isFile() 测试此抽象路径名表示的文件是否是一个标准文件。 long lastModified() 返回此抽象路径名表示的文件最后一次被修改的时间。 long length() 返回由此抽象路径名表示的文件的长度。(bytes) boolean mkdir() 创建此抽象路径名指定的目录。 boolean mkdirs() 创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。 URI toURI() 构造一个表示此抽象路径名的 file: URI。
还有一些其他的方法以及前面提到的list() 。有兴趣可以去查看API。
输入和输出
InputStream 和outputStream 是两个抽象类(前者需重写read() 方法,后者需重写write() 方法)。我们来看看常用的InputStream 和outputStream 的子类(他们的确重写了read和write 方法,不过通常我们会使用其更简便的方法)。
类 功能 构造器参数/如何使用 ByteArrayInputStream 允许将内存的缓冲区当作InputStream使用 缓冲区,字节将从中取出/作为一种数据源,将其与FilterInputStream对象相连以提供有用接口 StringBufferInputStream 将String转成InputStream 字符串,底层实现实际使用的是StringBuffer(源码中用的是String,这句话有待考量),已过时/作为一种数据源,将其与FilterInputStream对象相连以提供有用接口 FileInputStream 用于从文件中读取信息 文件名,File实例或FileDescriptor实例/作为一种数据源,将其与FilterInputStream对象相连以提供有用接口 PipedInputStream 产生用于写入相关PipedOutputStream的数据,实现管道化 的概念 PipedOutputStream/作为多线程中数据源,将其与FilterInputStream对象相连以提供有用接口 SequenceInputStream 将两个或多个InputStream对象转换成单一InputStream 两个InputStream对象或一个容纳InputStream对象的容器Enumeration/作为一种数据源,将其与FilterInputStream对象相连以提供有用接口 FilterInputStream 抽象类,作为装饰器 的接口,详情请看下一小节
下面是一个简单的测试类,简单了解一下输入流的用法。
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.io.StringBufferInputStream;
public class Test {
public static void main(String args[]) throws IOException {
StringBufferInputStream s = new StringBufferInputStream("123 123" );
ByteArrayInputStream ss = new ByteArrayInputStream("123 123" .getBytes());
SequenceInputStream sss = new SequenceInputStream(s, ss);
byte b[] = new byte[1024 ];
int in ;
StringBuilder sb = new StringBuilder();
while ((in = sss.read(b)) != -1 ) {
sb.append(new String(b, 0 , in ));
}
System.out.println(sb);
FileInputStream fin = new FileInputStream("c1.txt" );
sb.delete (0 , sb.length());
while ((in = fin.read(b)) != -1 ) {
sb.append(new String(b, 0 , in ));
}
System.out.println(sb);
}
}
-----------运行结果
123 123123 123
c1.txt的内容
类 功能 构造器参数/如何使用 ByteArrayOutputStream 在内存中创建缓冲区。所有送往流的数据都要放置在此缓冲区 缓冲区初始化尺寸(可选)/用于指定数据的目的地,将其与FilterOutputStream对象相连以提供有用接口 FileOutputStream 用于将信息写入文件 文件名,File实例或FileDescriptor实例/用于指定数据的目的地,将其与FilterOutputStream对象相连以提供有用接口 PipedOutputStream 任何写入其中的信息都会被自动作为相关PipedInputStream的输出,实现管道化 的概念 PipedInputStream/用于指定多线程的数据的目的地,将其与FilterInputStream对象相连以提供有用接口 FilterOutputStream 抽象类,作为装饰器 的接口,详情请看下一小节
下面是一个简单的测试类,简单了解一下输出流的用法。
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class Test {
public static void main (String args[]) throws IOException {
byte [] b = "hello world" .getBytes();
ByteArrayOutputStream out1 = new ByteArrayOutputStream();
out1.write(b);
out1.flush();
FileOutputStream fos = new FileOutputStream("c1.txt" , true );
fos.write(b);
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(pos);
pos.write(b);
byte [] buf = new byte [1024 ];
int size = pis.read(buf);
System.out.println(new String(buf, 0 , size));
pis.close();
pos.close();
fos.close();
out1.close();
}
}
添加属性和有用的接口
前一节还没有提到FilterInputStream 和FilterOutputStream 的用法。这两个类是用来提供装饰器类接口以控制特定输入输出流的。
FilterInputStream 类能够完成两件完全不同的事情。其中,DataInputStream 允许我们读取不同的基本类型 数据以及String 对象。搭配相应的DataOutputStream,我们就可以通过数据流将基本类型的数据从一个地方迁移到另一个地方。其他FilterInputStream 类则在内部修改InputStream的行为方式:是否缓冲,是否保留它读过的行,以及是否把单一字符推回输入流等。最后两个类看起来像是为了创建一个编译器,因此我们在一般编程中不会用到它们。 我们机会每次都要对输入进行缓冲,所以I/O类库把无缓冲输入作为特殊情况。 下面是一些FilterInputStream ,另外我还放入了FilterOutputStream 的子类PrintStream ,另外两个(DataOutputStream 和BufferedOutputStream )我直接在例子中做测试,就不列在表格里了。
类 功能 构造器参数/如何使用 DataInputStream 与DataOutputStream搭配使用,因此我们可以按照可移植方式从流读取基本类型数据(int,char,long等) InputStream/包含用于读取基本类型数据的全部接口 BufferedInputStream 使用它可以防止每次读取时都得进行实际写操作。代表”使用缓冲区” InputStream,可以指定缓冲区大小(可选)/本质上不提供接口,只不过是向进程中添加缓冲区所必需的。对接口对象搭配 LineNumberInputStream 跟踪行号 InputStream/仅增加了行号,已过时 PushbackInputStream 可以将字节回退到缓冲区 InputStream,可设置可回退缓冲字节大小/通常作为编译器的扫描器,之所以包含在内是因为Java编译器的需要,我们可能永远不会用到 PrintStream 用于产生格式化输出,有一些特别好用的方法 OutputStream,可以指定是否自动flush/下面有例子
上面书上一些描述已经过时了,我改写了一下。下面是一些例子:
import java.io .BufferedInputStream
import java.io .BufferedOutputStream
import java.io .DataInputStream
import java.io .DataOutputStream
import java.io .FileInputStream
import java.io .FileOutputStream
import java.io .IOException
import java.io .LineNumberInputStream
import java.io .PipedInputStream
import java.io .PipedOutputStream
import java.io .PushbackInputStream
public class Test {
public static void main(String args[]) throws IOException {
//用其他的InputStream 无法验证,比如ByteArrayInputStream
DataInputStream dis = new DataInputStream(new FileInputStream("c1.txt" ))
DataOutputStream dos = new DataOutputStream(new FileOutputStream("c1.txt" ))
dos.writeInt (1 )
dos.writeDouble (1.0 D)
dos.writeUTF ("miaoch" )
//dos.flush ()
System.out .println (dis.readInt ())
//System.out .println (dis.readInt ())
System.out .println (dis.readDouble ())
System.out .println (dis.readUTF ())
dis.close ()
dos.close ()
PipedInputStream pis = new PipedInputStream()
PipedOutputStream pos = new PipedOutputStream(pis)
dis = new DataInputStream(pis)
dos = new DataOutputStream(pos)
dos.writeUTF ("miaoch" )
//dos.flush ()
System.out .println (dis.readUTF ())
dis.close ()
dos.close ()
dis = new DataInputStream(System.in )
//System.out .println (dis.readInt ())
//System.out .println ((49 <<(8 *3 )) + (50 <<(8 *2 )) + (51 <<8 ) + 52 )
System.out .println (dis.readInt ())
System.out .println ((49 <<(8 *3 )) + (50 <<(8 *2 )) + (13 <<8 ) + 10 )
dis.close ()
//关于BufferedInputStream BufferedOutputStream
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("c1.txt" ))
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("c1.txt" ))
bos.write ("hello world\n" .getBytes ())
bos.flush ()
byte[] b = new byte[1024 ]
System.out .println (new String(b, 0 , bis.read (b)))
bos.write ("hello world1\n" .getBytes ())
bos.write ("hello world2\n" .getBytes ())
bos.write ("hello world3" .getBytes ())
bos.flush ()
bis.close ()
bos.close ()
//关于LineNumberInputStream 已过时
LineNumberInputStream lis = new LineNumberInputStream(new FileInputStream("c1.txt" ))
lis.setLineNumber (3 )
System.out .println (new String(b, 0 , lis.read (b)))
System.out .println (lis.getLineNumber ())
PushbackInputStream ppis = new PushbackInputStream(new FileInputStream("c1.txt" ), 10 )
int len = ppis.read (b)
System.out .println ("len==" + len)
System.out .println (new String(b, 0 , len))
//ppis.unread (b, len - 2 , 2 )
ppis.unread ('d' )
ppis.unread ('c' )
ppis.unread ('b' )
ppis.unread ('a' )
len = ppis.read (b)
System.out .println ("String==" + new String(b, 0 , len))
ppis.close ()
PrintStream ps = new PrintStream(new FileOutputStream("c2.txt" ))
//PrintStream ps = new PrintStream(new File("c1.txt" ))
ps.println (true)
ps.printf ("%s" , "hello" )
ps.append (new String("world" ))
ps.flush ()
ps.close ()
}
}
-------运行结果:
1
1.0
miaoch
miaoch
123 --->此处需要控制台输入12 或1234
825373453
825363722
hello world
hello world
hello world1
hello world2
hello world3
6
len==50
hello world
hello world1
hello world2
hello world3
String==abcd
Reader和Writer
由于InputStream 和OutputStream是面向字节的,因此Java1.1推出了新的IO类Reader 和Writer ,设计他们主要是为了国际化,他们能更好地处理16位的Unicode 字符。下面是他们之间的相似关系:
原先针对字节的类(数据来源) 相应的针对字符的类 InputStream Reader 适配器 InputStreamReader OutputStream Writer 适配器 OutputStreamWriter FileInputStream FileReader FileOutputStream FileWriter StringBufferInputStream(已弃用) StringReader 无 StringWriter ByteArrayInputStream CharArrayReader ByteArrayOutputStream CharArrayWriter PipedInputStream PipedReader PipedOutputStream PipedWriter
原先针对字节的类(过滤器) 相应的针对字符的类 FilterInputStream FilterReader FilterOutputStream FilterWriter(抽象类、没有子类) BuffererInputStream BufferedReader BuffererOutputStream BufferedWriter DataInputStream DataInputStream(其余可以直接用,readLine的时候使用BufferedReader) PrintStream PrintWriter LineNumberInputStream(已弃用) LineNumberReader PushbackInputStream PushbackReader
- 关于字符流的例子我就不举了,私下试试就清楚了。
自我独立的类:RandomAccessFile
RandomAccessFile适用于由大小已知的记录组成的文件。就跟我们打开一个记事本一样,可以边读边写。它是一个完全独立的类,没有继承于XXStream 或者Reader 和Writer 。从本质上讲,它的工作方式类似于把DataInputStream 和DataOutputStream 组合起来使用。它的大部分功能由nio 存储映射文件取代,所以这里就不再聊这个了。因为基本用不到这个类。
I/O流的典型使用方式
缓冲输入文件
如果想要打开一个文件用于字符输入,可以使用FileInputReader 。为了提高速度,我们希望能对那个文件进行缓冲,那么我们将所产生的引用传给一个BufferedReader 构造器。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BufferInputFile {
public static String read (String filename) throws IOException {
BufferedReader in = new BufferedReader(new FileReader(filename));
StringBuilder result = new StringBuilder();
String temp;
while ((temp = in .readLine()) != null ) {
result.append(temp + "\n" );
}
in .close();
return result.toString();
}
public static void main (String args[]) throws IOException {
System.out .println(read("c1.txt" ));
}
}
标准I/O重定向
Java的System类提供了一些简单的静态方法调用,以允许我们对标准输入、输出和错误I/O流进行重定向。当控制台有大量信息不好阅读时,我们可以将我们需要的一些信息输出到文件中。其实也用不到重定向,新建一个输出流即可。
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
public class Test {
public static void main (String []args) throws Exception {
new Thread(){
{
setDaemon(true );
start();
}
public void run () {
while (true ) {
System.out .println("无用信息" );
Thread.yield ();
}
}
};
Thread.sleep(100 );
PrintStream consoleOut = System.out ;
InputStream consoleIn = System.in ;
BufferedInputStream in = new BufferedInputStream(new FileInputStream("c1.txt" ));
PrintStream out = new PrintStream(new FileOutputStream("c2.txt" ));
synchronized (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();
System.setIn(consoleIn);
System.setOut(consoleOut);
System.setErr(consoleOut);
}
}
}
新I/O
JDK1.4的java.nio.* 引入了新的Java I/O类库,其目的在于提高速度。实际上,旧的I/O 包已经使用nio重新实现过,因此,即使我们不显式地使用nio编写代码,也能从中受益。 速度的提高来自于所使用的结构更接近于操作系统执行I/O的方式:通道 和缓冲器 。通道负责数据的真实传输,而缓冲器则是通过空间换取时间,避免读写太过频繁。 唯一与通道交互的缓冲器是ByteBuffer 。 接下来是一个关于管道 和缓冲器 的例子:
import java.io .FileInputStream
import java.io .FileOutputStream
import java.io .RandomAccessFile
import java.nio .ByteBuffer
import java.nio .channels .FileChannel
public class Test {
public static void main(String []args) throws Exception {
ByteBuffer buff = ByteBuffer.allocate (1024 )
FileChannel channel = new FileOutputStream("c2.txt" ).getChannel ()
channel.write (ByteBuffer.wrap ("some text " .getBytes ()))
channel.close ()
channel = new FileInputStream("c2.txt" ).getChannel ()
//channel.write (ByteBuffer.wrap ("error " .getBytes ()))
channel.read (buff)
buff.flip ()
//遍历buff的常用方式
while (buff.hasRemaining ()) {
System.out .print ((char) buff.get ())
}
buff.clear ()
channel.close ()
System.out .println ()
channel = new RandomAccessFile("c2.txt" , "rw" ).getChannel ()
channel.position (channel.size ())
channel.write (ByteBuffer.wrap ("hello world " .getBytes ()))
channel.position (0 )
channel.read (buff)
buff.flip ()
while (buff.hasRemaining ()) {
System.out .print ((char) buff.get ())
}
buff.clear ()
}
}
我们可以用下面的方式连接两个管道,其中的一些缓冲器的操作我们就可以不用管了,当然这种方式比较少用,如果用于复制文件的话,的确是个好方法:
import java.io .FileInputStream
import java.io .FileOutputStream
import java.io .RandomAccessFile
import java.nio .channels .FileChannel
public class Test {
public static void main(String []args) throws Exception {
FileChannel in = new FileInputStream("c1.txt" ).getChannel ()
FileChannel out = new FileOutputStream("c2.txt" ).getChannel ()
//FileChannel out = new RandomAccessFile("c2.txt" , "rw" ).getChannel ()
//in .transferTo (10 , in .size () - 20 , out )
out .transferFrom (in , 2 , in .size ())
in .close ()
out .close ()
}
}
关于ByteBuffer如何获得char[]的问题,前面用的是一个一个读取转换,但是遇到中文字符会出现显示错误。关于这个问题,书上有三种解决方式:
对写入的ByteBuffer进行编码(默认是System.getProperty(“file.encoding”)的编码格式 ),对读出的ByteBuffer解码(默认是UTF-16BE )。因为我设置的是GBK 编码,而解码用的是UTF-16BE ,只要修改其中一个即可(Charset.forName(“GBK”).decode(buff))。 对传入的ByteBuffer.asCharBuffer().put(content); 然后通过ByteBuffer.asCharBuffer()获得char[]结果(其实相当于用其内部指定的编码进行编码解码)。 将写入的ByteBuffer编码成”UTF-16BE “,(getBytes(“UTF-16BE”) )。 另外除了从ByteBuffer中读取char以外,还可以从其中读取其他的基本类型,方法是使用asIntBuffer(),asLongBuffer()等方法获取其他类型的Buffer,然后再向其中put()或者get(),其实其底层用的还是ByteBuffer(一次读取多少字节,返回什么类型被自动化了 ),只不过封装了一层代理,使其用起来更方便而已。 这里说明一下什么是低位优先(little endian) 和高位优先(big endian) 。不同机器可能会使用不同的字节排序来存储数据。当储存量大于1个字节时,就得考虑字节的存储问题。ByteBuffer 是以高位优先 的形式存储的。啥意思呢?就是说你如果往BuyteBuffer 里放入一个int 型数据,由于int 是四个字节,它会先把最高位的字节先存入ByteBuffer 。比如说这个int 是100 ,很明显其前三个字节都是0。如果此时按照低位优先存储的话,就会先把代表100的字节存入ByteBuffer ,然后再存倒数第二个,第三个,第四个字节。高位优先 存储的方式更容易让人理解。
import java.nio .ByteBuffer
import java.nio .ByteOrder
import java.util .Arrays
public class Test {
public static void main(String []args) throws Exception {
ByteBuffer bb = ByteBuffer.allocate (12 )
bb.asCharBuffer ().put ("占八字节" )
bb.putInt (8 , 100 )
System.out .println (Arrays.toString (bb.array ()))
bb.clear ()
bb.order (ByteOrder.LITTLE _ENDIAN)
bb.asCharBuffer ().put ("占八字节" )
bb.putInt (8 , 100 )
System.out .println (Arrays.toString (bb.array ()))
}
}
下面是一张nio类关系图: Buffer由数据和可以高效访问及操纵数据的四个索引 组成。这四个索引是mark(标记)、position(位置)、limit(界限)和capacity(容量) 。最常用的是position和limit,get()(put(val)也从position开始)不传参数方法会从position下标开始到limit结束(可能出现越界错误,需要通过hasRemaining()判断),下面是用于设置和复位索引以及查询它们的值的方法。这里顺便说一下,在用通道读取到缓冲器时,要调用flip()才能读取缓冲器里的内容是因为,通道写的过程中改变了position的值,所以我们只用从0读到position即可(将limit设为position,position设为0)。 上面的图还少了两个常用的方法
rewind(),将position设为0,mark设为-1。相当于复位。和clear()不同的是,clear()会将limit设为capacity。 reset(),判断mark的下标是否合理,合理的话设置position为mark,否则抛异常。
内存映射文件
内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件。有了内存映射文件,我们就可以假定整个文件都放在内存中,而且可以完全把它当做非常大的数组来访问。最大可支持2GB(int上限),其速度也比旧I/O来的快。
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class Test {
static int length = Integer.MAX_VALUE;
public static void main (String []args) throws Exception {
MappedByteBuffer out = new RandomAccessFile("c1.txt" , "rw" )
.getChannel().map(FileChannel.MapMode.READ_WRITE, 0 , length);
byte [] b = "哈" .getBytes();
for (int i=0 ; i < length / 2 ; i++) {
out .put(b);
}
System.out .println("Finished!" );
}
}
其中MappedByteBuffer 是ByteBuffer的子类。
文件上锁
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
public class Test {
public static void main (String []args) throws Exception {
RandomAccessFile file = new RandomAccessFile("c1.txt" , "rw" );
System.out .println("locking!" );
FileLock fl = file.getChannel().tryLock();
if (fl != null ) {
Thread.sleep(10000 );
fl.release();
System.out .println("Released ok!" );
}
file.close();
}
}
在locking期间,我们打开c1.txt,尝试修改保存,会得到下列提示。因为文件加锁是直接映射到本地操作系统的加锁工具,所以不必担心操作系统本地进程无法同步。
SocketChannel、DatagramChannel和ServerSocketChannel不需要加锁。因为他们本身就是针对单线程的。tryLock()是非阻塞式的,lock是阻塞式的。话虽然这么说,但是我下面的例子说明lock也不是阻塞式的。会直接弹出FL2 locking failed,可能是我例子写的不对。
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
public class Test {
public static void main (String []args) throws Exception {
final RandomAccessFile file = new RandomAccessFile("c1.txt" , "rw" );
new Thread() {
public void run () {
try {
System.out .println("thread locking!" );
FileLock fl = file.getChannel().lock ();
Thread.sleep(3000 );
fl.release();
System.out .println("thread release!" );
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
Thread.sleep(1000 );
FileLock fl = null ;
try {
fl = file.getChannel().lock ();
System.out .println("FL2 locking success" );
} catch (Exception e) {
System.out .println("FL2 locking failed" );
} finally {
if (fl != null ) {
fl.release();
System.out .println("Released ok!" );
}
}
}
}
另外对于映射文件的部分加锁,书中也给了例子,大致是使用LockAndModify()方法,上面的FileLock也可以部分加锁,这部分我就省略了。
压缩
关于压缩,我就不多说了,我之前收录过一个工具类,这里可以给大家参考一下:
package org.zip;
import java.io.*;
import java.util.zip.*;
public class ZipUtil {
private static void zip (ZipOutputStream out , File f, String base ) throws Exception {
if (f.isDirectory()) {
File[] files = f.listFiles();
base = (base .length() == 0 ? "" : base + "/" );
for (int i = 0 ; i < files.length; i++) {
zip(out , files[i], base + files[i].getName());
}
} else {
out .putNextEntry(new ZipEntry(base ));
BufferedInputStream in = new BufferedInputStream(new FileInputStream(f));
transfer(in , out );
in .close();
}
}
public static void zip (String inputFileName, String zipFileName) throws Exception {
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFileName));
zip(out , new File(inputFileName), "" );
out .close();
}
public static void unzip (String zipFileName, String unzipDir) throws Exception {
createDir(unzipDir);
ZipInputStream in = new ZipInputStream(new FileInputStream(zipFileName));
ZipEntry entry;
while ((entry = in .getNextEntry()) != null ) {
String fileName = entry.getName();
String tmp;
int index = fileName.lastIndexOf('/' );
if (index != -1 ) {
tmp = fileName.substring(0 , index);
tmp = unzipDir + "/" + tmp;
createDir(tmp);
}
fileName = unzipDir + "/" + fileName;
File file = new File(fileName);
file.createNewFile();
FileOutputStream out = new FileOutputStream(file);
transfer(in , out );
out .close();
}
in .close();
}
private static void transfer (InputStream in , OutputStream out ) throws Exception {
byte [] data = new byte [1024 ];
int index;
while ((index = in .read(data)) != -1 ) {
out .write(data, 0 , index);
}
}
private static File createDir (String path) {
File f = new File(path);
if (!f.isDirectory()) {
f.mkdirs();
}
return f;
}
public static void main (String args[]) throws Exception {
zip("D:\\test" , "D:\\test.zip" );
unzip("D:\\test.zip" , "D:\\test_2" );
}
}
对象序列化
普通的序列化就是将可序列化的对象 写入ObjectOutputStream ,然后我们可以从ObjectInputStream 读出对象。例如下面的例子就是利用序列化的特性来复制一个对象:
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.Serializable;
public class Test {
public static void main(String args[]) {
Bean bean1 = new Bean();
bean1.s = new String("hello" );
Bean bean2 = copy(bean1);
System.out.println(bean2.i);
System.out.println(bean2.s);
System.out.println(bean1 == bean2);
bean1.s = "world" ;
System.out.println(bean2.s);
}
private static <T extends Serializable> T copy(T obj) {
PipedOutputStream pos = null ;
PipedInputStream pis = null ;
try {
pos = new PipedOutputStream();
pis = new PipedInputStream(pos);
ObjectOutputStream ous = new ObjectOutputStream(pos);
ObjectInputStream ois = new ObjectInputStream(pis);
ous.writeObject(obj);
return (T) ois.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
pos.close();
pis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return null ;
}
}
class Bean implements Serializable {
int i;
String s;
}
序列化的控制
默认的序列化自然简单,但倘若我们只想序列化一部分域,那该如何控制呢?在特殊情况下,可通过实现Externalizable 接口来对序列化过程进行控制。这个接口继承了Serializable 接口,同时增添了两个新方法:writeExternal() 和readExternal() 。这两个方法会在序列化和反序列化还原的过程中被自动调用。我们稍稍修改上面的例子,再来做一个例子:
import java.io.*;
public class Test {
public static void main (String args[]) throws Exception {
Bean1 bean1 = new Bean1();
Bean2 bean2 = new Bean2();
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(pos);
ObjectOutputStream ous = new ObjectOutputStream(pos);
ObjectInputStream ois = new ObjectInputStream(pis);
ous.writeObject(bean1);
ous.writeObject(bean2);
ois.readObject();
ois.readObject();
pos.close();
pis.close();
}
}
class Bean1 implements Externalizable {
public Bean1 () {
System.out .println("Bean1 Constructor" );
}
public void writeExternal (ObjectOutput out ) throws IOException {
System.out .println("Bean1 write" );
}
public void readExternal (ObjectInput in ) throws IOException,
ClassNotFoundException {
System.out .println("Bean1 read" );
}
}
class Bean2 implements Externalizable {
public Bean2 () {
System.out .println("Bean2 Constructor" );
}
public void writeExternal (ObjectOutput out ) throws IOException {
System.out .println("Bean2 write" );
}
public void readExternal (ObjectInput in ) throws IOException,
ClassNotFoundException {
System.out .println("Bean2 read" );
}
}
------------------运行结果
Bean1 Constructor
Bean2 Constructor
Bean1 write
Bean2 write
Bean1 Constructor
Bean1 read
Bean2 Constructor
Bean2 read
我惊奇的发现Externalizable 在readObject 的时候居然会调用构造器。于是我有几个问题:
改变构造器的访问权限会如何? 没有默认构造器会如何? 答:在read的时候都会抛java.io.InvalidClassException 异常,异常信息是no valid constructor 。看来Externalizable 对象必须要有无参构造器且必须是public 的(猜测readObject() 方法中利用反射做了一些操作)。书上也说明了Externalizable 对象在反序列化时先要调用构造器(包括字段处的初始化),然后调用readExternal() 。下面是一个正确的Externalizable 序列化的例子:
import java.io.*;
public class Test {
public static void main (String args[]) throws Exception {
Bean1 bean1 = new Bean1();
bean1.i = 10 ;
bean1.s1 = new String("hello world" );
bean1.s2 = new String("change" );
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(pos);
ObjectOutputStream ous = new ObjectOutputStream(pos);
ObjectInputStream ois = new ObjectInputStream(pis);
ous.writeObject(bean1);
Bean1 bean1_copy = (Bean1) ois.readObject();
System.out .println(bean1_copy.i);
System.out .println(bean1_copy.s1);
System.out .println(bean1_copy.s2);
pos.close();
pis.close();
}
}
class Bean1 implements Externalizable {
public int i;
public String s1;
public String s2 = "defalut" ;
public Bean1 () {
System.out .println("Bean1 Constructor" );
}
public void writeExternal (ObjectOutput out ) throws IOException {
System.out .println("Bean1 write" );
out .writeInt(i);
out .writeObject(s1);
}
public void readExternal (ObjectInput in ) throws IOException,
ClassNotFoundException {
System.out .println("Bean1 read" );
i = in .readInt();
s1 = (String) in .readObject();
}
}
--------------------运行结果
Bean1 Constructor
Bean1 write
Bean1 Constructor
Bean1 read
10
hello world
defalut
相信看了这个例子以后,我们就该知道Externalizable 到底是如何工作了的吧?其实我们操作了Object的每一个域的动作,这样我们想要传就传,不传就不传,控制起来就很方便了。 虽然Externalizable 可以完成这种控制工作,Java也提供了一种在Serializable 中控制的方法,使用transient(瞬时) 关键字即可。
import java.io.*;
public class Test {
public static void main (String args[]) throws Exception {
Bean1 bean1 = new Bean1();
bean1.username = "miaoch" ;
bean1.password = "就不告诉你" ;
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(pos);
ObjectOutputStream ous = new ObjectOutputStream(pos);
ObjectInputStream ois = new ObjectInputStream(pis);
ous.writeObject(bean1);
Bean1 bean1_copy = (Bean1) ois.readObject();
System.out .println(bean1_copy.username);
System.out .println(bean1_copy.password);
System.out .println(bean1_copy.i);
pos.close();
pis.close();
}
}
class Bean1 implements Serializable {
public String username;
public transient String password = "defalut" ;
public transient int i = 100 ;
public Bean1 () {
System.out .println("Bean1 Constructor" );
}
}
import java.io.*;
public class Test {
public static void main(String args[]) throws Exception {
Bean1 bean1 = new Bean1();
bean1.username = "miaoch" ;
bean1.password = "就不告诉你" ;
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(pos);
ObjectOutputStream ous = new ObjectOutputStream(pos);
ObjectInputStream ois = new ObjectInputStream(pis);
ous.writeObject(bean1);
Bean1 bean1_copy = (Bean1) ois.readObject();
System.out.println(bean1_copy.username);
System.out.println(bean1_copy.password);
System.out.println(bean1_copy.i);
System.out.println(bean1_copy.id);
pos.close();
pis.close();
}
}
class Bean1 extends Bean2 implements Serializable {
public String username;
public transient String password = "defalut" ;
public transient int i = 100 ;
public Bean1() {
System.out.println("Bean1 Constructor" );
}
}
class Bean2 {
public transient int id = 2 ;
public Bean2() {
System.out.println("Bean2 Constructor" );
}
}
更加复杂的情况就不讨论了(例如父类不实现Serializable,为什么子类照样能成功序列化)另外书中还告知了其实在Serializable 可以添加(被反射判断了) 下列两个方法:
private void writeObject(ObjectOutputStream stream) throws IOException
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException
注意连权限修饰符也必须相同,下面是一个例子。他和Externalizable 的区别就是不会调用构造器和字段处初始化。且此时的transient 会失效(因为已由我们全权控制)。
import java.io.*;
public class Test {
public static void main (String args[]) throws Exception {
Bean1 bean1 = new Bean1();
bean1.username = "miaoch" ;
bean1.password = "就不告诉你" ;
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(pos);
ObjectOutputStream ous = new ObjectOutputStream(pos);
ObjectInputStream ois = new ObjectInputStream(pis);
ous.writeObject(bean1);
Bean1 bean1_copy = (Bean1) ois.readObject();
System.out .println(bean1_copy.username);
System.out .println(bean1_copy.password);
System.out .println(bean1_copy.i);
pos.close();
pis.close();
}
}
class Bean1 implements Serializable {
public transient String username;
public String password = "defalut" ;
public int i = 100 ;
public Bean1 () {
System.out .println("Bean1 Constructor" );
}
private void writeObject (ObjectOutputStream stream) throws IOException {
System.out .println("writeObject" );
stream.writeObject(username);
}
private void readObject (ObjectInputStream stream) throws IOException, ClassNotFoundException {
System.out .println("readObject" );
username = (String) stream.readObject();
}
}
不过最好不要这么做,也太混乱了。我只是觉得好玩就列在这了。
XML和Preferences
Java的序列化和反序列化只能在JVM上进行,而更普遍的做法是将Java对象转成XML或者JSON。这两种做法都很常见,只要去看看API就可以很容易的写出来了。 Preferences类似于Map、JSON,只能存储一些比较小的数据集合。它的特点是可以依附于一个节点。这个节点一般是一个Class对象,我们可以在其他类里通过这个节点(或者该节点的同级Class对象,其实只是为了获取路径)获得这个Preferences对象(windows中其实使用了注册表,这也是一种持久化)。
package test2
import java.util .prefs .Preferences
public class Test {
public static void main(String args[]) throws Exception {
Class.forName ("test2.Demo" )
Preferences pref_user = Preferences.userNodeForPackage (Test.class )
Preferences pref_system = Preferences.systemNodeForPackage (Test.class )
//test2.file .FileTest 更深1 级
Preferences pref_test = Preferences.systemNodeForPackage (test2.file .FileTest .class )
System.out .println (pref_user.get ("hello" , null))
System.out .println (pref_system.get ("hello" , null))
System.out .println (pref_test.get ("hello" , null))
System.out .println (pref_user.getInt ("count" , 0 ))
}
}
class Demo {
static {
//相当于用户变量和系统变量, 这个节点 同级都可以访问到
Preferences pref_user = Preferences.userNodeForPackage (Demo.class )
Preferences pref_system = Preferences.systemNodeForPackage (Demo.class )
int count = pref_user.getInt ("count" , 0 )
pref_user.putInt ("count" , ++count)
pref_user.put ("hello" , "pref_user" )
pref_system.put ("hello" , "pref_system" )
System.out .println ("Preferences 已设置" )
}
}
preferences.usernodeforpackage
代表得到hkey_current_user\software\javasoft\prefs
下的相对路径 preferences.systemnodeforpackage
代表得到 hkey_local_machine\software\javasoft\prefs
下的相对路径