Java基础——IO流(二)
来源:互联网 发布:慢性牙周炎 知乎 编辑:程序博客网 时间:2024/06/05 19:19
字符流的缓冲区
缓冲区的出现是为了提高流的操作效率而出现的。
所以在创建缓冲区之前,必须要先有流对象。
BufferedWriter
该缓冲区中提供了一个跨平台的换行符。newLine
//字符写入流缓冲区的基本方法演示import java.io.*;public class BufferedWriterDemo { //为了演示方便,这里先不进行异常的处理 public static void main(String[] args) throws IOException { //先创建一个写入流对象 FileWriter fw = new FileWriter("bufw.txt"); //创建一个写入流缓冲区对象,只要将流对象传进来即可 BufferedWriter bufw = new BufferedWriter(fw); //那么开始在缓冲区中写入数据: for (int x = 0;x<5 ;x++ ) { bufw.write("abcde"+x); bufw.flush();//记住:只要用到缓冲区就记得刷新 bufw.newLine();//该方法是缓冲区中特有的换行的方法,适用于各种平台(系统) } bufw.close();//这里缓冲区对象其实引用的是流对象,所以这里只要关闭了缓冲区对象的资源就可以了 }}
运行结果:
BufferedReader
该缓冲区提供了一个可以读取整行的方法readLine,方便与对文本数据的获取。
当返回 为空时,表示已经读到流的末尾。
返回的是回车符之前的数据,不包含回车符。
//读取流缓冲区的基本演示import java.io.*;public class BufferedReaderDemo { //为了演示方便,先不进行异常的处理 public static void main(String[] args) throws IOException { //先创建一个读取流对象,和文件关联 FileReader fr = new FileReader("bufw.txt"); //创建一个字符串缓冲区对象,将流对象作为参数传递给缓冲区对象的构造函数 BufferedReader bufr = new BufferedReader(fr); //开始读取文件中的数据 //缓冲区对象的readLine方法可以直接获取整行的数据, //返回包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null //这就限定了可以用while循环的条件 String line=null; while ((line = (bufr.readLine()))!=null) { System.out.println(line); } }}
运行结果为:
练习1:用缓冲区对象拷贝一个文本文件。
//用缓冲区对象拷贝一个文本文件import java.io.*;public class CopyTextByBuf { public static void main(String[] args) { //先创建一个流对象的引用 BufferedWriter bufw = null; BufferedReader bufr = null; try { bufw = new BufferedWriter(new FileWriter("textByCopy.txt")); bufr = new BufferedReader(new FileReader("BufferedWriterDemo.java")); //用readLine方法读取数据 String line = null; while ((line = bufr.readLine())!=null) { bufw.write(line); bufw.newLine();//readLine方法只获取数据,没有换行符,所以要自己添加换行符 bufw.flush();//每次存完记得刷新,以免出现意外情况倒置数据的丢失 } } catch (IOException e) { throw new RuntimeException("读取流对象创建失败"); } //关闭资源 finally { if (bufw!=null) { try { bufw.close(); } catch (IOException e) { throw new RuntimeException("写入流对象关闭失败"); } } if (bufr!=null) { try { bufr.close(); } catch (IOException e) { throw new RuntimeException("读取流对象关闭失败"); } } } }}
运行结果为:
readLine方法的原理
无论是读一行,获取多个字符,其实最终都是在硬盘上一个一个的读取,所以最终使用的还是read方法一次读一个的方法。
所以根据readLine方法的原理,我们来写一个自己的BufferedReader,实现和BufferedReader同样的方法
//写一个自己的BufferedReader,要求和BufferedReader的方法功能一样import java.io.*;class MyBufferedReader{ //因为BufferedReader的构造方法有流对象作为参数,这里也可以将参数传进来 private FileReader r; MyBufferedReader(FileReader r) { this.r = r; } public String myReadLine() throws IOException { //因为BufferedReader底层用的是数组来存储读到的字符,当读到回车符的时候,将数组中的字符转成字符串返回去 //这里为了演示方便就用StringBuilder来代替,效果一样的, //原理就是,read方法读一个字符,将该字符存储到StringBuilder中,当读到回车符的时候,就将StringBuilder中的字符串返回去。 StringBuilder sb = new StringBuilder(); int ch = 0; while ((ch = r.read())!=-1) { char c = (char)ch; //将返回的数字转换成字符 if(c=='\r') //判断是\r的话,继续循环 continue; if(c=='\n') //判断是\n的话,遇到了回车符,该行结束,将sb中的字符串返回去 return sb.toString(); else //如果没有遇到回车符号,就把读到的字符存储到字符串缓冲区中, sb.append(c); } if(sb.length()!=0) return sb.toString(); //当某一行没有回车符的时候,这一行会读到,并且存进sb中,但是读不到回车符, //不能将sb中剩下的东西返回去,所以这里要进行一次判断, //如果read方法运行完,sb中还有东西,就返回来。 return null; //如果read方法返回-1,说明read方法读不到数据了,退出while循环,按照 //BufferedReader中的规则,这里应该返回null, } //同样的BufferedReader中还有关闭资源的方法 public void myClose() throws IOException { r.close(); }}public class MyBufferedReaderDemo { //这里为了演示方便先不对异常进行处理 public static void main(String[] args) throws IOException { //创建流对象 FileReader fr = new FileReader("bufw.txt"); //创建缓冲区对象 MyBufferedReader mybuf = new MyBufferedReader(fr); //开始读取数据 String line = null; while ((line = mybuf.myReadLine())!=null) { System.out.println(line); } //关闭资源 mybuf.myClose(); }}
运行结果为:
我们把这种像BufferedReader中readLine方法这样的基于read方法的加强功能的方法的设计模式称为装饰设计模式。
装饰设计模式:
当想要对已有的对象进行功能增强时,
可以定义类,将已有的对象传入,基于已有的功能,并提供加强功能
那么自定义的该类称为装饰类。
装饰类通常会通过构造方法接收被装饰类的对象
并基于被装饰的对象的功能,提供更强的功能。
写一个装饰类的例子:
//装饰类的演示//古时候的人们生活条件差,吃饭仅仅是吃饭而已class Person{ public void chifan() { System.out.println("吃饭"); }}//改革开放以来,人们的生活水平好了,吃饭功能也增强了class SuperPerson{ private Person p; SuperPerson(Person p) { this.p = p; } public void superChifan() { System.out.println("开胃酒"); p.chifan(); System.out.println("甜点"); System.out.println("来一根"); }}public class SuperPersonDemo { public static void main(String[] args) { Person p = new Person(); SuperPerson sp = new SuperPerson(p); sp.superChifan(); }}
运行结果:
在以上的示例中,SuperPerson类对Person类的chifan方法的功能增强就是装饰设计模式了。
可是在以上程序中,使用装饰设计模式实现的功能用子类继承父类,然后复写父类的方法也可以实现相同的功能的,那么,装饰设计模式和继承的区别在哪里呢?
举个例子来说
我有自己的读取的一个类MyReader
同时有具体到读取文件类型的类比如:MyTextReader,MyMediaReader
而为了提高读取文件的功能,提供了提高效率的类,MyBufferedTextReder,MyBufferedMediaReader
那么类与类继承的体系应该是这样的
- MyReader
- MyTextReader
- MyBufferedTextReader
- MyMediaReader
- MyBufferedMediaReader
- 如果来了新的读取文件类型,这里还要改代码,不提倡使用这种方法,
- 而且每一个类都有自己的增强类,每次都这样写很麻烦,而且不利于程序的扩展性。
- MyTextReader
于是,我们想到了定义一个类,直接将对象传进来,在类中定义增强读取的方法。
class MyBufferedReader{ MyBufferedReader(MyTextReader mtr) { } MyBufferedReader(MyMediaReader mdr) { } .....}
还是不爽,为什么呢?因为每有一个对象创建,都要有一个新的构造函数建立,如果来了新的数据类型,还是要修改代码,于是我们想到了多态,提高其扩展性,只要在构造函数中传入父类对象。
class MyBufferedReader{ MyBufferedReader(MyReader mr) { }}
这样,不用每一个数据类型,都写一个构造函数,而且新来的数据类型,只要是MyReader的子类就可以直接传进来对象使用增强方法。
于是体系结构就变成了这样子:
- MyReader
- MyTextReader
- MyMediaReader
- MyBufferedReader
装饰类避免了继承体系的臃肿模式,降低了类与类之间的关系。
装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能。
所以装饰类和被装饰类通常都是 属于一个体系的。
于是我们就可以自己定义包装类了。
还是上边自定义readLine方法的例子
//自定义的装饰类,要求在Read的体系中import java.io.*;class MyBufferedReader extends Reader//在人家的体系中当然是先继承人家了{ //因为BufferedReader的构造方法有流对象作为参数,这里也可以将参数传进来 private Reader r; MyBufferedReader(Reader r) { this.r = r; } public String myReadLine() throws IOException { StringBuilder sb = new StringBuilder(); int ch = 0; while ((ch = r.read())!=-1) { char c = (char)ch; if(c=='\r') continue; if(c=='\n') return sb.toString(); else sb.append(c); } if(sb.length()!=0) return sb.toString(); return null; } //因为Read类中有抽象方法read和close,所以在这里要进行覆盖 public void close() throws IOException { r.close(); } public int read(char[] cbuf, int off, int len) throws IOException { return r.read(cbuf,off,len); } public void myClose() throws IOException { r.close(); }}public class MyBufferedReaderDemo { public static void main(String[] args) throws IOException { //创建流对象 FileReader fr = new FileReader("bufw.txt"); //创建缓冲区对象 MyBufferedReader mybuf = new MyBufferedReader(fr); //开始读取数据 String line = null; while ((line = mybuf.myReadLine())!=null) { System.out.println(line); } //关闭资源 mybuf.myClose(); }}
LineNumberReader
获取行号的缓冲区,继承自BufferedReader
那么就来演示一下
//获取行号的演示import java.io.*;public class LineNumberReaderDemo { public static void main(String[] args) { FileReader fr =null; try { fr = new FileReader("SuperPersonDemo.java"); //创建一个读取行号的对象, LineNumberReader lnr = new LineNumberReader(fr); //也有整行读取的方法 String line =null; while ((line = lnr.readLine())!=null) { //先写行号再写内容 System.out.println(lnr.getLineNumber()+" "+line); } } catch (IOException e) { throw new RuntimeException("读取失败"); } //关闭资源 finally { try { if(fr!=null) fr.close(); } catch (IOException e) { throw new RuntimeException("关闭资源失败"); } } }}
运行结果:
练习:定义一个自己的获取行号的方法。
//写一个自己的获取行号的对象import java.io.*;class MyLineNumberReader{ private FileReader fr; MyLineNumberReader(FileReader fr) { this.fr = fr; } private int count = 0;//这里定义一个行号,起始值为0 public String myReadLine() throws IOException { StringBuilder sb = new StringBuilder(); int num = 0; while ((num = fr.read())!=-1) { char ch = (char)num; if(ch=='\r') continue; if(ch=='\n') { //当读到回车符的时候,count自增一次 count++; return sb.toString(); } else sb.append(ch); } if((sb.length())!=0) return sb.toString(); return null; } public void mySetLineNumber(int count)//设置count的值 { this.count = count; } public int myGetLineNumber()//获取count值 { return count; }}public class MyLineNumberReaderDemo { public static void main(String[] args) { FileReader fr =null; try { fr = new FileReader("SuperPersonDemo.java"); //创建一个读取行号的对象, MyLineNumberReader mylnr = new MyLineNumberReader(fr); //也有整行读取的方法 String line =null; while ((line = mylnr.myReadLine())!=null) { //先写行号再写内容 System.out.println(mylnr.myGetLineNumber()+" "+line); } } catch (IOException e) { throw new RuntimeException("读取失败"); } //关闭资源 finally { try { if(fr!=null) fr.close(); } catch (IOException e) { throw new RuntimeException("关闭资源失败"); } } }}
运行结果:
发现,这里的代码和MyBufferedReader里边的代码只有几点不一样的地方,于是,我们可以让该获取行号的类继承MyBufferedReader类,将获取行号的类的代码进行优化。
class MyLineNumberReader extends MyBufferedReader{ MyLineNumberReader(FileReader fr) { super(fr); } private int count = 0;//这里定义一个行号,起始值为0 public String myReadLine() throws IOException { count++; return super.myReadLine(); } public void mySetLineNumber(int count)//设置count的值 { this.count = count; } public int myGetLineNumber()//获取count值 { return count; }}
其主要原理也就是在某些地方调用了父类(MyBufferedReader)的方法
字节流
InputStream读取字节流
OutputStream写入字节流
API中发现,和字符流中的方法差不多。。
直接演示
//字节流写入方法演示import java.io.*;public class FileStream { //为了演示方便,先不进行异常的处理 public static void main(String[] args) 2 IOException { writeFile(); } public static void writeFile() throws IOException { //创建一个字节流对象,并且和文件关联 FileOutputStream fos = new FileOutputStream("fos.txt"); //字节流,当然是要写入字节了。 fos.write("abcdef".getBytes()); }}
运行结果:
发现没有刷新动作,也没有关流的动作,但是文件确确实实写进去了。这是为什么呢?
这是因为,字符流底层用的也是字节的缓冲区,
因为编码表中一个字符对应的两个字节,所以在字符流中,当读取到一个字节的时候,会将字节都放在一个缓冲区中,
然后再去查表,找到字节相应的字符进行操作,
而在字节流中,底层数据类型是字节,不涉及到查表,所以可以实现读一个写一个的动作,
那么写完文件了,,我们试试读取文件
//字节流写入方法演示import java.io.*;public class FileStream { //为了演示方便,先不进行异常的处理 public static void main(String[] args) throws IOException { readText_1(); } //读取文件 public static void readText_1()throws IOException { //创建字节流读取对象,并与文件关关联 FileInputStream fis = new FileInputStream("fos.txt"); //read方法一样会返回int类型的值 int num = 0; while((num = fis.read())!=-1) { System.out.println((char)num); } fis.close(); }}
运行结果为:
但是这种方法一个字节一个字节的读取,效率超慢的,所以我们用数组缓冲区来提高效率。
//字节流写入方法演示import java.io.*;public class FileStream { //为了演示方便,先不进行异常的处理 public static void main(String[] args) throws IOException { readText_2(); } //读取文件 public static void readText_2()throws IOException { //创建字节流读取对象,并与文件关关联 FileInputStream fis = new FileInputStream("fos.txt"); int len = 0; //和字符流一样的方法过程,只是注意数据类型变成了字节 byte[] buf = new byte[1024]; while ((len = fis.read(buf))!=-1) { System.out.println(new String(buf,0,len)); } fis.close(); }}
运行结果:
这种方法效率稍高,
发现InputStream类中有一个available方法,返回int类型的值,
是流对象获取到的所有的字节数(包括\r\n这样的字符)
这样的话,我们就可以直接定义数组缓冲区的大小了。就不用再while循环了。
//字节流写入方法演示import java.io.*;public class FileStream { //为了演示方便,先不进行异常的处理 public static void main(String[] args) throws IOException { readText_3(); } //读取文件 public static void readText_3()throws IOException { //创建字节流读取对象,并与文件关关联 FileInputStream fis = new FileInputStream("fos.txt"); //和字符流一样的方法过程,只是注意数据类型变成了字节 byte[] buf = new byte[fis.available()];//这里定义一个和文件字符数量一样大小的数组。 //将读取到数据存到数组中 fis.read(buf); //将数组直接转换成字符串打印出来 System.out.println(new String(buf)); fis.close(); }}
运行结果:
这种方法只适用于操作比较小的数据,因为jvm分配的内存一共64M,万一文件过大了,容易造成内存溢出。所以在操作比较大的数据的时候还是以创建一个1024字节的数组来循环比较靠谱。
练习:拷贝一个图片:
//用字节流复制一张图片import java.io.*;public class CopyPic { public static void main(String[] args) { //在最外边创建两个引用 FileInputStream fis = null; FileOutputStream fos = null; try { //开始创建读取流和写入流,并和文件关联 fis = new FileInputStream("11.jpg"); fos = new FileOutputStream("22.jpg"); //开始读写操作 int len = 0; byte[] buf = new byte[1024]; while ((len = fis.read(buf))!=-1) { fos.write(buf,0,len); } } catch (IOException e) { throw new RuntimeException("读取失败"); } //关闭资源 finally { if (fis!=null) { try { fis.close(); } catch (IOException e) { throw new RuntimeException("读取流关闭失败"); } } if (fos!=null) { try { fos.close(); } catch (IOException e) { throw new RuntimeException("写入流关闭失败"); } } } }}
运行结果:
练习二:拷贝一个MP3文件,用缓冲区
//拷贝MP3用流缓冲区import java.io.*;public class CopyMp3 { public static void main(String[] args) { //在最外边建立一个引用 BufferedInputStream bufis = null; BufferedOutputStream bufos = null; try { //创建流对象 FileInputStream fis = new FileInputStream("1.mp3"); FileOutputStream fos = new FileOutputStream("2.mp3"); bufis = new BufferedInputStream(fis); bufos = new BufferedOutputStream(fos); //读取返回的字节 int by = 0; while ((by = bufis.read())!=-1) { bufos.write(by);//写入字节 } } catch (IOException e) { throw new RuntimeException("读取失败"); } //关闭资源 finally { if(bufis!=null) { try { bufis.close(); } catch (IOException e) { throw new RuntimeException("读取流关闭失败"); } } if(bufos!=null) { try { bufos.close(); } catch (IOException e) { throw new RuntimeException("写入流关闭失败"); } } } }}
实验结果:
其实字节流缓冲区中也是有一个数组的,加上缓冲区是为了增强read方法功能。
缓冲区读取数据时过程是这样的。
调用FileInputStream的read方法,从硬盘上读取1024个字节的数据,放在定义的长度为1024的数组中
然后再用BufferedInputStream中的read方法从缓冲区中获取数组中的元素。然后写入到硬盘上来。
具体过程图例:
那么了解了字节流缓冲区的原理呢,我们就自己定义一个字节流缓冲区。
//子定义一个字节流缓冲区import java.io.*;class MyBufferedInputStream{ private InputStream is; //因为底层是用的数组,所以这里需要定义指针,和计数器 //思路是: //先拿一堆数据过来,myRead方法通过指针获取数组中的值,然后count用来计算数组中的数据是否全部取出 //如果count为0了,再去取数据,如果不为0,再从缓冲区中取数据 private int pos = 0; private int count = 0; MyBufferedInputStream(InputStream is) { this.is = is; } //read方法一次只取一个字节 public int myRead() throws IOException { byte[] buf = new byte[1024]; //如果count为0,抓一把数据进来,读第一位数,返回去 if (count==0) { count = is.read(buf);//这个时候count的值就不是0了,如果读到末尾,发现count的值返回为-1 了, pos = 0; //说明数据已经读取完毕了,直接返回-1,读到末尾 if (count==-1) { return -1; } byte b = buf[pos]; pos++; count--; return b & 255;//把数组中的元素返回去,注意:这里返回去的是byte类型的,但是上边函数上声明的是int类型的 //所以会进行一次自动提升类型操作 //read方法一次读取一个字节,如果MP3文件的前8位都是1呢?那么myRead方法返回的数直接就是-1了 //这样的话在下边调用myRead方法的时候,遇到这样的情况,就会造成一种数据已经读完的假象 //造成数据复制的损失,怎么解决呢? //假如遇到了全是1的情况,b的值为-1,二进制类型为1111-1111 //这里return b会进行一次,提升,提升为int类型的,完了之后b的值还是-1,其二进制形式为 //11111111-11111111-11111111-11111111 //但是我们可以在int类型的前24位补0,只留下最后的8位,这样就可以解决这种情况了 //只获取最后的八位,只要用这个数和255进行与运算就可以了。原理是: // 11111111-11111111-11111111-11111111 -1 //&00000000-00000000-00000000-11111111 255 //=00000000-00000000-00000000-11111111 255 //所以这里进行的一次之获取后八位的运算方法就是&255 } else if(count>0)//第二次进来的时候count就不为0了,所以不用抓数据,直接取就可以了 { byte b = buf[pos]; pos++; count--; return b &255;//把数组中的元素返回去, } return -1; } public void myClose()throws IOException { is.close(); }}class MyBufferedInputStreamDemo{ public static void main(String[] args) { //在最外边建立一个引用 MyBufferedInputStream bufis = null; BufferedOutputStream bufos = null; try { //创建流对象 FileInputStream fis = new FileInputStream("1.mp3"); FileOutputStream fos = new FileOutputStream("2.mp3"); bufis = new MyBufferedInputStream(fis); bufos = new BufferedOutputStream(fos); //读取返回的字节 int by = 0; while ((by = bufis.myRead())!=-1) { bufos.write(by);//写入字节 } } catch (IOException e) { throw new RuntimeException("读取失败"); } //关闭资源 finally { if(bufis!=null) { try { bufis.myClose(); } catch (IOException e) { throw new RuntimeException("读取流关闭失败"); } } if(bufos!=null) { try { bufos.close(); } catch (IOException e) { throw new RuntimeException("写入流关闭失败"); } } } }}
运行结果:
获取键盘录入
读取键盘上录入的数据实际上也是读取流在操作:
简单演示一下获取键盘录入的例子
//读取键盘录入import java.io.*;public class SystemInDemo { public static void main(String[] args) throws IOException { //获取一个读取流对象,与键盘录入相关联 InputStream is = System.in; int ch = 0; while((ch = is.read())!=-1) { System.out.println(ch);//打印所有读到的内容 } }}
运行结果为:
发现,我只输入了一个a,却打印了三个值,
这是因为我还敲了一次回车,虚拟机把回车键的ASCII值也打印出来了。
那么需求变了,我要让键盘上敲的字母打印出来全部大写的,而且一直敲到over,程序就结束。
//读取键盘录入import java.io.*;public class SystemInDemo { public static void main(String[] args) { //获取一个读取流对象,与键盘录入相关联 InputStream is = null; try { is = System.in; int num = 0; //穿件一个字符串缓冲区,将读取到的字符先存到缓冲区中, StringBuilder sb = new StringBuilder(); while((num = is.read())!=-1) { char ch = (char)num; if(ch=='\r') continue; if(ch=='\n') { //碰到回车符,先进行判断是否为over,如果不是的话打印字符串,如果是的话,跳出循环 String s = sb.toString(); if(s.equals("over")) break; System.out.println(s.toUpperCase()); //每打印完一次,sb就要清空一次,要不然前边的东西会一直占着 sb.delete(0,sb.length()); } else sb.append(ch); } } catch (IOException e) { throw new RuntimeException("流创建失败"); } }}
运行结果:
转换流对象
发现上边的有一部分代码和在自定义readLine方法的时候写的代码有点相似。
那么就想到,用键盘录入的时候,能不能直接用readLine方法,直接读取一整行呢?
可是,readLine方法是在BufferedReader中的,属于字符流对象的,
而键盘录入对象是属于字节流的,在InputStream中的。
InputStreamReader:将字节流转换为字符流对象,可以使用readLine方法。
代码实例:
//转换流对象演示import java.io.*;public class TransStreamDemo { //为了演示方便,先不进行异常的处理 public static void main(String[] args) throws IOException { //获取一个键盘录入对象 InputStream in = System.in; //将字节流装换为字符流对象 InputStreamReader isr = new InputStreamReader(in); //为了提高效率,用到字节流缓冲区对象 BufferedReader bufr = new BufferedReader(isr); String line = null; while ((line = bufr.readLine())!=null) { if("over".equals(line)) break; System.out.println(line.toUpperCase()); } bufr.close(); }}
运行结果为:
OutputStreamWriter
将字符流转换为字节流
比如,在上边的例子中,键盘录入的是字符,但是存在硬盘的时候用的就是字节,所以需要将字符流转换为字节流。
示例:
//转换流对象演示import java.io.*;public class TransStreamDemo { //为了演示方便,先不进行异常的处理 public static void main(String[] args) throws IOException { //获取一个键盘录入对象 InputStream in = System.in; //将字节流装换为字符流对象 InputStreamReader isr = new InputStreamReader(in); //为了提高效率,用到字节流缓冲区对象,这里边才有readLine方法 BufferedReader bufr = new BufferedReader(isr); OutputStream out = System.out; OutputStreamWriter osw = new OutputStreamWriter(out); BufferedWriter bufw = new BufferedWriter(osw);//一样的,这里边才有通用的换行newLine方法 String line = null; while ((line = bufr.readLine())!=null) { if("over".equals(line)) break; bufw.write(line); bufw.newLine();//在录入数据的时候不带的回车符,所以需要自己添加 bufw.flush();//每次写完要记得刷新一下,要不然出不来数据 } bufr.close(); bufw.close(); }}
运行结果
流操作的基本规律
通过两个明确来完成
- 明确源和目的
- 源:输入流。InputStream ;Reader
- 目的:输出流:OutputStream Writer
- 操作的数据是否是存文本
- 是:字符流
- 不是:字节流
- 当体系明确后,再明确要使用哪个具体的对象
- 通过设备来进行区分
- 源设备:内存,硬盘,键盘
- 目的设备:内存,硬盘,控制台。
- 通过设备来进行区分
练习:将一个图片文件存储到另一个文件中,复制文件,要求按照以上格式完成三个明确
//复制图片,要求完成三个明确import java.io.*;public class CopyPicTest { public static void main(String[] args) { /* 源:输入流:Reader,InputStream 目的:输出流:Writer,OutputStream 操作的数据是否是纯文本? 不是:InputStream,和OutputStream 设备: 源:硬盘中的文件 FileInputStream 母的,硬盘中的文件 FileOutputStream 需要提高效率吗?是的,BufferedInputStream,BufferedOutputStream */ //开始按照分析思路创建对象 BufferedInputStream bufis =null; BufferedOutputStream bufos =null; try { //开始读写操作 bufis = new BufferedInputStream(new FileInputStream("11.jpg")); bufos = new BufferedOutputStream(new FileOutputStream("22.jpg")); int by = 0; while ((by = bufis.read())!=-1) { bufos.write(by); } } catch (IOException e) { throw new RuntimeException("读取写入失败"); } //关闭资源 finally { if(bufos!=null) { try { bufos.close(); } catch (IOException e) { throw new RuntimeException("写入流关闭失败"); } } if(bufis!=null) { try { bufis.close(); } catch (IOException e) { throw new RuntimeException("读取流关闭失败"); } } } }}
练习二:将键盘录入的数据保存到一个文件中
//将键盘录入的数据保存到一个文件中/*源:InputStream Reader是不是纯文本?是!Reader设备:键盘。对应的对象是System.in不是选择Reader吗?System.in对应的不是字节流吗?为了操作键盘的文字数据方便,转换成字符流按照字符串操作是最方便的。所以既然明确了Reader那么就将System.in转换为Reader用了Reader中的转换流,InputStreamReader需要提高效率吗?需要,BufferedReader目的:OutputStream Writer是否是存文本。是!Writer设备:硬盘,一个文件,使用FileWriter需要提高效率吗?需要BufferedWriter bufw = new BufferedWriter()*/import java.io.*;public class Test2 { public static void main(String[] args) { BufferedReader bufr = null; BufferedWriter bufw = null; try { bufr = new BufferedReader(new InputStreamReader(System.in)); bufw = new BufferedWriter(new FileWriter("systemin.txt")); String line = null; while ((line = bufr.readLine())!=null) { if("over".equals(line)) break; bufw.write(line); bufw.newLine(); bufw.flush(); } } catch (IOException e) { throw new RuntimeException("读写发生异常啦"); } finally { if (bufr!=null) { try { bufr.close(); } catch (IOException e) { throw new RuntimeException("读取流发生异常"); } } if (bufw!=null) { try { bufw.close(); } catch (IOException e) { throw new RuntimeException("写入流发生异常"); } } } }}
扩展一下:
想要把录入的数据按照指定的编码表(UTF-8),将数据存到文件中。
目的:OutputStream,Writer
是否是存文本?是,Writer
设备,硬盘,一个文件,使用FileWriter
但是FileWriter是使用默认的编码表GBK
但是存储时,需要加入指定 编码表utf-8,而指定的编码表只有转换流可以指定
所以要使用的对象是OutputStreamWriter
而该转换流对象要接收一个字节输出流,而且还可以操作的文件的字节输出流FileOutputStream
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(“d.txt”),”utf-8”);
需要高效吗?需要
BufferedWriter bufw = nwe BufferedWriter(osw);
所以记住,转换流什么时候使用,字符和字节之间的桥梁,通常,涉及到字符编码转换时,需要用到转换流
指定编码表的键盘录入写入文件的程序:
//将键盘录入的数据保存到一个文件中,指定utf-8码表import java.io.*;public class Test2 { public static void main(String[] args) { BufferedReader bufr = null; BufferedWriter bufw = null; try { bufr = new BufferedReader(new InputStreamReader(System.in)); //指定编码表,只能用转换流指定。 bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("d2.txt"),"utf-8")); String line = null; while ((line = bufr.readLine())!=null) { if("over".equals(line)) break; bufw.write(line); bufw.newLine(); bufw.flush(); } } catch (IOException e) { throw new RuntimeException("读写发生异常啦"); } finally { if (bufr!=null) { try { bufr.close(); } catch (IOException e) { throw new RuntimeException("读取流发生异常"); } } if (bufw!=null) { try { bufw.close(); } catch (IOException e) { throw new RuntimeException("写入流发生异常"); } } } }}
运行结果,指定GBK和utf-8的写入同样的字,你好,两个文件的占内存大小不一样。
改变标准输入输出设备
System.setIn(InputStream is)//将标准输入流从键盘录入改为一个字节读取流
System.setOut(PrintStream ps);//将标准的输出流从控制台改为一个打印流(参数为一个文件名称)
演示一下:
//转换流对象演示import java.io.*;public class TransStreamDemo2 { //为了演示方便,先不进行异常的处理 public static void main(String[] args) throws IOException { //把默认的输入流改变为文件读取流 System.setIn(new FileInputStream("CopyPic.java")); //获取一个标准录入对象 InputStream in = System.in; //将字节流装换为字符流对象 InputStreamReader isr = new InputStreamReader(in); //为了提高效率,用到字节流缓冲区对象,这里边才有readLine方法 BufferedReader bufr = new BufferedReader(isr); //改变标准输出流改变为一个文件 System.setOut(new PrintStream("zz.txt")); OutputStream out = System.out; OutputStreamWriter osw = new OutputStreamWriter(out); BufferedWriter bufw = new BufferedWriter(osw);//一样的,这里边才有通用的换行newLine方法 String line = null; while ((line = bufr.readLine())!=null) { if("over".equals(line)) break; bufw.write(line); bufw.newLine();//在录入数据的时候不带的回车符,所以需要自己添加 bufw.flush();//每次写完要记得刷新一下,要不然出不来数据 } bufr.close(); bufw.close(); }}
运行结果为:
将异常的信息打印到异常日志文件中,带上时间。
//将异常信息导入到异常日志文件中,要带上时间import java.io.*;import java.util.*;import java.text.*;public class ExceptionLogDemo { public static void main(String[] args) { //发现异常 try { int[] arr = new int[2]; System.out.println(arr[4]);//创建一个数据角标越界的异常 } //进行处理 catch (Exception e) { //加上时间,获取能够看得懂的时间信息 Date d = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 kk:mm:ss"); String time = sdf.format(d); //将标准输出流换成文件打印流,并且与日志文件相关联 try { PrintStream ps = new PrintStream("exception.log"); System.setOut(ps); } catch (IOException ex) { throw new RuntimeException("异常日志文件创建失败"); } System.out.println(time); e.printStackTrace(System.out);//将异常打印在指定的输出流中 } }}
运行结果:
想要将系统信息,输出到指定的文件夹
发现系统信息对象Properties有自己的指定输出流的方法list
于是,我们就可以直接进行调用:
//将系统信息输出到指定的文件中import java.io.*;import java.util.*;public class SystemInfo { public static void main(String[] args) { //获取系统信息 Properties prop = System.getProperties(); //Properties中的list方法,可以将系统信息输出到指定的输出流 try { //输出流和文件相关联 prop.list(new PrintStream("systeminfo.txt")); } catch (IOException e) { e.printStackTrace(); } }}
运行结果:
- Java基础—IO流(二)
- 黑马程序员——Java基础--IO流(二)
- Java基础——IO流(二)
- 黑马程序员——Java基础--------IO流(二)
- 黑马程序员——JAVA基础----IO流(二)
- Java基础——Java重点基础之IO流(二)
- Java基础--IO流(二)
- Java基础--------IO流(二)
- 黑马程序员——Java基础---IO流(二)
- 黑马程序员——Java基础--IO(二)
- java基础/IO流(二)
- java基础整理二十(IO流二)
- Java——IO流(二)
- Java—IO流详解(二)
- 黑马程序员——java基础——IO流(二)
- 黑马程序员——Java基础——IO流(二)
- 黑马程序员——JAVA基础------IO流(二)----字节流
- 黑马程序员——java基础—IO流(二)
- 运行百度地图demo时出现XML file line #6: Error inflating class com.baidu.mapapi.map.MapView的错误
- android 剪裁图片
- datanode无法启动
- BeeFramework框架学习之二(自定义提示框 照相机的实现)
- 《重构--改善既有代码的设计》--处理概括关系(11)
- Java基础——IO流(二)
- Linux配置文件系统路径
- 树形结构 数据组织方式
- 素材库亮相CityMaker7.0,轻松解决“烦人”的模型管理问题
- java的循环引用
- SET FOREIGN_KEY_CHECKS=0;在Mysql中取消外键约束。
- oracle PL/SQL
- 字符全排列
- phonegap ios7 启动页缩小,有白色底边