Java I/O流
Java IO流通常是基于字节或者基于字符的。字节流通常以“stream”命名,比如 InputStream 和 OutputStream。除了DataInputStream 和DataOutputStream 还能够读写int, long, float和double类型的值以外,其他流在一个操作时间内只能读取或者写入一个原始字节。
字符流通常以“Reader”或者“Writer”命名。字符流能够读写字符(比如Latin1或者Unicode字符)。可以浏览Java Readers and Writers获取更多关于字符流输入输出的信息。
流和数组不一样,不能通过索引读写数据。在流中,你也不能像数组那样前后移动读取数据,除非使用RandomAccessFile 处理文件。流仅仅只是一个连续的数据流。
在Java IO中,流是一个核心的概念。流从概念上来说是一个连续的数据流。既可以从流中读取数据,也可以往流中写数据。流与数据源或者数据流向的媒介相关联。
在Java IO中流既可以是字节流 (以字节为单位进行读写),也可以是字符流 (以字符为单位进行读写)。
类 InputStream, OutputStream, Reader 和 Writer
一个程序需要 InputStream 或者 Reader 从数据源读取数据,需要 OutputStream 或者 Writer 将数据写入到目标媒介中。
Java I/O中各类的用途
文件访问
网络访问
内存缓存访问
线程内部通信(管道)
缓冲
过滤
解析
读写文本 (Readers / Writers)
读写基本类型数据 (long, int etc.)
读写对象
InputStream
java.io.InputStream 类是所有Java IO输入流的基类。如果你正在开发一个从流中读取数据的组件,请尝试用 InputStream 替代任何它的子类(比如 FileInputStream) 进行开发。这么做能够让你的代码兼容任何类型而非某种确定类型的输入流。
组合流
你可以将流整合起来以便实现更高级的输入和输出操作。比如,一次读取一个字节是很慢的,所以可以从磁盘中一次读取一大块数据,然后从读到的数据块中获取字节。为了实现缓冲,可以把 InputStream 包装到 BufferedInputStream 中。
代码示例
InputStream input = new BufferedInputStream(new FileInputStream(“d:\test\input-file.txt”));
一、I/O文件
1、通过Java IO读文件
如果你需要在不同端之间读取文件,你可以根据该文件是二进制文件还是文本文件来选择使用 FileInputStream或者FileReader。
这两个类允许你从文件开始到文件末尾一次读取一个 字节 或者 字符,或者将读取到的字节写入到字节数组或者字符数组。你不必一次性读取整个文件,相反你可以按顺序地读取文件中的字节和字符。
2、通过Java IO写文件
如果你需要在不同端之间进行文件的写入,你可以根据你要写入的数据是二进制型数据还是字符型数据选用 FileOutputStream 或者 FileWriter。
你可以一次写入一个字节或者字符到文件中,也可以直接写入一个 字节数组 或者 字符数据。数据按照写入的顺序存储在文件当中。
3、通过Java IO随机存取文件
随机存取并不意味着你可以在真正随机的位置进行读写操作,它只是意味着你可以跳过文件中某些部分进行操作,并且支持同时读写,不要求特定的存取顺序。
这使得 RandomAccessFile 可以覆盖一个文件的某些部分、或者追加内容到它的末尾、或者删除它的某些内容,当然它也可以从文件的任何位置开始读取文件。
//文件流范例,打开一个文件的输入流,读取到字节数组,再写入另一个文件的输出流
@Test
public void test1() {
try {
FileInputStream fileInputStream = new FileInputStream(new File("a.txt"));
FileOutputStream fileOutputStream = new FileOutputStream(new File("b.txt"));
byte []buffer = new byte[128];
while (fileInputStream.read(buffer) != -1) {
fileOutputStream.write(buffer);
}
//随机读写,通过mode参数来决定读或者写
RandomAccessFile randomAccessFile = new RandomAccessFile(new File("c.txt"), "rw");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
二、字符流和字节流
Reader
Reader类是Java IO中所有Reader的基类。子类包括 BufferedReader,PushbackReader,InputStreamReader,StringReader和其他Reader。
Writer
Writer类是Java IO中所有Writer的基类。子类包括BufferedWriter和PrintWriter等等。
InputStream
public static void main(String[] args) throws Exception{
File f = new File("d:" + File.separator + "test.txt");
InputStream input = new FileInputStream(f);
int len = 0;
byte b[] = new byte[1024]; //所有的内容读到此数组中,数组大小 由文件决定
int temp = 0;
while((temp = input.read()) != -1) {
//将每次的读取内容给temp变量。如果temp的值不是-1,则表示文件没有读完
b[len] = (byte)temp;
len ++ ;
}
input.read(b);
input.close();
System.out.println("内容为:" + new String(b, 0, len)); //把byte数组变为字符串输出【从0---len的长度】
}
OutputStream
public static void main(String[] args) throws Exception { //异常抛出,不处理
//第1步:使用File类找到一个文件
File f = new File("d:" + File.separator + "test.txt"); //声明File对象【没有就会创建】
//第2步:通过子类实例化对象
OutputStream out = new FileOutputStream(f); //准备好一个输出的对象,通过对象多态性,进行实例化
//第3步:进行写操作
String str = "Hello Mona!";
byte b[] = str.getBytes(); //只能输出字符数组,so将字符串变成byte数组
out.write(b); //将内容输出,保存文件
//第4步:关闭输出流
out.close();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
File readFile = new File("readFile.txt");
File writeFile = new File("writeFile.txt");
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
// 从程序的角度出发,从C盘的myFile.txt文件读进来,是输入流;
fileInputStream = new FileInputStream(readFile);
// 从程序的角度出发,输出到别出去,是输出流.
fileOutputStream = new FileOutputStream(writeFile);
try {
int c = 0;
while ((c = fileInputStream.read()) != -1) {
fileOutputStream.write(c);
}
System.out.println("成功输出文件.");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// 关闭输入流;
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 关闭输出流;
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
三、IO管道
//使用管道来完成两个线程间的数据点对点传递
public void test2() throws IOException {
PipedInputStream pipedInputStream = new PipedInputStream();
PipedOutputStream pipedOutputStream = new PipedOutputStream(pipedInputStream);
new Thread(new Runnable() {
@Override
public void run() {
try {
pipedOutputStream.write("hello input".getBytes());
pipedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
byte []arr = new byte[128];
while (pipedInputStream.read(arr) != -1) {
System.out.println(Arrays.toString(arr));
}
pipedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
管道和线程
请记得,当使用两个相关联的管道流时,务必将它们分配给不同的线程。read() 方法和 write() 方法调用时会导致流阻塞,这意味着如果你尝试在一个线程中同时进行读和写,可能会导致线程死锁。
管道的替代
除了管道之外,一个JVM中不同线程之间还有许多通信的方式。实际上,线程在大多数情况下会传递完整的对象信息而非原始的字节数据。但是,如果你需要在线程之间传递字节数据,Java IO的管道是一个不错的选择。
四、Java IO:网络
当两个进程之间建立了网络连接之后,他们通信的方式如同操作文件一样:
利用InputStream读取数据,利用OutputStream写入数据。换句话来说,Java网络API用来在不同进程之间建立网络连接,而Java IO则用来在建立了连接之后的进程之间交换数据。
基本上意味着如果你有一份能够对文件进行写入某些数据的代码,那么这些数据也可以很容易地写入到网络连接中去。你所需要做的仅仅只是在代码中利用OutputStream替代FileOutputStream进行数据的写入。因为FileOutputStream是OuputStream的子类,所以这么做并没有什么问题。
//从网络中读取字节流也可以直接使用OutputStream
public void test3() {
//读取网络进程的输出流
OutputStream outputStream = new OutputStream() {
@Override
public void write(int b) throws IOException {
}
};
}
public void process(OutputStream ouput) throws IOException {
//处理网络信息
//do something with the OutputStream
}
五、字节和字符数组
从InputStream或者Reader中读入数组
从OutputStream或者Writer中写数组
在java中常用字节和字符数组在应用中临时存储数据。而这些数组又是通常的数据读取来源或者写入目的地。如果你需要在程序运行时需要大量读取文件里的内容,那么你也可以把一个文件加载到数组中。
前面的例子中,字符数组或字节数组是用来缓存数据的临时存储空间,不过它们同时也可以作为数据来源或者写入目的地。
举个例子:
//字符数组和字节数组在io过程中的作用
public void test4() {
//arr和brr分别作为数据源
char []arr = {'a','c','d'};
CharArrayReader charArrayReader = new CharArrayReader(arr);
byte []brr = {1,2,3,4,5};
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(brr);
}
六、System.in, System.out, System.err
System.in, System.out, System.err这3个流同样是常见的数据来源和数据流目的地。使用最多的可能是在控制台程序里利用System.out将输出打印到控制台上。
JVM启动的时候通过Java运行时初始化这3个流,所以你不需要初始化它们(尽管你可以在运行时替换掉它们)。
System.in
System.in是一个典型的连接控制台程序和键盘输入的InputStream流。通常当数据通过命令行参数或者配置文件传递给命令行Java程序
的时候,System.in并不是很常用。图形界面程序通过界面传递参数给程序,这是一块单独的Java IO输入机制。
System.out
System.out是一个PrintStream流。System.out一般会把你写到其中的数据输出到控制台上。System.out通常仅用在类似命令行工具的
控制台程序上。System.out也经常用于打印程序的调试信息(尽管它可能并不是获取程序调试信息的最佳方式)。
System.err
System.err是一个PrintStream流。System.err与System.out的运行方式类似,但它更多的是用于打印错误文本。
一些类似Eclipse的程序,为了让错误信息更加显眼,会将错误信息以红色文本的形式通过System.err输出到控制台上。
//测试System.in, System.out, System.err
public static void main(String[] args) {
int in = new Scanner(System.in).nextInt();
System.out.println(in);
System.out.println("out");
System.err.println("err");
//输入10,结果是
// err(红色)
// 10
// out
}
七、字符流的Buffered和Filter
BufferedReader能为字符输入流提供缓冲区,可以提高许多IO处理的速度。你可以一次读取一大块的数据,而不需要每次从网络或者磁盘中一次读取一个字节。特别是在访问大量磁盘数据时,缓冲通常会让IO快上许多。
BufferedReader和BufferedInputStream的主要区别在于,BufferedReader操作字符,而BufferedInputStream操作原始字节。只需要把Reader包装到BufferedReader中,就可以为Reader添加缓冲区(默认缓冲区大小为8192字节,即8KB)。
Reader input = new BufferedReader(new FileReader("c:\\data\\input-file.txt"));
也可以通过传递构造函数的第二个参数,指定缓冲区大小:
Reader input = new BufferedReader(new FileReader("c:\\data\\input-file.txt"), 8 * 1024);
这个例子设置了8KB的缓冲区。最好把缓冲区大小设置成1024字节的整数倍,这样能更高效地利用内置缓冲区的磁盘。
除了能够为输入流提供缓冲区以外,其余方面BufferedReader基本与Reader类似。BufferedReader还有一个额外readLine()方法,可以方便地一次性读取一整行字符。
BufferedWriter
与BufferedReader类似,BufferedWriter可以为输出流提供缓冲区。可以构造一个使用默认大小缓冲区的BufferedWriter(
Writer writer = new BufferedWriter(new FileWriter("c:\\data\\output-file.txt"), 8 * 1024);
注:默认缓冲区大小8 * 1024B)
Writer writer = new BufferedWriter(new FileWriter("c:\\data\\output-file.txt"));
也可以手动设置缓冲区大小:
Writer writer = new BufferedWriter(new FileWriter("c:\\data\\output-file.txt"), 8 * 1024);
为了更好地使用内置缓冲区的磁盘,同样建议把缓冲区大小设置成1024的整数倍。除了能够为输出流提供缓冲区以外,其余方面BufferedWriter基本与Writer类似。类似地,BufferedWriter也提供了writeLine()方法,能够把一行字符写入到底层的字符输出流中。
值得注意是,你需要手动flush()方法确保写入到此输出流的数据真正写入到磁盘或者网络中。
FilterReader
与FilterInputStream类似,FilterReader是实现自定义过滤输入字符流的基类,基本上它仅仅只是简单覆盖了Reader中的所有方法。
目前我并没发现这个类明显的用途。除了构造函数取一个Reader变量作为参数之外,我没看到FilterReader任何对Reader新增或者修改的地方。如果你选择继承FilterReader实现自定义的类,同样也可以直接继承自Reader从而避免额外的类层级结构。