对程序语言的设计者而言,创建一个好的输入输出(I/O)系统是一项艰难的任务。 -----------引自《java编程思想》
这是因为,其中的困难不仅在于各种I/O源端和想要与之通信的接收端(文件、控制台、网路链接等),而且还需要以多种不同的方式与它们进行通信(顺序,随机存取,缓冲,二进制,按字符,按行,按字)。
java类库通过创建大量的类来解决这个难题,java从1.0版本开始,I/O库发生明显变化,在原来面向字节的类中添加了面向字符和基于Unicode的类。在JDK1.4中,添加了nio类,来帮助改进性能及功能。(虽说java I/O设计的初衷是为了避免过多的类,但好像结果并不是如此)。
编程语言中,经常会使用“流”的概念,它代表任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象。“流”这个概念屏蔽了实际的I/O设备中处理数据的细节。
java类库中的I/O分成输入和输出两部分,可以在JDK文档中的类层结构中查看到:通过继承,任何继承自InputStream或Read的类都有含有read()的方法,任何继承自OutputStream()或write()的类,都有write()的方法。java通过它们来读写单个字节或字节数组。
但是,我们通常不会用到这些方法。它们之所以存在是因为别的类可以使用它们。以便提供更多的接口。
因此,我们很少使用单一的类来创建流对象,而是通过叠合多个对象来提供所期望的功能(这就是在Java的I/O系统中所大量使用的装饰器设计模式)。
InputStream:
《编程思想》书上说InputStream的作用是用来表示哪些不同数据源产生输入的类。其实这句话对我来说并不是很好理解,但是如果把InputStream、OutputStream和Read、Write做对比就不难发现,实际上都是分别就是读和写,只不过stream是字节流,而read,write是字符流。就是说InputStream就是用来从数据源读取字节流的。
那么,数据源包括哪些呢?
- 字节数组 ByteArrayInputStream
- String对象 StringBufferInputStream
- 文件 FileInputStream
- “管道” PipedInputStream
- 一个由其他种类的流组成的序列,以便我们可以将它们手机合并到一个流内 SequenceInputStream
- 其他数据源,比如Internet连接等
可以看到InputStream继承自Object,而它的直接子类就包含了上述的那些数据源。
OutputStream:
用来写字节流的类。直接子类包括:字节数组,文件,管道。
InputStream和OutputStream各自都包含了一个装饰器:FilterInputStream和FileterOutputStream。这就引出了java I/O中装饰器的内容,
先看看FilterInputStream和FileterOutputStream在JDK中子父类的继承结构:
用的最多的应该也就是缓冲器吧,缓冲器下包裹着输入输出流来构建一个I/O。
当Java发展到JDK 1.1版本的时候,增加了Reader和Writer。用来提供兼容Unicode与面向字符的I/O功能。这是因为涉及Reader和Writer继承层次结构主要是为了国际化。老的I/O流继承层次结构仅支持8位字节流,并且不能很好的处理16位的Unicode字符流。所以才会有Reader和Writer。但是具体使用还是需要根据情况,适当选择字节流或字符流来处理I/O。
当如果你需要实现字节流到字符流的转换时,这时候就会用上另一种涉及模式:适配器模式。InputStreamReader和OutputStreamWriter类。
第一个例子:一个读取指定文件内容的类
package io;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
/*
* 用FileReader类来读取指定文件的内容
* FileReader外面包裹了装饰类BufferedReader
*/
public class BufferedInputFile {
public static String read(String filename) throws IOException{
//通过FileReader来读取某个指定文件的内容
BufferedReader in = new BufferedReader(new FileReader(filename));
String s;
StringBuilder sb = new StringBuilder();
//BufferedReader提供了readLine()方法,当返回null时,表示达到文件末尾。
while((s = in.readLine()) != null){
sb.append(s + "\n");
}
in.close();
return sb.toString();
}
public static void main(String[] args) throws IOException{
//因为这里是在java工程下,所以路径为./src/io
System.out.println(read("./src/io/BufferedInputFile.java"));
}
}
看一下FileReader类的继承结构:
它提供的构造方法可以传入文件名的字符串或传入一个File类。
第二个例子:源是字符串时,用StringReader来读取的类
package io;
import java.io.IOException;
import java.io.StringReader;
/*
* 源是字符串的时的读取类
*/
public class MemoryInput {
public static void main(String[] args) throws IOException{
//StringReader类是一个源为字符串的字符流
StringReader in = new StringReader(
//BufferedInputFile.read是上一个例子从文件中读取字符串的方法
BufferedInputFile.read("./src/io/MemoryInput.java"));
int c;
while((c = in.read()) != -1){
//read()方法是以int形式返回下一个字节,因此类型转换为char才能正确打印
System.out.println((char)c);
}
}
}
第三个例子:
package io;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.IOException;
/*
* 源是字节数组时的读取类
*/
public class FormattedMemoryInput {
public static void main(String[] args) throws IOException{
/*
* 采用这种写法,用readByte()一次一个的字节来读取字符,
* 那么,任何字节值都是合法的,因此返回值不能用来检测输入是否结束。
* 可以用下面被注释叼的代码来修改
*/
try{
DataInputStream in = new DataInputStream(new ByteArrayInputStream(
BufferedInputFile.read(
"./src/io/FormattedMemoryInput.java").getBytes()));
while(true)
System.out.println((char)in.readByte());
}catch(EOFException e){
System.err.println("End of Stream");
}
/*
* 用available()方法来查看还有多少可供存取的字符
*/
// DataInputStream in = new DataInputStream(
// new BufferedInputStream(
// new FileInputStream("./src/io/FormattedMemoryInput.java")));
// while(in.available() != 0)
// System.out.println((char)in.readByte());
}
}
下面在介绍几个关于文件输出的例子:
package io;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
/*
* 把从BasicFileOutput.java文件中读取到的内容写到BasicFileOuput.out中
* 并用PrintWriter来输出
*/
public class BasicFileOutput {
static String file = "./src/io/BasicFileOuput.out";
public static void main(String[] args) throws IOException{
BufferedReader in = new BufferedReader(
new StringReader(
//工程中类文件的实际路径
BufferedInputFile.read("./src/io/BasicFileOutput.java")));
PrintWriter out = new PrintWriter(
new BufferedWriter(new FileWriter(file)));
/*
* 在java SE5中,为PrintWriter添加了一个辅助构造器,使得你不必在每次
* 希望创建文本文件并向其写入时,都去执行所有的装饰工作。
* 用法如下
*/
//PrintWriter out = new PrintWriter(file);
//标记行号
int lineCount = 1;
String s;
while((s = in.readLine()) != null)
out.println(lineCount++ + ":" + s);
out.close();
System.out.println(BufferedInputFile.read(file));
}
}
第二个关于数据存储和恢复的demo
package io;
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;
/*
* 存储和恢复数据的demo
* 用DataOutputStream存储数据,并用DataInputStream恢复数据
*
* 对字符串的读写使用UTF-8(一种适合于java的UTF-8的变体,如果用非java来写的UTF-8数据
* 需要经过一些特殊的代码转换)
*/
public class StoringAndRecoveringData {
public static void main(String[] args) throws IOException{
DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream("./src/io/data.txt")));
out.writeDouble(3.1415926);
out.writeUTF("That is pi");
out.writeDouble(1.41413);
out.writeUTF("square root of 2");
out.close();
DataInputStream in = new DataInputStream(
new BufferedInputStream(
new FileInputStream("./src/io/data.txt")));
System.out.println(in.readDouble());
System.out.println(in.readUTF());
System.out.println(in.readDouble());
System.out.println(in.readUTF());
}
}
这里要提一下关于字符编码的问题,UTF-8是一种多字节格式,其编码格式根据实际使用的字符集会有所变化。如果我们使用的是 ASCII或者几乎都是ASCII字符(只占7位),那么就显得极其浪费空间及带宽,所以UTF-8把ASCII字符编码成单一字节的形式,而非ASCII字符则编码成两到三个字节的形式。
最后,写一个工具类,来方便今后的使用:
package io;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.TreeSet;
/*
* 对文件读写的工具类
*/
public class TextFile extends ArrayList<String>{
public static String read(String filename){
StringBuilder sb = new StringBuilder();
try{
BufferedReader in = new BufferedReader(
new FileReader(new File(filename).getAbsolutePath()));
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).getAbsolutePath());
try{
out.print(text);
}finally{
out.close();
}
}catch(IOException e){
throw new RuntimeException(e);
}
}
public TextFile(String filename, String splitter){
super(Arrays.asList(read(filename).split(splitter)));
if(get(0).equals(""))
remove(0);
}
public TextFile(String filename){
this(filename, "\n");
}
public void write(String filename){
try{
PrintWriter out = new PrintWriter(new File(filename).getAbsolutePath());
try{
for(String item : this)
out.print(item);
}finally{
out.close();
}
}catch(IOException e){
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
String file = read("./src/io/TextFile.java");
write("./src/io/test.txt", file);
TextFile text = new TextFile("./src/io/test.txt");
text.write("./src/io/test2.txt");
TreeSet<String> words = new TreeSet<String>(
new TextFile("./src/io/TextFile.java", "\\w+"));
System.out.println(words.headSet("a"));
}
}