程序运行需要数据,数据的获取往往需要跟外部系统进行通信,外部系统可能是文件、数据库、其他程序、网络、IO设备等等。
io流就是连通程序与外部系统的一条数据通道,以军队为例,程序就是指挥中心,io流就是通信兵,外部系统就是各个军团。所以io流是以程序为中心,程序从外部系统获得数据叫做输入流,程序输出数据给外部系统从而可以操作外部系统叫做输出流。
前面提到外部系统可能是文件、数据库、其他程序、网络、IO设备等,
比如输入:
- 读取硬盘上的文件内容到程序。例如:播放器打开一个视频文件、word打开一个doc文件。
- 读取网络上某个位置内容到程序。例如:浏览器中输入网址后,打开该网址对应的网页内容;下载网络上某个网址的文件。 读取数据库系统的数据到程序。
- 读取某些硬件系统数据到程序。例如:车载电脑读取雷达扫描信息到程序;温控系统等。
比如输出:
- 将数据写到硬盘中。例如:我们编辑完一个word文档后,将内容写到硬盘上进行保存。
- 将数据写到数据库系统中。例如:我们注册一个网站会员,实际就是后台程序向数据库中写入一条记录。
- 将数据写到某些硬件系统中。例如:导弹系统导航程序将新的路径输出到飞控子系统,飞控子系统根据数据修正飞行路径。
因此io流会针对不同的外部系统做不同的数据通道,理解了这,就不会反感下面的各种io流种类了,其本质上就是为了处理不同的数据,而有了针对的数据通道。
io流由一些不同具体类实现,然后定义io对象,也就是说指挥部分让某一个通信兵(比如张三)去干活,张三就去各个军团得到数据(张三就记住了数据)。
Java为我们提供了多种多样的IO流,我们可以根据不同的功能及性能要求挑选合适的IO流。
Java中的IO流体系:
这个体系有部分内容之后会提到,这里先说一点:电脑中的任何文件都是以二进制存储,8个二进制为一个字节,一个字节可表示一个字母,3个字节可表示一个汉字。其中字母和汉字分别称作一个字符。因此,可以按照是一个字节一个字节传输还是通过一个字符一个字符传输,将io流分成:
1.字节流:以字节为单位获取数据,命名上以Stream结尾的流一般是字节流, 如FileInputStream、FileOutputStream。
2.字符流:以字符为单位获取数据,命名上以Reader/Writer结尾的流一般是字符流,如FileReader、FileWriter。
举个栗子:使用流读取文件
import java.io.*;
public class TestIO2 {
public static void main(String[] args) {
FileInputStream fis = null; //将 io对象定义在try外面是为了成为全局对象。
try {
fis = new FileInputStream("d:/a.txt"); //这里可Ctrl+T查看一下FileInputStream源码。这里创建io对象。指挥部门让张三去找信息的时候,
会说一个目的地(这个目的地是错的或者不存在),张三找不到就会抛出异常。
StringBuilder sb = new StringBuilder();
int temp = 0;
//当temp等于-1时,表示已经到了文件结尾,停止读取
while ((temp = fis.read()) != -1) { //fis就是io对象,fis.read() 就是把它得到的数据按照字节一个字节一个字节的输出。
sb.append((char) temp);
}
System.out.println(sb);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//这种写法,保证了即使遇到异常情况,也会关闭流对象。之所以要关闭对象,就好像指挥中心让张三去拿信息,不管张三能不能拿到,都要让张三从这次任务中结束,要么下一次任务指挥中心就以为张三还忙着,就只能找另一个通信兵,但是通信兵是有限的,都不关闭,通信员数量就不够了。从系统来讲,就是系统资源不够了。
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
文件字节流
- FileInputStream通过字节的方式读取文件,适合读取所有类型的文件(图像、视频、文本文件等)。
- FileOutputStream通过字节的方式写数据到文件中,适合所有类型的文件。
利用文件流实现文件的复制:
package cn.io;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test111 {
public static void main(String[] args) {
copyFile("d:/a.txt", "d:/b.txt");
}
public static void copyFile(String src, String dec) {
FileInputStream fis = null;
FileOutputStream fos = null;
//为了提高效率,设置缓存数组!(读取的字节数据会暂存放到该字节数组中)
byte[] buffer = new byte[1024];
int temp = 0;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dec);
//边读边写
//temp指的是本次读取的真实长度,temp=-1时表示读取结束
while ((temp = fis.read(buffer)) != -1) {
/*将缓存数组中的数据写入文件中,注意:写入的是读取的真实长度;
*如果使用fos.write(buffer)方法,那么写入的长度将会是1024,即缓存
*数组的长度*/
fos.write(buffer, 0, temp);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//两个流需要分别关闭,先打开的流最后关闭
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在使用文件字节流时,我们需要注意以下两点:
1.为了减少对硬盘的读写次数,提高效率,通常设置缓存数组。相应地,读取时使用的方法为:read(byte[] b);写入时的方法为:write(byte[ ] b, int off, int length)。
2. 程序中如果遇到多个流,每个流都要单独关闭,防止其中一个流出现异常后导致其他流无法关闭的情况。
文件字符流
文件字节流可以处理所有的文件,但是字节流不能很好的处理Unicode字符,经常会出现“乱码”现象。所以,我们处理文本文件,一般可以使用文件字符流,它以字符为单位进行操作。
使用FileReader与FileWriter实现文本文件的复制:
fr = new FileReader("d:/a.txt");
fw = new FileWriter("d:/d.txt");
//为了提高效率,创建缓冲用的字符数组
char[] buffer = new char[1024];
//边读边写
while ((len = fr.read(buffer)) != -1) {
fw.write(buffer, 0, len);
}
缓冲字节流
当对文件或者其他数据源进行频繁的读写操作时,效率比较低(因为频繁读写底层硬件),这时如果使用缓冲流就能够更高效的读写信息。因为缓冲流是先将数据缓存起来(将硬件数据缓存入内存),然后当缓存区存满后或者手动刷新时再一次性的读取到程序或写入目的地。
因此,缓冲流还是很重要的,我们在IO操作时记得加上缓冲流来提升性能。
BufferedInputStream和BufferedOutputStream这两个流是缓冲字节流,通过内部缓存数组来提高操作流的效率。
fis = new FileInputStream(src);
fos = new FileOutputStream(dec);
//使用缓冲字节流包装文件字节流,增加缓冲功能,提高效率
//缓存区的大小(缓存数组的长度)默认是8192,也可以自己指定大小
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
while ((temp = bis.read()) != -1) {
bos.write(temp);
}
通常直接定义缓冲流:
FileOutputStream fos = new BufferedInputStream(new FileInputStream(src));
BufferedOutputStream bos = BufferedOutputStream(new FileOutputStream(dec));
1.在关闭流时,应该先关闭最外层的包装流,即“后开的先关闭”。
2.缓存区的大小默认是8192字节,也可以使用其它的构造方法自己指定大小。
缓冲字符流
BufferedReader/BufferedWriter增加了缓存机制,大大提高了读写文本文件的效率,同时,提供了更方便的按行读取的方法:readLine();处理文本时,我们一般可以使用缓冲字符流。
使用BufferedReader与BufferedWriter实现文本文件的复制
while ((tempString = br.readLine()) != null) {
//将读取的一行字符串写入文件中
bw.write(tempString);
//下次写入之前先换行,否则会在上一行后边继续追加,而不是另起一行
bw.newLine();
}
字节数组流
ByteArrayInputStream和ByteArrayOutputStream经常用在需要流和数组之间转化的情况!
说白了,FileInputStream是把文件当做数据源。ByteArrayInputStream则是把内存中的”某个字节数组对象”当做数据源。
byte[] b = "abcdefg".getBytes();
StringBuilder sb = new StringBuilder();
ByteArrayInputStream bais =new ByteArrayInputStream(b);
while ((temp = bais.read()) != -1) {
sb.append((char) temp);
}
数据流
数据流将“基本数据类型与字符串类型”作为数据源,就是说io流可以写入或读取特定数据类型的值(如:int、double、String等)。
DataInputStream和DataOutputStream的使用
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data.txt")));
DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream("data1.txt")));
//将如下数据写入到文件中
dos.writeChar('a');
dos.writeInt(10);
dos.writeDouble(Math.random());
dos.writeBoolean(true);
dos.writeUTF("北京尚学堂");
//手动刷新缓冲区:将流中数据写入到文件中
dos.flush();
//直接读取数据:读取的顺序要与写入的顺序一致,否则不能正确读取数据。
System.out.println("char: " + dis.readChar());
System.out.println("int: " + dis.readInt());
System.out.println("double: " + dis.readDouble());
System.out.println("boolean: " + dis.readBoolean());
System.out.println("String: " + dis.readUTF());
对象流
数据流只能实现对基本数据类型和字符串类型的读写,并不能读取对象(字符串除外),如果要对某个对象进行读写操作,需要一对新的处理流。
ObjectInputStream/ObjectOutputStream是以“对象”为数据源,但是必须将传输的对象进行序列化与反序列化操作。
/**使用对象输出流将数据写入文件*/
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(new File("d:/bjsxt.txt"))));
// 使用Object输出流
//对象流也可以对基本数据类型进行读写操作
oos.writeInt(12);
oos.writeDouble(3.14);
oos.writeChar('A');
oos.writeBoolean(true);
oos.writeUTF("北京尚学堂");
//对象流能够对对象数据类型进行读写操作
//Date是系统提供的类,已经实现了序列化接口
//如果是自定义类,则需要自己实现序列化接口
oos.writeObject(new Date());
/**使用对象输入流将数据读入程序*/
ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(new File("d:/bjsxt.txt"))));
// 使用Object输入流按照写入顺序读取
System.out.println(ois.readInt());
System.out.println(ois.readDouble());
System.out.println(ois.readChar());
System.out.println(ois.readBoolean());
System.out.println(ois.readUTF());
System.out.println(ois.readObject().toString());
1.对象流不仅可以读写对象,还可以读写基本数据类型。
2.使用对象流读写对象时,该对象必须序列化与反序列化。
3.系统提供的类(如Date等)已经实现了序列化接口,自定义类必须手动实现序列化接口。
转换流
InputStreamReader/OutputStreamWriter用来实现将字节流转化成字符流。比如,如下场景:
System.in是字节流对象,代表键盘的输入,如果我们想按行接收用户的输入时,就必须用到缓冲字符流BufferedReader特有的方法readLine(),但是经过观察会发现在创建BufferedReader的构造方法的参数必须是一个Reader对象,这时候我们的转换流InputStreamReader就派上用场了。
而System.out也是字节流对象,代表输出到显示器,按行读取用户的输入后,并且要将读取的一行字符串直接显示到控制台,就需要用到字符流的write(String str)方法,所以我们要使用OutputStreamWriter将字节流转化为字符流。
创建字符输入和输出流:使用转换流将字节流转换成字符流
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
// 使用字符输入和输出流
String str = br.readLine();
// 一直读取,直到用户输入了exit为止
while (!"exit".equals(str)) {
// 写到控制台
bw.write(str);
bw.newLine();// 写一行后换行
bw.flush();// 手动刷新
// 再读一行
str = br.readLine();
}
结束!