黑马程序员----IO流(上)

来源:互联网 发布:数据产品质量的承诺 编辑:程序博客网 时间:2024/06/05 09:56

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


IO流

1.什么是IO流?

我们可以想象成一条河流,但是这条河比较特别,它里面流淌的不是水,而是数据。


IO流分类:

1.既然是河流,那么肯定得有方向吧,也就是按照流向分类:输入流,输出流。
2.我们知道数据分很多种对吧,根据流淌的数据类型分类:字节流,字符流。

IO体系基类:

InputStream字节输入流
OutputStream字节输出流
Reader字符输入流
Writer字符输出流

IO体系子类命名特点:

子类名=功能+父类名,例如子类FileWriter:就是这个子类是操作文件的,且是Writer的一个子类。

举例使用该体系一员:

FileWriter fw=new FileWriter("demo.txt");fw.write("123");fw.flush();fw.close();
我们看到这是一个处理文件的字符输出流,也就是说将数据写到文件中,那么可知我们必须得有一个文件吧,"demo.txt"就是传入那个文件名的。如果该文件不存在,那么就在指定路径下创建它,如果存在,则覆盖它
fw.write("123")向流中写入数据,注意还不是写到文件中,而是先写入流中,这就好像我们先把数据扔到河里,但是这些东西还没有随着河水流入目的地。
fw.flush()将当前流中的数据写入到目的地文件中,就好像对河中数据进行一次集体打捞,捞到的全部拿到目的地存放。
fw.close()关闭流资源,我们知道流作为一种资源,在使用完毕后是要关闭的,否则会导致内存泄露,在close方法内部会调用一次flush进行数据打捞

IOException处理流程:

try{FileWriter fw = new FileWriter("demo.txt");fw.write("123");fw.flush();}catch(IOException e){//处理该打开或者写入异常}finally{if(fw!=null)try{fw.close();}catch(IOException e){//处理关闭异常}}

文件续写:

我们发现,按照上述方式打开文件,写入数据会将原本的内容覆盖掉,那么如果我们是打算接着文件中已有内容续写呢?这时就需要使用FileWriter的另一个构造函数了。
FileWriter fw=new FileWriter("demo.txt",true);
第二个参数表示是否以附加的方式写入数据,我们传入true就不会覆盖掉原本的数据了。
代码:
//文件的续写package com.helong.filewriterdemo2;import java.io.*;class FileWriterDemo2 {public static void main(String[] args) {FileWriter fw=null;try{fw=new FileWriter("Demo2.txt",true);//true表示以续写的方式打开文件fw.write("\r\n第二行\r\n第三行");}catch (IOException e){e.printStackTrace();}finally{try{if(fw!=null)fw.close();}catch (IOException e){e.printStackTrace();}}}}



文本拷贝原理:

将源文件数据读取到流中,到目的地创建新文件,将流中数据写入目的地文件中。


拷贝文件练习:

package com.helong.copyfile;import java.io.*;class CopyFile {public static void main(String[] args) throws IOException{//copy1();copy2();}//这种方法效率太低,每次取出一个字符然后写入,要重复很多次硬盘和内存之间的交互private static void copy1()throws IOException{//每次取出一个字符,写入目的地文件FileWriter fw = new FileWriter("Demo(2).txt");FileReader fr = new FileReader("Demo.txt");for(int c=fr.read();c!=-1;c=fr.read()){fw.write(c);}fr.close();fw.close();}private static void copy2(){FileWriter fw=null;FileReader fr=null;try{fw=new FileWriter("Demo(3).txt");fr=new FileReader("Demo.txt");char[] arr=new char[1024];for(int count=fr.read(arr);count!=-1;count=fr.read(arr)){fw.write(arr,0,count);}}catch (IOException e){e.printStackTrace();}finally{try{if(fw!=null)fw.close();}catch (IOException e){e.printStackTrace();}finally{try{if(fr!=null)fr.close();}catch (IOException e){e.printStackTrace();}}}}}

运行图:

IO流小练习:

//打印一个java文件,输出到控制台package com.helong.filetest;import java.io.*;class FileTest {public static void main(String[] args) throws IOException{FileWriter fw=new FileWriter("FileText.java");fw.write("class Demo\r\n");fw.write("{\r\n");fw.write("void show(){.....}");fw.write("\r\n}");fw.close();FileReader fr=new FileReader(args[0]);char[] arr=new char[1024];for(int count=fr.read(arr);count!=-1;count=fr.read(arr)){System.out.print(count+":::"+new String(arr,0,count));}fr.close();}}
运行图:

/*FileWriter的基本方法(构造函数,write,close,flush)IOException的基本处理流程*/package com.helong.filewriterdemo;import java.io.*;class FileWriterDemo {public static void main(String[] args) {FileWriter fw=null;try{fw = new FileWriter("Demo.txt");fw.write("使用FileWriter类创建文件并写入这句话!\n\t");fw.write("  ..........再写一句把");}catch (IOException e){System.out.println("构造函数或者write方法:"+e.toString());}finally{try{if(fw!=null)fw.close();}catch (IOException e){System.out.println("close方法:"+e.toString());}}}}
运行图:



//文件读取方式1package com.helong.filereaderdemo;import java.io.*;class FileReaderDemo {public static void main(String[] args) throws IOException{FileReader fr = new FileReader("Demo.txt");for(int ch=fr.read();ch!=-1;ch=fr.read()){System.out.print((char)ch);}fr.close();}}
运行图:



//文件读取方式2,以数组形式package com.helong.filereaderdemo2;import java.io.*;class FileReaderDemo2 {public static void main(String[] args) throws IOException{FileReader fr =new FileReader("Demo.txt");char[] arr=new char[1024];//通常数组大小设置为1024的整数倍for(int count=fr.read(arr);count!=-1;count=fr.read(arr)){System.out.println(count+":::"+new String(arr,0,count));}fr.close();}}

运行图:



2.字符流缓冲区对象

在没有缓冲区之前,我们总是将数据一个字节一个字节的传入目的地,这样效率非常低,而缓冲区对象的出现解决了这个问题,它就好像是一个蓄水池,水(数据)不再是直接流入目的地,而是先流到蓄水池中,当蓄水池中水满了,再一次性运到目的地中。这样,就减少了频繁的硬盘与内存的交互,提高了效率。同时我们知道蓄水池的出现就是为了水服务的,也就是说先有水后有了蓄水池,因此当我们创建一个蓄水池时,必须传入水流作为参数


缓冲区对象:

BufferedWriter:有了这个类我们可以将一个FileWriter对象作为参数传给该类对象,再操作流时就可以使用该缓冲区对象的方法来提高效率了。
特别方法newLine():我们知道,在不同操作系统下,换行是由不同符号代替的,例如windows下为"\r\n",linux下为"\n",因此我们编写IO程序时,对于这个问题非常麻烦,要判断是哪个操作系统,再决定使用哪一种换行,而缓冲区对象提供了newLine方法,该方法是跨平台的换行,我们使用更加方便。
BufferedReader:对应BufferedWriter的类。
特别方法readLine():读取一行数据,内部是调用FileReader的read方法,读取一行的数据但是不包括回车换行符。

装饰设计模式:

我们发现,其实BufferedWriter相对于FileWriter来说,功能基本一致,无非就是在效率,方便等方面更好了,而且在BufferedWriter方法,内部也大量的使用到了FileWriter的方法,这使得我们很疑惑,BufferedWriter在体系中与FileWriter是平行的,但是它更像是FileWriter的加强版,而不是一个独立的类,其实当我们看到BufferedWriter的构造函数时我们也会有所察觉,这个类是没有空参数构造函数的,创建该类对象,必须传递一个FileWriter对象进来,这是什么做法呢?我们不得不提一种设计模式:装饰设计模式。

当想要对已有的类进行增强时,将已有类的对象传入自定义的类中,并基于已有类方法提供增强版方法。这个新的类就是已有类的装饰类。因为当我们想增强一个类时,不建议修改其源代码,因此一般都会使用装饰类。装饰类一般通过构造函数接收被装饰的对象,基于被装饰对象的方法提供更强功能

特点:

1.包含被装饰类的一个对象。

2.一般无空参数构造函数,那个对象也是通过构造函数传递给该类的。

3.基于对象的方法,提供增强的方法。

装饰与继承的区别:

细心的朋友会发现,这个所谓的增强版的类貌似使用继承也可以啊,我们可以设计一个类,该类继承于FileWriter,复写增强其中我们想要增强的方法,提供更高效率,更方便的方法,这不也能解决问题吗?那我们为什么要多此一举搞个装饰类出来呢?

我们来仔细分析一下:

例如此时有一个类MyReader,其有两个子类MyFileReader,MyTextReader,我们发现这两个子类的读取方法效率不高,想提高它的效率怎么办呢?改源代码是不可能的,以前我们可能会说定义新的类继承这两个子类,然后复写其中需要提高效率的方法就可以了。

这样做也不是不可以,体系如下:

MyReader:

|--MyFileReader

|--MySuperFileReader

|--MyTextReader

|--MySuperTextReader

我们发现,这样的解决方式虽然也能解决问题,但是会使得该体系过于臃肿,如果还有别的子类,那就还要再定义类,而且灵活性不强,扩展性极差我们再来看看使用装饰设计模式来解决这个问题

MyReader

|--MyFileReader

|--MyTextReader

|--MySuperReader

class MySuperReader extends MyReader
{
MySuperReader(MyReader r)//利用多态,可以处理所有MyReader的子类对象
{}
//基于MyReader的方法,提供对应的增强方法。
}

我们发现,这种方式的好处在于,1.扩展性强,2.代码简洁,3.灵活性强,如果有方法不想增强的可以直接使用传入对象的原方法。

缓冲区对象练习:

//使用缓冲区对象来提高FileWriter的效率//写入99乘法表import java.io.*;class BufferedWriterDemo {public static void main(String[] args) throws IOException{//使用蓄水池前肯定要有个水流吧FileWriter fr=new FileWriter("buff.txt");BufferedWriter bw=new BufferedWriter(fr);for(int i=1;i<=9;i++){for(int j=1;j<=i;j++){bw.write(j+"*"+i+"="+(i*j)+"\t");}bw.newLine();bw.flush();//每次都刷新是为了防止程序突然停止后数据没有能写入硬盘中,例如写BLOG时,一般都是每隔10秒就存一次的,这是一个道理}bw.close();}}
运行图:



//使用BufferedReaderDemo对象提高读取的效率import java.io.*;class BufferedReaderDemo {public static void main(String[] args) throws IOException{FileReader fr=new FileReader("buff.txt");BufferedReader br=new BufferedReader(fr);for(String line=br.readLine();line!=null;line=br.readLine()){System.out.println(line);}br.close();}}
运行图:



练习装饰设计模式:

//装饰设计模式:自定义一个类,该类中包含被装饰类对象,基于该对象的方法,提供更好更强的方法class Person{void eat(){System.out.println("吃白饭");}void play(){System.out.println("玩泥巴");}}class SuperPerson{private Person p;SuperPerson(Person p){this.p=p;}void eat(){p.eat();System.out.println("吃牛肉");System.out.println("吃鸡肉");System.out.println("吃鱼肉");}void play(){p.play();System.out.println("玩电脑");System.out.println("玩手机");System.out.println("玩平板");}}class DecorationDemo {public static void main(String[] args) {SuperPerson sp=new SuperPerson(new Person());sp.eat();sp.play();}}
运行图:


//使用装饰设计模式abstract class Animal{abstract void eat();abstract void sleep();abstract void play();abstract void work();}class Cat extends Animal{void eat(){System.out.println("吃鱼");}void sleep(){System.out.println("趴着睡");}void play(){System.out.println("玩毛线");}void work(){System.out.println("抓老鼠");}}class Dog extends Animal{void eat(){System.out.println("吃骨头");}void sleep(){System.out.println("躺着睡");}void play(){System.out.println("玩飞盘");}void work(){System.out.println("看门");}}class SuperAnimal{private Animal a=null;SuperAnimal(Animal a){this.a=a;}void eat(){a.eat();System.out.println("还会自己收拾哦");}void sleep(){a.sleep();System.out.println("而且不打呼噜哦");}void play(){a.play();System.out.println("和主人一起玩哦");}void work(){a.work();}}class DecorationDemo2 {public static void main(String[] args) {SuperAnimal sa=new SuperAnimal(new Cat());sa.eat();sa.sleep();sa.play();sa.work();sa=new SuperAnimal(new Dog());sa.eat();sa.sleep();sa.play();sa.work();}}

运行图:



IO异常的一种处理方式:

/*将异常信息存放到特定的文件中*/import java.io.*;import java.util.*;import java.text.*;class ExceptionInfoHandle{public static void rememberExceptionInfo(String info)throws IOException{Date d=new Date();SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");String date=sdf.format(d);/*//方法1*/BufferedWriter bw=new BufferedWriter(new FileWriter("ExceptionInfo.txt",true));bw.write(date);bw.newLine();bw.write(info);bw.newLine();bw.newLine();bw.flush();/*//方法2System.setOut(new PrintStream(new FileOutputStream("ExceptionInfo.txt",true)));System.out.println(date);System.out.println(info);System.out.println();*/}}class ExceptionInfo {public static void main(String[] args) throws IOException{try{int a=12/0;}catch (Exception e){ExceptionInfoHandle.rememberExceptionInfo(e.toString());}try{int[] arr=new int[2];System.out.println(arr[3]);}catch (Exception e){ExceptionInfoHandle.rememberExceptionInfo(e.toString());}}}
运行图:


手写实现BufferedReader的readLine和close方法:

//自己实现BufferedReader的readLine和close功能//思路:其实BufferedReader内部的readLine方法也是在调用FileReader的read读取一个字符的方法import java.io.*;class MyBufferedReader{private FileReader fr=null;MyBufferedReader(FileReader fr){this.fr=fr;}public String myReadLine()throws IOException{//定义一个临时存储数据的容器//BufferedReader中使用数组,我们此处使用StringBuilderStringBuilder sb=new StringBuilder();for(int ch=fr.read(),temp;ch!=-1;ch=fr.read()){//如果文件中有单独的\r或者\n,那么将被跳过//if(ch=='\r')continue;//if(ch=='\n')return sb.toString();//'\n'表示读到了一行的结尾处,因此返回字符串if(ch=='\r'){if((temp=fr.read())=='\n')//连续的'\t''\n'表示这是个换行符return sb.toString();else//单独的'\t',不是换行符的一部分,也应该存入容器中{sb.append(ch);sb.append(temp);}}elsesb.append((char)ch);}if(sb.length()!=0)return sb.toString();return null;}public void myClose()throws IOException{fr.close();}}class MyBufferedReaderDemo{public static void main(String[] args) throws IOException{FileReader fr=new FileReader("buff.txt");MyBufferedReader mbr=new MyBufferedReader(fr);for(String line=mbr.myReadLine();line!=null;line=mbr.myReadLine()){System.out.println(line);}}}
运行图:



3.字节流

使用字节流写入数据是不需要刷新的当然也是需要关闭资源的),这是因为字节流是一个字节一个字节的操作数据,因此不会放到流中,而是直接放到目的地,而字符流不同,由于一个字符是两个字节,因此它要先把一个字节放到流中,等下一个字节来时,凑成一个字符再写入目的地
字节流体系:
InputStream:读
|--FileInputStream
|--BufferedInputStream
OutputStream:写
|--FileOutputStream
|--BufferedOutputStream


常用子类:

FileOutputStream:写入单个字节或者字节数组。将其他要写的数据转换成字节数组,例如"helong".getBytes()
FileInputStream:available()方法返回剩下的可操作的估计字节数。使用该方法返回值定义数组大小。
问题:如果文件过大,那么使用该方法返回值创建数组时会内存溢出。
两个装饰类:BufferedOutputStream,BufferedInputStream。

两个方法的特点:

int read(),write(int)特点:我们使用read来读取数据时,每次虽然只读取一个字节,但是却返回一个int变量,这是为什么呢
原因:由于使用字节流对象可能读取到任何数据(文字,图片,音乐等),例如1111-1111,因为像这样的一个字节的值换成十进制为-1,如果直接就返回这个字节,那么在外面判断时就会误以为已经读完了数据。这是不对的,因此java的做法是将一个byte提升为int,但是如果直接提升的话,那结果就变成了1111-1111 1111-1111 1111-1111 1111-1111这样,可是我们发现这个结果还是-1,因此我们的目的是保留后八位的同时,将前面全部补0,因此我们将这个结果&255,来保留后八位
而对于write来说,虽然参数是一个int,但是我们都知道在这个int中,只有后八位是我们需要的,而前八位是补的0,因此write是做了强转动作来只取后八位的。

转换流:

InputStreamReader:传入一个字节流对象,得到一个字符流对象,这样就可以使用字符流缓冲区对象的方法了,例如BufferedReader.readLine()
OutputStreamWriter:传入一个字节流输出对象(例如:System.out),得到一个字符流输出写对象
出现转换流的原因:我们知道FileReader处理纯文本文件的读取操作,而FileReader是InputStreamReader的子类,这其实表示用InputStreamReader也能处理纯文本文件的读取,只是需要指定流和编码方式比较麻烦,而FileReader则不需要指定流,且默认采用GBK编码表,但是这也是它的局限性,不能处理UTF-8编码的文件

转换流特有功能:转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符


键盘录入(最简写法):

BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

键盘录入练习:

//键盘录入import java.io.*;class ReadIn {public static void main(String[] args) throws IOException{InputStream in=System.in;while(true){StringBuffer sb=new StringBuffer();for(int ch=in.read();ch!='\r'&&ch!='\n';ch=in.read()){sb.append((char)ch);}in.read();if(sb.toString().equals("over"))break;elseSystem.out.println(sb.toString().toUpperCase());}}}
运行图:

控制台输出:

BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out));

字节流练习:

//测试FileOutputStream和FileInputStream使用import java.io.*;class FileStream {public static void main(String[] args) throws IOException{input_3();}private static void input_1()throws IOException{FileInputStream fis=new FileInputStream("FileStream.txt");for(int ch=fis.read();ch!=-1;ch=fis.read())//一个字节一个字节的读数据{System.out.print((char)ch);}fis.close();}private static void input_2()throws IOException{FileInputStream fis=new FileInputStream("FileStream.txt");byte[] buf=new byte[1024];for(int len=fis.read(buf);len!=-1;len=fis.read(buf))//一次性读多个字节,然后一起操作{System.out.println(new String(buf,0,len));}fis.close();}private static void input_3()throws IOException{FileInputStream fis=new FileInputStream("FileStream.txt");byte[] buf=new byte[fis.available()];fis.read(buf);System.out.println(new String(buf));fis.close();}private static void output_1()throws IOException{FileOutputStream fos=new FileOutputStream("FileStream.txt");fos.write("helong is so handsome!\r\nmuhhhhh".getBytes());fos.close();//即便没有刷新,也会将数据写入目的地,这是因为字节流操作的是字节这种最小单元,因此不需要有中间处理,直接就存到了目的地。}}
运行图:


//转换流练习import java.io.*;class TranslateStream {public static void main(String[] args) throws IOException{//数据源//BufferedReader br=new BufferedReader(new InputStreamReader(System.in/*数据源是标准输入,也就是键盘*/));BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream("trans.txt")/*数据源是标准输入,也就是键盘*/));//目的地BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out/*目的地是控制台*/));//BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream("trans.txt")/*目的地一个文件夹*/));for(String line=br.readLine();line!=null;line=br.readLine()){if(line.equals("over"))break;bw.write(line.toUpperCase());bw.newLine();bw.flush();}br.close();bw.close();}}
运行图:

复制图片:
//复制一张图片/*思路:1.以字节形式读取该图片FileInputStream2.创建一个对应格式的图片文件到目的地FileOutputStream3.循环读写4.关闭资源*/import java.io.*;class CopyPic {public static void main(String[] args){FileInputStream fis=null;FileOutputStream fos=null;try{fis=new FileInputStream("使用缓冲区对象读取数据.png");fos=new FileOutputStream("使用缓冲区对象读取数据(2).png");byte[] buf=new byte[1024];for(int len=fis.read(buf);len!=-1;len=fis.read(buf)){fos.write(buf,0,len);}}catch (IOException e){throw new RuntimeException("复制图片失败");}finally{try{if(fis!=null)fis.close();}catch (IOException e){throw new RuntimeException("读取流关闭失败");}try{if(fos!=null)fos.close();}catch (IOException e){throw new RuntimeException("写入流关闭失败");}}}}

运行图:



4.流操作基本流程

1.找到源流对象,进行包装----InputStream,Reader
2.找到目的流对象,进行包装----OutputStream,Writer
3.操作的数据是否为纯文本----决定使用字符流还是字节流
4.通过设备确定使用的具体对象(源设备:内存,硬盘,键盘。目的设备:内存,硬盘,控制台)
5.是否需要提高效率,是就使用包装类
使用流操作流程解决问题:
//将文本文件打印到控制台上,使用三个明确的流程import java.io.*;class TranslateStreamTest {public static void main(String[] args) throws IOException{/*明确源:1.源:文本文件2.纯文本:Reader3.设备:硬盘--FileReader*/BufferedReader br=new BufferedReader(new FileReader("根据步骤分析流操作.txt"));/*明确目的:1.目的:控制台2.纯文本:Writer,字符流对象3.设备:控制台,System.out字节流对象(需要转换,将字节流对象转为字符流对象,需要转换流)*/BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out));for(String line=br.readLine();line!=null;line=br.readLine()){bw.write(line);bw.newLine();}bw.close();br.close();}}




------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

0 0
原创粉丝点击