黑马程序员--IO总结

来源:互联网 发布:花呗怎么在淘宝买东西 编辑:程序博客网 时间:2024/05/09 16:28

IO流的做用:用来处理设备之间的数据传输。

流的分类:

流按操作数据分为两种:字节流与字符流。

按流分向分为:输入流,输出流。

字节流的由来:

    因为后期编码表的不断出现,识别某一文字的码表不唯一,比如中文,GBKunicode都可以识别,就出现了编码问题。如:中文字节数据gbk -à 流处理unicode 来处理 -à数据错误,所以就有了字符流的出现。(虽然字节流也能处理问题,但比较麻烦)

    字符流其实就是:字节流+编码表的一个封装体。

Io流常用的基类:

字节流的基类:

       InputStream OutputStream

字符流的基类:

       Reader  Writer

学习io流体系:看顶层(父类的共性功能),用底层(底层的具体对象)。

该体系的一个好处是:每个子类的后缀名都是所属体系的父类的名称,很容易区分所属体系,而前缀是流的功能体现。


需求:将一段文字数据写到硬盘上。

FileWriter中没有方法,方法都是继承父类Writer的。

Writer中的方法:

       write(char[] buf):写入字符数组。

       Write(int c):写入单个字符。

       Write(String c):写入字符串。

       Write(String str , int off ,int len):写入字符串的某一部分。

       Write(char[] buf ,int off ,int len):写入字符数组的某一部分。

       Flush():刷新流。

       Close():关闭流。

Reader中的方法:

       Int Read():一次读取一个字符,返回作为整数读取的字符,如果读到末尾则返回-1

       Int Read(char[] buf):将字符读入数组,,并返回读取到的字符个数。

Int Read(char[] buf ,int off, int len):将字符读入数组的某一部分。

Close():关闭流。

注意read()方法是一次读取一个字符进内存,read(char[] buf)方法一次读取一堆数据进字符数组中,所以使用read(char[] buf)方法来读取比较高效。

字节流读取代码演示:

需求:将c盘下的文本文件复制到d盘中。

复制方式一:

public class CopyTest {

    public static void main(String[] args) throws IOException {

       //1、创建字符读取流对象和源相关联。

       FileReader fr = new FileReader("c:\\demo.text");

       //2、创建字符输出流对象,明确要存储数据的目的。

       FileWriter fw= new FileWriter("copy_demo.txt");

       //3、进行读写操作,读一个,就写一个。

       int ch = 0;

       while((ch=fr.read())!=-1){

           fw.write(ch);

       }

       //4、关闭资源。

       fw.close();

       fr.close();

    }

}

复制方式二:使用缓冲区数组,使用的就是可以操作数组的读写方法。

public class CopyTest2 {

    public static void main(String[] args) throws IOException {

       //1、创建字符输入流和字符输出流。

       FileReader fr = fr = new FileReader("demo.txt");

       FileWriter fw = fw = new FileWriter("copy_demo2.txt")

       //3,定义一个数组缓冲区。用于缓冲读取到的数据。

       char[] buf = new char[1024];

       //4,读写操作。

       int len = 0;

       while((len = fr.read(buf))!=-1){

           fw.write(buf,0,len);

       } 

       fw.close();

       fr.close(); 

    }

}

IO中的异常处理规范示例:

public class FileWriterDemo2 {

    public static void main(String[] args){

       //为了close()方法的引用有效。

       FileWriter fw = null;

       try {

           fw = new FileWriter("d:\\demo.txt");

           fw.write("abcde");

           fw.flush();

          

       } catch (IOException e) {

           System.out.println(e.toString());

       }finally{

//         if(fw!=null) //这里一定要判断下流是否创建成功,不然出现两个异常提示。

           //这里也要try,因为close()方法也会抛异常。

           try{

              fw.close();

           }catch(IOException e){

              //相关的代码处理,比如说,将关闭失败的异常信息记录到日志文件中。

              throw new RuntimeException("关闭失败");

          }     

       }

    }

}

Flush()方法和close()方法的区别?

       Flush:仅仅是将缓冲中的数据刷新到目的,流可以继续使用,可以刷新多次。

       Close:将缓冲区中的数据刷新到目的,流关闭,流不能继续运行,只能使用一次。

字符流的缓冲区:增强高效读写。

       BufferedReader

       BufferedWriter

缓冲区给流的操作动作(读写)提高效率,所以缓冲区的对象建立必须要有流对象。同时在构造时可以指定缓冲区大小,或者使用默认大小,一般默认就足够。

缓冲区中的read()方法和流中的read()的区别?

缓冲区中的read()方法的读取原理:是使用Reader中的read(char[] buf)方法读取一批数据到缓冲数组中,然后再使用read()方法从缓冲区中一次取一个,而且每次都是从缓冲区中取,所以效率比较高。
readLine():一次读取一行。 

readLine()方法可以读取一行的原理,使用buf.read()方法从缓冲区中取出字符存储到readLine()方法的容器中(也就是容器中还有容器),当取出的字符是回车符时,就将存储的数据作为字符串返回,返回的字符串中是不带回车符的。

装饰设计模式:

       解决问题:给已有的对象提供增强额外功能,还不用对原有对象进行修改。

       这种设计方式比继承更灵活,避免了继承的臃肿,IO中频繁的用到了装饰设置模式。

       装饰类和被装饰类都属于同一个体系。

       装饰类往往会提供构造方法用于接收被装饰对象,比如:BufferedReader(Reader in);

装饰类LineNumberReader增强的是行号功能,提供了设置行号和获取行号功能。

       setLineNumber(int lineNumber):设置当前的行号。

       getLineNumber():获取当前的行号。

装饰设计模式代码演示:

//被装饰类。

class Person{

    public void eat(){

       System.out.println("吃饭");

    }

}

//装饰类。

class SuperPerson {

    private Person p;

    //提供构造方法用于接收被装饰的类。

    SuperPerson(Person p){

       this.p = p;

    }

    public void eat(){

       p.eat(); //调用原有功能。

       System.out.println("开胃酒");//增加功能。

       System.out.println("甜品");//增加功能。

    }

}

字节流:

字符流和字节流的操作方式基本上一样,只是字节流操作的单位是字节,字符流操作的单位是字节。

字节流的读取方法:

Read():一次读取一个字节,返回下一个数据字节,如果没有返回-1

Read(byte[] b):一次读取一个字节数组,返回读入缓冲区的字节总数,没有则返回-1

字节流的写方法:

Write(int b):一次写入一个字节。

Write(byte[] b):一次写入一个字节数组的数据。

字节输出流为什么不需要用flash()方法刷新?

       字符流的写入会先将这些数据进行临时存储,并查指定的编码表,再按照指定的编码表中的内容写入到目的地中。例如:"你好"FileWriter-->编码表GBK-->数字字节->目的地。而字节流处理的数据不一定都是文字数据,所以不需要指定查表的。直接在操作文件数据时,就将具体的字节数据写入到了目的地。

字符流可以复制图片吗?

不能,就算复制,复制完的图片就跟原本的图片不一样了,因为字符流操作的都是字符数据,而且字符流操作的数据都会多做了一步动作,都会去查相应的表,所以字符流只能操作纯文本的数据。

 转换流:

字节流--à字符流的桥梁。InputStreamReader

字符流--à字节流的桥梁。OutputStreamWriter

转换流使用代码:

需求:读取键盘录入的数据,将这些数据转成大写打印在屏幕上,如果录入的是over,程序结束。

分析为什么要使用转换流:

分析发现如果使用readLine方法的方式来读取,会更容易,但是readLine()读取一行的方式,是字符流BufferedReader对象中过的方法,而System.in键盘录入是字节流。想要readLine中的方法,可以使用转换流将字节流转换成字符流,这样就可以使用readLine中的方法了。

public class TransStreamDemo {

    public static void main(String[] args) throws IOException {   

       //使用转换流将System.in转换成字符流,再和BufferedReader,就可以使用readLine方法读取一行。

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

       BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(newFileOutputStream("tempfile\\out.txt"))); 

       String line = null;

       //readLine()方法一次读取一行。

       while((line=bufr.readLine())!=null){     

           if("over".equals(line))

              break;    

//         System.out.println(line.toUpperCase());

           bufw.write(line.toUpperCase());

           bufw.newLine();

           bufw.flush();

       }     

       //可以不用关流。

//     osw.close(); 

    }

} 

 

File类:

       专门用于描述系统中文件或文件夹的对象,可以用于操作文件或文件夹的属性信息。

学习File类具体方法使用,还是按照面向对象的原则:

分析如果将一个文件或文件夹封装成对象,那么这个对象具备什么功能方便对文件或文件夹的操作呢?

File类中的成员:

1、构造函数。

       File(File parent,String child);

       File(String parent,String child);根据父parentchild子路径名创建File实例。

       File(String pathname);通过将制定字符串路径名称创建File实例。

2、字段:

       分隔符:

              路径名称分隔符 \\  /  static String separator;

              路径分隔符 ;

3、方法:

       获取信息:

              String name = file.getName();获取名称。

              String absPath = file.getAbsolutePath();获取绝对路径。

              String path = file.getPath();获取路径。

              String dir = file.getParent();获取父目录。

              long len = file.length();获取字节大小。

              long len2 = file.getFreeSpace();获取指定盘的总容量。

              long time = file.lastModified();获取文件最新修改的时间。

                     获取时间:1、将毫秒值转成Date时间对象。2、对Date对象进行格式化。

              判断:is开头的方法。

              boolean isFile();是否是文件。

              boolean isDirectory();是否是文件夹。

                     要判断是文件还是目录的前提,必须要存在,所以要用exists()判断文件是否存在。

              创建:

              boolean createNewFile();创建文件,如果文件不存在就创建,如果存在就不创建。

              boolean mkdir();创建一个文件夹。

              boolean mkdirs();创建多级目录。删除的

              删除:

              boolean delete();删除就为true

              boolean exists();判断文件是否存在。

              boolean renameTo(File dest);重命名路文件,有剪切的作用。

 

              static File[] listRoots();获取系统中有效的盘符存进数组。需要遍历数组。

              String[] list();将指定目录下的文件或文件夹的名称存储到字符数组中,包含隐藏文件。

              String[] list(FilenameFilter filter);返回经过过滤器过滤的文件或文件夹名。

                     FilenameFilter接口,过滤器。

                                   boolean accept(File dir,String name);参数为文件路径和要过滤的文件名。

                                   如果名称包含在指定列表中则返回true,否则返回false

                     boolean endsWith(".java");后缀名是.java的就返回真。

                     File[] listFiles();将路径下的所有文件和文件夹封装成对象,存到数组中。

过滤器的原理:

       1、首先获取到该目录所有内容,用list();

       2、对这写内容进行遍历。

              在遍历中加入条件,条件:accept(dir,name);

       3、凡是符合条件,accept方法返回true,就将这些符合条件的进行存储。

       4、将存储的内容返回,这样就得到了过滤后的内容。

递归:

递归就是函数自身调用自身(直接或间接)。

递归使用注意:

1、  一定要定义条件,否则会发生StackOverflowError异常。

2、  一定要注意递归的次数。

什么时候使用递归?

       当一个功能被复用,每次这个功能需要的参数都是根据上一个功能的得出的。

下面代码可以可以分析递归的使用:

public class DiGuiDemo {

    public static void main(String[] args) {

       show(3);

       show2(3); 

       int sum = getSum(5);

       System.out.println("sum="+sum);

    }

    //求和运算。分析结果。

    public static int getSum(int num){

       if(num==1)

           return 1;    

       return num+getSum(num-1);      

    }

    //分析结果。

    public static void show(int num){

       if(num==0)

           return ;

       show(num-1);

       System.out.println("num="+num);//打印的结果是1,2,3,为什么?

    }

    //分析结果。

    public static void show2(int num){    

       if(num==0)

           return ;

       System.out.println("num="+num);//打印的结果是3,2,1为什么?

       show(num-1);

    }

    public static void  method(){  

//     method();

    }

}

练习:获取指定目录中的所有内容(包含子目录中的内容)。用递归方式。

public class FileTest {

    public static void main(String[] args) {     

       File dir = new File("e:\\");

       showDir(dir,0);

    }

    //递归。

    public static void showDir(File dir,int count){ 

       System.out.println(getSpace(count)+dir.getName());  

       count++;

       File[] files = dir.listFiles();

//     if(files!=null)//健壮性判断。

       for(File f : files){

           if(f.isDirectory()){

              showDir(f,count);

           }

           else

              System.out.println(getSpace(count)+f.getName());

       }

    }

    //定义一个,用来获取目录缩进的量。

    private static String getSpace(int count) {

      

       StringBuilder sb = new StringBuilder();

       for(int x=0; x<count; x++){

           sb.append("|--");

       }  

       return sb.toString();

    }

}

练习:删除一个带内容的文件夹。(递归)

思路:

        1,删除一个带内容的目录,必须按照window的规则,从里往外删。

       2,要删除最里面,如果做到的呢?可以使用递归。

public class FileTest2 {

    public static void main(String[] args) {

       File dir = new File("e:\\demodir");   

       removeDir(dir);

    }

    public static void removeDir(File dir){

       File[] files = dir.listFiles();

       for(File file : files){

           if(file.isDirectory()){

              removeDir(file);

           }else{

              System.out.println(file+":"+file.delete());

           }

       }

       System.out.println(dir+":"+dir.delete());

    }

}

Properties:该类表示了一个持久的属性集。

1、 PropertiesMap接口中Hashtable的子类。

2、 该类上没有定义泛型,因为它的键值都是固定的字符串类型。

3、 因为存储的都是字符串数据,通常都作为属性信息存在。

4、 该集合最大的特点就是可以和IO技术相结合。也就是说该集合中的数据可以来自流,也可以将集合中的数据写入流中。

Properties类的基本方法:

       setProperty(String key,String value):调用Hashtableput方法,为集合添加键和值。

       Set<String> StringPropertyNames():反回此属性列表中的键集。

       getProperty(String key):通过键取值。

Properties方法中和流相关联的方法:

List(PrintStream):将集合中的数据打印到控制台上,一般用于程序调试。

       LoadReader):将流中的数据存储到集合中。

       Store(Writer writer String comments):将集合中的数据写入到输出流中关联的目的。

       想要对硬盘中的文本数据进行修改,只能使用load方法将数据读取到集合中,然后,对集合数据通过相同键设值,然后再用store()方法将集合中修改过的数据写入文本中。

Properties集合的应用:可以用于简单配置文件信息。

       标准化配置信息一般用xml的方式来完成。

练习:定义一个功能用于记录住软件运行的次数,如果运行次数大于5次。不要在运行并给出提示:试用次数已到,请注册!给钱!

 思路:

    1、计数器。而且这个计数器必须软件运行结束后,持久化存储。

    2、每次软件启动都要读取这个记录了计数器的数据的文件。并将计数器的值取出自增后,在重新存储。

    3、数值需要名称标记,就有了键值对。这就是map集合,还要操作硬盘设备上的数据,就使用到了IO流。mapio结合的对象正好有Properties.

代码演示:

public class PropertiesTest {

    public static void main(String[] args) throws IOException {

       if(countDemo()){

           //运行程序代码。

           System.out.println("运行程序");

       }else{

          

           System.out.println("试用次数已到,请注册!给钱!");

       }

    }

    public static boolean countDemo() throws IOException{

      

       Properties prop = new Properties();

      

       int count = 0;

      

       //配置文件。

       File confile = new File("config.txt");

       if(!confile.exists())

           confile.createNewFile();

       FileInputStream fis = new FileInputStream(confile);

      

       //将流中的数据加载到prop中。

       prop.load(fis);

      

       //获取配置文件中的次数。

       String value = prop.getProperty("count");

       if(value!=null){

           count = Integer.parseInt(value);

           if(count>=5){

//            System.out.println("试用次数已到,请注册!给钱!");

              return false;

           }

       }

       count++;

       System.out.println("运行"+count+"");

       //将具体的键和次数存储到集合中。

       prop.setProperty("count", String.valueOf(count));//count+"";

       //将集合中的数据写入到文件中持久化。

       FileOutputStream fos = new FileOutputStream(confile);

       prop.store(fos, "");

       fos.close();

       fis.close();

      

       return true;
 
    }

}

IO中的其他功能流:

打印流:

       PrintWriterPrintStream

特点

1、  打印流为其他输出流添加了功能,使他们能够方便的打印各种数据值表示形式。

2、  提供了一系列的打印功能,可以打印任何数据。

3、  它的特有方法不抛出异常。(打印方法)

构造方法:该流是一个处理目的的流对象。

       目的设备:

1、  File对象。

2、  字符串路径。

3、  字节输出流。

打印流PrintStream中的writeprint方法的区别?

Write():一次只写一个字节。Read()方法一次读取一个字节,读取到内存中提升为整数4个字节,所以write()写方法,只会将参数的最后一个字节写入目的。

Print方法,可以将参数的数据表现形式打印到目的中,原理是print方法内部将传入参数转成字符串,再write到目的,所以可以保证数据的原有表现形式。(write(String.valueOf(i))。

PrintWriter字符打印流:

PrintStream打印的所有字符都使用平台默认的字符编码转换为字节,在需要写入字符而不是写入字节的情况下,应该使用PrintWriter类。所以PrintWriter类使用的频率比较高。

构造函数:接收的参数跟字节打印流的参数差不多,只是多了个字符输出流。

       目的设备:

1、  字符输出流。

还能指定编码表,一般不用PritWriter定义的编码表,因为要操作的编码表有专用的对象,转换流,转换流,不仅能设置写的编码表,还可以设置读的编码表。

自动刷新:

打印流写的方法都会先进缓冲区中。想要自动刷新,在构造时将autoFlush设为true,则printlnprintfformat方法将刷新输出缓冲区。

 SequenceInputStream序列流:可以将多个流进行逻辑串联(进行合并,变成一个流,操作起来更方便,因为将多个员变成了一个源)。

构造方法:

       SequenceInputStream(Eunmeration <? Extends InputStream >e):接收一个枚举。

       SequenceInputStream(InputStream s1 InputStream s2):接收两个流。


操作流的四个明确:
1
,明确源和目的。

    源:InputStream   Reader 一定是被读取的。

    目的:OutputStream  Writer 一定是被写入的。    

2,处理的数据是否是纯文本的数据?

    是:使用字符流。Reader Writer

    否:使用字节流。    InputStream OutputStream   

    如果是源并且是纯文本,Reader

    如果是目的并且是纯文本,Writer  

    到这里,两个明确确定完,就可以确定出要使用哪个体系。 

    接下来,就应该明确具体这个体系要使用哪个具体的对象。

3,明确数据所在的设备:

    源设备:

        键盘(System.in)

        硬盘(FileXXX)FileReader FileInputStream

        内存(数组)ByteArrayInputStream CharArrayReader StringReader

        网络(Socket)

    目的设备:

        显示器(控制台System.out)

        硬盘(FileXXX)FileWriter FileOutputStream

        内存(数组)ByteArrayOutputStream CharArrayWriter StringWriter

        网络(Socket) 

4,明确是否需要额外功能?

    1,是否需要高效?缓冲区Buffered 四个。

    2,是否需要转换?转换流 InputStreamReader OutputStreamWriter  

    3,是否操作基本数据类型? DataInputStream  DataOutputStream

    4,是否操作对象(对象序列化)? ObjectInputStream ObjectOutputStream

    5,需要对多个源合并吗? SequenceInputStream

    6,需要保证数据的表现形式到目的地吗? PrintWriter

 

0 0
原创粉丝点击