参考文章:
http://www.cnblogs.com/oubo/archive/2012/01/06/2394638.html (Java IO流学习总结)
http://www.importnew.com/21556.html (Java 编程要点之 I/O 流详解)
一、流的概念
1:流的概念
在程序中所有的数据都是以流的方式进行传输或保存的,程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要使用输出流完成。
程序中的输入输出都是以流的形式保存的,流中保存的实际上全都是字节文件。
Java流操作有关的类或接口:
2:流的分类
(1)根据处理数据类型的不同分为:字符流和字节流
(2)根据数据流向不同分为:输入流和输出流
Java流类图结构:
3:字符流和字节流
字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。 字节流和字符流的区别:
(1)读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
(2)处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。
4:输入流和输出流
对输入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。
二、I/O流
1:字节流(Byte Streams)
字节流处理原始的二进制数据 I/O。输入输出的是8位字节,相关的类为 InputStream 和 OutputStream。
用法:
public class CopyBytes {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("resources/xanadu.txt");
out = new FileOutputStream("resources/outagain.txt");
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
CopyBytes花费其大部分时间在简单的循环里面,从输入流每次读取一个字节到输出流,如图所示:
注意:记得始终关闭流
不再需要一个流记得要关闭它,这点很重要。所以,CopyBytes使用finally块来保证即使发生错误两个流还是能被关闭。这种做法有助于避免严重的资源泄漏。
一个可能的错误是,CopyBytes无法打开一个或两个文件。当发生这种情况,对应解决方案是判断该文件的流是否是其初始null值。这就是为什么CopyBytes可以确保每个流变量在调用前都包含了一个对象的引用。
2:字符流(Character Streams)
字符流处理字符数据的 I/O,自动处理与本地字符集转化。在程序中一个字符等于两个字节,那么java提供了Reader、Writer两个专门操作字符流的类。
public class CopyCharacters {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
FileReader inputStream = null;
FileWriter outputStream = null;
try {
inputStream = new FileReader("resources/xanadu.txt");
outputStream = new FileWriter("resources/characteroutput.txt");
int c;
while ((c = inputStream.read()) != -1) {
outputStream.write(c);
}
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
}
3:字节流与字符流
(1)字符流使用字节流字符流往往是对字节流的“包装”。字符流使用字节流来执行物理I/O,同时字符流处理字符和字节之间的转换。例如,FileReader 使用 FileInputStream,而 FileWriter使用的是 FileOutputStream。
有两种通用的字节到字符的“桥梁”流:InputStreamReader 和 OutputStreamWriter。当没有预包装的字符流类时,使用它们来创建字符流。
(2)字节流与字符流的区别
字节流在操作的时候本身是不会用到缓冲区(内存)的,是与文件本身直接操作的,而字符流在操作的时候是使用到缓冲区的。
字节流在操作文件时,即使不关闭资源(close方法),文件也能输出,但是如果字符流不使用close方法的话,则不会输出任何内容,说明字符流用的是缓冲区,并且可以使用flush方法强制进行刷新缓冲区,这时才能在不close的情况下输出内容。
(3)如何使用字节流还是字符流
在所有的硬盘上保存文件或进行传输的时候都是以字节的方法进行的,包括图片也是按字节完成,而字符是只有在内存中才会形成的,所以使用字节的操作是最多的。
4:缓冲流(Buffered Streams)
使用非缓冲 I/O 这意味着每次读或写请求是由基础 OS 直接处理。这可以使一个程序效率低得多,因为每个这样的请求通常引发磁盘访问,网络活动,或一些其它的操作,而这些是相对昂贵的。为了减少这种开销,所以 Java 平台实现缓冲 I/O 流。缓冲输入流从被称为缓冲区(buffer)的存储器区域读出数据;仅当缓冲区是空时,本地输入 API 才被调用。同样,缓冲输出流,将数据写入到缓存区,只有当缓冲区已满才调用本机输出 API。IO的缓冲区的存在就是为了提高效率,把要操作的数据放进缓冲区,然后一次性把缓冲区的内容写到目的地,而不是写一次就往目的地写一次。
用于包装非缓存流的缓冲流类有4个:BufferedInputStream 和 BufferedOutputStream 用于创建字节缓冲字节流,BufferedReader 和 BufferedWriter 用于创建字符缓冲字节流。
(1)BufferWriter类
FileWriter fw =null;
try {
fw =new FileWriter("test.txt");
//使用缓冲区必须要与一个流对象相关联
BufferedWriter bw =new BufferedWriter(fw);
bw.write("hello world!");
//使用缓冲区的时候要注意刷新
bw.flush();
//关闭缓冲区的对象,实际上是关闭与它关联的流对象最好放在finally执行
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
(2)BufferReader类
FileReader fr =new FileReader("test.txt");
BufferedReader br =new BufferedReader(fr);
String line =null;
//注意readLine方法读取的内容不包括换行符
while((line=br.readLine())!=null){
System.out.println(line);
}
(3)刷新缓冲流
刷新缓冲区是指在某个缓冲的关键点就可以将缓冲输出,而不必等待它填满。
一些缓冲输出类通过一个可选的构造函数参数支持 autoflush(自动刷新)。当自动刷新开启,某些关键事件会导致缓冲区被刷新。例如,自动刷新 PrintWriter 对象在每次调用 println 或者 format 时刷新缓冲区。查看 Formatting 了解更多关于这些的方法。
如果要手动刷新流,请调用其 flush 方法。flush 方法可以用于任何输出流,但对非缓冲流是没有效果的。
5:面向行的IO(整行读写)
字符 I/O 通常发生在较大的单位不是单个字符。一个常用的单位是行:用行结束符结尾。行结束符可以是回车/换行序列(“\r\n”),一个回车(“\r”),或一个换行符(“\n”)。支持所有可能的行结束符,程序可以读取任何广泛使用的操作系统创建的文本文件。我们必须使用两个类,BufferedReader 和PrintWriter 的。
public class CopyLines {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
BufferedReader inputStream = null;
PrintWriter outputStream = null;
try {
inputStream = new BufferedReader(new FileReader("resources/xanadu.txt"));
outputStream = new PrintWriter(new FileWriter("resources/characteroutput.txt"));
String l;
while ((l = inputStream.readLine()) != null) {
outputStream.println(l);
}
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
}
调用readLine按行返回文本行。方法使用println输出带有当前操作系统的行终止符的每一行。这可能与输入文件中不是使用相同的行终止符。
6:扫描(Scanning)和格式化(Formatting)
扫描和格式化允许程序读取和写入格式化的文本。
I/O 编程通常涉及对人类喜欢的整齐的格式化数据进行转换。为了帮助您与这些琐事,Java 平台提供了两个API。scanning API 使用分隔符模式将其输入分解为标记。formatting API 将数据重新组合成格式良好的,人类可读的形式。
(1)扫描
默认情况下,Scanner 使用空格字符分隔标记。(空格字符包括空格,制表符和行终止符。)
public class ScanXan {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
Scanner s = null;
try {
s = new Scanner(new BufferedReader(new FileReader("resources/xanadu.txt")));
while (s.hasNext()) {
System.out.println(s.next());
}
} finally {
if (s != null) {
s.close();
}
}
}
}
虽然 Scanner 不是流,但你仍然需要关闭它,以表明你与它的底层流执行完成。
调用 useDelimiter() ,指定一个正则表达式可以使用不同的标记分隔符。例如,假设您想要标记分隔符是一个逗号,后面可以跟空格。
s.useDelimiter(",\\s*");
转换成独立标记
该 ScanXan 示例是将所有的输入标记为简单的字符串值。Scanner 还支持所有的 Java 语言的基本类型(除 char),以及 BigInteger 和 BigDecimal 的。此外,数字值可以使用千位分隔符。因此,在一个美国的区域设置,Scanner 能正确地读出字符串“32,767”作为一个整数值。
这里要注意的是语言环境,因为千位分隔符和小数点符号是特定于语言环境。所以,下面的例子将无法正常在所有的语言环境中,如果我们没有指定 scanner 应该用在美国地区工作。可能你平时并不用关心,因为你输入的数据通常来自使用相同的语言环境。可以使用下面的语句来设置语言环境:
s.useLocale(Locale.US);
public class ScanSum {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
Scanner s = null;
double sum = 0;
try {
s = new Scanner(new BufferedReader(new FileReader("resources/usnumbers.txt")));
s.useLocale(Locale.US);
while (s.hasNext()) {
if (s.hasNextDouble()) {
sum += s.nextDouble();
} else {
s.next();
}
}
} finally {
s.close();
}
System.out.println(sum);
}
}
(2)格式化
IO的格式化对于java简单来说就是system.out.print();
实现格式化流对象要么是 字符流类的 PrintWriter 的实例,或为字节流类的 PrintStream 的实例。
注:对于PrintStream对象,你很可能只需要System.out和System.err。当你需要创建一个格式化的输出流,请实例化PrintWriter,而不是PrintStream。
像所有的字节和字符流对象一样,PrintStream 和 PrintWriter 的实例实现了一套标准的 write 方法用于简单的字节和字符输出。此外,PrintStream 和 PrintWriter 的执行同一套方法,将内部数据转换成格式化输出。提供了两个级别的格式:
1)print 和 println 在一个标准的方式里面格式化独立的值 。
2)format 用于格式化几乎任何数量的格式字符串值,且具有多种精确选择。
print 和 println 方法
调用 print 或 println 输出使用适当 toString 方法变换后的值的单一值。
format 方法
该 format 方法用于格式化基于 format string(格式字符串) 多参。格式字符串包含嵌入了 format specifiers (格式说明)的静态文本;除非使用了格式说明,否则格式字符串输出不变。