- 一、流的概述
- 为进行数据的输入/输出操作,Java中把不同的输入/输出源(键盘、文件、网络连接等)抽象表述为“流”(stream), Java中,文件的输入输出功能通过流来实现。
- 流(Stream)是从起源(source)到接收(sink)的有序数据,即:可以理解为一组有顺序的、有起点和终点的动态数据集合
- java.io包中定义了多个流类型(类或抽象类)来实现输入/输出功能。(java.nio,其中n指new)
- 两种基本的流:(按数据流的方向)
- 输入流:只能从中读取字节数据,而不能向其写出数据
- 输出流:只能向其写字节数据,而不能从中读取数据
- 按照流所处理的数据单位不同可以分为:
- 字节流:用于处理字节数据。
- 字符流:用于处理Unicode字符数据。
- 按照功能不同可以划分为:
- 节点流
- 处理流
【文件输入输出的原理】
IO流是专门用来处理内容的(不仅文件,任何信息[网络传递的信息])
流是数据的有序集合,且为动态的
- 二、流的分类
JDK所提供的所有流类型位于包java.io内,这些流都分别继承以下4种抽象流类型:
| 字节流 | 字符流 |
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
处理文本文件用字节流、字符流均可,但字符流效率更高
处理二进制文件只能使用字节流
【节点流和处理流】
可以从/向一个特定的IO设备(如磁盘、网络)读/写数据的流,称为节点流。节点流也被称为低级流,只有节点流才能直接连接数据源和程序。
数据源可连接网络、文件、显示器等,故统称数据源
实现对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读/写功能的流,称为处理流。处理流也被称为高级流。
小结:只有节点流(低级流)才能连接数据源,处理流(高级流)不能直接与数据源连接,它只能包在节点流(低级流)的外面。
【节点流类型】
类型 | 字节流 | 字符流 |
File(文件) | FileInputStream FileOutputStream | FileReader FileWriter |
Memory Array | ByteArrayInputStream ByteArrayOutputStream | CharArrayReader CharArrayWriter |
Memory String | - | StringReader StringWriter |
Pipe(管道) | PipedInputStream PipedOutputStream | PipedReader PipedWriter |
【处理流类型】
类型 | 字节流 | 字符流 |
Buffering | BufferedInputStream BufferedOutputStream | BufferedReader BufferedWriter |
Filtering | FilterInputStream FilterOutputStream | FilterReader FilterWriter |
转换流 | - | InputStreamReader OutputStreamWriter |
Object Serialization | ObjectInputStream ObjectOutputStream | - |
Data Conversion | DataInputStream DataOutputStream | - |
Counting | LineNumberInputStream | LineNumberReader |
Pecking ahead | PushbackInputStream | PushbackReader |
Printing | PrintStream | PrintWriter |
- 三、InputStream/OutputStream
InputStream/OutputStream用于处理字节数据。它们读/写流的方式都是以字节为单位进行的。
注: InputStream/OutputStream这是两个抽象类,使用时需要使用它们的具体子类。
【InputStream类层次】
【常见InputStream类】
低级InputStream类:
- InputStream(它是一个抽象类)
- ByteArrayInputStream
- PipedInputStream
- FileInputStream
高级InputStream类:
- DataInputStream
- BufferedInputStream
【InputStream基本方法】
三个基本的read方法:
- int read():读取一个字节,并将它返回,如果返回-1,则已到达输入流的末尾,即代表读取完毕。
- int read(byte[] buffer):将数据读入一个字节数组,同时返回读取的字节数,如果读取前已到达输入流的末尾,则返回-1。
- int read(byte[] buffer, int offset, int length):将数据读入一个字节数组,放到数组的offset指定的位置开始,并用length来指定读取的最大字节数,如果读取前已到达输入流的末尾,则返回-1。(用的比较少)
其它方法:
- void close():关闭输入流。
- int available():返回可以从中读取的字节数。
- long skip(long n):在输入流中跳过n个字节,将实际跳过的字节数返回。
- boolean markSupported():判断流是否支持标记功能。
- void mark(int readlimit):在支持标记的输入流的当前位置设置一个标记。
- void reset():返回到流的上一个标记。注意必须流支持标记功能。
package com.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStream_Demo_1 {
//未使用自定义的缓冲区
public static void main(String[] args) {
int b = 0;
FileInputStream fis = null;
try {
//把管子插到文件上
fis = new FileInputStream("c:/a.txt");
int sum = 0;
//开始读文件内容,read()方法每次只读取一个字节
while((b = fis.read()) != -1){
System.out.println((char)b);
sum++;
}
System.out.println();
System.out.println("共读取了" + sum + "个字节。");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(fis !=null){
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileInputStream fis = null;
byte[ ] buff = new byte[1024];
int n = 0;
try{
//连接程序与文件
fis = new FileInputStream(”c:/aa.txt”);
// 从文件读取数据
while((n = fis.read(buff))!=-1){
for(int i = 0; i< n; i++){
System.out.print((char)buffer[i]);
}
}
}catch (FileNotFoundException e){
System.out.println("没有找到文件");
}
我们自定义的缓冲区(buffer),用于提高效率,同时还可以延长硬盘的寿命。
可以通过字节(字符)数组创建字符串对象
创建字符串对象的四种方式:
byte[] buffer = new byte[4096];//字节数组
char[] buff = new char[4096];//字符数组
//1、通过双引号创建;
String str = "";
//2、通过new关键字创建
String str = new String();
//3、通过字节数组创建
String str = new String(buffer);
String str = new String(buffer,0,5);//从字节数组的哪个下标开始,转换几个
//4、通过字符数组创建
String str = new String(buff);
String str = new String(buff,0,5);
【使用输入流读取文件内容步骤】
输入流读取文件的步骤:
- 创建字节输入流对象,负责读取文件
- 创建中转站数组,存放读取的内容
- 读取文件内容到数组
- 输出保存在数组中的文件内容
- 关闭输入流
【OutputStream类层次】
继承自OutputStream的流是用于从程序中输出数据,且数据的处理单位为字节(8bit)。
【OutputStream类】
低级OutputStream
- OutputStream(它是一个抽象类)
- ByteArrayOutputStream
- PipedOutputStream
- FileOutputStream
高级OutputStream
- DataOutputStream
- BufferedOutputStream
【OutputStream基本方法】
三个基本的write方法:
- void write(int c)
- void write(byte[] buffer)
- void write(byte[] buffer, int offset, int length)(用的最多)
其它方法
- void close()
- void flush():清空缓冲区
注:在关闭输出流之前,要先调用一次flush()方法,然后再执行close()方法。这是因为如果此时缓冲区中存有数据,并且缓冲区中的数据没有将缓冲区填满时,输出流是不会自动执行flush()操作的,如果此时直接使用close()方法,则表示强制关闭输出流,这时,缓冲区中的数据就不会再写入到目标数据源中,这样导致数据不准确的问题发生。
示例:通过FileInputStream和FileOutputStream,实现从一个文本文件拷贝内容到另一个文件。(即:文本文件复制)
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(desFile);
int bytesRead;
//没有使用自定义缓冲区
while((bytesRead=fis.read()!=-1){
fos.write(bytesRead);
}
package com.io;
import java.io.*;
public class FileOutputStream_Demo_2 {
//使用了自定义的缓冲区
/**
* 功能:使用FileInputStream和FileOutputStream配置自定义的缓冲区完成文件内容的复制
*/
public void fileContentCopyWithBuffer(){
int b = 0;
//自定义缓冲区
byte[] buff = new byte[1024];
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("c:/a.txt");//a.jpg
fos = new FileOutputStream("c:/b.txt");//b.jpg
while((b = fis.read(buff)) != -1){
fos.write(buff, 0, b);
}
//如果使用数组作为缓冲区,在最后建议调用一次flush()方法,防止在缓问区未满时,导致剩余数据写不到目标数据源中。
fos.flush();
} catch (FileNotFoundException e2) {
System.out.println("找不到指定文件");
} catch (IOException e1) {
System.out.println("文件复制错误");
}finally{
try{
if(fis != null){
fis.close();
}
}catch(IOException e){
e.printStackTrace();
} finally{
try{
if(fos != null){
fos.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
System.out.println("文件内容已复制。。。");
}
public static void main(String[] args) {
FileOutputStream_Demo_2 fd = new FileOutputStream_Demo_2();
fd.fileContentCopyWithBuffer();
}
}
字节流既能复制二进制文件(图片jpg等)也能复制文本文件(txt等)
//当指定第2个参数为true时,表示如果b.txt文件存在,则不覆盖b.txt文件中的原内容,而是将新内容追加到原内后的后面,如果不指定第2个参数,则会将b.txt文件中的内容全部清空,然后再将新内容写入到b.txt文件中
fos = new FileOutputStream("c:/b.txt",true);
package com.io;
import java.io.*;
public class FileOutputStream_Demo_4 {
/**
* 功能:使用FileInputStream和FileOutputStream配置自定义的缓冲区完成文件的剪切
*
*/
public void fileCutWithBuffer(){
int b = 0;
//自定义缓冲区
byte[] buff = new byte[1024];
FileInputStream fis = null;
FileOutputStream fos = null;
//使file对象指向磁盘上的a.txt文件,将来file对象在程序中就代表了a.txt文件
File file = new File("c:/a.txt");//实现文件剪切功能使用
try {
//由于此时的file对象在程序中就代表了文件,所以将管子直接插到file对象上就相当于把管子插到磁盘文件上
fis = new FileInputStream(file);
fos = new FileOutputStream("c:/demo/a.txt");
while((b = fis.read(buff)) != -1){
fos.write(buff, 0, b);
}
//如果使用数组作为缓冲区,在最后建议调用一次flush()方法,防止在缓问区未满时,导致剩余数据写不到目标数据源中。
fos.flush();
} catch (FileNotFoundException e2) {
System.out.println("找不到指定文件");
} catch (IOException e1) {
System.out.println("文件复制错误");
}finally{
try{
if(fis != null){
fis.close();
}
}catch(IOException e){
e.printStackTrace();
} finally{
try{
if(fos != null){
fos.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
System.out.println("文件剪切成功。。。");
//放到该位置上表示,将所有输入、输出流全部关闭后再实现将源文件删除,符合逻辑思维方式。
file.delete();
}
public static void main(String[] args) {
FileOutputStream_Demo_4 fd = new FileOutputStream_Demo_4();
fd.fileCutWithBuffer();
}
}
- 四、Reader/Writer(字符流)
Reader/Writer处理的是字符类型的数据。它处理流的方式是以字符为单位进行的。
Reader/Writer和InputStream/OutputStream一样,也分为节点流(低级流)和处理流(高级流)。
Reader和InputStream一样,用于从流中读取数据。它和InputStream的区别在于,InputStream以字节为单位操作流,而Reader以字符为单位操作流。(使用方式基本一样,只操作流时单位不同)
【Reader类层次】
继承自Reader的流是用于向程序中输入数据,且数据的单位为字符(16bit)。
【Reader相关类】
低级Reader类
- CharArrayReader
- StringReader
- PipedReader
- FileReader
高级Reader类
- BufferedReader
- InputStreamReader
- LineNumberReader
【Reader常用方法】
读取方法:
- int read():用于从流中读出一个字符,并将它返回,如果返回-1,则已到输入流的末尾。返回的这个int类型的数就是当前字符的Unicode码 。
- int read(char[] buffer):将从流中读出的字符放到字符数组buffer中,返回读出的字符数,如果读取前已到输入流末尾,则返回-1。
- int read(char[] buffer, int offset, int length):将读出的字符放到字符数组的指定offset开始的空间,每次最多读出length个字符,如果读取前已到输入流末尾,则返回-1。
其他方法:
- void close():关闭Reader流。
- boolean ready():判断流是否已经准备好被读取。
- skip(long n):跳过指定的n个字符。
- boolean markSupported():和InputStream中的markSupported方法类似。
- void mark(int readAheadLimit):和InputStream中的mark方法类似。
- void reset():和InputStream中的reset方法类似。
FileReader fr = new FileReader("C:/mydata.txt");
int record = 0;
while ((record = fr.read()) != -1) {
System.out.println((char)c);
}
【使用Reader读取文件步骤】
Reader读取文件的步骤:
- 创建字符输入流对象,负责读取文件
- 创建中转站数组,存放读取的内容
- 读取文件内容到数组
- 输出保存在数组中的文件内容
- 关闭输入流
【Writer类层次】
继承自Writer的流是用于从程序中输出数据,且数据的单位为字符(16bit)。
【Writer相关类】
低级Writer类
- CharArrayWriter
- StringWriter
- PipedWriter
- FileWriter
高级Writer类
- BufferedWriter
- OutputStreamWriter
- PrintWriter
【Writer常用方法】
写入方法
- void write(int c):将参数c的低16位组成字符写入到流中。
- void write(char[] buffer):将字符数组buffer中的字符写入到流中。
- void write(char[] buffer, int offset, int length):将字符数组buffer中从offset开始的length个字符写入到流中。
- void write(String string):将string字符串写入到流中。
- void write(String string, int offset, int length):将字符string中从offset开始的length个字符写入到流中。
其他方法
- void close():和OutputStream的close方法类似。
- void flush():和OutputStream的flush方法类似。
FileWriter fw = null;
fw = new FileWriter("c:/unicode.dat");
for(int c=0;c<=20000;c++){
fw.write(c);
}
//当指定第2个参数为true时,表示如果unicode.dat文件存在,则不覆盖unicode.dat文件中的原内容,而是将新内容追加到原内后的后面,如果不指定第2个参数,则会将unicode.dat文件中的内容全部清空,然后再将新内容写入到unicode.dat文件中
fw = new FileWriter("c:/unicode.dat", true);
//FileWriter默认第二个参数为false,若无第二个参数则为覆盖;想追加内容可设置第二个参数为true
fw = new FileWriter("c:/data2.txt");//覆盖
fw = new FileWriter("c:/data2.txt",true);//追加
package com.io;
import java.io.*;
public class FileWriter_Demo_4 {
/**
* 实现文件备份功能,使用了自定义的缓冲区
*/
public void fileBackup(){
FileReader fr = null;
FileWriter fw = null;
int b = 0;
char[] buffer = new char[1024];
try {
fr = new FileReader("c:/a.txt");
fw = new FileWriter("c:/a.bak");
while((b = fr.read(buffer)) != -1) {
fw.write(buffer, 0, b);
}
fw.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
try {
if(fr != null){
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally{
try {
if(fw != null){
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("文件备份成功。。。");
}
public static void main(String[] args) throws Exception {
FileWriter_Demo_4 fd = new FileWriter_Demo_4();
fd.fileBackup();
}
}
- 五、缓冲流
缓冲流是把数据从原始流成块地读入或把数据积累到一个大数据块后再成批写出,通过减少系统资源的读写次数来加快程序的执行。BufferedOutputSream或BufferedWriter类仅仅在缓冲区满或调用flush()方法时才将数据写到目的地。
JDK提供了四种常用的缓存流:
- BufferedReader
- BufferedWriter
- BufferedInputStream
- BufferedOutputStream
缓冲流是过滤流(处理流),在创建具体的流时,需要给出一个InputStream/OutputStream、Reader/Writer类型的流作为前端流(细管),并可以指明缓冲区的大小。
常用缓冲流:
- BufferedInputStream与BufferedOutputStream
- BufferedReader与BufferedWriter
缓冲输入流支持其父类的mark()和reset()方法:
- mark()用于“标记”当前位置,就像加入了一个书签,可以使用
- reset()方法返回这个标记重新读取数据。(了解即可)
BufferedReader提供了readLine()方法用于读取一行字符串(以\r或\n为结束标志,即:当读取到\r或\n就结束本次读取,并把读到到的字符串返回)。
BufferedWriter提供了newLine()用于写入一个换行分隔符。
注:对于输出的缓冲流,写出的数据会先在内存中缓存,使用flush()方法将会使内存中的数据立刻写出。
缓冲流自带缓冲区不需自定义缓冲区
package com.io;
import java.io.*;
public class BufferedReader_Demo {
public static void main(String[] args) {
//细管
FileReader fr = null;
//粗管
BufferedReader br = null;
try {
//细管可以直接插到文件上
fr = new FileReader("c:/database.properties");
//粗管只能包在细管上
br = new BufferedReader(fr);
String s = null;
//读取
while((s = br.readLine()) != null){
System.out.println(s);
}
} catch (IOException e) {
e.printStackTrace();
}finally{
try{
if(br != null){
br.close();
}
/*
//注:当关闭外部包着的处理流(粗管)时,内部的节点流(细管)也会自动随之关才,可以不用再手动进行显示的关闭.
if(fw != null){
fw.close();
}
if(fr != null){
fr.close();
}
*/
}catch(IOException e){
e.printStackTrace();
}
}
}
}
package com.io;
import java.io.*;
public class BufferedWriter_Demo {
public static void main(String[] args) {
//细管
FileWriter fw = null;
//粗管
BufferedWriter bw = null;
try {
//细管可以直接插到文件上
fw = new FileWriter("c:/data2.txt");
//粗管只能包在细管上
bw = new BufferedWriter(fw);
String s = null;
for(int i=1;i<=100;i++){
s = String.valueOf(Math.random());
bw.write(s);
//写入一个换行分隔符,该分隔符属于控制符,不会显示在文件内容中。即:起到回车换行的作用,即产生一个新行
bw.newLine();
}
//切记:因为使用了BufferedWriter这个粗管,该粗管带有缓冲区,一定要人为调用flush()方法
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}finally{
try{
if(bw != null){
bw.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
}
- 六、转换流
InputStreamReader和OutputStreamWriter用于将字节到字符之间的转换。
InputStreamReader需要和InputStream套接。
OutputStreamWriter需要和OutputStream套接。
转换流在构造时可以指定其编码集合。例如:InputStream isr = new InputStreamReader(System.in, "ISO8859-1");
转换流不能处理二进制文件,二进制文件只能用字节流来处理