相关概念
流的概念:
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。流的源端和目的端可简单地看成是数据的生产者和消费者,对输入流,可不必关心它的源端是什么,只要简单地从流中读数据,而对输出流,也可不知道它的目的端,只是简单地往流中写数据。
流的划分:
按照流向划分数据流可分为输入流和输出流。
输入流:从磁盘,光盘,网络,键盘输入数据到内存。io包中的输入流都继承自InputStream或Reader。
输出流:从内存输出数据到磁盘,磁带,显示器等地。io包中的输出流都继承自OutputStream或Writer。
按照数据组织形式划分数据流可分为字节流和字符流。
字节流:byte为数据的最小单位。io包中的输入流都继承自InputStream或OutputStream。
字符流:char为数据的最小单位。io包中的输入流都继承自Reader或Writer。
按照功能划分数据流可分为低级流(节点流)和高级流(处理流)。
低级流(节点流):节点流是可以直接从/向一个特定的数据源(例如磁盘、内存、网络)读/写数据的流。
高级流(处理流):高级流不可直接从数据源读/写数据,而是连接在已存在的流(可以是低级流或高级流)之上,为流添加额外的扩展功能。
输入输出流总体划分
字节流分类及其作用
字符流分类及其作用
通用API(黑体为常用API)
OutputStream
public abstract void write(int b) //向输出流写入单个字节
public void write(byte[] data) //将字节数组data中数据写入到输出流中
public void write(byte[] data, int offset, int length) //将字节数组data中“offset开始,共length长“的数据写入到输出流中
public void flush() //刷新该输出流,强制数据写入到输出流
void close() //关闭输出流并释放与该流关联的所有系统资源
InputStream
int available() //可以不受阻塞地从此输入流读取(或跳过)的估计字节数
void close() //关闭此输入流并释放与该流关联的所有系统资源
void mark(int readlimit) //在此输入流中标记当前的位置,如果读取字节数超过readlimit,则不标记任何字节
boolean markSupported() //测试此输入流是否支持 mark 和 reset 方法
abstract int read() //从输入流中读取数据的下一个字节
int read(byte[] b) //从输入流中读取一定数量的字节,并将其存储在字节数组 b 中
int read(byte[] b, int off, int len) //将输入流中从"off开始的最多 len字节数据"读入 byte 数组
void reset() //将此流重新定位到最后一次对此输入流调用 mark 方法时的位置
long skip(long n) //跳过和丢弃此输入流中数据的 n 个字节
Reader
abstract void close() //关闭该流并释放与之关联的所有资源
void mark(int readAheadLimit) //标记流中的当前位置
boolean markSupported() //判断此流是否支持 mark() 操作
int read() //读取单个字符
int read(char[] cbuf) //将字符读入数组
abstract int read(char[] cbuf, int off, int len) //将字符读入数组的“offset开始,共length长”的部分
int read(CharBuffer target) //试图将字符读入指定的字符缓冲区
boolean ready() //判断是否准备读取此流
void reset() //将此流重新定位到最后一次对此输入流调用 mark 方法时的位置
long skip(long n) //跳过个字符
Writer
Writer append(char c) //将单个字符添加到输出流 追加模式
Writer append(CharSequence csq) //将指定字符序列添加到输出
Writer append(CharSequence csq, int start, int end) //将指定字符序列的子序列添加到输出流
abstract void close() //关闭此流
abstract void flush() //刷新该流的缓冲
void write(char[] cbuf) //写入字符数组
abstract void write(char[] cbuf, int off, int len) //写入字符数组的某一部分
void write(int c) //写入单个字符
void write(String str) //写入字符串
void write(String str, int off, int len) //写入字符串的某一部分
部分流的特点
ByteArrayOutputStream:写入ByteArrayOutputStream的数据被写入到一个byte数组,缓冲区会随着数据的不断写入而自动增长;可使用toByteArray()和toString()获取数据;无法关闭,调用close方法仍然可以之后向该流写入数据。(ByteArrayInputStream类似)
public static void main(String args[]) throwsIOException {
ByteArrayOutputStream baos= new ByteArrayOutputStream(1); //缓冲区长度为4
baos.write("helloworld".getBytes()); //可写入超过1字节数据
byte b[] =baos.toByteArray();
String str= baos.toString(); //toByteArray和toString方法
baos.close(); //尝试关闭
baos.write("!".getBytes()); //仍可写入
baos.writeTo(System.out); //写到特定输出流中//结果:helloworld!
}
FileoutputStream:用于向文件进行写入操作,getFD方法可获取文件描述符,getChannel方法可获取文件通道。
FileOutputStream fos = new FileOutputStream("fa.txt");
FileChannel fc=fos.getChannel();
FileDescriptor fd= fos.getFD();
ObjectOutputStream:组合ObjectInputStream来进行对基本数据或者对象的持久存储。
PipedOutputStream:和PipedInputStream一起使用,能实现多线程间的管道通信。
DataOutputStream:装饰其他的输出流,允许应用程序以与机器无关方式向底层写入基本Java数据类型。(DataInputStream类似效果)
DataOutputStream dos = new DataOutputStream(newByteArrayOutputStream());
dos.writeDouble(3.14); //写入double类型
dos.writeBoolean(true); //写入布尔类型
dos.writeChar(97); //写入char类型
BufferedOutputStream:为另一个输出流添加缓冲功能,BufferedInputStream类似效果。
PrintStream:装饰其他输出流,为其他输出流添加功能,方便的打印各种数据值。
ByteArrayOutputStream baos = newByteArrayOutputStream();
PrintStream ps= newPrintStream(baos);
ps.format("%-3.2f",3.1415926);
baos.writeTo(System.out);//结果:3.14
PushbackInputStream:允许从流中读取数据,然后在需要时推回该流。
byte[] b = "heo".getBytes();
PushbackInputStream pis= new PushbackInputStream(newByteArrayInputStream(b),3); //回推缓存三字节
byte[] bb = new byte[5];
pis.read(bb,0,2); //将b中数据he读入输入流中
pis.unread("ll".getBytes()); //将ll放入回推缓存
pis.read(bb,2,3); //读取回推缓存数据后再读取b中数据
System.out.println(newString(bb));//结果//hello
OutputStreamWriter:从字符流到字节流的桥接,自动将要写入流中的字符编码成字节。
ByteArrayOutputStream baos = newByteArrayOutputStream();
OutputStreamWriter osw= new OutputStreamWriter(baos,"utf-8"); //指定编码
osw.write("Sakura\n");
osw.write("最好了");
osw.flush();
baos.writeTo(System.out);//结果//Sakura//最好了
各种流的使用场景
一,数据输入还是输出
1)输入:使用Reader、InputStream 类型的子类。
2)输出:使用Writer、OutputStream 类型的子类。
二,数据格式
1) 二进制格式:使用InputStream、OutputStream 及其所有带 Stream结尾的子类。
2)纯文本格式:使用Reader、Writer 及其所有带 Reader、Writer 的子类。
三,数据源
1) 文件:字节流使用FileInputStream和FileOutputStream;对于字符流使用FileReader和 FileWriter。
2) 字节数组:则使用ByteArrayInputStream和ByteArrayOutputStream。
3) 字符数组:则使用CharArrayReader和CharArrayWriter。
4) String对象:字节流使用StringBufferInputStream和StringBufferOuputStream;字符流使用StringReader和StringWriter。
六、特殊需要
1) 转换流:InputStreamReader、OutputStreamWriter。
2) 对象输入输出:ObjectInputStream、ObjectOutputStream。
3)线程间通信:PipeInputStream、PipeOutputStream、PipeReader、PipeWriter。
4)合并输入:SequenceInputStream。
5)格式化输出,则使用PrintStream或PrintWriter。
6)更特殊的需要:PushbackInputStream、PushbackReader、LineNumberInputStream、LineNumberReader。
七、缓冲
字节流使用BufferedInputStream和BufferedOutputStream;字节流使用BufferedReader和BufferedWriter。
一个实例
复制文件夹的类
import java.io.*;public classTest{public static voidmain(String[] args) {
copyClass cc= new copyClass("src\\old","src\\new");try{long start =System.currentTimeMillis();
cc.copyDir(cc.getSource(),cc.getDestination(),false);long end =System.currentTimeMillis();
System.out.println(end-start);
}catch(IOException e){
e.printStackTrace();
}
}
}classcopyClass{privateFile source;privateFile destination;
copyClass(String source, String destination){this.source = newFile(source);this.destination = newFile(destination);
}void copyContent(File source,File destination,boolean byByte) throwsIOException {if(byByte){ //字节数组复制
BufferedInputStream bis = new BufferedInputStream(newFileInputStream(source));
BufferedOutputStream bos= new BufferedOutputStream(newFileOutputStream(destination));byte[] b = new byte[1024];while (bis.available()!= 0){
bis.read(b);
bos.write(b);
bos.flush();
}
bis.close();
bos.close();
}else { //单个字节复制
FileInputStream fis = newFileInputStream(source);
FileOutputStream fos= newFileOutputStream(destination);while (fis.available()!=0){int i =fis.read();
fos.write(i);
}
fis.close();
fos.close();
}
}void copyDir(File source, File destination,boolean byBytes) throwsIOException{if (!source.isDirectory()){ //是文件则复制其内容
copyContent(source,destination,byBytes);
}else{if (destination.mkdirs()){ //是文件夹则递归复制
File[] fs =source.listFiles();for (int i = 0; i < fs.length; i++) {
String fileName=fs[i].getName();
File newDestination= new File(destination.getPath() + File.separator +fileName);
copyDir(fs[i], newDestination,byBytes);
}
}else{
System.out.print("Creat dirs failed!");
}
}
}publicFile getSource() {returnsource;
}public voidsetSource(File source) {this.source =source;
}publicFile getDestination() {returndestination;
}public voidsetDestination(File destination) {this.destination =destination;
}
}
结果
//采用单个字节复制方法耗时
1317
//采用缓冲区字节数组复制方法耗时
52
//减少了超过95%的时间
至关重要的是:除非数据流非常小,否则都应该使用数组来进行流的处理,必要时可用BufferedInputStream或BufferedOutputStream或BufferedWriter/BufferedReader来进行缓冲处理。