--------- android培训java培训、期待与您交流!----------



一、流的分类

1、字节流与字符流

(1)区别

   ①字节注读取的时候,读到一个字节就返回一个字节。字符流使用了字节流读到一个或多个字节

(中文对应的字节数是两个,UTF-8码表中是3个字节)时。先去查指定的编码表,将查到的字符返回。

   ②字节流主要是由InputStream和OutputStream作为基类,而字符则主要由Reader和Writer作为基类。

   ③字节流可以独步一时所有类型数据,如图片,mp3,flv,avi等。而字符流只能处理字符数据,如文本。

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


2、输入流与输出流

按照流的流向来分,可以分为输入流与输出流

  • 输入流:只能从中读取数据,不能向其写入数据。

  • 输出流:只能向其写入数据,不能从其读取数据。

注意:划分输入/输出流是进从程序运行所在内存角度来考虑的。


3、节点流与处理流

  • 节点流:可以从/向一个特定的IO设备(如磁盘、网络)读/写数据的流。也称为低级流。

  • 处理流:对一个已存在的流进行连接或封装,通过封装后的流来实现数据的读/写。也称为高级流。

   实际上,Java使用处理流来包装节点流是一种典型的装饰设计模式,通过使用处理流来包装不同

的节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入/输出功能,因

此处理流也被称为包装流。


二、Java输入/输出流的体系

1、首先如图示(常见):

黑马程序员-Java基础知识预备之Java输入与输出_IO流

2、以表格的形式展现,如下表:

    分   类
    字节输入流    字节输出流   字符输入流   字符输出流
抽象基类InputStreamOutputStreamReaderWriter
访问文件FileInputStreamFileOutputStreamFileReaderFileWriter
访问数组ByteArrayInputStreamByteArrayOutputStreamCharArrayReaderCharArrayWriter
访问管道PipedInputStreamPipedOutputStreamPipedReaderPipedWriter
访问字符串

StringReaderStringWriter
缓冲流BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter
转换流

InputStreamReaderOutputStreamWriter
对象流ObjectInputStreamObjectOutputStream

抽象基类FilterInputStreamFilterOutputStreamFilterReaderFilterWriter
打印流
PrinntStream
PrintWriter
推回输入流PushbackInputStream
PushbackReader
特殊流DataInputStreamDataOutputStream

   注:表中的粗体字标出的类代表节点流,必须直接与指定的物理接点关联。加下划线的代表抽象基类,无法直接创建实例。


三、字符流

1、字符流:因为文件编码的不同,而有了对字符进行高效操作的字符流对象。

原理:其实就是基于字节流读取字节时,去查了指定的码表,也就是底层运用了字节流。

Reader(输入)
    |--InputStreamReader
        |--FileReader:专门用于处理文件的字符读取流对象。
Writer(输出)
    |--OutputStreamWriter
        |--FileWriter:专门用于处理文件的字符写入流对象。

2、在Reader里包含了如下常见的方法:

  • int read():从输入流中读取单个字符,返回所读取的字符数据(字符数据可直接转换为int类型)

,如果读到末尾,则返回-1.

  • int read(char[] cbuf):从输入流中最多读取cbuf.length个字符的数据,并将其存储在字符数组

cbuf中,返回实际读取的字符数,如果读到流的末尾,则返回-1;

  • int read(char[] cbuf, int off, int len):从输入流中最多读取len个字符的数据,并将其存储

在字符数组cbuf中,放入数组中时,从off位置开始,返回实际读取的字符数。如果读到流的末尾,则返回-1;

  • close():读取字符,其实用的是window系统的功能,就希望使用完毕后,进行资源的释放。


3、在Writer里包含了如下常见方法:

  • void write(int ch):将一个字符写入到流中。

  • void write(char[] buf):将一个字符数组写入到流中。

  • void write(String str):将一个字符串写入到流中。

  • flush():刷新流,将流中的数据刷新到目的地中,流还存在。

  • close():关闭资源,在关闭前会先调用flush(),刷新流中的数据去目的地,然后流关闭。


4、下面举几个例子来说明:

(1)、FileReader

class FileReaderTest
{
    public static void main(String[] args)
    {
        try
        {
            //创建一个字符读取流
            FileReader fr = new FileReader("FileReaderTest.java");
            //创建一个长为1024的容器
            char[] cbuf = new char[1024];
            //用于保存实际读取的字符数。
            int len = 0;
            while((len = fr.read(cbuf)) != -1)
            {
                //取出容器中的字符,将字符数组转换成字符串输出。
                System.out.println(new String(buf, 0 , len));
            }
        }
        catch (IOException ioe)
        {
            ioe.printStackTrace();
        }
    }
}

(2)、FileWriter:该类没有特有的方法只有自己的构造函数,用于处理文本文件,有默认的编码表(GBK),该类中有临时缓冲。

class FileWriterTest
{
    public static void main(String[] args)throws IOException
    {
        FileWriter fw = new FileWriter("temp.txt");
        fw.write("我爱\r\n");
        fw.write("编程\r\n");
        fw.close();
    }
}

5、字符流的缓冲区:缓冲区的出现提高了对流的操作效率。

(1)原理:其实就是将数组进行封装。

(2)对应的对象:

  • BufferedWriter:特有方法:newLine():跨平台的换行符。

  • BufferedReader:特有方法:readLine():一次读一行,到行标记时,将行标记之前的字符数据作为字符串返回。当读到末尾时,返回null。

(3)、在使用缓冲区对象时,要明确,缓冲的存在是为了增强流的功能而存在,所以在建立缓冲区

对象时,要先有流对象存在。其实缓冲内部就是在使用流对象的方法,只不过加入了数组对数据进行了临时存储。为了提高操作数据的效率。

(4)写入缓冲区对象。

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

(6)readLine()方法的原理

   其实缓冲区中的该方法,用的还是与缓冲区关联的流对象的read方法。只不过,每一次读到一个字符,先不进行具体操作,先进行临时存储。当读取到回车标记时,将临时容器中存储的数据一次性返回。既然明确了原理,我们也可以实现一个类似功能的方法。代码体现,自定一个一次读一行的方法,并增加了行号的功能。

class MyLineNumberReader
{
    private Reader r;
    MyLineNumberReader(Reader r)
    {
        this.r = r;
    }
    //因为这是自定义的方法,只要抛出异常即可,让调用它的对象去处理。
    public String myReadLine()throws IOException
    {
        lineNumber++;//如果不加此句,则行号全部为0
        //定义一个容器,原理就在这里。
        StringBuilder sb = new StringBuilder();
        int ch = 0;
        while((ch = r.read()) != -1)
        {
            if(ch == '\r')
                continue;
            if(ch == '\n')
                return sb.toString();
            else
                sb.append((char)ch);
        }
        return null;
    }
    public void setLineNumber(int lineNumber)
    {
        this.lineNumber = lineNumber;
    }
    public int getLineNumber()
    {
        return lineNumber;
    }
    public void myClose()throws IOException
    {
        r.close();
    }
}


四、字节流

1、抽象基类:InputStream, OutputStream。

2、字节流可以操作任何数据。

3、字节流使用的数组是字节数组,byte[];用法和字节流差不多,所以这里就不做绍。

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

   因为read方法读到末尾时返回的是一1,而在所操作的数据中的很容易出现连续多个1的情况,而连续读到8个1,就是一1,导致读取会提前停止。所以将读到的一个字节给提升为一个int类型的数值,但是只保留原字节,并在剩余二进制位补0,对于write方法,可以一次写入一个字节,但接收的是一个int类型数值。只写入该int类型的数值的最低一个字节(8位)。


五、转换流

1、是字节流和字符流之间的桥梁。


2、该流对象中可以对读取到的字节数据进行指定编码表的编码转换。


3、什么时候使用呢?

   ①当字节和字符之间有转换动作时。

   ②流操作的数据需要进行编码表的指定时。


4、具体的对象体现:

  • InputStreamReader:字节到字符的桥梁。

  • OutputStreamWriter:字符到字节的桥梁。

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


5、构造函数:

  • InputStreamReader(InputStream):通过该构造函数初始化使用的是本系统默认的编码表GBK。

  • InputStreamReader(InputStream,String charSet):通过该构造函数初始化,可以指定编码表。

  • OutputStreamWriter(OutputStream):通过该构造函数初始化使用的是本系统默认的编码表GBK。

  • OutputStreamWriter(OutputStream,String charSet):通过该构造函数初始化,可以指定编码表。

   注意:Windows下简体中默认使用GBK字符集,而Linux下简体中文默认使用UTF-8字符集。


六、打印流

1、该流提供了打印方法,可以将各种数据类型的数据都原样打印。


2、字节打印流:PrintStream


构造函数可以接收的参数类型:

(1).file对象,File

(2).字符串路径 String

(3).字节输出流。OutputStream


3、字符打印流:PrintWriter

构造函数可以接收物参数类型。

(1).file对象,File

(2).字符串路径 String

(3).字节输出流。OutputStream

(4).字符输出流。Writer


七、随机访问:RandomAccessFile

1、该类不算是IO体系中子类,而是直接继承Object类。


2、IO包中成员。因为它具备读和写功能。


3、内部封闭了一个数组,而且通过指针对数组的元素进行操作。


4、可以通过getFilePointer获取指针位置,同时可以通过seek改变指针的位置。


5、完成读写的原理就是内部封装了字节输入流和输出流。


6、通过构造函数可以看出,该类只能操作文件,而且操作文件还有模式:只读r,读写rw等。

  • 如果模式为只读r,不会创建文件。会去读取一个已存在,如果该文件不存在,则会出现异常。

  • 如果模式为rw.操作的文件不存在,会自动创建,如果存在则不会覆盖。


八、对象序列化

1、必须实现以下两接口之一:Serializable、Externalizable。


2、使用的对象流:ObjectInputStream、ObjectOutputStream。

   可以通过这两个流对象直接操作己有对象并将对象进行本地持久化存储,存储后的对象可以进行网络传输。


3、Serializable:该接口其实就是一个没有方法的标记接口。用于给类指定一个UID。

   该UID是通过类中的可序列化成员的数字签名运算出来的一个long型的值。只要是这些成员没有变化,那么该值每次运算都一样。该值用于判断被序列化的对象和类文件是否兼容。如果被序列化的对象需要被不同的类版本所兼容。可以在类中自定义UID。

   定义方式:static final long serialVersionUID二42L;

注意:对应静态的成员变量,不会被序列化。对应非静态也不想被序列化的成员而言,可以通过transient关键字修饰。通常,这两个对象成对使用。


九、总结

1、因为IO的类太多,我们在开发的时候怎么选呢?

主要还是要明确源和目的,只要明确了源和目的,这样就很容易理解和运用IO流对象了。


2、流操作的基本规律:

最痛苦的就是流对象有很多,不知道该用哪一个。

通过三个明确来完成:

①明确源和目的。

   源:输入流。

   InputStream  Reader

   目的:输出流。

   OutputStream Writer

②操作的数据是否是纯文本。

   是:字符流

   不是:字节流

③当体系明确后,再明确要使用哪个具体的对象。

   通过设备来进行区分:

   源设备:内存、硬盘、键盘

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


下面通过三个例子来说明:

*********************************************************************

需求一:将一个文本文件中数据存储到另一个文件中,复制文件。

1.源:因为是源,所以使用读取流 InputStream  Reader

是不是操作文本文件。

是!这时就可以选择Reader

这样体系就明确了。


接下来明确要使用该体系中的哪个对象。

明确设备:硬盘。上的一个文件

Reader体系中可以操作文件的对象是FileReader


是否需要提高效率:是! 加入Reader体系中缓冲区BufferedReader

FileReader fr = new FileReader("a.txt");
BufferedReader bufr = new BufferedReader(fr);

2.目的:OutputStream Writer

是否是纯文本

是!Writer。

设备:硬盘。上一个文件

Writer体系中可以操作文件的对象FileWriter


是否需要提高效率:是! 加入Reader体系中缓冲区BufferedWriter


FileWriter fw = new FileWriter("b.txt");
BufferedWriter bufw = new BufferedWriter(fw);


*********************************************************************

需求二:将一个图片文件中数据存储到另一个文件中,复制文件。要按照上述格式说明三个明确。


1.源:因为是源,所以要用到输入流:InputStream  Reader

是不是纯文本,

不是!所以选用字节流的InputStream。

这样体系就明确了

接下来,我们还要明确体系中具体使用的对象。

通过设备来区分

本次设备:硬盘,上的一个文件

InputStream体系中可以操作文件的对象是:FileInputStream


是否需要提高效率:是! 加入InputStream中子类FilterInputStream的缓冲区BufferedInputStream

FileInputStream fis = new FileInputStream("c:\\1.jpg");
BufferedInputStream bufs = new BufferedInputStream(fis);


2.目的:OutputStream  Writer

是否是纯文本,

不是!-->OutputStream

设备:硬盘,上的一个文件

OutputStream中可以操作文件的对象:FileOutputStream


是否需要提高效率:是!加入OutputStream中子类FilterOutputStream的缓冲区BufferedOutputStream


FileOutputStream fos = new FileOutputStream("c:\\0.jpg");
BufferedOutputStream bfos = new BufferedOutputStream(fos);

*********************************************************************


需要三:将键盘录入的数据保存到一个文件中。

这个需求中有源和目的都存在。

那么分别分析

源:InputStream Reader

是不是纯文本?是!Reader


设备:键盘。对应的对象是System.in

不是选择Reader吗?System.in对应的不是字节流吗?

为了操作键盘的文本数据方便。转成字符流按照字符串操作是最方便的。

所以既然明确了Reader,那么就将System.in转成字符流。

用了Reader体系中转换流,InputStreamReader


InputStreamReader isr = new InputStreamReader(System.in);

需要提高效率吗?需要! BufferedReader


BufferedReader bufr = new BufferedReader(isr);


目的:OutputStream Writer

是否是纯文体?是!Writer

设备:硬盘。一个文件,使用FileWriter。(默认字符编码GBK)

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

需要提高效率吗?需要

BufferedWriter bufw = new BufferedWriter(fw);

*********************************************************************


--------- android培训java培训、期待与您交流!----------