JavaのIO流

一、流的概念

数据流是一组有序有起点和终点的数据序列。从某个源读取数据的流称为输入流、向一个目标写入数据的流称为输出流
输入流和输出流合并,统称为IO流
Java中通过IO流处理设备和线程之间数据传输。与IO流相关的类在 java.io 包中。

二、流的分类与类层次图

通常,按数据的流向不同,可以将数据流分为输入流输出流
在Java中,根据操作的数据单元类型不同,可以分为字节流字符流
当IO流需要处理的数据为字符或字符串时,宜使用字符流;否则宜使用字节流
由这两种分类方法,可以将Java的IO流分为四个大类,每个大类都是一个抽象类

三、基本IO流操作

Java的IO流的类种多、不同的类对应不同的使用场景。这里说明最简单最常用的两种使用:控制台IO文件IO

1.控制台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。
JavaのIO流_第1张图片
好吧,这就超纲了…正好也把跑偏的题拉回来,至少我们知道了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流称为包装流
包装流主要分为以下几类:

1)缓冲流

缓冲流是一种包装流,在IO流中发挥缓存作用,提高读取和写入速度。有如下四种:

  1. BufferedInputStream(字节输入)
  2. BufferedOutputStream(字节输出)
  3. BufferedReader(字符输入)
  4. BufferWriter(字符输出)

下面是缓冲流的使用方式。

//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);
2)转换流

转换流用于字节流字符流的转换。有如下两种:

  1. InputStreamReader(输入字节流转输入字符流)
  2. OutputStreamWriter(输出字节流转输出字符流)

下面是转换流的使用方式。

//InputStream in
//OutputStream out
Reader rr = new InputStreamReader(in);
Writer wr = new OutputStreamWrietr(out);
3)内存流

IO流的读取来源写入目标均为内存空间内存流的显著特点是:close方法对内存流无效;有如下三类:

  1. ByteArrayInputStreamByteArrayOutputStream(字节内存流)
  2. CharArrayReaderCharArrayWriter(字符内存流)
  3. StringReaderStringWriter(字符串内存流)
4)合并流

合并流顾名思义,用于把多个输入流合并为一个。由于合并时按顺序读取,也叫顺序流

//InputStream a,b;
SequenceInputStream seinput = new SequenceInputStream(a,b);

2.文件IO流

文件IO流是一种节点流,它的读取来源写入目标都是计算机硬盘。下面是文件IO相关的类:

● File类

File不是一种流,但是文件IO中的重要类。
File类是抽象表达计算机硬盘中文件或目录的类。File对象代表一个计算机中的文件或目录。
该类主要用于创建目录、文件查找和删除等。具体的方法见Java文档。

● FileInputStream类/FileOutputStream类

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类

FileReader/FileWriter文件字符输入/输出流,用于从文件中/向文件里 读取/写入字符数据。
这两个类的使用方法与FileInputStream/FileOutputStream相似,此处不再赘述。

3.简单应用

下面的代码将两个二进制文件的内容合并,并储存为一个文本文件。

	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流还有用于多线程的管道流、用于包装和装饰的过滤流、用于对象序列化的对象流,以后学习时再总结。

你可能感兴趣的:(Java)