注意try块的使用:由于finally语句较臃肿,Java7允许try关键字后面跟一对圆括号,括号中声明或初始化那些必须在程序结束时显式关闭的资源。这些资源必须实现AutoCloseable接口或其子接口Closeable接口。
AutoCloseable接口里的close()方法声明抛出了Exception,可以抛出任何异常;而其子接口Closeable接口里的close()方法声明抛出了IOException,只能抛出IOException及其子类。
工厂类、工具类(factory class)
Java的IO通过java.io包下的类和接口来支持,主要包括输入、输出两种IO流,每种输入、输出流又可分为字节流和字符流两大类。IO流使用了一种装饰器设计模式,它将IO流分成底层节点流和上层处理流。
java.nio及其子包下提供了一系列全新的API,也称NIO 2。
Java对象的序列化机制,可以报内存中Java对象转换成二进制字节流,这样就可以把Java对象存储到磁盘里,或在网络上传输Java对象。
File类
访问文件和目录
文件过滤器
File类的list()方法可以接收一个FilenameFile参数,可以只列出符合条件的文件。
IO流
java.io包中。
Java把不同的输入/输出源(如键盘、文件、网络连接等)抽象描述为流(stream)。
输入流主要由InputStream和Reader作为基类,输出流主要由OutputStream和Writer作为基类。
字节流(数据单位是字节(byte,8bit))主要由InputStream和OutputSteam作为基类,字符流(数据单位是字符(char,两个字节(16bit)))主要由Reader和Writer作为基类。
InputStream和Reader、OutputStream和Writer都是抽象类,无法直接创建实例。但它们分别有一个读取文件输入、输出流。以InputStream和Reader为例,用于读取文件的输入流是FileInputStream和FileReader,它们都是节点流,直接和指定文件关联。
从输入流中读取数据,抽象为使用一个竹筒到水管中取水。每个中文字符占两个字节。
1 | FileInputStream fis = new FileInputStream(“a.java”); //创建字节输入流 |
其中FileInputStream中的int read(byte[] b)方法:Reads up to b.length bytes of data from this input stream into an array of bytes. Return total number of bytes read into the buffer, or -1 if there is no more data because the end of the file has been reached
与JDBC编程一样,程序里打开的IO资源不属于内存里的资源,垃圾回收机制无法回收它们,所以应当显式关闭打开的IO资源。
Java7改写了所有的IO资源类,它们都实现了AutoCloseable接口,因此都可以通过自动关闭资源的try语句来关闭这些IO流。
输入输出流体系
上面介绍了4个基类访问文件的节点流的用法,使用起来繁琐,可借助处理流简化编程。
处理流的功能:使用处理流来包装节点流,,隐藏底层设备上节点流的差异,并对外提供更方便的输入/输出方法,让我们只关注高级流的操作。
节点流:
处理流:
处理流的用法
只需要在创建处理流时传入一个节点流作为构造参数即可。
下面使用PrintStream处理流来包装OutputStream:
1 | public class PrintStreamTest{ |
标准输出(Sytem.out)的类型就是PrintStream。
通常如果需要输出文本内容,都应该将输出流包装成PrintStream后进行输出。
输入/输出流体系
通常有一个规则:如果进行输入/输出的内容是二进制内容,应该考虑使用字节(byte)流;如果进行输入/输出的内容是文本内容,则应该考虑使用字符(char)流。
转换流
将字节流转换成字符流:InputStreamReader,OutputStreamWriter。
System.in代表标准输入,即键盘输入,但这个标准输入流是InputStream类(字节流)的实例,使用不太方便,而且键盘输入的都是文本内容,所以可以使用InputStreamReader将其转换成字符输入流。将普通Reader包装成BufferedReader(处理流),利用readLiner()方法可以一次读取一行的内容。
1 | public class KeyinTest{ |
BufferedReader具有缓冲功能,它可以逐行读取文本。以换行符为标识,如果没有读到换行符,则程序阻塞,等到读到换行符为止。
注意:
System.exit(int status)这个方法是用来结束当前正在运行中的java虚拟机。
System.exit(0)是正常退出程序,而System.exit(1)或者说非0表示非正常退出程序。
推回输入流
PushbackInputStream,PushbackReader。
这两个推回输入流都带有一个推回缓冲区,当程序调用两个推回输入流的unread()方法时,系统会把指定数组的内容退回到该缓冲区里。
重定向输入/输出流
Java的标准输入/输出通过System.in和System.out来代表,在默认情况下分别代表键盘和显示器。
//创建PrintStream(打印)输出流,将节点流包装成处理流
PrintStream ps = new PrintStream(new FileOutputStream(“out.tex”));
//将标准输出重定向到ps输出流
System.setOut(ps);
JVM读写其它进程的数据
使用Runtime对象的exec()方法课可以运行平台上的其它程序,该方法产生一个Process对象,代表由该java程序启动的子进程。
让程序和其进程进行通信:
1 | //运行javac命令,返回运行该命令的子进程 |
上面程序分析:
p.getErrorStream():p进程的标准错误流输入
InputStreamReader:标准输入是文本输入,将文本转换成字符流
BufferedReader:将普通Reader包装成BufferedReader,利用readLiner()方法可以一次读取一行的内容。
衡量输入输出总是站在本程序的角度。
RandomAccessFile
它与普通的输入/输出流不同的是,RadomAccessFile支持随机(任意更贴切)访问方式,程序可以直接跳转到文件的任意地方来读写数据(允许文件自由地移动文件指针,任意访问文件的制定位置)。因此对于只访问文件的部分内容,使用RandomAccessFile更好。
如果程序需要向已存在的文件中追加内容,则应该使用RandomAccessFile。
局限:只能读写文件,不能读写IO节点。
创建RandomAccessFile对象需要制定mode参数:
(1)“r”:只读方式打开文件
(2)“rw”:读、写
(3)“rws”:相比于rw模式,对文件内容或元数据的每个更新都要同步写入底层存储设备。
(4)“rwd”:相比于rw模式,对文件内容的每个更新都要同步写入底层存储设备。
RandomAccessFile不能直接向文件中的指定位置添加内容,否则会覆盖原文件中的内容,需要先把插入点后面的内容放入缓冲区,等把需要的内容写入文件后,再将缓冲区中内容追加到文件后面。
1 | import java.io.RandomAccessFile; |
对象序列化(Serialize)
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流(字节序列),将对象保存到磁盘中,或允许在网络中直接传输对象。
对象的序列化(Serialize)指将一个Java对象写入IO流;对象的反序列化(Deserialize)则指从IO流中恢复该Java对象。
这个对象必须是可序列化的(serializable),它的类必须实现两个接口之一:
(1) Serializable 该接口是一个标记接口,只表明该类的实例是可序列化的,实现该接口无法实现任何方法。
(2) Externalizable(可外部化的)
所有可能在网络上传输的对象都应该是可序列化的。比如RMI(Remote Method Invoke,远程方法调用,是Java EE的基础)过程中的参数和返回值。
使用对象流实现序列化
通常建议:程序创建的每个JavaBean类都实现Serializable。
1) 某个类实现Serializable接口;
1 | class Person implements java.io.Serializable{} |
2) 创建一个ObjectOutputStream,这个输出流是一个处理流,所以必须建立在其它节点流的基础上;
1 | ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(“object.txt”)); |
3) 调用ObjectOutputStream对象的writeObject()方法输出可序列化对象;
1 | Person per = new Person(“悟空”, 500); |
如果希望从二进制中恢复Java对象,需要反序列化。
4) 创建一个ObjectInputStream,这个输入流是一个处理流,所以必须建立在其它节点流的基础上;
1 | ObjectInputStream oos = new ObjectInputStream(new FileInputStream(“object.txt”)); |
5) 调用ObjectInputStream对象的readObject()方法读取流中的对象;
1 | Person per = (Person)ois.readObject(); //从输入流中读取一个对象,并强制转换成Person类 |
反序列化读取的知识Java对象的数据,而不是Java类,因此采用反序列化恢复Java对象时,必须提供Java对象所属类的class文件。
对象引用的序列化
前面的Person类的两个成员变量分别是String类型和int类型,如果某个成员变量不是String或基本类型,而是引用类型,那么这个引用类必须是可序列化的。
如程序序列化一个Teacher对象时,该对象持有一个Person类对象的引用,为了在反序列化时可以正常恢复该Teacher对象,程序会顺带将Person对象也进行序列化,所以Person类也必须是可序列化的。
为了防止多次重复序列化一个对象,Java序列化机制采用了一种特殊的序列化算法:
(1) 所有保存到磁盘中的对象都有一个序列化编号。
(2) 当程序试图序列化一个对象时,程序会先检查该对象是否已经被序列化,如果在本次虚拟机中没有被序列化过,系统才会将该对象转换成字节序列并输出。
(3) 如果已经被序列化过,则程序直接输出一个序列化编号。
但这样也会出现一个问题:当程序再次调用writeObject()方法时,程序只是输出前面的序列化编号,即使后面该对象的实例变量值已经改变,改变的实例变量值也不会输出。所以对于可变对象一定要注意。
自定义序列化
当对某个对象进行序列化时,系统会自动把该对象的所有实例变量依次序列化,如果某个实例变量引用到另一个对象,则被引用对象也会被序列化。这种序列化会一直持续下去,称为递归序列化。
但在一些特殊场景下,不希望将实例变量序列化,或者某个实例变量的类型不应该实例化,则在不需要序列化的实例变量前加transient关键字(短暂的,瞬时的)。
但这样也出现一个问题:被transient修饰的实例变量将完全隔离在序列化机制之外,导致使用反序列化恢复对象时,无法获得该实例变量的值。
另一种自定义序列化机制
完全有我们决定存储和恢复对象数据,必须实现Externalizable接口。
版本
反序列化对象时需要提供该对象的class文件,如何保证两个class文件兼容?若不指定serialVersionUID类变量,不利于程序在不同JVM之间移植,因为不同编译器对该类变量的计算策略可能不同。
解决:为每个要序列化的类中添加private static final serialVersionUID这个类变量,具体数值自己定义。
NIO(New IO)
传统输入/输出是基于阻塞式的,通过字节移动来处理,即面向流的输入/输出系统一次只能处理一个字节,效率不高。
NIO采用内存映射的方式,将文件或文件的一段区域映射到内存,这样就可以像访问内存一样访问文件了。
Channel(通道)和Buffer(缓冲)是NIO中两个核心对象。Channel是对传统输入/输出的模拟,不同之处是增加了map()方法进行数据映射,将Channel对应的部分或全部数据映射成Buffer。Buffer是一个容器,本质是一个数组(类似于前面的“竹筒”)。程序不能直接访问Channel中的数据,Channel的读取必须通过Buffer交互。
InuputStream/OutputStream<->Channel<-(map())->Buffer
使用Buffer
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
Buffer是一个抽象类,常用子类是ByteBuffer、CharBuffer。它没有构造器,通过static Buffer allocate(int capacity) 来创建对象。
Buffer装入数据后,调用Buffer的flip()方法将limit设为position,目的是将没有数据的区域封印起来,避免程序从Buffer中去除null值。
使用Channel
import java.nio.channels.FileChannel;
Java为Channel接口提供了DatagramChannel(UDP网络通信的Channel)、FileChannel、Pipe.SinkChannel、Pipe.SourceChannel(支持线程之间通信的管道Channel)、SelectableChannel、ServerSocketChannel、SoceketChannel(支持TCP网络通信的Channel)等实现类。
所有的Channel都不应该使用构造器来直接创建,而是通过传统的节点InputStream和OutputStream的getChannel()方法来返回对应的channel。
map()方法的方法签名为:MappedByteBuffer map(FileChannel.MapMode mode, long position, long size)
1 | import java.io.File; |
1 | /* |
字符集和Charset
import java.nio.CharBuffer;s
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
获取Java所支持的全部字符集
1 | sortedMap<String, Charset> = Charset.avaliableCharsets(); |
keySet()方法获取map中所有key值,get(Object key)方法获取相应key值对应的value。
创建字符集对象forName(…)方法。字符集的创建、编解码操作步骤如下(以简体中文为例):
1)创建字符集对象
Charset cn = Charset.forName(“GBK”);
2)char <-> byte, String <-> byte,
a) 只是进行简单的编、解码操作,CharBuffer和ByteBuffer都有方法直接转换
ByteBuffer encode(CharBuffer cb)
CharBuffer decode(ByteBuffer bb)
ByteBuffer encode(String str)
b) 若是进行较复杂的操作
获取字符集对象cn对应的编码器、解码器
CharsetEncoder cnEncoder = cn.newEncoder();
CharsetDecoder cnDecoder = cn.newDecoder();
3)创建CharBuffer对象
CharBuffer cbuff = CharBuffer.allocate(8);
4)将CharBuffer中的字符序列转换成字节序列
ByteBuffer bbuff = cnEncoder.encode(cbuff);
for (int i = 0; I < bbuff.capacity(); i ++){
bbuff.get(i);
}
文件锁
可以有效地阻止多个进程并发修改同一个文件。
在NIO中,FileChannel提供的lock()/tryLock()方法返回文件锁FileLock对象,由FileLock执行文件锁定功能。
1)lock()、lock(long position, long size, boolean shared):当试图锁定某个文件时,如果无法得到文件锁,程序将一直阻塞;
2)tryLock()、tryLock(long position, long size, boolean shared):尝试锁定文件,当获得了文件锁,返回该文件锁,否则返回null。
其中shared参数为true是,表明该锁是一个共享锁,允许多个进程读取文件,但阻止其他进程获得对该文件的排他锁;false为排他锁,它将锁住对该文件的读写,lock()/tryLock()默认为排他锁。
FileChannel channel = new FileOutputStream(“a.txt”).getChannel();
FileLock lock = channel.tryLock(); //使用非阻塞方式对文件进行加锁
Thread.sleep(10000); //程序暂停10s,加锁的文件将阻止其他程序对其进行修改
lock.release(); //释放锁
缺陷:文件锁虽然可以控制并发访问,但对于高并发访问的情形,推荐使用数据库。
Java7的NIO.2
Java7对NIO进行了重大改进:
1)新增java.nio.file包及其子包(对文件IO和文件系统访问支持)。
2)新增java.nio.channels包下以Asynchronous开头的Channel接口和类(基于异步Channel的IO)。
Path、Paths、Files核心API
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
为了弥补File类操作文件系统的不足,引入了Path接口,代表一个平台无关的平台路径。
Files、Paths工具类,Files包含大量工具方法,Paths包含两个返回Path的静态工厂方法。
早期File类不足,提供Files和Paths工具类,其中Files是一个操作文件的工具类;Paths返回Path类型。
1)获取Path对象:Paths的get()方法
Path path = Paths.get(String first, String…more)
2)使用Java8新增的Stream API列出当前目录下所有文件和目录:
Files.list(Paths.get(“.”)).forEach(path -> System.out.println(path);
使用FileVisitor遍历文件和目录
以前遍历指定目录下的所有文件和子目录需要进行递归,较复杂且灵活性不高。Files类提供了如下方法来遍历文件和子目录:
walkFileTree(Path start, FileVisitor<? super Path> visitor)
FileVisitor代表一个文件访问器。遍历文件和目录会触发FileVisitor中相应的方法。可以通过继承SimpleFileVisitor(FileVisitor的实现类)来实现文件访问器,这需要根据需要选择性地重写(@Override)指定方法。
1 | import java.io.IOException; |
使用WatchService监控文件变化
import java.nio.file.FileSystems;
import java.nio.file.WatchService;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchKey;
import java.nio.file.WatchEvent;
WatchService类的对象是操作系统原生的文件系统监控器。NIO.2的Path类提供如下方法来监听文件系统变化:
1) 获取当前OS平台下的文件系统监控器(文件系统的WatchService对象)
WatchService watchService = FileSystems.getDefault().newWatchService();
2) register(注册)
register(WatchService watcher, WatchEvent.Kind<?>…events)
WatchService代表一个文件系统监听服务。用监听器watcher监听path路径下的events类型的事件。
3) 调用WatchService的如下方法获取被监听目录的文件变化事件:
WatchKey poll(…):获取下一个WathKey,如果没有就立即返回null。
WatchKey take():获取下一个WathKey,如果没有就一直等待。
其中WatchKey表示WathService注册的监控对象的token(标记)。
WathcKey pollEvent():检索并删除此watch key的所有未决事件,并返回已检索事件的列表。
访问文件属性(Attribute)
在java.nio.file.attribute包下,如
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileOwnerAttributeView;
import java.nio.file.attribute.UserDefinedFileAttributeView;等
File类可以访问一些简单的文件属性(attribute,属性;归属),如文件的大小、修改时间、文件是否隐藏、是文件还是目录等。Java7的NIO.2在java.nio.file.attribute包下提供了大量的工具类,这些工具类主要分为两类:
1)***AttributeView:某种文件属性的“视图”,FileAttributeView是其它***AttributeView的父接口;
2)***Attributes:某种文件属性的集合,一般通过***AttributeView对象来获取。
具体常用方法:
(1)identify the required view(创建需要的view):
*** AttributeView ***View = Files.getFileAttributeView(path, ***AttributeView.class);
(2)获取访问***属性的***FileAttributes(先确保能读取文件属性):
***FileAttributes ***Attribs = ***View.readAttributes();
(3)访问***属性(如具体的创建时间、上次修改时间等):
***Attribs.creationTime().toMillis(); //访问文件的创建时间
…