流(Stream)的概念源自UNIX中管道的概念,管道是一条不间断的字节流,用来实现程序或进程之间的通信。一个流必有源端和目的端(可以是内存、磁盘文件等。)流的源端和目的端可以简单的看成字节的生产者和消费者。
根据
根据
根据
Java IO流的继承结构图如下:
在讲解流之前,先讲解一下之后和流操作紧密相关的文件操作类
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public File(String pathName) | 构造 | 给定一个要操作文件的完整路径 |
2 | public boolean exists() | 普通 | 判定给定路径是否存在 |
3 | public File getParentFile() | 普通 | 找到一个指定路径的父路径 |
4 | public boolean mkdirs() | 普通 | 创建指定目录 |
5 | public boolean createNewFile() | 普通 | 创建文件 |
6 | public boolean delete() | 普通 | 删除文件 |
7 | public boolean isDirectory() | 普通 | 判断给定路径是否是文件夹 |
8 | public boolean isFile() | 普通 | 判断给定路径是否是文件 |
9 | public boolean isHidden() | 普通 | 判断是否隐藏 |
10 | public boolean renameTo(File dest) | 普通 | 为文件重命名 |
11 | public long lastModified() | 普通 | 文件的最后一次修改时间 |
12 | public long length() | 普通 | 取得文件的大小 |
13 | public String getName() | 普通 | 取得文件名称 |
14 | public [] File listFiles() | 普通 | 将目录中的文件以File对象数组的方式返回 |
所有的
所有的
对于InputStream、OutputStream类而言,本身定义的是一个抽象类(abstract class),按照抽象类的使用原则来讲,需要定义抽象类的子类,根据功能的不同,使用不同的子类完成。
文件操作流的常用方法
类名称 | No. | 方法名称 | 类型 | 描述 |
---|---|---|---|---|
1 | public FileInputStream(File file) | 实例化FileInputStream,用于从指定文件中读取数据 | ||
2 | public FileInputStream(Stirng name) | 实例化FileInputStream,用于从指定文件路径中读取数据 | ||
InputStream | 3 | public void close() | 普通 | 关闭输入流 |
InputStream | 4 | public int read() | 普通 | 从指定输入流中读取一个字节数据 |
InputStream | 5 | public int read(byte [] data) | 普通 | 从指定流中读取多个字节 |
InputStream | 6 | public int read(byte [] data, int off, int len) | 普通 | 从指定流中读取指定多个字节 |
7 | public FileOutputStream(File file) | 实例化FileOuputStream,用于新建数据 | ||
8 | public FileOutputStream(File file,boolean append) | 实例化FileOutputStream,用于追加数据 | ||
OutputStream | 9 | public void close() | 普通 | 关闭输出流 |
OutputStream | 10 | public abstract void write(int b) | 普通 | 向文件输出单个字节 |
OutputStream | 11 | public void write(byte [] b) | 普通 | 向文件输出一组字节数据 |
OutputStream | 12 | public void write(byte [] b, int off, int len) | 普通 | 向文件输出部分字节数据 |
在以上的例子中,通过FileInputStream的read()方法从/home/linyimin/DaSi/java/InputStream.txt文件中读取信息,通过FileOutputStream的write()方法向/home/linyimin/DaSi/java/InputStream.txt文件写入“Hello World.”。在此操作中如果InputStream.txt已经存在,会先清空内容在写入信息,如果不存在,会自动创建InputStream.txt文件后在写入数据。如果希望在已存在的文件之后添加数据,应使用public FileOutputStream(fileOut,true)构造方法进行实例化对象,表示向已有文件中追加写入数据而不是覆盖已有数据。
在某个操作需要发生IO操作,但又不希望有一些临时文件产生,可以使用内存操作流完成,即以内存为操作的终端,以发生IO操作关系。用于以IO流的方式来完成对字节数组内容的读写,来支持类似内存虚拟文件或者内存映射文件的读写。
ByteArrayInputStream:字节数组输入流,继承自InputStream,它会在内存中创建一个字节数组缓冲区,实例化后的字节数组会保存在此缓冲区中。也就是内存操作类的常用方法
类名称 | No. | 方法 | 类型 | 描述 |
---|---|---|---|---|
1 | public ByteArrayInputStream(byte [] buf) | 将字节流转换称输入流 | ||
2 | public int read() | 普通 | 从输入流中读取一个字节数据 | |
3 | public int read(byte [] data) | 普通 | 从指定流中读取多个字节 | |
4 | public int read(byte [] data, int off, int len) | 普通 | 从指定流中读取指定多个字节 | |
5 | public ByteArrayOutputStream() | 创建一个32个字节 | ||
6 | public ByteArrayOutputStream(int size) | 根据参数指定大小创建缓冲区 | ||
7 | public void write(int b) | 普通 | 往输出流中写入一个字节数据 | |
8 | public void write(byte [] b) | 普通 | 往输出流中写入一个字节数组数据 | |
9 | public void write(byte [] b, int off, int len) | 普通 | 往输出流中写入指定多个字节数据 | |
10 | public String toString() | 普通 | 使用默认编码将缓冲区数据编码成字符串并返回 | |
11 | public byte [] toByteArray() | 普通 | 将缓冲区数据通过字节数组返回 |
程序运行结果: abcdefghijklmnopqrstuvwsyz a b c d e f g h i j k l m n o p q r s t u v w s y z ABCDEFGHIJKLMNOPQRSTUVWSYZ
在上面的例子中,我们通过字符串获取字节数组将其作为ByteArrayInputStream的数据流来源,然后通过读取ByteArrayInputStream的数据,将读到的数据写入到ByteArrayOutputStream中。
System.in是System类中的一个InputStream类型的常量,用于系统输入。系统输入针对标准的输入设备——键盘,也就是俗称的键盘输入数据,由于System.in是InputStream类型数据,所以接收的数据是字节型。通过调用read()函数完成键盘输入数据操作。
程序运行结果: 输入多个字节数据: Hello 中国 Hello 中国
一个字节接一个字节输入数据: Hello 中国 H E L L O Ä ¸ Å › ½
在上面的例子中,可以发现从键盘输入数据时,既可以多个字节数据同时输入,也可以一个接一个字节输入,但应注意的是,
BufferedInputStream是缓冲输入流,继承于FilterInputStream。作用是为其它输入流提供缓冲功能。BufferedInputStream会将输入流数据分批读取,每次读取一部分数据到缓冲中,操作完缓冲中的数据之后,在从输入流中读取下一部分的数据。即BufferedInputStream内部有一个字节数组缓冲区,每次执行read操作的时候就从这buf中读取数据,从buf中读取数据没有多大的开销。如果buf中已经没有了要读取的数据,那么就去执行其内部绑定的InputStream的read方法,而且是一次性读取很大一块数据,以便填充满buf缓冲区。于我们在执行BufferedInputStream的read操作的时候,很多时候都是从缓冲区中读取的数据,这样就大大减少了实际执行其指定的InputStream的read操作的次数,也就提高了读取的效率。
BufferedOutputStream是输出缓冲流,与BufferedInputStream相对,继承于FilterOutputStream。作用是为输出流提供缓冲功能。BufferedOutputStream内部有一个字节缓冲区buf,在执行write操作时,将要写入的数据先一起缓存在一起,将其存入字节缓冲区buf中,当buf被填充完毕的时候会调用BufferedOutputStream的flushBuffer方法,该方法会通过调用其绑定的OutputStream的write方法将buf中的数据进行实际的写入操作并将buf的指向归零(可以看做是将buf中的数据清空)。如果想让缓存区buf中的数据理解真的被写入OutputStream中,可以调用flush方法,flush方法内部会调用flushBuffer方法。由于buf的存在,会大大减少实际执行OutputStream的write操作的次数,优化了写的效率。
缓冲操作的常用方法
类名称 | No. | 方法 | 类型 | 描述 |
---|---|---|---|---|
1 | public BufferedInputStream(InputStream in) | 实例化BufferedInputStream,使用默认缓冲区大小 | ||
2 | public BufferedInputStream(InputStream in, int size) | 实例化BufferedInputStream,指定缓冲区大小 | ||
3 | public int read() | 普通 | 从输入流中读取一个字节数据 | |
4 | public int read(byte [] data) | 普通 | 从指定流中读取多个字节 | |
5 | public int read(byte [] data, int off, int len) | 普通 | 从指定流中读取指定多个字节 | |
6 | public BufferedOutputStream(OutputStream out) | 实例化BufferedOutputStream,使用默认缓冲区大小 | ||
7 | public BufferedOutputStream(OutputStream out, int size) | 实例化BufferedOutputStream,指定缓冲区大小 | ||
8 | public void write(int b) | 普通 | 向输出缓冲区写入一个字节数据 | |
9 | public void write(byte [] data) | 普通 | 向输出缓冲区写入多个字节数据 | |
10 | public void write(byte [] data, int off, int len) | 普通 | 向输出缓冲取写入指定多个字节 | |
11 | public void flush() | 普通 | 清空缓冲区,缓存区buf中的数据理解真的被写入OutputStream中 |
代码示例
import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;public class TestDemo{ public static void main(String [] args) throws IOException{ File fio = new File(File.separator + "home" + File.separator + "linyimin" + File.separator + "DaSi" + File.separator + "java" + File.separator + "InputStream.txt"); File fout = new File(File.separator + "home" + File.separator + "linyimin" + File.separator + "DaSi" + File.separator + "java" + File.separator + "OutputStream.txt"); // InputStream.txt不存在则创建文件 if(!fio.exists()){ fio.createNewFile(); } // OutputStream.txt不存在则创建文件 if(!fout.exists()){ fout.createNewFile(); } // 实例化BufferedInputStream BufferedInputStream ibuf = new BufferedInputStream(new FileInputStream(fio)); // 实例化BufferedOutputStream BufferedOutputStream obuf = new BufferedOutputStream(new FileOutputStream(fout)); byte [] data = new byte[1024]; // 从BufferedInputStream中多个字节数据 int len = ibuf.read(data); while(len != -1){ // 向BufferedOutputStream中写入多个数据 obuf.write(data, 0, len); // 继续从BufferedInputStream中读取多个字节数据 len = ibuf.read(data); } // 清空输出缓存区 obuf.flush(); // 关闭输出输入流 ibuf.close(); obuf.close(); }}程序运行结果: 程序运行前:OutputStream.txt文件不存在,InputStream.txt文件的内容为: Hello World. 1234567890 程序运行后:OutputStream.txt文件的内容为: Hello World. 1234567890
在上面的例子中,实现了将InputStream.txt拷贝到OutputStream.txt。其实不通过BufferedInputStream 和 BufferedOutputStream也可以完成这样的工作,使用这个两个类的好处是提升了文件拷贝的效率。下面进行验证:
直接使用InputStream和OutputStream完成大文件赋值代码
import java.io.File;import java.io.InputStream;import java.io.OutputStream;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.math.BigDecimal;public class TestDemo{ public static void main(String [] args) throws IOException{ File fio = new File("/home/linyimin/Downloads/linux_11gR2_database_2of2.zip"); File fout = new File("/home/linyimin/Downloads/linux_11gR2_database_2of2-copy.zip"); InputStream in = null; OutputStream out = null; // 创建文件 try{ fout.createNewFile(); in = new FileInputStream(fio); out = new FileOutputStream(fout); } catch(Exception e){ e.printStackTrace(); } // 获取复制文件大小,并转换成单位M,保留两位小数 double length = new BigDecimal(fio.length() / 1024.0 / 1024).divide(new BigDecimal(1), 2, BigDecimal.ROUND_HALF_UP).doubleValue(); // 文件复制开始时间 long start = System.currentTimeMillis(); // 从输入流中读取多个字节数据 byte [] data = new byte[1024]; int len = in.read(data); while(len != -1){ // 将从输入流中读出的数据写入输出流中 out.write(data); // 继续从输入流中读取数据 len = in.read(data); } // 文件复制结束时间 long end = System.currentTimeMillis(); // 将文件复制时间转换成秒并保留三位小数 double time = new BigDecimal((end - start) / 1000.0).divide(new BigDecimal(1), 3, BigDecimal.ROUND_HALF_UP).doubleValue(); System.out.println("复制大小为:" + length + "M的文件共花费时间:" + time + "s"); }}程序运行结果:
复制大小为:949.25M的文件共花费时间:49.048s
代码
import java.io.File;import java.io.InputStream;import java.io.OutputStream;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.math.BigDecimal;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;public class TestDemo{ public static void main(String [] args) throws IOException{ File fio = new File("/home/linyimin/Downloads/linux_11gR2_database_2of2.zip"); File fout = new File("/home/linyimin/Downloads/linux_11gR2_database_2of2-copy.zip"); InputStream in = null; OutputStream out = null; // 创建文件 try{ fout.createNewFile(); in = new BufferedInputStream(new FileInputStream(fio)); out = new BufferedOutputStream(new FileOutputStream(fout)); } catch(Exception e){ e.printStackTrace(); } // 获取复制文件大小,并转换成单位M,保留两位小数 double length = new BigDecimal(fio.length() / 1024.0 / 1024).divide(new BigDecimal(1), 2, BigDecimal.ROUND_HALF_UP).doubleValue(); // 文件复制开始时间 long start = System.currentTimeMillis(); // 从输入流中读取多个字节数据 byte [] data = new byte[1024]; int len = in.read(data); while(len != -1){ // 将从输入流中读出的数据写入输出流中 out.write(data); // 继续从输入流中读取数据 len = in.read(data); } // 文件复制结束时间 long end = System.currentTimeMillis(); // 将文件复制时间转换成秒并保留三位小数 double time = new BigDecimal((end - start) / 1000.0).divide(new BigDecimal(1), 3, BigDecimal.ROUND_HALF_UP).doubleValue(); System.out.println("复制大小为:" + length + "M的文件共花费时间:" + time + "s"); }}程序运行结果:
复制大小为:949.25M的文件共花费时间:32.93s
比较以上两个程序的执行结果,使用缓冲区操作的效率确实要提高不少。
管道流操作主要用于两个线程之间进行管道通信。PipedInputStream和PipedOutputStream一般是结合使用的,一般在一个线程中执行PipedOutputStream 的write操作,而在另一个线程中执行PipedInputStream的read操作。单独使用PipedInputStream或单独使用PipedOutputStream时没有任何意义的,必须将二者通过connect方法(或在构造函数中传入对应的流)进行连接绑定,如果单独使用其中的某一个类,就会触发IOException: PipeNotConnected.
代码示例
import java.io.IOException;import java.io.PipedInputStream;import java.io.PipedOutputStream;public class TestDemo{ public static void main(String [] args)throws Exception{ // 实例化PipedOutputStream final PipedOutputStream out = new PipedOutputStream(); final PipedInputStream in = new PipedInputStream(out); // 使用匿名内部类实现线程t1 Thread t1 = new Thread(new Runnable(){ @Override public void run(){ try { out.write("Hello Pipe.".getBytes()); } catch (IOException e) { e.printStackTrace(); } } }); // 使用匿名内部类实现线程t2 Thread t2 = new Thread(new Runnable(){ @Override public void run(){ int len = 0; try { // 从线程t1中的输出流中读取数据 while((len = in.read()) != -1){ System.out.print((char)len + " "); } } catch (IOException e) { } System.out.println(); } }); // 启动线程 t1.start(); t2.start(); }}程序运行结果 H e l l o P i p e .
在上面的程序中,我们创建了两个线程,并通过构造方法将PipedInputStream流和PipedOutputStream绑定。线程t1在运行时往输出流中写入字节数据,而线程t2在运行时阻塞式的执行read操作,等待获取数据,并输出。
注:
如果一个类已经实现了序列化接口,那么此类的对象就可以经过二进制数据流进行传输,但是还需要对象输出流ObjectOutputStream和对象输入流ObjectInputStream完成对象的输入和输出.
ObjectOutputStream具有一系列writeXXX方法,在其构造函数中可以传入一个OutputStream,可以方便的向指定的输出流中写入基本类型数据以及String,比如writeBoolean、writeChar、writeInt、writeLong、writeFloat、writeDouble、writeCharts、writeUTF等,除此之外,ObjectOutputStream还具有writeObject方法。在执行writeObject操作时将对象进行序列化成流,并将其写入指定的输出流中。ObjectInputStream与ObjectOutputStream相对应,ObjectInputStream有与OutputStream中的writeXXX系列方法完全对应的readXXX系列方法,专门用于读取OutputStream通过writeXXX写入的数据。ObjectOutputStream和ObjectInputStream常用方法
类名称 | No. | 方法 | 类型 | 描述 |
---|---|---|---|---|
1 | public ObjectOutputStream(OutputStream out) | 实例化指定输出流的对象 | ||
2 | public void writeObject(Object obj) | 普通 | 向指定输流写入数据 | |
3 | public ObjectInputStream(InputStream in) | 实例化指定输入流的对象 | ||
4 | public void readObject(Object obj) | 普通 | 从指定输入流读出数据 |
代码示例-序列化
import java.io.File;import java.io.Serializable;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;// 定义可以被序列化的类class Person implements Serializable{ private String name; private int age; public Person(String name, int age){ this.name = name; this.age = age; }}public class TestDemo{ public static void main(String [] args) throws FileNotFoundException, IOException{ // 实例化序列化对象 Person per = new Person("张三", 20); File file = new File("/home/linyimin/DaSi/java/serializable.txt"); // 实例化ObjectOutputStream对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); // 序列化对象 oos.writeObject(per); // 关闭输出流 oos.close(); }}程序运行结果: 文件serializable.txtx中的内容: /AC/ED/00sr/00Person:H3/BC/B0/FC/00I/00ageL/00namet/00Ljava/lang/String;xp/00/00/00t/00张三
本程序使用ObjectOutputStream将一个已经实例化的对象序列化到文件中.
代码示例-反序列化
import java.io.File;import java.io.Serializable;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.ObjectInputStream;//定义可以被序列化的类class Person implements Serializable{ private String name; private int age; public Person(String name, int age){ this.name = name; this.age = age; } @Override public String toString(){ return "姓名:" + this.name + " 年龄:" + this.age; }}public class TestDemo{ public static void main(String [] args) throws FileNotFoundException, IOException, ClassNotFoundException{ // 实例化ObjectInputStream对象 File fio = new File("/home/linyimin/DaSi/java/serializable.txt"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fio)); // 实现反序列化 Person per = (Person) ois.readObject(); ois.close(); System.out.println(per); }}程序运行结果:
姓名:张三 年龄:20
本程序通过ObjectInputStream类将之前已经被序列化的数据反序列化为对象.
字符流和字节流十分相似,主要区别在于:
字节流是针对字节的操作,而字符流是针对字符的操作字节流在进行IO操作时,直接针对的是操作的数据终端(如文件),而字符流是针对缓存区(可以理解为内存)的操作,然后由缓冲区操作终端(如文件)下面主要介绍字符流较于字节流的区别之处:
使用Writer类进行输出的最大方便之处在于:可以直接输出字符串数据,而不像OutputStream类一样需要调用getBytes()函数将字符串转换成字节数组.
代码示例
import java.io.File;import java.io.Writer;import java.io.FileWriter;public class TestDemo{ public static void main(String [] args)throws Exception{ File file = new File("/home/linyimin/DaSi/java/out.txt"); // 如果文件不不存在,创建文件 if(!file.exists()){ file.createNewFile(); } // 实例化FileWriter对象 Writer out = new FileWriter(file); String str = "Hello World."; // 直接向文件中输出字符串 out.write(str); // 需要关闭输出流,数据才会从缓存区写到文件中 out.close(); }}程序运行结果
创建out.txt文件,并写入”Hello World."
本程序通过FileWriter完成直接将字符串写入指定文件中.
所谓转换流指的是字节流向字符流转换的操作流.由InputStreamReader和OutputStreamWriter两个类完成.
InputStreamReader继承自Reader: InputStream –> Reader(使用构造方法完成)OutputStreamWriter继承自Writer:OutputStream –> Writer(使用构造方法完成)代码示例
import java.io.File;import java.io.Writer;import java.io.OutputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.OutputStreamWriter;public class TestDemo{ public static void main(String [] args) throws IOException{ File file = new File("/home/linyimin/DaSi/java/out.txt"); // 实例化字节输出流 OutputStream out = new FileOutputStream(file); // 将字节输出流转换成字符输出流 Writer wout = new OutputStreamWriter(out); String str = "使用OutputStreamWriter完成输出流转换操作"; // 直接向文件输出字符串 try { wout.write(str); } catch (IOException e) { e.printStackTrace(); } // 关闭输出流,完成IO操作 wout.close(); }}程序运行结果
向文件out.txt中输出字符串"使用OutputStreamWriter完成输出流转换操作"
注:
新闻热点
疑难解答