数据流是一组有序、有起点和终点的数据序列。从某个源读取数据的流称为输入流、向一个目标写入数据的流称为输出流。
输入流和输出流合并,统称为IO流。
Java中通过IO流处理设备和线程之间的数据传输。与IO流相关的类在 java.io 包中。
通常,按数据的流向不同,可以将数据流分为输入流和输出流。
在Java中,根据操作的数据单元类型不同,可以分为字节流和字符流。
当IO流需要处理的数据为字符或字符串时,宜使用字符流;否则宜使用字节流。
由这两种分类方法,可以将Java的IO流分为四个大类,每个大类都是一个抽象类。
Java的IO流的类种多、不同的类对应不同的使用场景。这里说明最简单最常用的两种使用:控制台IO和文件IO。
Java控制台的输入由 System.in 获取。查看其源码如下:
public static final InputStream in = null;
显然,System.in是一个输入流的静态常量。
大家都知道,InputStream是一个抽象类,自然System.in是指向它众多子类中的一个实例。具体可以用代码验证:
System.out.println(System.in);
//Output:java.io.BufferedInputStream@1f32e575
由输出可以得知,System.in指向一个BufferedInputStream实例。
这里问题又出现了,上面的源码都已经指定它为常量了,并且已经赋值null。为什么它又会指向一个实例对象呢?
翻看源码,在System类中发现一个代码块:
static {
registerNatives();
}
不知道有多少朋友跟我一样刚看到这愣了,反正我之前没见过这个语法。
百度之后得知,这是一个静态初始化块,在加载类的时候执行。可以猜想System.in应该是在registerNatives()这个函数里设置。
然而我自己写了一下,发现一般的方法并不能通过编译:
static {
i = 5; //错误行:不能对final字段i赋值
}
static final int i = 1;
网上找了很多,有大神给出解释:在源码中使用了一种native方法,可以跳出Java的语法限制,重新绑定System.in。
好吧,这就超纲了…正好也把跑偏的题拉回来,至少我们知道了System.in指向bufferedInputStream对象,这就足够我们使用了。
常用方法:
System.in.read(); //读取一个字节
System.in.read(byte[] b); //读取一串字节,并存入字节数组b中
System.in.read(byte[] b,int off,int len); //读取len长的字节,存入字节数组b中off开始的位置
Java控制台的输出由 System.out 完成。不同于System.in,源码直接指明它是PrintStream的一个实例。
public static final PrintStream out = null;
作为输出流的一种,System.out也能像其它输出流一样使用write方法写数据:
int a = System.in.read();
System.in.close();
System.out.write(a);
System.out.close();
这就是一个最简单的单字节复读机write方法跟read方法一样有三个重载函数,不过功能反向,这里就不赘述了。
然而作为特殊的PrintStream,他有高贵的"三大件":
System.out.print(Object o); //输出o.toString(),不换行
System.out.println(Object o); //输出o.toString(),并换行
System.out.printf(String format,Object... args); //学过C语言的痛哭流涕,这是熟悉的味道
print方法和println方法都很好理解,printf方法是一种利用参数表的格式化输出方法,简单演示一下它的用法:
//用法1:向后逐个读取参数
System.out.printf("It's %d:%d o'clock.",21,50);
//Output:It's 21:50 o'clock.
//用法2:指定参数位置
System.out.printf("This is %1$d, This is also %1$d, This is still %1$d.",6);
//Output:This is 6, This is also 6, This is still 6.
此前已经接触过输入流的同学都知道,实际应用中我们很少直接使用System.in,更常用的是这些:
BufferedInputStream in = new BufferedInputStream(System.in); //Before JDK-5
Scanner in = new Scanner(System.in); //After JDK-5
在上面的代码中,子类将基类对象作为构造函数的参数,以此在原对象的基础上,获得更多的功能。
这就是Java设计模式中的包装模式。
由于IO流中,大家存储的都是数据流,众多类型只是使用场景不同。为了过渡不同场景间IO流,隐藏它们之间的差异,并能有更方便的IO操作,IO流中大量使用了包装模式。
Java的IO流中,直接对特定场景读写数据的称为节点流,对一个已存在IO流连接封装的IO流称为包装流。
包装流主要分为以下几类:
缓冲流是一种包装流,在IO流中发挥缓存作用,提高读取和写入速度。有如下四种:
下面是缓冲流的使用方式。
//InputStream/Reader in;
//OutputTream/Writer out;
BufferedInputStream bis = new BufferedInputStream(in);
BufferedOutputStream bos = new BufferedOutputStream(out);
BufferedReader bos = new BufferedReader(in);
BufferedWriter bos = new BufferedWriter(out);
转换流用于字节流到字符流的转换。有如下两种:
下面是转换流的使用方式。
//InputStream in
//OutputStream out
Reader rr = new InputStreamReader(in);
Writer wr = new OutputStreamWrietr(out);
IO流的读取来源和写入目标均为内存空间。内存流的显著特点是:close方法对内存流无效;有如下三类:
合并流顾名思义,用于把多个输入流合并为一个。由于合并时按顺序读取,也叫顺序流。
//InputStream a,b;
SequenceInputStream seinput = new SequenceInputStream(a,b);
文件IO流是一种节点流,它的读取来源和写入目标都是计算机硬盘。下面是文件IO相关的类:
File不是一种流,但是文件IO中的重要类。
File类是抽象表达计算机硬盘中文件或目录的类。File对象代表一个计算机中的文件或目录。
该类主要用于创建目录、文件查找和删除等。具体的方法见Java文档。
FileInputStream/FileOutputStream是文件字节输入/输出流,用于从文件中/向文件里 读取/写入字节数据。
注意:当输入流对应的文件不存在时,将抛出FileNotFoundException;输出流默认情况下对应文件存在时,将清除原有内容。
要创建这两个类的实例,有两种可用的构造函数:
//1
FileInputStream fis = new FileInputStream(filepath);
FileOutputStream fos = new FileOutputStream(filepath);
//2
File file = new File(filepath);
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(file);
FileOutputStream的构造函数可选第二个参数,该参数接受一个boolean值以决定是否启用追加模式(不删除原有内容)。
这两个类的常用特有方法:
protected void finalize()throws IOException; //清除流与文件的连接
public int available() throws IOException; //仅Input,返回下一次读取时可不受阻塞读入的字节数
FileReader/FileWriter是文件字符输入/输出流,用于从文件中/向文件里 读取/写入字符数据。
这两个类的使用方法与FileInputStream/FileOutputStream相似,此处不再赘述。
下面的代码将两个二进制文件的内容合并,并储存为一个文本文件。
File f1 = new File("a.bin");
File f2 = new File("b.bin");
File resultFile = new File("result.txt");
FileWriter fw = new FileWriter(resultFile);
BufferedInputStream bis1 = new BufferedInputStream(new FileInputStream(f1));
BufferedInputStream bis2 = new BufferedInputStream(new FileInputStream(f2));
SequenceInputStream is = new SequenceInputStream(bis1, bis2);
Reader r = new InputStreamReader(is);
int len;
char[] buffer = new char[1024];
while((len = r.read(buffer)) != -1)
fw.write(new String(buffer,0,len));
r.close();
fw.close();
另外常用的IO流还有用于多线程的管道流、用于包装和装饰的过滤流、用于对象序列化的对象流,以后学习时再总结。