Java输入输出流
相关概念
流的概念:
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。流的源端和目的端可简单地看成是数据的生产者和消费者,对输入流,可不必关心它的源端是什么,只要简单地从流中读数据,而对输出流,也可不知道它的目的端,只是简单地往流中写数据。
流的划分:
按照流向划分数据流可分为输入流和输出流。
输入流:从磁盘,光盘,网络,键盘输入数据到内存。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[]) throws IOException { 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(new ByteArrayOutputStream()); dos.writeDouble(3.14); //写入double类型 dos.writeBoolean(true); //写入布尔类型 dos.writeChar(97); //写入char类型
BufferedOutputStream:为另一个输出流添加缓冲功能,BufferedInputStream类似效果。
PrintStream:装饰其他输出流,为其他输出流添加功能,方便的打印各种数据值。
ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(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(new String(bb)); //结果 //hello
OutputStreamWriter:从字符流到字节流的桥接,自动将要写入流中的字符编码成字节。
ByteArrayOutputStream baos = new ByteArrayOutputStream(); 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 class Test{ public static void main(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(); } } } class copyClass{ private File source; private File destination; copyClass(String source, String destination){ this.source = new File(source); this.destination = new File(destination); } void copyContent(File source,File destination,boolean byByte) throws IOException { if(byByte){ //字节数组复制 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source)); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(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 = new FileInputStream(source); FileOutputStream fos = new FileOutputStream(destination); while (fis.available()!=0){ int i = fis.read(); fos.write(i); } fis.close(); fos.close(); } } void copyDir(File source, File destination,boolean byBytes) throws IOException{ 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!"); } } } public File getSource() { return source; } public void setSource(File source) { this.source = source; } public File getDestination() { return destination; } public void setDestination(File destination) { this.destination = destination; } }
结果
//采用单个字节复制方法耗时 1317 //采用缓冲区字节数组复制方法耗时 52 //减少了超过95%的时间
至关重要的是:除非数据流非常小,否则都应该使用数组来进行流的处理,必要时可用BufferedInputStream或BufferedOutputStream或BufferedWriter/BufferedReader来进行缓冲处理。