Java---IO流知识总结

一、结构图



二、分类
     IO 流:用于处理设备上的数据。 设备:硬盘,内存,键盘录入。
    
    IO流分类:
          1,根据处理的数据类型不同:字节流和字符流。
          2,根据流向不同:输入流和输出流。

    字符流的由来:
          因为文件编码的不同,而有了对字符进行高效操作的字符流对象。
        原理:其实就是基于字节流读取字节时,去查了指定的码表。

     字节流和字符流的区别:
          1,字节流读取的时候,读到一个字节就返回一个字节。
               字符流使用了字节流读到一个或多个字节(中文对应的字节数是两个,在 UTF-8 码表中是 3 个字节)时。先去查指定的编码表,将查到的字符返回。

          2,字节流可以处理所有类型数据,如图片,mp3,avi。   而字符流只能处理字符数据。

结论: 只要是处理纯文本数据,就要优先考虑使用字符流。除此之外都用字节流。

IO 体系 所具备的基本功能就有两个 :读 和 写。
     1,字节流: InputStream(读),OutputStream(写)。
    2,字符流: Reader(读),Writer(写)。

基本的读写操作方式:
          因为数据通常都以文件形式存在, 所以就要找到 IO 体系中可以用于操作文件的流对象。 通过名称可以更容易获取该对象。
        因为 IO 体系中的子类名后缀,绝大部分是父类名称。而前缀,都是体现子类功能的名字。

字符流:
Reader
     |--InputStreamReader
          |--FileReader:专门用于处理文件的字符读取流对象。

Writer
     |--OutputStreamWriter
          |--FileWriter:专门用于处理文件的字符写入流对象。

Reader 中的常见的方法:
     1,int read(): 读取一个字符。返回的是读到的那个字符。如果读到流的末尾,返回-1.
     2,int read(char[]): 将读到的字符存入指定的数组中,返回的是读到的字符个数,也就是往数组里 装的元素的个数。如果读到流的末尾,返回-1.
    3,close():  读取字符其实用的是 window 系统的功能,就希望使用完毕后,进行资源的释 放。

Writer 中的常见的方法:
     1,write(ch): 将一个字符写入到流中。
     2,write(char[]): 将一个字符数组写入到流中。
     3,write(String): 将一个字符串写入到流中。
     4,flush():刷新流,将流中的数据刷新到目的地中,流还存在。
     5,close():关闭资源:在关闭前会先调用 flush(),刷新流中的数据去目的地。然流 关闭。

FileWriter:
     该类没有特有的方法。只有自己的构造函数。
     特点:
          1,用于处理文本文件。
          2,该类中有默认的编码表,
          3,该类中有临时缓冲。
    
构造函数 :在写入流对象初始化时,必须要有一个存储数据的目的地。
     FileWriter(String filename):
     该构造函数做了什么事情呢?
          1,调用系统资源。
          2,在指定位置,创建一个文件。
                 注意:如果该文件已存在,将会被覆盖。

     FileWriter(String filename,boolean append): 该构造函数:
            当传入的 boolean 类型值为 true 时,会在指定文件末尾处进行数 据的续写。

FileReader:
     1,用于读取文本文件的流对象。
     2,用于关联文本文件。
     构造函数:在读取流对象初始化的时候,必须要指定一个被读取的文件。
    
     如果该文件不存在会发生 FileNotFoundException.
     FileReader(String filename);

     对于读取或者写入流对象的构造函数,以及读写方法, 还有刷新关闭功能都会抛出 IOException 或其子类。 所以都要进行处理。或者 throws 抛出,或者 try catch处理;

另一个小细节:
     当指定绝对路径时,定义目录分隔符有两种方式:
          1,反斜线 但是一定要写两个。\\  new FileWriter("c:\\demo.txt");
          2,斜线  /
     写一个即可。 new FileWriter("c:/demo.txt”);

字符流的缓冲区:
     缓冲区的出现提高了对流的操作效率。
     原理:其实就是将数组进行封装。
     对应的对象:
          BufferedWriter:
               特有方法: newLine():写一个换行符,跨平台的换行符。
          BufferedReader:
               特有方法: readLine():一次读一行,到行标记时,将行标记之前的字符数据作为字符串返 回。当读到末尾时,返回 null。不包含回车符。

     在使用缓冲区对象时,要明确, 缓冲的存在是为了增强流的功能而存在,   所以在建立缓冲区对象时,要先有流对象存在。
     其实缓冲内部就是在使用流对象的方法,只不过加入了数组对数据进行了临时存储。为 了提高操作数据的效率。

代码上的体现:
     写入缓冲区对象。
     //建立缓冲区对象必须把流对象作为参数传递给缓冲区的构造函数。
          BufferedWriter bufw = new BufferedWriter(new FileWriter("buf.txt"));
          bufw.write("abce");//将数据写入到了缓冲区。
          bufw.flush();//对缓冲区的数据进行刷新。将数据刷到目的地中。
          bufw.close();//关闭缓冲区,其实关闭的是被包装在内部的流对象。
     读取缓冲区对象。
          BufferedReader bufr = new BufferedReader(new FileReader("buf.txt"));
          String line = null;
     //按照行的形式取出数据。取出的每一个行数据不包含回车符。
          while((line=bufr.readLine())!=null) {
               System.out.println(line);
        }
          bufr.close();

练习 :通过缓冲区的形式,对文本文件进行拷贝。
     public static void main(String[] args) {
          BufferedReader bufr = new BufferedReader(new FileReader("a.txt"));
          BufferedWriter bufw = new BufferedWriter(new FileWriter("b.txt"));
          String line = null;
         
          while((line=bufr.readLine())!=null) {
               bufw.write(line);
               bufw.newLine();
               bufw.flush();
          }
          bufw.close();
          bufr.close();
    }

readLine():方法的原理
     其实缓冲区中的该方法,用的还是与缓冲区关联的流对象的 read 方法。 只不过,每一次读到一个字符,先不进行具体操作,先进行临时存储。
     当读取到回车标记时,将临时容器中存储的数据一次性返回。

     它的出现 基于流并增强了流的功能 这也是一种设计模式的体现: 装饰设计模式   对一组对象进行功能的增强。
     该模式和继承有什么区别呢? 它比继承有更好的灵活性。 通常装饰类和被装饰类都同属与一个父类或者接口。

字节流:
     字节流可以操作任何数据。
     抽象基类:InputStream,OutputStream。
     注意:字符流使用的数组是字符数组。char [] chs
                            字节流使用的数组是字节数组。byte [] bt

     FileOutputStream fos = new FileOutputStream("a.txt");
     fos.write("abcde");//直接将数据写入到了目的地。
     fos.close();//只关闭资源。
     FileInputStream fis = new FileInputStream("a.txt");
     //fis.available();//获取关联的文件的字节数。

     //如果文件体积不是很大。 可以这样操作。 但是这有一个弊端,就是文件过大,大小超出 jvm 的内容空间时,会内存溢出。  
        byte[] buf = new byte[fis.available()];//创建一个刚刚好的缓冲区。
         fis.read(buf);
        System.out.println(new String(buf));

转换流:
     特点:
          1,是字节流和字符流之间的桥梁。
          2,该流对象中可以对读取到的字节数据进行指定编码表的编码转换。

     什么时候使用呢?
          1,当字节和字符之间有转换动作时。
          2,流操作的数据需要进行编码表的指定时。

     具体的对象体现:
          1,InputStreamReader:字节到字符的桥梁。
          2,OutputStreamWriter:字符到字节的桥梁。

     这两个流对象是字符流体系中的成员。 那么 它们有转换作用,而本身又是字符流。所以在构造的时候,需要传入字节流对象进 来

     构造函数:
          InputStreamReader(InputStream):通过该构造函数初始化, 使用的是本系统默认的编码表 GBK。
          InputStreamReader(InputStream,String charSet):通过该构造函数初始化,可以指定编码表。
          OutputStreamWriter(OutputStream):通过该构造函数初始化,使用的是本系统默认的编 码表 GBK。
          OutputStreamWriter(OutputStream,String charSet):通过该构造函数初始化,可以指定编码 表。

     操作文件的字符流对象是转换流的子类。
           Reader
               |--InputStreamReader
                |--FileReader

          Writer
               |--OutputStreamWriter
               |--FileWriter

     转换流中的 read 方法。已经融入了编码表 在底层调用字节流的 read 方法时将获取的一个或者多个字节数据进行临时存储, 并去查指定的编码表,如果编码           表没有指定, 查的是默认码表。那么转流的 read 方法就可以返回一个字符比如中文。
     转换流已经完成了编码转换的动作,对于直接操作的文本文件的 FileReaer 而言,就不 用在重新定义了, 只要继承该转换流,获取其方法,就可以直接操作文本文件中的字符数据了。

注意:
     在使用 FileReader 操作文本数据时,该对象使用的是默认的编码表。 如果要使用指定编码表时,必须使用转换流。

     FileReader fr = new FileReader("a.txt");//操作 a.txt 的中的数据使用的本系统默认的 GBK 。 操作 a.txt 中的数据使用的也是本系统默认的 GBK。
     InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));
     这两句的代码的意义相同。

     如果 a.txt 中的文件中的字符数据是通过 utf-8 的形式编码。 那么在读取时,就必须指定编码表。
     那么转换流必须使用: InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"utf-8”);

IO 其他对象:

1,打印流,
          PrintStream:
                是一个字节打印流 ,System.out 对应的类型就是 PrintStream。 它的构造函数可以接收三种数据类型的值。
                1,字符串路径。
              2 ,File 对象。
              3 ,OutputStream。
         
          PrintWriter:
               是一个字符打印流 。构造函数可以接收四种类型的值。
               1,字符串路径。
                2,File 对象。
               对于 1,2 类型的数据,还可以指定编码表。也就是字符集。
               3,OutputStream
               4,Writer
               对于 3,4 类型的数据,可以指定自动刷新。
               注意:该自动刷新值为 true 时,只有三个方法可以用:println,printf,format.
              
          如果想要 既有自动刷新,又可执行编码 。如何完成流对象的包装?
               PrintWrter pw =  new PrintWriter(new OutputSteamWriter(new FileOutputStream("a.txt"),"utf-8"),true);
          如果想要提高效率。还要使用打印方法。
               PrintWrter pw = new PrintWriter(new BufferdWriter(new OutputSteamWriter(new FileOutputStream("a.txt"),"utf-8")),true);
    
2,管道流。
          PipedInputStream 和  PipedOutputStream
     特点:
          读取管道流和写入管道流可以进行连接。
     连接方式:
          1,通过两个流对象的构造函数。
          2,通过两个对象的 connect 方法。

     通常两个流在使用时,需要加入多线程技术,也就是让读写同时运行。 注意;对于 read 方法。该方法是阻塞式的,也就是没有数据的情况,该方法会等待。

3,RandomAccessFile:
          该对象并不是流体系中的一员。 该对象中封装了字节流,同时还封装了一个缓冲区(字节数组),通过内部的指针来操作 数组中的数据。

          特点:
          1,该对象只能操作文件,所以构造函数接收两种类型的参数。
               a,字符串路径。
               b,File 对象。
          2,该对象既可以对文件进行读取,也可以写入。
               在进行对象实例化时,必须要指定的该对象的操作模式,r rw 等。

     该对象中有可以直接操作基本数据类型的方法。
     该对象最有特点的方法:
          skipBytes():跳过指定的字节数。
          seek():指定指针的位置。
          getFilePointer():获取指针的位置。
     通过这些方法,就可以完成对一个文件数据的随机的访问 想读哪里就读哪里,想往哪里写就往哪里写。
     该对象功能,可以读数据,可以写入数据,如果写入位置已有数据,会发生数据覆盖。 也就是可以对数据进行修改。
     在使用该对象时,建议数据都是有规则的。或者是分段的。

     注意;该对象在实例化时,如果要操作的文件不存在,会自动建立。
                  如果要操作的文件存在,则不会建立,如果存在的文件有数据, 那么在没有指定指针位置的情况下,写入数据,会将文件开头的数据覆盖。
                  可以用于多线程的下载,也就是通过多线程往一个文件中同时存储数据。

序列流。也称为合并流。
     SequenceInputStream:
     特点:可以将多个读取流合并成一个流。这样操作起来很方便。
     原理:其实就是将每一个读取流对象存储到一个集合中。最后一个流对象结尾作为这个 流的结尾。

     两个构造函数:
          1,SequenceInputStream(InputStream in1,InputStream in2)  
               可以将两个读取流合并成一个流。
          2,SequenceInputStream(Enumeration<? extends InputStream> en) 
               可以将枚举中的多个流合并成一个流。 
               作用: 可以用于多个数据的合并。
  
 如果要一个对文件数据切割。  一个读取对应多了输出。
     FileInputStream fis = new FileInputStream("1.mp3");
     FileOutputStream fos  = null;
     byte[] buf = new byte[1024*1024]; //是一个 1MB 的缓冲区。

     int len = 0;
     int count = 1;

     while((len=fis.read(buf))!=-1)  {
          fos = new FileOutputStream((count++)+".part);
          fos.write(buf,0,len);
          fos.close();
     }
     fis.close();

    这样就是将 1.mp3 文件切割成多个碎片文件。 想要合并使用 SequenceInputStream 即可。
    对于切割后,合并是需要的一些源文件的信息。 可以通过配置文件进行存储。该配置可以通过键=值的形式存在。 然后通过 Properties 对象进行数据的加载和获取。

File 类:
     该类的出现是对文件系统的中的文件以及文件夹进行对象的封装
     可以通过对象的思想来操作文件以及文件夹。

     1,构造函数:
               File(String filename):将一个字符串路径(相对或者绝对)封装成 File 对象,该路径是可存在的,也可以是不存在(需要判断)。
               File(String parent,String child);
               File(File parent,String child);

     2,特别的字段:separator:跨平台的目录分隔符。
             例子:File file = new File("c:"+File.separator+"abc"+File.separator+"a.txt");

     3,常见方法:
          1,创建:
               boolean createNewFile () throws IOException: 创建文件,如果被创建的文件已经存在,则不创建。
               boolean mkdir( ): 创建文件夹。
               boolean mkdirs( ): 创建多级文件夹。

          2,删除:
               boolean delete( ):可用于删除文件或者文件夹。
                         注意:对于文件夹只能删除不带内容的空文件夹 对于带有内容的文件夹,不可以直接删除,必须要从里往外删除。
               void deleteOnExit( ): 删除动作交给系统完成。无论是否反生异常,系统在退出时执行删除动作。

          3,判断:
               boolean canExecute():
               boolean canWrite( ):
               boolean canRead( );
               boolean exists( ):判断文件或者文件夹是否存在。
               boolean isFile( ): 判断 File 对象中封装的是否是文件。
               boolean isDirectory( ):判断 File 对象中封装的是否是文件夹。
               boolean isHidden( ):判断文件或者文件夹是否隐藏。在获取硬盘文件或者文件夹 时,
               对于系统目录中的文件,java 是无法访问的,所以在遍历,可以避免遍历 隐藏文件。

          4,获取:
               getName( ):获取文件或者文件夹的名称。
               getPath( ):File 对象中封装的路径是什么,获取的就是什么。
               getAbsolutePath( ):无论 File 对象中封装的路径是什么,获取的都是绝对路径。
               getParent( ): 获取 File 对象封装文件或者文件夹的父目录。
              注意:如果封装的是相对路径,那么返回的是 null.

               long length( ):获取文件大小。
               long lastModified( ):获取文件或者文件最后一次修改的时间。
               static File [] listRoots():获取的是被系统中有效的盘符。
               String[] list( ):获取指定目录下当前的文件以及文件夹名称。
               String[] list(Filenamefilter): 可以根据指定的过滤器,过滤后的文件及文件夹名 称。
               File[] listFiles( ):获取指定目录下的文件以及文件夹对象。

          5,重命名:
               renameTo(File):
               File f1 = new File("c:\\a.txt");
               File f2 = new File("c:\\b.txt");
               f1.renameTo(f2);//将 c 盘下的 a.txt 文件改名为 b.txt 文件。

操作基本数据类型的流对象
    DataInputStream
        DataInputStream(InputStream);
    操作基本数据类型的方法:
        int readInt():一次读取四个字节,并将其转成 int 值。
        boolean readBoolean():一次读取一个字节。
        short readShort();
        long readLong();
               剩下的数据类型一样。
        String readUTF():按照 utf-8 修改版读取字符。注意,它只能读 writeUTF()写入的字符 数据。

    DataOutputStream
        DataOutputStream(OutputStream):
    操作基本数据类型的方法:
        writeInt(int):一次写入四个字节。
            注意和 write(int)不同。write(int)只将该整数的最低一个 8 位写入。剩余三个 8 位丢弃。
        writeBoolean(boolean);
        writeShort(short);
        writeLong(long);
            剩下是数据类型也也一样。
        writeUTF(String):按照 utf-8 修改版将字符数据进行存储。只能通过 readUTF 读取。
        通常只要操作基本数据类型的数据。就需要通过 DataStram 进行包装。

          通常成对使用。

操作数组的流对象
1,操作字节数组
        ByteArrayInputStream

        ByteArrayOutputStream
            toByteArray();
            toString();
            writeTo(OutputStream);

2,操作字符数组。
        CharArrayReader
        CharArrayWriter

         对于这些流,源是内存。目的也是内存 而且这些流并未调用系统资源。使用的就是内存中的数组。 所以这些在使用的时候不需要 close。
        操作数组的读取流在构造时,必须要明确一个数据源。所以要传入相对应的数组。 对于操作数组的写入流,在构造函数可以使用空参数。因为它内置了一个可变长度数组 作为缓冲区。

        这几个流的出现其实就是通过流的读写思想在操作数组。 类似的对象同理: StringReader, StringWriter

编码转换
        在 io 中涉及到编码转换的流是转换流和打印流。 但是打印流只有输出。 在转换流中是可以指定编码表的。 默认情况下,都是本机默认的码表。GBK. 这个编码表怎么来的? System.getProperty("file.encoding");

常见码表:
    ASCII:美国标准信息交换码。使用的是 1 个字节的 7 位来表示该表中的字符。
    ISO8859-1:拉丁码表。使用 1 个字节来表示。
    GB2312:简体中文码表。
    GBK:简体中文码表,比 GB2312 融入更多的中文文件和符号。
    unicode:国际标准码表。都用两个字节表示一个字符。
    UTF-8:对 unicode 进行优化,每一个字节都加入了标识头。

编码转换:
    字符串  —> 字节数组  :编码。通过 getBytes(charset);
    字节数组-->字符串 : 解码。通过 String 类的构造函数完成。String(byte[],charset);
    如果编错了,没救! 如果编对了,解错了,有可能还有救!

    String s = "你好";
    //编码。
        byte[] b = s.getBytes("GBK");
    //解码。
        String s1 = new String(b,"iso8859-1");
        System.out.println(s1);

    //想要还原。
    /*  对 s1 先进行一次解码码表的编码。获取原字节数据。
        然后在对原字节数据进行指定编码表的解码。
    */
        byte[] b1 = s1.getBytes("iso8859-1");
        String s2 = new String(b1,"gbk");
        System.out.println(s2);//你好。

        这种情况在 tomcat 服务器会出现。 因为 tomcat 服务器默认是 iso8859-1 的编码表。 所以客户端通过浏览器向服务端通过 get 提交方式提交中文数据时, 服务端获取到会使用 ISO8859-1 进行中文数据解码。会出现乱码。 这时就必须要对获取的数据进行 iso8859-1 编码。然后在按照页面指定的编码表进行解码即可。
        而对于 post 提交,这种方法也通用。但是 post 有更好的解决方式。 request.setCharacterEncoding("utf-8");即可。 所以建立客户端提交使用 post 提交方式。

相关知识:

1.如果启用了自动刷新,则只有在调用 println、printf 或 format 的能够实现数据写入到流所对应的文件

2.序列化流: 把对象写入到流中
        ObjectOutputStream

         构造方法:
             ObjectOutputStream()
             ObjectOutputStream(OutputStream out)
         
         方法:
             public final void writeObject(Object obj) throws IOException
             将指定的对象写入 ObjectOutputStream

 反序列化流:从流中读取对象
         ObjectInputStream
             构造方法:
                  ObjectInputStream()
                  ObjectInputStream(InputStream in)

             方法:
             public final Object readObject() throws IOException, ClassNotFoundException
             从 ObjectInputStream 读取对象
注意: 使用序列化与反序列化的类通过实现 java.io.Serializable 接口以启用其序列化功能。
              未实现此接口的类将无法使其任何状态序列化或反序列化, 序列化接口没有方法或字段,仅用于标识可序列化的语义。
             使用transient关键字声明不需要序列化的成员变量。

Properties和IO流的结合使用
    public void load(InputStream stream)
    public void load(Reader reader) 把流所对应的文件中的数据,读取到集合中
    public void store(OutputStream stream,String comments)
    public void store(Writer writer,String comments) 把集合中的数据,存储到流所对应的文件中

close()和flush()的区别:
    flush():将缓冲区的数据刷到目的地中后,流可以使用。
    close():将缓冲区的数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底层资源。这个动作一定做。
          //记住,只要一读取键盘录入,就用这句话。
                BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
          //输出到控制台
               BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

流的操作规律:
1,明确数据源和目的地。( 明确输入流还是输出流。
       数据源:就是需要读取,可以使用两个体系:InputStream、Reader;
        目的地:就是需要写入,可以使用两个体系:OutputStream、Writer;

2,操作的数据是否是纯文本数据?( 明确字符流还是字节流
      如果是:数据源:Reader
                    目的地:Writer
      如果不是:数据源:InputStream
                           目的地:OutputStream

3,虽然确定了一个体系,但是该体系中有太多的对象,到底用哪个呢?
          明确操作的数据设备。
               数据源对应的设备:硬盘(File),内存(数组),键盘(System.in)
               数据汇对应的设备:硬盘(File),内存(数组),控制台(System.out)。

4,需要在基本操作上附加其他功能吗?比如缓冲。
          如果需要就进行装饰。

    数据汇:一个文件,硬盘。
          既然是数据汇,那么一定是输出流,可以用的 OutputStream,Writer。 往文件中存储的都是文本数据,那么可以使用字符流较为方便:Writer.
          因为操作的是一个文件。所以使用 Writer 中的 FileWriter。
          是否要提高效率呢? 是,那就使用 BufferedWriter.  
               BufferedWriter bufr = new BufferedWriter(new FileWriter("a.txt"));

需求:
1,将键盘录入的数据存储到一个文件中。
          数据源:System.in。
          既然是源,使用的就是输入流,可用的体系有 InputStream,Reader。
          因为键盘录入进来的一定是纯文本数据,所以可以使用专门操作字符数据的 Reader。 发现 System.in 对应的流是字节读取流 。所以要将其进行转换,将字节转成 字符即可。 所以要使用 Reader 体系中:InputStreamReader。
          接下来,是否需要提高效率呢?如果需要,那么就加入字符流的缓冲区: BufferedReader
          BufferedReader bur = new BufferedReader(new InputStreamReader(System.in));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值