1 ,IO流概述
2,输出流,输入流方法的应用
3,缓冲区的应用
4,转换流的应用
5,流操作规律
java把所有传统的流类型都放在java.io包中,用以实现输入/输出功能。
1.1,输出流和输入
->输入流:只能从中读取数据,而不能向其写入数据
->输出流:只能向其写入数据,而不能从中读取数据
java的输入流主要由InputStream和Reader作为基类,而输出流则主要由OutputStream和Writer作为基类,他们都是抽象基类,无法直接创建对象。
1.2,字节流和字符流
字节流和字符流的用法几乎一样,区别在于字节流和字符流所操作的数据单元不同。
字节流主要由InputStream和OutputStream作为基类,而字符流则主要由Reader和Writer作为基类。
字符流两个基类:融合了编码表,可以指定编码表。
2,输出流,输入流方法的应用
2.1在Reader里主要有读的3个方法。
public int read() 读取单个字符
返回:如果已到达流的末尾,则返回 -1
public int read(char[] cbuf) 将字符读入数组
返回:读取的字符数,如果已到达流的末尾,则返回 -1
public abstract int read(char[] cbuf,int off,int len) 将字符读入数组的某一部分
返回:读取的字符数,如果已到达流的末尾,则返回 -1
InputStream类里还有一个 available()方法,具体用法如下
public static void readFile_3()throws IOException { FileInputStream fis = new FileInputStream("fos.txt"); //int num = fis.available();//字节个数 byte[] buf = new byte[fis.available()];//定义一个刚刚好的缓冲区不用再循环了 fis.read(buf); System.out.println(new String(buf)); fis.close(); }
下面代码是对上述方法的简单使用:
class FileReadDemo { public static void main(String[] args) { } public static void method_1() { //创建一个文件读取流对象,和指定名称的文件相关联。 //要保证该文件是已经存在的,如果不存在,会发生异常,FileNotFoundException FileReader fr = new FileReader("demo.txt"); int ch = 0; while((ch=fr.read())!=-1)//已到末尾返回-1 { System.out.println("ch="+(char)ch); } fr.close(); } public static void method_2() { FileReader fr = new FileReader("demo.txt"); char[] buf = new char[1024];//定义一个字符数组,用于存储读到的字符 int num = 0; while((num=fr.read(buf))!=-1) { //无论buf数组里多少字符,长度都是1024 System.out.print(new String(buf,0,num));//new String(buf,0,num) String类的构造器,可将数组封装成字符串 } fr.close(); } }
注意:程序里打开的文件IO资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件IO资源。
2.2OutputStream和Writer也非常相似,两个流都提供了如下3个方法。
->void write(int c) 写入单个字符
->public void write(byte[]/char[] buf) 将字节数组 /字符数组中的数据输出到指定流中
->public void write(byte[]/char[] buf,int off,int len) 将字节数组/字符数组中从off位置开始,长度为len的字节/字符输出到输出流中。
因为字符流直接以字符作为操作单位,所以Writer可以用字符串来代替字符数组,即以String对象作为参数。Writer里还包含如下两个方法。
void write(String str);将str字符串里包含的字符输出到指定输出流中。
void write(String str,int off,int len): 将str字符串里从off位置开始,长度为len的字符输出到指定输出流中。
下面用代码来简单说明输出流的用法:
class FileWriterDemo { public static void main(String[] args) { //创建一个FileWriter对象,该对象一被初始化,就必须要明确被操作的文件 //而且该文件会被创建到指定的目录下。如果该目录下已有同名文件,将被覆盖。 //其实该步就是在明确数据要存放的目的地 FileWriter fw = new FileWriter("demo.txt"); //传递一个true参数。代表不覆盖已有的文件,并在已有文件的末尾处进行数据续写。 //FileWriter fw = new FileWriter("demo.txt",true); //调用writer方法,将字符串写入到流中。 fw.write("asdasd"); //刷新流对象中的缓冲中的数据。 //将数据刷到目的地中、 //fw.flush(); //关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据。 //将数据刷到目的地中 //和flush区别;flush刷新后,流可以继续使用,close刷新后,会将流关闭。 fw.close(); } } class FileWriterDemo2 { public static void main(String[] args) { FileWriter fw = null;//在代码块外创建引用才能在所有代码块中使用引用。 try { fw = new FileWriter("demo.txt"); fw.write("ddsffdhgjf"); } catch (IOException e) { System.out.println("catch:"+e.toString()); } finally { try { if(fw!=null)//如果没有判断,可能产生空指针异常 fw.close();//也会抛出异常 } catch (IOException e) { System.out.println(e.toString()); } } } }
输出流和输入流结合的简单应用:
将一个文本文件里的内容复制到另一个文件里。
class CopyTest { public static void main(String[] args) throws IOException { } //从才盘读一个字符就往d盘写一个字符 public static void copy_1()throws IOException { //创建目的地 FileWriter fw = new FileWriter("RuntimeDemo_copy.txt"); //与已有文件关联 FileReader fr = new FileReader("RuntimeDemo.java"); int ch = 0; while((ch=fr.read())!=-1) { fw.write(ch); } fw.close(); fr.close(); } public static void copy_2() { FileWriter fw = null; FileReader fr = null; try { fw = new FileWriter("SystemDemo_copy.txt"); fr = new FileReader("SystemDemo.java"); char[] buf = new char[1024]; int len = 0; while((len=fr.read(buf))!=-1) { fw.write(buf,0,len); } } catch (IOException e) { throw new RuntimeException("读写失败"); } finally { if(fr!=null) try { fr.close(); } catch (IOException e) { } if(fw!=null) try { fw.close(); } catch (IOException e) { } } } }
</pre><p><span style="font-size:18px">3,缓冲区的应用</span></p><p><span style="color:#993300">缓冲区的出现是为了提高流的操作效率而出现的。</span></p><span style="color:#993300">所以在创建缓冲区之前,必须要先有流对象。</span>BufferedWriter该缓冲区中提供了跨平台的换行符。<span style="color:#ff0000">newLine();</span><p>BufferedReader该缓冲区提供了更为高效率的读方法</p><p><span style="color:rgb(255,0,0)">readLine()</span></p><p><span style="color:#ff0000">注意:</span>当返回null时。表示读到文件末尾。readLine方法返回的时候只返回回车符之前的数据内容。并不返回回车符。</p><p></p><p><span style="font-size:14px; color:#ff0000">readLine的原理:</span></p><p><span style="white-space:pre"></span>无论是读一行,读取多个字符,其实最终都是在硬盘上一个一个读取。所以最终使用的还是read方法一次读一个的方法。</p><p><span style="white-space:pre"></span>在readLine方法里封装了一个存放字符的容器,当读到一行的时候,返回这个容器的字符。</p><p>代码实现如下:</p><p></p><p></p><p><span style="white-space:pre"></span></p><pre name="code" class="java">class MyBufferedReader extends Reader { private Reader r; MyBufferedReader(Reader r) { this.r = r; } // 可以一次读一行数据的方法。 public String myReadLine()throws IOException { //定义一个临时容器,原BufferReader封装的是字符数组。 //为了方便,定义一个StringBuilder容器,因为最终还是要将数据变成字符串。 StringBuilder sb = new StringBuilder(); int ch = 0; while((ch=r.read())!=-1) { if(ch==' ') continue; if(ch==' ') return sb.toString(); else sb.append((char)ch); } if(sb.length()!=0)//防止有的行没有换行符,则在缓冲区里的数据就不能被返回。 return sb.toString(); return null; }
通过缓冲区来进行文本文件的复制,可以提高效率。
class CopyTextByBuf { public static void main(String[] args) { BufferedReader bufr = null; BufferedWriter bufw = null; try { bufr = new BufferedReader(new FileReader("BufferedWriterDemo.java")); bufw = new BufferedWriter(new FileWriter("bufwriter_Copy.txt")); String line = null; while((line=bufr.readLine())!=null) { bufw.write(line); bufw.newLine(); bufw.flush(); } } catch (IOException e) { throw new RuntimeException("读写失败"); } finally { try { if(bufr!=null) bufr.close(); } catch (IOException e) { throw new RuntimeException("读取关闭失败"); } try { if(bufw!=null) bufw.close(); } catch (IOException e) { throw new RuntimeException("写入关闭失败"); } } } }
BufferedReader有一个子类为LineNumeberReader 表示跟踪行号的缓冲字符输入流,
里面定义了setLineNumber(int)和getLineNumber(),他们分别可用于设置和获取当前行号。
通过字节流缓冲区复制一段MP3
<pre name="code" class="java">class CopyMp3 { public static void main(String[] args) throws IOException { long start = System.currentTimeMillis(); copy_2(); long end = System.currentTimeMillis(); System.out.println(end-start+"毫秒"); } //通过字节流的缓冲区完成复制。 public static void copy_1()throws IOException { BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("c:\0.mp3")); BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\1.mp3")); int by = 0; while((by=bufis.read())!=-1)//一个一个往外取 { bufos.write(by); } bufos.close(); bufis.close(); } public static void copy_2()throws IOException { MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream("c:\0.mp3")); BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\2.mp3")); int by = 0; while((by=bufis.myRead())!=-1)//一个一个往外取 { bufos.write(by);//<span style="font-family: Arial, Helvetica, sans-serif;">InputStream的write是将指定字节写到输出流从缓冲区里面读,</span>只写最低8位,不然最后复制的文件大于原文件4倍 } bufos.close(); bufis.myClose(); } } //自定义字节缓冲区 class MyBufferedInputStream { private InputStream in; private byte[] buf = new byte[1024]; private int pos = 0,count = 0; MyBufferedInputStream(InputStream in) { this.in = in; } //一次读一个字节,从缓冲区(字节数组)获取、 public int myRead()throws IOException//为什么返回的不是byte,被提升。由4个字节存放 //在读到8个1时为了避免和判断结束标记-1相同,可以在保留8个1的情况下,在前面补0,那就不是-1是255.在判断的时候就不会跳出while循环 { //通过in对象读取硬盘上的数据,并存储 buf中。 if(count==0) { count = in.read(buf);//count为0的时候才读取数据到缓冲区 if(count<0) return -1; pos = 0; byte b = buf[pos]; count--; pos++; return b&255;//在前面补0,保留最低8位 } else if(count>0)//count大于0的时候,说明缓冲区还有数据,不需要读,只需要取 { byte b = buf[pos]; count--; pos++; return b&255; } return -1; } public void myClose()throws IOException { in.close(); } }
为什么是0kb?因为读的第一个字节是-1, MP3是2进制数据,读1个字节,读了8个2进制位,连续读了8个1,是-1. 1111-1111 -->提升为一个int类型,为4个字节。那还不是-1吗?是-1的原因是因为在8个1前面补的是1导致的 那么我只要在前面补0,既可以保留原字节数据不变,又可以避免-1的出现 怎么补0呢。 1111-1111& 00000000 00000000 00000000 1111-1111 --------------------------------------read方法在做提升,write在做强转
4,转换流的应用
转换流什么时候使用,字符和字节之间的
4.1读取键盘录入System.out:对应的是标准的输出设备,控制台。System.in:对应的是标准的输入设备,键盘。
class ReadIn { public static void main(String[] args) throws IOException { InputStream in = System.in; int ch = 0; StringBuilder sb = new StringBuilder();//可变长度 while(true) { int ch = in.read(); if(ch==' ') continue; if(ch==' ') { String s = sb.toString(); if("over".equals(s)) break; System.out.println(s.toUpperCase());//变成大写 sb.delete(0,sb.length());//打印完一次要清空缓冲区 } else sb.append((char)ch); } in.close(); } }
4.2转换流
以上键盘录入一行数据并打印其大写,发现其实就是一行数据的原理。也就是readLine方法
能不能直接使用readLine方法来完成键盘录入的一行数据的读取呢?
readLine方法是字符流BufferedReader类中方法。
而键盘录入的read方法是字节流InputStream的方法。
那么能不能将字节流转成字符流在使用字符流缓冲区的readLine方法呢?通过转换流可以实现
具体用法见以下代码
import java.io.*; class TransStreamDemo { public static void main(String[] args) throws IOException { //获取键盘录入对象,字节流 InputStream in = System.in; //将字节流对象转成字符流对象,使用转换流。InputStreamReader InputStreamReader isr = new InputStreamReader(in); //为了提高效率,将字符流进行缓冲技术高效操作。使用BufferedReader BufferedReader bufr = new BufferedReader(isr); //BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); //键盘录入就输以上语句, //字符流通向字节流的桥梁 OutputStream out = System.out; OutputStreamWriter osw = new OutputStreamWriter(out); BufferedWriter bufw = new BufferedWriter(osw); String line = null; while((line=bufr.readLine())!=null) { if("over".equals(line)) break; //System.out.println(line.toUpperCase()); bufw.write(line.toUpperCase()+);//要刷新,数据才能出来。 bufw.newLine();//换行。 bufw.flush(); } bufr.close(); } }
5,流操作规律
5.1通过两个明确来完成。
1,明确源和目的。
源:输入流。
目的:输出流。
2,明确操作的数据是否是纯文本。
是:用字符流。
不是:字节流
3,当体系明确后,在明确要使用哪个具体的对象。
通过设备进行区分:
源设备:内存,硬盘,键盘。
目的设备:内存,硬盘,控制台。
例子:
1,将一个文本文件中数据存储到另一个文件中,复制文件。
源:因为是源所以选择读取流,InputStream Reader
操作文本文件,所以选择Reader
设备是硬盘上的文件,所以选择FileReader
BufferedReader bufr = new BufferedReader(new FileReader("a.txt"));
目的:OutputStream Writer
操作文本文件,所以选择Writer
设备是硬盘上的文件,所以选择FileWriter
BufferedWriter bufw = new BufferedWriter(new FileWriter("b.txt"));
2,将键盘录入的数据保存到一个文件中。
源:InputStream Reader
操作纯文本 Reader
设备:键盘,对应对象时System.in
System.in对应的是字节流,为了操作键盘的文本数据方便,
转成字符流按照字符串操作是最方便的。
所以既然明确了Reader。那么将System.in转换成Reader。
用了Reader体系中转换流,InputStreamReader
InputStreamReader isr = new InputStreamReader(System.in);
加入缓冲区提高效率。
BufferedReader bufr = new BufferedReader(isr);
目的:OutputSteam Writer
操作存文本 Writer
设备:硬盘(一个文件)FileWriter
FileWriter fw = new FileWriter("w.txt");
加入缓冲区提高效率
BufferedWriter bufw = new BufferedWriter(fw);
扩展,想要把录入的数据按照指定的编码表(utf-8),将数据存到文件中
目的:OutputSteam Writer
操作存文本 Writer
设备:硬盘(一个文件)FileWriter
但是存储时,需要加入指定编码表,而指定的编码表只有转换流可以指定。
所以要使用的对象时OutputStreamWriter.
而该转换流对象要接收一个字节输出流,而且还可以操作的文件的字节输出流,FileOutputStream
OutputStreamWriter osw = new OutputStream(new FileOutputStream("d.txt"),"utf-8");
5.2改变标准输入输出设备
System.in默认的输入设备是键盘
System .out默认的输出设备是控制台
通过System类的setIn(InputStream)和setOut(OutputStream)和改变标准输入输出设备,
比如:
System.setIn(new FileInputStream("PersonDemo.java"));
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
这样标准输入设备就是一个文件。