一、JAVA流式输入/输出原理
在Java程序中,对于数据的输入/输出操作以“流”(Stream)方式进行;J2SDK提供了各种各样的“流” 类,用以获取不同种类的数据:程序中通过标准的方法输入或输出数据。
读入写出
流是用来读写数据的,java有一个类叫File,它封装的是文件的文件名,只是内存里面的一个对象,真 正的文件是在硬盘上的一块空间,在这个文件里面存放着各种各样的数据,我们想读文件里面的数据怎 么办呢?是通过一个流的方式来读,咱们要想从程序读数据,对于计算机来说,无论读什么类型的数据 都是以010101101010这样的形式读取的。怎么把文件里面的数据读出来呢?你可以把文件想象成一个 小桶,文件就是一个桶,文件里面的数据就相当于是这个桶里面的水,那么我们怎么从这个桶里面取水 呢,也就是怎么从这个文件读取数据呢。
常见的取水的办法是我们用一根管道插到桶上面,然后在管道的另一边打开水龙头,桶里面的水就开始 哗啦哗啦地从水龙头里流出来了,桶里面的水是通过这根管道流出来的,因此这根管道就叫流,JAVA里 面的流式输入/输出跟水流的原理一模一样,当你要从文件读取数据的时候,一根管道插到文件里面去, 然后文件里面的数据就顺着管道流出来,这时你在管道的另一头就可以读取到从文件流出来的各种各样 的数据了。当你要往文件写入数据时,也是通过一根管道,让要写入的数据通过这根管道哗啦哗啦地流 进文件里面去。除了从文件去取数据以外,还可以通过网络,比如用一根管道把我和你的机子连接起
来,我说一句话,通过这个管道流进你的机子里面,你马上就可以看得到,而你说一句话,通过这根管 道流到我的机子里面,我也马上就可以看到。有的时候,一根管道不够用,比方说这根管道流过来的水 有一些杂质,我们就可以在这个根管道的外面再包一层管道,把杂质给过滤掉。从程序的角度来讲,从 计算机读取到的原始数据肯定都是010101这种形式的,一个字节一个字节地往外读,当你这样读的时候 你觉得这样的方法不合适,没关系,你再在这根管道的外面再包一层比较强大的管道,这个管道可以把010101帮你转换成字符串。这样你使用程序读取数据时读到的就不再是010101这种形式的数据了,而 是一些可以看得懂的字符串了。
二、输入输出流分类
Java.io 包中定义了多个流类型(类或抽象类)来实现输入/输出功能;可以从不同的角度对其进行分类:
- 按数据流的方向不同可以分为输入流和输出流
- 按照处理数据单位不同可以分为字节流和字符流
- 按照功能不同可以分为节点流和处理流
我们来理解两个概念:
-
字节流:最原始的一个流,读出来的数据就是010101这种最底层的数据表示形式,只不过它是按 照字节来读的,一个字节(Byte)是8位(bit)读的时候不是一个位一个位的来读,而是一个字节 一个字节来读。
-
字符流:字符流是一个字符一个字符地往外读取数据。一个字符是2个字节
J2SDK所提供的所有流类型位于包 Java.io内,都分别继承自以下四种抽象流类型。
输入流:InputStream(字节流),Reader(字符流)
输出流:OutPutStream(字节流),Writer(字符流)
这四个类都是抽象类,可以把这四个类想象成四根不同的管道。一端接着你的程序,另一端接着数据 源,你可以通过输出管道从数据源里面往外读数据,也可以通过输入管道往数据源里面输入数据,总 之,通过这四根管道可以让数据流进来和流出去。
io包里面定义了所有的流,所以一说流指的就是io包里面的
什么叫输入流?什么叫输出流?
用一根管道一端插进文件里,一端插进程序里面,然后开始读数据,那么这是输入还是输出呢? 如果站在文件的角度上,这叫输出。
如果站在程序的角度上,这叫输入。
记住,以后说输入流和输出流都是站在程序的角度上来说。
三、节点流和处理流
你要是对原始的流不满意,你可以在这根管道外面再套其它的管道,套在其它管道之上的流叫处理流。 为什么需要处理流呢?这就跟水流里面有杂质,你要过滤它,你可以再套一层管道过滤这些杂质一样。
3.1. 节点流类型
类型 | 字符流 | 字节流 |
---|---|---|
File(文件) | FileReader、FileWriter | FileInputStream、FileOutputStream |
Memory Array | CharArrayReader、CharArrayWriter | ByteArrayInputStream、ByteArrayOutputStream |
Memory String | StringReader、StringWriter | - |
Pipe(管道) | PipedReader、PipedWriter | PipedInputStream、PipedOutputStream |
节点流就是一根管道直接插到数据源上面,直接读数据源里面的数据,或者是直接往数据源里面写入数 据。典型的节点流是文件流:文件的字节输入流(FileInputStream),文件的字节输出流
(FileOutputStream),文件的字符输入流(FileReader),文件的字符输出流(FileWriter)。
3.2. 处理流类型
处理类型 | 字符流 | 字节流 |
---|---|---|
Buffering | BufferedReader、BufferedWriter | BufferedInputStream、BufferedOutputStream |
Filtering | FilterReader、FilterWriter | FilterInputStream, FilterOutputStream |
Converting between bytes and chaacter | InputStreamReader、OutputStreamWriter | - |
Object Serialization | - | ObjectInputStream、ObjectOutputStream |
Data conversion | - | DataInputStream、DataOutputStream |
Counting | LineNumberReader | LineNumberInputStream |
Peeking ahead | PusbackReader | PushbackInputStream |
Printing | PrintWriter | PrintStream |
处理流是包在别的流上面的流,相当于是包到别的管道上面的管道。
四、InputStream(输入流)
我们看到的具体的某一些管道,凡是以InputStream结尾的管道,都是以字节的形式向我们的程序输入 数据。
继承自InputStream的流都是用于向程序中输入数据,且数据的单位为字节(8bit);下图中深色为节点 流,浅色为处理流。
4.1.InputStream的基本方法
//读取一个字节并以整数的形式返回(0~255)
//如果返回-1就说明已经到了输入流的末尾
int read() throws IOException
//读取一系列字节并存储到一个数组buffer
//返回实际读取的字节数,如果读取前已到输入流的末尾,则返回-1
int read(byte[] buffer) throws IOException
//读取length个字节
//并存储到一个字节数组buffer,从length位置开始
//返回实际读取的字节数,如果读取前以到输入流的末尾返回-1.
int read(byte[] buffer,int offset,int length) throws IOException
//关闭流释放内存资源
void close() throws IOException
//跳过n个字节不读,返回实际跳过的字节数
long skip(long n) throws IOException
read()方法是一个字节一个字节地往外读,每读取一个字节,就处理一个字节。read(byte[] buffer)方法读取数据时,先把读取到的数据填满这个byte[]类型的数组buffer(buffer是内存里面的一块缓冲区),然 后再处理数组里面的数据。这就跟我们取水一样,先用一个桶去接,等桶接满水后再处理桶里面的水。 如果是每读取一个字节就处理一个字节,这样子读取也太累了。
4.2 案例
以File(文件)这个类型作为讲解节点流的典型代表
【源码查看,分析结构】
【演示:使用FileInputStream流来读取FileInputStream.java文件的内容】
package com.kuang.chapter;
import java.io.*;
public class TestFileInputStream {
public static void main(String args[]) {
int b = 0;// 使用变量b来装调用read()方法时返回的整数
FileInputStream in = null;
// 使用FileInputStream流来读取有中文的内容时,读出来的是乱码,因为使用InputStream流里面的read()方法读取内容时是一个字节一个字节地读取的,而一个汉字是占用两个字节的,所以读取出来的汉字无法正确显示。
// FileReader in = null;
// 使用FileReader流来读取内容时,中英文都可以正确显示,因为Reader流里面的read()方法是一个字符一个字符地读取的,这样每次读取出来的都是一个完整的汉字,这样就可以正确显示了。
try {
in = new FileInputStream("E:\\教学\\班级\\Test\\Lesson2\\src\\com\\kuang\\chapter\\Student.java");
// in = new FileReader("E:\\教学\\班级\\Test\\Lesson2\\src\\com\\kuang\\chapter\\Student.java");
} catch (FileNotFoundException e) {
System.out.println("系统找不到指定文件!");
System.exit(-1);// 系统非正常退出
}
long num = 0;// 使用变量num来记录读取到的字符数
// 调用read()方法时会抛异常,所以需要捕获异常
try {
while ((b = in.read()) != -1) {
// 调用int read() throws Exception方法时,返回的是一个int类型的整数
// 循环结束的条件就是返回一个值-1,表示此时已经读取到文件的末尾了。
// System.out.print(b+"\t");//如果没有使用“(char)b”进行转换,那么直接打印出来的b就是数字,而不是英文和中文了
System.out.print((char) b);
// “char(b)”把使用数字表示的汉字和英文字母转换成字符输入
num++;
}
in.close();// 关闭输入流
System.out.println();
System.out.println("总共读取了" + num + "个字节的文件");
} catch (IOException e1) {
System.out.println("文件读取错误!");
}
}
}
五、OutputStream(输出流)
继承自OutputStream的流是用于程序中输出数据,且数据的单位为字节(8bit):下图中深色的为节点 流,浅色为处理流。
5.1.OutputStream的基本方法
//向输出流中写入一个字节数据,该字节数据为参数b的低8位
void write(int b) throws IOException
//将一个字节类型的数组中的数据写入输出流
void write(byte[] b) throws IOException
//将一个字节类型的数组中的从指定位置(off)开始的len个字节写入到输出流
void write(byte[] b,int off,int len) throws IOException
//关闭流释放内存资源
void close() throws IOException
//将输出流中缓冲的数据全部写出到目的地
void flush() throws IOException