在Java程序中,对于数据的输入/输出以”流”(Stream)方式进行
java.io 包定义了多个流类型:
1) 按数据流方向分为 – 输入流和输出流
2) 按数据单位分为 – 字节流和字符流
3) 按功能分为 – 节点流和处理流
所有流类型均位于java.io包内,分别继承以下四种抽象流类型:
字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer
注:输入输出流,都是站在【程序】的角度上来说,从文件读数据这叫输入流,往文件写数据叫输出流
一、字节 I/O 操作(InputStream和OutputStream)相关类图
1) InputStream是一个抽象类,核心方法是read()、read(byte b[])、read(byte b[], int off, int len),从不同的数据源读取数据。
这些数据源有:
① 字节数组。
② String对象。
③ 文件。
④ “管道”,一端输入,另一端输出。
⑤ 一个由其他种类的流组成的序列。
⑥ 其他数据源,如Internet等。
每一种数据源都对应相应的InputStream子类:
ByteArrayInputStream:缓冲区,处理字节数组,允许将内存的缓冲区当作InuputStream。
StringBufferInputStream:将String转换成 InputStream,底层使用StringBuffer实现。
FileInputStream:从文件中读取信息。
PipedInputStream:从管道读数据,最为多线程中的数据源。
SequenceInputStream:将多个流对象转换成一个InputStream。
FilterInputStream:“装饰器”类的基类(这里是装饰器模式),为其它InputStream类提供功能:
DataInputStream:用于读取基本类型数据。
BufferedInputStream:使用缓冲区,防止每次读取时都得进行实际写操作。
LineNumberInputStream:跟踪输入流中的行号,getLineNumber()、setLineNumber(int)。
PushbackInputStream:具有能弹出一个字节的缓冲区,可以将读到的最后一个字符回退。
2) OutputStream是一个抽象类,核心方法是write(int b)、write(byte b[])、write(byte b[], int off, int len),决定输出的目标:字节数组、文件或管道。
ByteArrayOutputStream:将数据写入一个byte数组缓冲区。
FileOutputStream:将数据写入文件。
PipedOutputStream:指定多线程数据的目的地,向与其它线程共用的管道中写入数据,用于多线程之间任务的通信。
FilterOutputStream:“装饰器”类,提供特定的输出流:
DataOutputStream:与DataInputStream搭配使用,用于写入基本类型数据。
BufferedOutputStream:使用缓冲区,避免每次发送数据时都进行实际的写操作,可使用 flush() 清空缓冲区。
PrintStream:产生格式化输出。
二、字符 I/O 操作(Reader和Writer)相关类图
三、基于磁盘的I/O操作(File类)
File类既能代表一个特定文件的名称,又能代表一个目录下的一组文件的名称。
【1】文件、目录的创建和删除
public class FileOperation {
public static void main(String[] args) {
// 使用 File.separator 系统默认名称分隔符,Windows和Linux文件路径分割符不同
String fileName = "C:" + File.separator + "hello.txt";
File file = new File(fileName);
// 创建一个新文件
try {
// 指定的文件不存在且成功创建返回 true;文件已存在返回 false
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
// 删除文件
if(file.exists()){
// 成功删除文件或目录时返回 true
file.delete();
}
// 创建一个文件夹
String dirName = "C:" + File.separator + "hello";
File dirFile = new File(dirName);
dirFile.mkdir();
dirFile.isDirectory(); // 判断是否为目录
}
}
【2】查看目录列表
public class FileOperation {
public static void main(String[] args) {
File path = new File("C:\\");
String[] list;
// 查看目录所有内容
list = path.list();
// 查看目录指定内容(以 .txt 结尾的)
final String regStr = ".+.txt";
// 使用一个匿名的目录过滤器内部类,通过正则表达式验证过滤,list()可以回调accept()方法
list = path.list(new FilenameFilter() {
private Pattern pattern = Pattern.compile(regStr);
@Override
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
});
for(String dirItem : list){
System.out.println(dirItem);
}
}
}
四、I/O流典型使用方式
尽管可以通过不同的方式组合 I/O 流类,但我们可能也就只用到其中的几种组合。
【1】缓冲输入文件(读取)
public class BufferedInputFile {
public static String read(String fileName) throws Exception{
BufferedReader br = new BufferedReader(new FileReader(fileName));
String str;
StringBuffer sb = new StringBuffer();
while((str = br.readLine()) != null){
sb.append(str + "\n");
}
br.close();
return sb.toString();
}
public static void main(String[] args) throws Exception {
System.out.println(read("src/com/test/BufferedInputFile.java"));
}
}
【2】从内存输入(读取)
public class MemoryInput {
public static void main(String[] args) throws Exception {
StringReader in = new StringReader(BufferedInputFile.read("src/com/test/MemoryInput.java"));
int c;
while((c = in.read()) != -1){
System.out.println((char)c);
}
}
}
【3】基本文件输出
/**
* FileWriter向文件写入数据,使用BufferWriter将其封装缓冲输出,为了格式化装饰成 PrintWriter
*
*/
public class BasicFileOutput {
static String file = "BasicFileOutput.out";
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new StringReader(BufferedInputFile.read("src/com/test/BasicFileOutput.java")));
// PrintWriter有个构造函数,内部实现了BufferedWriter缓冲
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
//可以简写为 PrintWriter out = new PrintWriter(file);
int lineCount = 1;
String s;
while((s = br.readLine()) != null){
out.println(lineCount++ + ": " + s);
}
out.close();
System.out.println(BufferedInputFile.read(file));
}
}
【4】存储和恢复数据
/**
* 存储和恢复数据,使用 DataOutputStream写入数据,并用 DataInputStream 恢复数据
* 这些流可以是任何形式,以文件为例,对读写都进行缓冲处理
*/
public class StoringAndRecoveringData {
public static void main(String[] args) throws Exception {
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(
new FileOutputStream("C:" + File.separator + "Data.txt")));
out.writeDouble(3.14159265);
out.writeUTF("That was PI");
out.writeDouble(1.41413);
out.writeUTF("Square root of 2");
out.close();
DataInputStream in = new DataInputStream(new BufferedInputStream(
new FileInputStream("C:" + File.separator + "Data.txt")));
System.out.println(in.readDouble());
System.out.println(in.readUTF());
System.out.println(in.readDouble());
System.out.println(in.readUTF());
in.close();
}
}
【5】标准 I/O
Java提供了System.in、System.out和System.err,System.in是一个没有被包装过未经加工的 InputStream
public class Echo {
public static void main(String[] args) throws IOException {
// 将System.in包装成BufferedReader,使用readLine()一次读取一行
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str;
while((str = br.readLine()) != null && str.length() != 0){
System.out.println(str);
}
// 将System.out转换成PrintWriter
PrintWriter out = new PrintWriter(System.out, true); // 第二个参数 true 开启自动清空功能
out.println("Hello, world");
// 标准 I/O 重定向 操作的是字节流
// System类提供了一些简单的静态方法,允许对标准输入、输出和错误I/O流进行重定向
// System.setIn(InputStream) System.setOut(PrintStream) System.setErr(PrintStream)
PrintStream console = System.out; // System.out对象的引用
BufferedInputStream in = new BufferedInputStream(new FileInputStream("src/com/test/Echo.java"));
PrintStream _out = new PrintStream(new BufferedOutputStream(new FileOutputStream("test.out")));
System.setIn(in);
System.setOut(_out);
System.setErr(_out);
str = null;
while((str = br.readLine()) != null && str.length() != 0){
System.out.println(str); // 标准输出将重定向到另一个文件
}
_out.close();
System.setOut(console); // 恢复了System.out对象的引用
}
}
五、Java NIO
JDK1.4 的java.nio.* 包中引入了新的java I/O类库,目的在于提高速度。与传统的 IO 相比主要有以下区别:
① Java IO是面向流的,Java NIO是面向缓冲区(块)的
② Java IO各种流是阻塞的,当一线程调用read()或write()时,该线程被阻塞,直到有数据被读取或写入;Java NIO是非阻塞的,一线程从某通道请求读取数据,若目前没有数据可用,则不保持线程阻塞,直到有数据可供读取,该线程可继续做其他的事情,在写入的时候也是,不等待它完全写入,线程也可以同时去做其他的事情
Java NIO 由以下3个核心部分组成:Channel(通道)、Buffer(缓冲区)、Selector(选择器)。
(1)Channel(通道):Java NIO的通道类似流,但又有些不同:
① 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
② 通道可以异步地读写。
③ 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入
在Java NIO中最重要的通道的实现:
① FileChannel:从文件中读写数据
② DatagramChannel:能通过UDP读写网络中的数据
③ SocketChannel:能通过TCP读写网络中的数据
④ ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel
(2)Buffer(缓冲区):用于和NIO通道进行交互,数据是从通道读入缓冲区,从缓冲区写入到通道中的
使用Buffer读写数据一般遵循以下四个步骤:
① 写入数据到Buffer
② 调用flip()方法
③ 从Buffer中读取数据
④ 调用clear()方法或者compact()方法
向Buffer写入数据,当要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式,读取之前写入到buffer的所有数据。一旦数据读取完毕,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()或compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据,任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
(2.1)Buffer的capacity、position和limit
① capacity:不管Buffer处于读模式还是写模式,其返回缓冲区的容量
② position:初始值为0,最大值为capacity-1 。写数据到Buffer时,表示当前位置,没插入一条数据,向前移动一个单元;读取数据时,从某个位置读取数据,当Buffer切换为读模式时,position会被重置为0,读取一条数据时,会移动到下一个可读的位置。
③ limit:在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。
当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)
Buffer的分配,要想获得一个Buffer对象首先要进行分配,如分配48字节capacity的ByteBuffer:ByteBuffer buf = ByteBuffer.allocate(48),分配一个可存储1024个字符的CharBuffer:CharBuffer buf = CharBuffer.allocate(1024);
向Buffer写数据,两种方式:
① 从Channel写到Buffer
int bytesRead = inChannel.read(buf); //read into buffer
② 通过Buffer的put()方法写到Buffer里
buf.put(127);
从Buffer中读数据,两种方式:
① 从Buffer读取数据到Channel
int bytesWritten = inChannel.write(buf);
② 使用get()方法从Buffer中读取数据
byte aByte = buf.get();
rewind()方法
Buffer.rewind()将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)
mark()与reset()方法
通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。
buffer.mark();
//call buffer.get() a couple of times, e.g. during parsing.
buffer.reset(); //set position back to mark.
【1】FileChannel
用于读取、写入、映射和操作文件的通道,在现版本中,可从现有的 FileInputStream、FileOutputStream 或 RandomAccessFile 对象获得文件通道,方法是调用该对象的 getChannel 方法,这会返回一个连接到相同底层文件的文件通道。
public class GetFileChannel {
public static void main(String[] args) throws Exception {
FileChannel fileChannel = new FileOutputStream("data.txt").getChannel();
// 将 byte 数组包装到缓冲区中,并将数据写入通道(文件)
fileChannel.write(ByteBuffer.wrap("FileOutputStream--FileChannel Test".getBytes()));
fileChannel.close(); // 关闭此通道
// 向文件末尾增加数据 rw读写 r只读
fileChannel = new RandomAccessFile("data.txt", "rw").getChannel();
// 调用position()方法获取FileChannel的当前位置,调用position(long pos)方法设置FileChannel的当前位置
// FileChannel实例的size()方法将返回该实例所关联文件的大小
fileChannel.position(fileChannel.size()); // 设置此通道的文件位置,末尾
fileChannel.write(ByteBuffer.wrap("RandomAccessFile--FileChannel Test".getBytes()));
fileChannel.close();
// FileChannel.force(boolean metaData)方法将通道里尚未写入磁盘的数据强制写到磁盘上
// true 表示是否同时将文件元数据(权限信息等)写到磁盘上
// 读取文件
fileChannel = new FileInputStream("data.txt").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024); // 分配一个缓冲对象
fileChannel.read(buffer); // 将字节序列从此通道读入给定的缓冲区,返回读取的字节数
buffer.flip(); // 切换到读模式,让别人做好读取字节的准备
while(buffer.hasRemaining()){
System.out.print((char)buffer.get());
}
}
}
上述代码中,在读取通道数据并输出时,使用(char)buffer.get()每次读取一个字节,然后强制转换为char类型(这种方法显然比较原始),如果不强制转换,将会是乱码,这是因为ByteBuffer 存储的是普通的字节,为了把它们转换成字符,有两种方式可以解决:① 在输入它们的时候对其进行编码;② 从缓冲器输出时对它们进行解码。
从api文档中可以看到CharBuffer有一个toString()方法可以返回缓冲器包含的所有的字符,而ByteBuffer可以调用asCharBuffer()方法作为 char 类型缓冲区
public static void main(String[] args) throws Exception {
final int BSIZE = 1024;
FileChannel fileChannel = new RandomAccessFile("data.txt", "rw").getChannel();
fileChannel.write(ByteBuffer.wrap("some text".getBytes()));
fileChannel.close();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
fileChannel = new FileInputStream("data.txt").getChannel();
fileChannel.read(buffer);
buffer.flip();
// 在这里直接调用asCharBuffer()显示不行输出乱码
System.out.println(buffer.asCharBuffer());
buffer.rewind(); // 将position设回0,可以重读Buffer中的所有数据
/** 1) 在输出之前进行解码 Decoding**/
// 获取系统编码,使用java.nio.charset.Charset进行解码
// decode(ByteBuffer bb) 返回一个CharBuffer对象
String encoding = System.getProperty("file.encoding");
System.out.println("使用 " + encoding +" 解码输出:" + Charset.forName(encoding).decode(buffer));
/** 2) 在写入通道之前进行编码,这样输出才有意义 **/
fileChannel = new FileOutputStream("data1.txt").getChannel();
// 使用 UTF-8 写入
fileChannel.write(ByteBuffer.wrap("some text2".getBytes("UTF-16BE")));
fileChannel.close();
// 读取
fileChannel = new FileInputStream("data1.txt").getChannel();
buffer.clear();
fileChannel.read(buffer);
buffer.flip();
System.out.println(buffer.asCharBuffer());// 正常输出
/** 3) 在写入通道使用buffer.asCharBuffer() **/
fileChannel = new FileOutputStream("data3.txt").getChannel();
buffer = ByteBuffer.allocate(24);
buffer.asCharBuffer().put("some text3");
fileChannel.write(buffer);
fileChannel.close();
// 读取
fileChannel = new FileInputStream("data3.txt").getChannel();
buffer.clear();
fileChannel.read(buffer);
buffer.flip();
System.out.println(buffer.asCharBuffer());// 正常输出
}
Java IO学习总结:
http://www.cnblogs.com/rollenholt/archive/2011/09/11/2173787.html
http://blog.csdn.net/zhangerqing/article/details/8466532
http://www.importnew.com/17735.html
http://blog.csdn.net/maritimesun/article/details/7973603
http://ifeve.com/java-nio-vs-io/