IO流主要是用来处理设备之间的数据传输。
流按操做的数据可分为字符流和字节流。字节流用于处理二进制文件,好比音频、视频等;字符流用于处理带有中文字符的文件,好比文本文件。html
流按照数据的流向可分为输入流和输出流。输入输出是相对于内存来讲的,输入就是把某个地方(好比磁盘)的数据读到内存中,输出就是把数据从内存中写到某个地方(好比磁盘)。
java中流的体系结构以下,因为实现类实在太多,标记颜色的为经常使用的类。java
FileOutputStream
咱们经常使用来向文件中写入数据的流是FileOutputStream
,主要用于写入诸如图像数据之类的原始字节的流。git
构造函数以下:api
FileOutputStream(File file) 建立一个向指定 File 对象表示的文件中写入数据的文件输出流。 FileOutputStream(File file, boolean append) 建立一个向指定 File 对象表示的文件中写入数据的文件输出流。 FileOutputStream(FileDescriptor fdObj) 建立一个向指定文件描述符处写入数据的输出文件流,该文件描述符表示一个到文件系统中的某个实际文件的现有链接。 FileOutputStream(String name) 建立一个向具备指定名称的文件中写入数据的输出文件流。 FileOutputStream(String name, boolean append) 建立一个向具备指定 name 的文件中写入数据的输出文件流。
写入的方法有3个,具体以下:数组
void write(byte[] b) 将 b.length 个字节从指定 byte 数组写入此文件输出流中。 void write(byte[] b, int off, int len) 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。 void write(int b) 将指定字节写入此文件输出流。
举个例子:app
String filePath = "D:\\Learning\\doc\\test.txt"; // 向test.txt文件中写入数据 FileOutputStream fos=new FileOutputStream(filePath); fos.write("学习IO流".getBytes()); fos.close();
FileInputStream
和FileOutputStream对应的是FileInputStream
,用于读取诸如图像数据之类的原始字节流,文件不存在会建立。函数
构造函数以下:性能
FileInputStream(File file) 经过打开一个到实际文件的链接来建立一个 FileInputStream,该文件经过文件系统中的 File 对象 file 指定。 FileInputStream(FileDescriptor fdObj) 经过使用文件描述符 fdObj 建立一个 FileInputStream,该文件描述符表示到文件系统中某个实际文件的现有链接。 FileInputStream(String name) 经过打开一个到实际文件的链接来建立一个 FileInputStream,该文件经过文件系统中的路径名 name 指定。
读取的方法以下,read方法今后输入流中读取数据字节。若是没有输入可用,则此方法将阻塞。若是由于已经到达文件末尾而没有更多的数据,则返回 -1
。学习
int read() 今后输入流中读取一个数据字节。 int read(byte[] b) 今后输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。 int read(byte[] b, int off, int len) 今后输入流中将最多 len 个字节的数据读入一个 byte 数组中。
举个例子:测试
String filePath = "D:\\Learning\\doc\\test1.txt"; //读取test文件内容 FileInputStream fis=new FileInputStream(filePath);//方式一:一个字节一个字节的读取 int ch=0; while ((ch=fis.read())!=-1) { System.out.print((char)ch); } //方式二:批量读取到缓冲区,推荐使用方式 int len=0; byte[] buffer=new byte[1024]; while ((len=fis.read(buffer))!=-1){ System.out.print(new String(buffer,0,len)); } //方式三:一次性读取,不建议使用,若是文件过大,那么数组会很是大,可能内存不够,建立数组也会很是耗时 //fis.available()获取文件的字节数 byte[] buf = new byte[fis.available()]; fis.read(buf); System.out.println(new String(buf)); fis.close();
下面代码演示了如何拷贝文件:
//需求:把vedio拷贝到vedio1 String sourceFilePath = "D:\\Learning\\doc\\vedio.avi"; String destFilePath = "D:\\Learning\\doc\\vedio1.avi"; //建立读取文件对象 FileInputStream fis=new FileInputStream(sourceFilePath); //建立写入文件对象 FileOutputStream fos=new FileOutputStream(destFilePath); //建立1M的缓冲区 byte[] buffer =new byte[1024*1024*1]; //每次读取的长度,可能最后一次的读取长度并无buffer.length那么多 int len=0; while ((len=fis.read(buffer))!=-1){ fos.write(buffer,0,len); } fos.close(); fis.close();
固然,若是是很小的文件,也能够直接向下面这样,但对于大文件,禁止使用:
//需求:把vedio拷贝到vedio1 String sourceFilePath = "D:\\Learning\\doc\\vedio.avi"; String destFilePath = "D:\\Learning\\doc\\vedio1.avi"; //建立读取文件对象 FileInputStream fis=new FileInputStream(sourceFilePath); //建立写入文件对象 FileOutputStream fos=new FileOutputStream(destFilePath); byte[] buf = new byte[fis.available()]; fis.read(buf); fos.write(buf); fos.close(); fis.close();
上面咱们手动建立了缓冲区用来提升字节读写效率,java也提供了一样的功能,即BufferInputStream,BufferOutputStream。
BufferedInputStream 为另外一个输入流添加一些功能,即缓冲输入以及支持 mark 和 reset 方法的能力。在建立 BufferedInputStream 时,会建立一个内部缓冲区数组。
构造函数以下:
BufferedInputStream(InputStream in) 建立一个 BufferedInputStream 并保存其参数,即输入流 in,以便未来使用。 BufferedInputStream(InputStream in, int size) 建立具备指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便未来使用。
读取方法以下:
int read() 参见 InputStream 的 read 方法的常规协定。 int read(byte[] b, int off, int len) 今后字节输入流中给定偏移量处开始将各字节读取到指定的 byte 数组中。
该类实现缓冲的输出流。经过设置这种输出流,应用程序就能够将各个字节写入底层输出流中,而没必要针对每次字节写入调用底层系统。
构造方法以下:
BufferedOutputStream(OutputStream out) 建立一个新的缓冲输出流,以将数据写入指定的底层输出流。 BufferedOutputStream(OutputStream out, int size) 建立一个新的缓冲输出流,以将具备指定缓冲区大小的数据写入指定的底层输出流。
write方法以下:
void write(byte[] b, int off, int len) 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此缓冲的输出流。 void write(int b) 将指定的字节写入此缓冲的输出流。
下面来看看如何使用:
//需求:把vedio拷贝到vedio1 String sourceFilePath = "D:\\Learning\\doc\\vedio.avi"; String destFilePath = "D:\\Learning\\doc\\vedio1.avi"; //建立读取文件对象 FileInputStream fis=new FileInputStream(sourceFilePath); //建立写入文件对象 FileOutputStream fos=new FileOutputStream(destFilePath); BufferedInputStream bis=new BufferedInputStream(fis); BufferedOutputStream bos=new BufferedOutputStream(fos); int len =0; while ((len=bis.read())!=-1){ bos.write(len); } //缓冲流是在普通流的基础上多了一个缓冲区 , // 当读或写的数据达到必定量,再自动往文件里进行读写,若是没有装满缓冲区文件就已经读(写)完毕 , // 那么这个时候已经读写到缓冲区的数据须要手动使其朝目标文件进行读写. //因此使用缓冲流必定要调用flush方法 bos.flush(); bis.close(); bos.close();
固然,也能够本身再建立一个缓冲区进一步提升效率,也能够在构造函数的第二个参数传入缓冲区大小:
import java.io.*; class Test { public static void main(String[]args) throws IOException { //测试拷贝一个大小为200M时的性能问题 test1(); test2(); test3(); } public static final int BUFFER_SIZE = 1024 * 1024 * 1; /** * 方式一:不加缓冲区的状况 * * @throws IOException */ public static void test1() throws IOException { //需求:把vedio拷贝到vedio1 String sourceFilePath = "E:\\desktop.zip"; String destFilePath = "E:\\desktop1.zip"; //建立读取文件对象 FileInputStream fis = new FileInputStream(sourceFilePath); //建立写入文件对象 FileOutputStream fos = new FileOutputStream(destFilePath); BufferedInputStream bis = new BufferedInputStream(fis); BufferedOutputStream bos = new BufferedOutputStream(fos); long start = System.currentTimeMillis(); int len = 0; while ((len = bis.read()) != -1) { bos.write(len); } long end = System.currentTimeMillis(); System.out.println(end - start);//21365 bos.flush(); bis.close(); bos.close(); } /** * 方式二:加缓冲区的状况 * @throws IOException */ public static void test2() throws IOException { //需求:把vedio拷贝到vedio1 String sourceFilePath = "E:\\desktop.zip"; String destFilePath = "E:\\desktop2.zip"; //建立读取文件对象 FileInputStream fis = new FileInputStream(sourceFilePath); //建立写入文件对象 FileOutputStream fos = new FileOutputStream(destFilePath); BufferedInputStream bis = new BufferedInputStream(fis); BufferedOutputStream bos = new BufferedOutputStream(fos); long start = System.currentTimeMillis(); byte[] buffer = new byte[BUFFER_SIZE]; int len = 0; while ((len = bis.read(buffer)) != -1) { bos.write(buffer, 0, len); } long end = System.currentTimeMillis(); System.out.println(end - start);//975 bos.flush(); bis.close(); bos.close(); } /** * 方式3:构造函数加入缓冲大小的状况 * @throws IOException */ public static void test3() throws IOException { // 需求:把vedio拷贝到vedio1 String sourceFilePath = "E:\\desktop.zip"; String destFilePath = "E:\\desktop3.zip"; //建立读取文件对象 FileInputStream fis = new FileInputStream(sourceFilePath); //建立写入文件对象 FileOutputStream fos = new FileOutputStream(destFilePath); BufferedInputStream bis = new BufferedInputStream(fis, BUFFER_SIZE); BufferedOutputStream bos = new BufferedOutputStream(fos, BUFFER_SIZE); long start = System.currentTimeMillis(); int len = 0; while ((len = bis.read()) != -1) { bos.write(len); } long end = System.currentTimeMillis(); System.out.println(end - start);//3036 bos.flush(); bis.close(); bos.close(); } }
因而可知,手动加缓冲区的效率最高。
上面对于IO的异常处理是直接抛出,并未处理,下面介绍一下IO流的正确异常处理方式:
String filePath = "D:\\Learning\\doc\\test.txt"; FileOutputStream fos= null; try { fos = new FileOutputStream(filePath); fos.write("123456".getBytes()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally {
if(fos!=null){ try { //必定要记得关闭流,不然文件可能一直被占用而没法操做 fos.close(); } catch (IOException e) { e.printStackTrace(); }
} }
上面介绍了字节流,若是咱们写入的是中文,而后使用字节流读取出来,就会出现乱码。
String filePath = "D:\\Learning\\doc\\test.txt"; FileOutputStream fos= null; FileInputStream fis =null; try { fos = new FileOutputStream(filePath); fis=new FileInputStream(filePath); fos.write("学习IO流".getBytes()); int ch; while ((ch=fis.read())!=-1){ System.out.print((char)ch);//å¦ä¹ IOæµ } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { try {
//忘了判断null fis.close(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } }
为何会乱码呢?由于java中的char存的是Unicode字符,占用2个字节,而中文也是2个字节,上面的代码每次去一个字节区unicode字符集中查找,确定找到的不是对应的中文,所以乱码。下面对上面的代码改造一下,手动指定编码方式,就能够解决乱码问题。关于字符的编码问题能够参考这里。
String filePath = "D:\\Learning\\doc\\test.txt"; FileOutputStream fos= null; FileInputStream fis =null; try { fos = new FileOutputStream(filePath); fis=new FileInputStream(filePath); fos.write("学习IO流".getBytes("UTF-8")); byte [] buffer =new byte[1024]; int len; while ((len=fis.read(buffer))!=-1){ System.out.print(new String(buffer,0,len,"UTF-8")); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { try {
//忘了判断null fis.close(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } }
上面的代码相对比较繁琐,java给咱们提供了字符流,方便对中文等字符进行处理,下面来看看经常使用的FileReader和FileWriter。
FileWriter 用于写入字符流。用来写入字符文件的便捷类。使用平台(操做系统)默认字符编码。好比Windows简体中文版操做系统使用的编码为gbk。
构造函数以下:
FileWriter(File file) 根据给定的 File 对象构造一个 FileWriter 对象。 FileWriter(File file, boolean append) 根据给定的 File 对象构造一个 FileWriter 对象。 FileWriter(FileDescriptor fd) 构造与某个文件描述符相关联的 FileWriter 对象。 FileWriter(String fileName) 根据给定的文件名构造一个 FileWriter 对象。 FileWriter(String fileName, boolean append) 根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象。
写入方法以下:
void write(char[] cbuf) 写入字符数组。 void write(int c) 写入单个字符。 void write(String str) 写入字符串。 void write(char[] cbuf, int off, int len) 写入字符数组的某一部分。 void write(String str, int off, int len) 写入字符串的某一部分。
举个例子:
String filePath = "D:\\Learning\\doc\\test.txt"; FileWriter fw= null; try { fw = new FileWriter(filePath); fw.write("学习IO流"); } catch (IOException e) { e.printStackTrace(); }finally { try { //字符流若是不关闭,写入不会成功,由于字符流关闭以前会先刷新一下 //也能够手动刷新
//忘了判断null fw.flush(); fw.close(); } catch (IOException e) { e.printStackTrace(); } }
FileReader
用于读取字符流。用来读取字符文件的便捷类。
构造方法以下:
FileReader(File file) 在给定从中读取数据的 File 的状况下建立一个新 FileReader。 FileReader(FileDescriptor fd) 在给定从中读取数据的 FileDescriptor 的状况下建立一个新 FileReader。 FileReader(String fileName) 在给定从中读取数据的文件名的状况下建立一个新 FileReader。
读取方法以下:
int read() 读取单个字符。 int read(char[] cbuf) 将字符读入数组。 int read(char[] cbuf, int off, int len) 将字符读入数组的某一部分。 int read(CharBuffer target) 试图将字符读入指定的字符缓冲区。
举个例子:
String filePath = "D:\\Learning\\doc\\test.txt"; FileReader fr = null; try { fr = new FileReader(filePath); //方式一:一次读取一个字符(2个字节) // int ch = 0; // while ((ch = fr.read()) != -1) {
//此处若是不转为字符串,会查找Unicode,而字符串也是使用的平台默认编码,因此须要转成字符串才不会乱码 // System.out.print((char) ch + ""); // } //方式二:批量读取字符到缓冲区 char[] buffer =new char[1024]; int len = 0; while ((len = fr.read(buffer)) != -1) { System.out.print(new String(buffer,0,len)); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fr != null) { fr.close(); } } catch (IOException e) { e.printStackTrace(); } }
下面代码演示了如何复制文本文件:
//复制文本文件 String sourceFilePath = "D:\\Learning\\doc\\test.txt"; String destFilePath = "D:\\Learning\\doc\\test1.txt"; FileReader fr = null; FileWriter fw=null; try { fr = new FileReader(sourceFilePath); fw=new FileWriter(destFilePath); char[] buffer =new char[1024]; int len = 0; while ((len = fr.read(buffer)) != -1) { fw.write(buffer,0,len); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fr != null) { fr.close(); } if (fw != null) { fw.close(); } } catch (IOException e) { e.printStackTrace(); } }
上面咱们手动建立了缓冲区用来提升字符读写效率,java也提供了一样的功能,即BufferReader,BufferWriter。
从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。能够指定缓冲区的大小,或者可以使用默认的大小。大多数状况下,默认值就足够大了。
构造函数以下:
BufferedReader(Reader in) 建立一个使用默认大小输入缓冲区的缓冲字符输入流。 BufferedReader(Reader in, int sz) 建立一个使用指定大小输入缓冲区的缓冲字符输入流。
reader方法以下:
int read() 读取单个字符。 int read(char[] cbuf, int off, int len) 将字符读入数组的某一部分。 String readLine() 读取一个文本行。
将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。能够指定缓冲区的大小,或者接受默认的大小。在大多数状况下,默认值就足够大了。
构造方法以下:
BufferedWriter(Writer out) 建立一个使用默认大小输出缓冲区的缓冲字符输出流。 BufferedWriter(Writer out, int sz) 建立一个使用给定大小输出缓冲区的新缓冲字符输出流。
writer方法以下:
void newLine() 写入一个行分隔符。 void write(char[] cbuf, int off, int len) 写入字符数组的某一部分。 void write(int c) 写入单个字符。 void write(String s, int off, int len) 写入字符串的某一部分。
接下来看看如何使用:
//复制文本文件 String sourceFilePath = "D:\\Learning\\doc\\git简单操做.txt"; String destFilePath = "D:\\Learning\\doc\\test1.txt"; BufferedReader br = null; BufferedWriter bw = null; try { FileReader fr = new FileReader(sourceFilePath); FileWriter fw = new FileWriter(destFilePath); br = new BufferedReader(fr); bw = new BufferedWriter(fw); String line = null;//一行一行的读取和写入 while ((line = br.readLine()) != null) { bw.write(line); bw.newLine(); } //使用缓冲对象必定要flush bw.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (br != null) { br.close(); } if (bw != null) { bw.close(); } } catch (IOException e) { e.printStackTrace(); } }
跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。
默认状况下,行编号从 0 开始。该行号随数据读取在每一个行结束符处递增,而且能够经过调用 setLineNumber(int) 更改行号。
但要注意的是,setLineNumber(int) 不会实际更改流中的当前位置;它只更改将由 getLineNumber() 返回的值。
下面看看简单的使用方式:
String sourceFilePath = "D:\\Learning\\doc\\git简单操做.txt"; FileReader fr = null; LineNumberReader lnr =null; try { fr = new FileReader(sourceFilePath); lnr = new LineNumberReader(fr); String line = null; lnr.setLineNumber(100);//设置起始行号为100 while((line=lnr.readLine())!=null){ System.out.println(lnr.getLineNumber()+":"+line); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { if(lnr!=null){ try { lnr.close(); } catch (IOException e) { e.printStackTrace(); } } }
字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集能够由名称指定或显式给定,或者能够接受平台默认的字符集。
为了达到最高效率,可要考虑在 BufferedReader 内包装 InputStreamReader。例如:
BufferedReader in= new BufferedReader(new InputStreamReader(System.in));
构造方法以下:
InputStreamReader(InputStream in) 建立一个使用默认字符集的 InputStreamReader。 InputStreamReader(InputStream in, Charset cs) 建立使用给定字符集的 InputStreamReader。 InputStreamReader(InputStream in, CharsetDecoder dec) 建立使用给定字符集解码器的 InputStreamReader。 InputStreamReader(InputStream in, String charsetName) 建立使用指定字符集的 InputStreamReader。
读取方法以下:
int read() 读取单个字符。 int read(char[] cbuf, int offset, int length) 将字符读入数组中的某一部分。
举个例子,好比获取键盘输入的内容,并打印在控制台上,要求不能用Scanner对象:
//需求:把键盘输入的内容打印在控制台 InputStreamReader isr = new InputStreamReader(System.in); //方式一:直接读取 // int ch = 0; // while ((ch=isr.read())!=-1){ // System.out.print((char) ch+""); // } //方式二:读到缓冲区 int len = 0; char [] buffer =new char[1024]; while ((len=isr.read(buffer))!=-1){ System.out.print(new String(buffer,0,len)); } //方式三:使用缓冲流提供的便捷方法 BufferedReader br =new BufferedReader(isr) ; String line=null; while((line=br.readLine())!=null){ System.out.print(line); } }
是字符流通向字节流的桥梁:可以使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集能够由名称指定或显式给定,不然将接受平台默认的字符集。
为了得到最高效率,可考虑将 OutputStreamWriter 包装到 BufferedWriter 中,以免频繁调用转换器。例如:
Writer out = new BufferedWriter(new OutputStreamWriter(System.out));
构造方法以下:
OutputStreamWriter(OutputStream out) 建立使用默认字符编码的 OutputStreamWriter。 OutputStreamWriter(OutputStream out, Charset cs) 建立使用给定字符集的 OutputStreamWriter。 OutputStreamWriter(OutputStream out, CharsetEncoder enc) 建立使用给定字符集编码器的 OutputStreamWriter。 OutputStreamWriter(OutputStream out, String charsetName) 建立使用指定字符集的 OutputStreamWriter。
写入方法以下:
void write(char[] cbuf, int off, int len) 写入字符数组的某一部分。 void write(int c) 写入单个字符。 void write(String str, int off, int len) 写入字符串的某一部分。
举个例子,好比把文本文件的内容打印在控制台上,要求不能用print方法:
OutputStreamWriter osw=new OutputStreamWriter(System.out); String line="学习IO流"; //方式一:直接写入 // osw.write(line); // osw.close(); //方式二:使用缓冲流高效写入 BufferedWriter bw=new BufferedWriter(osw); bw.write(line); bw.close();
再来个例子,将键盘录入的数据写入到一个文件中:
String filePath="E:\\test.txt"; //需求:将键盘录入的数据写入到一个文件中。 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath))); String line = null; while((line=bufr.readLine())!=null){ if(":wq".equals(line)) break; bufw.write(line.toUpperCase()); bufw.newLine(); bufw.flush(); }
上面的例子举得有点牵强,正在的用处在于上面这两个对象能够手动指定编码,好比读取文本文件,必须指定utf-8来编码。用FileReader是作不到的。
InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\test.txt"),"utf-8");
参考文档:http://tool.oschina.net/apidocs/apidoc?api=jdk-zh
参考代码:毕老师35天