黑马程序员——java基础---I/O流

时间:2023-02-17 16:50:49

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

 

一、I/O流概述

  概念:I/O流用来处理设备之间的数据传输。Java对数据的操作是通过流的方式,而操作流的对象都放在IO包中。

  分类

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

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

   IO流常用基类

         字符流的抽象基类:Reader——Writer

         字节流的抽象基类:InputStream——OutputStream 

      注意:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。如FileReader、FileInputStream等。

二、字符流

   FileReader和FileWriter专门用于操作文件。

   创建文件:

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

      创建一个FileWriter对象。该对象一被初始化就必须要明确被操作的文件。而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖。其实该步就是在明确数据要存放的目的地。

     FileWriter fw1 = new FileWriter("test.txt",true);

      创建一个FileWriter对象。传递一个true参数,代表不覆盖已有的文件。并在已有文件的末尾处进行数据续写(如果没有该文件则会创建一个)。

     fw.write("abcde");

      调用write方法,将字符串写入到流中。

     fw.flush();

   刷新流对象中的缓冲中的数据,为了将数据写入目标文件。如果不刷新文件将一直在缓冲中直到fw关闭。

     fw.close();

      关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据,将数据刷到目的地中。

     close和flush区别:

        flush刷新后,流可以继续使用,close刷新后,会将流关闭。

   读取文件:

   1、按字符读取

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

      创建一个文件读取流对象,和指定名称的文件相关联。要保证该文件是已经存在的,如果不存在,会发生异常 FileNotFoundException。

1 int ch = 0;
2 while((ch=fr.read())!=-1)
3 {
4     System.out.println((char)ch);
5 }
6     fr.close();

      通过调用读取流对象的read方法。read()一次读一个字符,而且会自动读取下一个。返回的是该字符对应的整数,如果读到流的末尾就返回-1。

   2、按字符数组读取

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

      建立一个流对象,将已存在的一个文件加载进流。

     char[] ch = new char[1024];

      创建一个临时存放数据的数组。

1 int num = 0;
2 while((num=fr.read(ch))!=-1)
3 //read(char[])返回的是读到字符个数。如果数据没有超过数组长度if也可以,但如果超出了数组长度就要用while,所以使用while
4 {
5     System.out.println(new String(ch,0,num));
6 }
7     fr.close();

          通过调用流对象的读取方法将流中的数据读入到字符数组中。返回的是读取到的字符个数。如果到达流的末尾就返回-1.

     IO异常的处理:

创建文件时:

 1         FileWriter fw = null;
 2         try
 3         {
 4             fw = new FileWriter("Test.txt");
 5             fw.write("text");
 6         }
 7         catch (IOException e)
 8         {
 9             System.out.println(e.toString());
10         }
11         finally
12         {
13             If(fw!=null)
14             try
15             {
16                 fw.close();
17             }
18             catch (IOException e)
19             {
20                 System.out.println(e.toString());
21             }            
22         }

读取文件时:

 1         FileReader fr = null;
 2         try
 3         {
 4             fr = new FileReader("c:\\test.txt");
 5             char[] buf = new char[1024];
 6             int len= 0;
 7             while((len=fr.read(buf))!=-1)
 8             {
 9                 System.out.println(new String(buf,0,len));
10             }
11         }
12         catch (IOException e){
13             System.out.println("read-Exception :"+e.toString());
14         }
15         finally
16         {
17             if(fr!=null)
18             {
19                 try
20                 {
21                     fr.close();
22                 }
23                 catch (IOException e)
24                 {
25                     System.out.println("close-Exception :"+e.toString());
26                 }
27             }
28         }

     定义文件路径时,一定要将文件路径书写正确注意'\'和'/'的区别。在读取文件时,如果文件不存在,否则会抛出异常。

文件复制简易代码:

 1 import java.io.FileReader;
 2 import java.io.FileWriter;
 3 import java.io.IOException;
 4 
 5 public class CopyFile {
 6 
 7     public static void main(String[] args) throws IOException {
 8         
 9         //创建要写如的文件
10         FileWriter fw = new FileWriter("a.txt");
11         //读取已有的文件
12         FileReader fr = new FileReader("b.txt");
13         int ch = 0;
14         while((ch=fr.read()) != -1){
15             fw.write(ch);
16         }
17         fw.close();
18         fr.close();
19     }
20 
21 }

     字符流缓冲区:字符流缓冲区BufferedWriter和BufferedReader

        好处:缓冲区的出现提高了对数据的读写效率。缓冲区是为了提高流的操作效率而出现的。所以得先有流对象,缓冲区才会起作用。

  字符写入流缓冲区:BufferedWriter 

        该缓冲区中提供了一个跨平台的换行符,newLine()方法。

  字符读取流缓冲区:BufferedReader 

        该缓冲区提供了一个一次读一行的方法readLine(),方便于对文本数据的获取。当返回null时,表示读到文件末尾。

     readLine方法返回的时候只返回回车符之前的数据内容。并不返回回车符。其实现原理还是read()方法。

模拟BufferedReader代码:

 1 import java.io.*;
 2 class MyBufferedReader extends Reader
 3 {
 4     private Reader r;
 5     MyBufferedReader(Reader r)
 6     {
 7         this.r = r;
 8     }
 9     //可以一次读一行数据的方法。
10     public String myReadLine()throws IOException
11     //该异常应该抛不应try,因为这是一个功能,为了被其它使用者调用,出现的问题也应该由调用者处理
12     {
13         //定义一个临时容器。BufferReader封装的是字符数组。为了演示方便。定义一个StringBuilder容器。因为最终要将数据变成字符串。
14         StringBuilder sb = new StringBuilder();
15         int ch = 0;
16         while((ch=r.read())!=-1)
17         {
18             if(ch=='\r')
19                 continue;
20             if(ch=='\n')
21                 return sb.toString();
22             else
23                 sb.append((char)ch);
24         }
25         if(sb.length()!=0)
26             return sb.toString();
27         return null;        
28     }
29     //覆盖Reader类中的抽象方法。
30     public int read(char[] cbuf, int off, int len) throws IOException
31     {
32         return r.read(cbuf,off,len) ;
33     }
34     public void close()throws IOException
35     {
36         r.close();
37     }
38     public void myClose()throws IOException
39     {
40         r.close();
41     }
42 }

     装饰设计模式:

  概念:自定义一个类,将已有对象传入,基于已有的功能,并提供加强功能,该类就称为装饰类。装饰类通常会通过构造方法接收被装饰的对象,并在对象的基础功能上,提供更强的功能。通常情况下,装饰类和被装饰类都会同属于一个接口或者类,是同一体系中的成员。

  装饰和继承的区别:以前是通过继承让每一个子类都具备缓冲功能。那么做继承体系会很复杂,并不利于扩展。现在优化思想。单独描述一下缓冲内容。将需要被缓冲的对象传递进来。也就是,谁需要被缓冲,谁就作为参数传递给缓冲区。这样继承体系就变得很简单。优化了体系结构。装饰模式比继承要灵活。避免了继承体系臃肿。而且降低了类与类之间的关系。装饰类因为是增强已有对象,它具备的功能和已有的是相同的,只不过提供了更强功能。所以装饰类和被装饰类通常是都属于一个体系中的。

装饰类demo:

 1 class Person
 2 {
 3     public void chifan()
 4     {
 5         System.out.println("吃饭");
 6     }
 7 }
 8 class SuperPerson
 9 {
10     private Person p ;
11     SuperPerson(Person p)
12     {
13         this.p = p;
14     }
15     public void superChifan()
16     {
17         System.out.println("开胃酒");
18         p.chifan();
19         System.out.println("甜点");
20         System.out.println("来一根");
21     }
22 }
23 class  PersonDemo
24 {
25     public static void main(String[] args)
26     {
27         Person p = new Person();
28         //p.chifan();
29         SuperPerson sp = new SuperPerson(p);
30         sp.superChifan();
31     }
32 }

     LineNumberReader类:BufferedReader的子类,跟踪行号的缓冲字符输入流。此类定义了方法setLineNumber(int lineNumber)和getLineNumber(),它们可分别用于设置和获取当前行号。默认情况下,行编号从0开始。

三、字节流

  特点:不但可以操作字节流,还可以字符流等其它流媒体。

  字节流读一个字节的read()方法为什么返回值类型不是byte,而是int?

  因为有可能会读到连续8个二进制1的情况,8个二进制1对应的十进制是-1.那么就会数据还没有读完,就结束的情况。因为我们判断读取结束是通过结尾标记-1来确定的。所以,为了避免这种情况将读到的字节进行int类型的提升。并在保留原字节数据的情况前面了补了24个0,变成了int类型的数值。而在写入数据时,write()方法其实在做强转动作,只写该int类型数据的最低8位。所以,read()方法在做提升,write()方法在做强转。可以保证原数据不变化。

字节流自定义缓冲区:

 1 import java.io.*;
 2  class MyBufferedInputStream
 3  {
 4      private InputStream in;
 5      private byte[] buf = new byte[1024*4];
 6      private int pos = 0,count = 0;
 7      MyBufferedInputStream(InputStream in)
 8      {
 9          this.in = in;
10     }
11     //一次读一个字节,从缓冲区(字节数组)获取。
12     public int myRead()throws IOException
13     {
14         //通过in对象读取硬盘上数据,并存储buf中。
15             if(count==0){
16             count=in.read(buf);
17             if(count<0)
18                 return -1;
19             pos=0;
20         }
21         byte b=buf[pos];
22         pos++;
23         count--;
24         return b&255;//将b提升为int型并在前面补0
25     }
26     public void myClose()throws IOException
27     {
28         in.close();
29     }
30 }

 

四、转换流

    读取键盘的录入:System.in和System.out,分别代表了系统标准的输入和输出。默认的输入设备是键盘,输出设备是显示器。

    System.in:类型是InputStream,对应的标准输入设备:键盘。

         System.out:类型是PrintStream,它是 OutputStream 的子类 FilterOutputStream 的子类,默认的输出设备:控制台。

    InputStreamReader 和 OutputStreamWriter:

    InputStreamReader :字节转字符,字节流通向字符流的桥梁。将字节解码为字符,专门用于操作字节流的字符流对象。

      字节流转换为字符流 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

      OutputStreamWriter :字符转字节,字符流通向字节流的桥梁。将字符编码成字节。

      字符流转换为字节流 BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

  注意:转换流的原因是因为它可以指定编码表,它是字符流与字节流之间的桥梁,方便了字符流与字节流之间的操作。

  转换流的应用:字节流处理的是文本数据,即字节流中的数据都是字符时,转成字符流操作更高效。

  流操作的基本规律

    1、明确源和目的。

          源:输入流 InputStream   Reader

          目的:输出流 OutputStream  Writer

    2、操作的数据是否是纯文本。

          是:字符流。

          不是:字节流。

      3、明确使用哪个对象后。通过设备来进一步区分:

          源设备:内存,硬盘。键盘

          目的设备:内存,硬盘,控制台。

  转换流什么时候用?

        转换流是字符和字节之间的桥梁,通常,涉及到字符编码转换时,需要用到转换流。

  注意:可以通过System类的setIn(),setOut()方法对默认设备进行改变。

五、File类

  用来将文件或者文件夹封装成对象,方便对文件与文件夹的属性信息进行操作。File对象可以作为参数传递给流的构造函数。流对象也能操作文件,但不能操作文件夹和文件的属性信息。流只能操作数据,想要操作被数据封装成的文件的信息,必须使用file对象。

   File类常见方法:

      1、创建

            createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false。

              mkdir() :只能创建一级文件夹。

          mkdirs():可以创建多级文件夹。

       2、删除。

          delete():删除失败返回false。如果文件正在被使用,则删除不了返回false。

          deleteOnExit():在程序退出时删除指定文件。

       3、修改

          renameTo(File dest): 重新命名此抽象路径名表示的文件。有剪切的效果。

       4、判断

          canExecute() :判断文件是否可执行。

          exists() :文件是否存在.用流操作对象时,如果文件存在了才能去读取,如果文件不存在流一读就会抛异常。可以先用本方法判断。

              isFile():是否是文件。

        isDirectory():是否是目录。

                 isHidden():是否是隐藏文件。

                 isAbsolute():是否是绝对路径。
 
     5、获取信息

                 length() :文件大小,不能获取文件夹大小。

                 lastModified(): 最后一次修改的时间。

                 listRoots():返回当前的系统盘符。

                 getName()

                 getPath()

                 getAbsolutePath():当路径是绝度路径时,path和AbsolutePath返回的是一样的,当路径是相对路径时,path返回相对路径,AbsolutePath返回的是当前路径的前面加上所属目录的绝对路径。

                 getParent():getParent()+getName()=getPath()

                 getAbsoluteFile():File和String可以互换,字符串new一下可以变成对象,对象toString一下变成字符串。

                 getParentFile()

      6、其它

          list() :返回指定路径下的文件和文件夹名称,包括隐藏文件。调用list方法的file对象必须是封装了一个目录,该目录还必须存在。当对象是一个文件时,返回的是null,会造成空指针异常。如果对象是一个空目录,则返回一个长度为0的数组。

                 listFiles() :返回指定路径下的文件和文件夹对象。不能拿文件夹内的文件。

六、Properties类

   概念:它是Hashtable的子类。具备Map集合的特点,而且它里面存储的键值对都是字符串,是集合中和IO技术相结合的集合容器。

   特点:可以用于键值对形式的配置文件。那么在加载数据时,需要数据有固定格式:键=值。

   常用方法
       load(InputStream inStream):从输入流中读取属性列表(键和元素对)。

          load(Reader reader) :按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。

          list(PrintStream out) :将属性列表输出到指定的输出流。    

          list(PrintWriter out):将属性列表输出到指定的输出流。  

         setProperty(String key, String value) :调用 Hashtable 的方法 put。 改变的是内存中的结果,store方法是将内存中结果保存到文件中。   

      getProperty(String key) :用指定的键在属性列表中搜索属性。      

      stringPropertyNames() J:返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键。   

      store(OutputStream out, String comments) :以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。     

      store(Writer writer, String comments): 以适合使用 load(Reader) 方法的格式,将此 Properties 表中的属性列表(键和元素对)写入输出字符流。 
七、字符编码

   字符流的出现是为了方便操作字符,更重要是的加入了编码转换。

   编码表出现的原因:计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。这就是编码表。
   常见的编码表

     ASCII: 美国标准信息交换码。用一个字节的7位可以表示。

     ISO8859-1:拉丁码表,欧洲码表。用一个字节的8位表示。

     GB2312:中国的中文编码表,用两个字节表示。GBK:中国的中文编码表升级,融合了更多的中文文字符号。

     Unicode:国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java语言使用的就是unicode。

     UTF-8:最多用三个字节表示一个字符。一个字节可以的用一个字节,一个字节无法表示的用两个字节,两个字节无法表示的用3个字节,一个字节0开头,两个字节分别用110和10开头,三个字节分别用1110、10和10开头。

   转换流的编码应用:可以将字符以指定编码格式存储;可以对文本数据指定编码格式来解读;指定编码表的动作由构造函数完成。

   1、将“你好”两个字符查指定的utf-8的码表,获取对应的数字,并写入到text.txt文件中。

1 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("text.txt"),"utf-8");
2 osw.write("你好");
3 osw.close();

 

      2、读取硬盘上的文件数据,将获取到的数据查指定utf-8的码表来解析该数据。

1 InputStreamReader isr = new InputStreamReader(new FileInputStream("text.txt"),"utf-8");
2 char[] buf = new char[10];
3 int num = isr.read(buf);
4 String s = new String(buf,0,num);
5 System.out.println(s);

 

     传入编码表的方法都会抛出不支持编码异常(UnsupportedEncodingException)。

  字符编码:

    编码:字符串变成字节数组。

    String-->byte[]: str.getBytes(charsetName);

    解码:字节数组变成字符串。

    byte[]-->String: new String(byte[],charsetName);