简述JAVA IO流以及IO流中的适配器模式、装饰模式

摘要:读完本章节,您对java 的IO流有更清晰深刻的认识,对适配器模式、装饰模式也有初步的了解。

        一、关于流引用百度百科上的解释:

        流是一种抽象概念,它代表了数据的无结构化传递。按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列。从流中取得数据的操作称为提取操作,亦称读操作;而向流中添加数据的操作称为插入操作,亦称写操作。用来进行输入输出操作的流就称为IO流。换句话说说,IO流就是以流的方式进行输入输出。应用场景如Java Web上的文件上传与下载、磁盘文件的读操作写操作,这些都需要用到流。

       二、流的分类:

        按流的流向分为输入流、输出流,InputStream、Reader及其所有子类都是输入流,OutputStream、Writer及其所有子类都是输出流,从流中读数据用输入流,写数据到流中用输出流。

        按流的数据单位分为字节流、字符流,InputStream、OutputStream及其所有子类都是字节流,Reader、Writer及其所有子类都是字符流,当操作流中的数据含有中文的时候,请务必使用字符流进行读写。如果流中含有中文而使用字节流进行读写有可能出现中文乱码、数据失真现象,因为数据如果是GBK编码一个中文汉字含有两个字节,如果是UTF-8编码一个中文汉字含有三个字节,读写的时候如若处理不当,很容易产生中文乱码。

       按流的功能分为节点流、处理流(过滤流),节点流是指从特定地方读写的流类如磁盘或者一块内存区域内存,比如ByteArrayInputStream、FileInputStream等,处理流是指通过已经存在的输入输出流构造的流,比如BufferedInputStream、DataInputStream等

        三、输入流详解

        a、字节输入流InputStream及其子类,

             InputStream主要方法:

            1、public abstract int read() throws IOException;

           从输入流中读取数据的下一个字节。返回0到255范围内的int值。如果因为已经到达流末尾而没有可用的字节, 则返回-1。在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。子类必须提供此方法的实现。

            2、public int read(byte b[]) throws IOException {...};

           从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中。以整数形式返回实际读取的字节个数。在输入数据可用、检测到文件末尾或者抛出异常前,此方法一直阻塞。如果数组b的长度为0,则不读取任何字节并返回0;否则,尝试读取至少一个字节。如果因为流位于文件末尾而没有可用的字节,则返回值-1;否则,至少读取一个字节并将其存储在b中。将读取的第一个字节存储在元素b[0]中,下一个存储在b[1]中,以此类推。读取的字节数最多等于b的长度。设k为实际读取的字节数;这些字节将存储在b[0]到b[k-1]中,不影响b[k]到b[b.length-1]的元素。类InputStream的read(b)方法的效果等同于read(b,0,b.length)。

             3、public int read(byte b[], int off, int len) throws IOException {...};

              将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。如果 len 为 0,则不读取任何字节并返回 0;否则,尝试读取至少一个字节。如果因为流位于文件末尾而没有可用的字节,则返回值 -1;否则,至少读取一个字节并将其存储在 b 中。将读取的第一个字节存储在元素 b[off] 中,下一个存储在 b[off+1] 中,依次类推。读取的字节数最多等于 len。设 k 为实际读取的字节数;这些字节将存储在 b[off]b[off+k-1] 的元素中,不影响 b[off+k]b[off+len-1] 的元素。在任何情况下,b[0]b[off] 的元素以及 b[off+len]b[b.length-1] 的元素都不会受到影响。类 InputStreamread(b, off, len) 方法重复调用方法 read()。如果第一次这样的调用导致 IOException,则从对 read(b, off, len) 方法的调用中返回该异常。如果对 read() 的任何后续调用导致 IOException,则捕获该异常并将其视为到达文件末尾;到达该点时读取的字节存储在 b 中,并返回发生异常之前读取的字节数。在已读取输入数据 len 的请求数量、检测到文件结束标记、抛出异常前,此方法的默认实现将一直阻塞。建议子类提供此方法更为有效的实现

       4、public long skip(long n) throws IOException {...};

       跳过和丢弃此输入流中数据的 n 个字节。出于各种原因,skip 方法结束时跳过的字节数可能小于该数,也可能为 0。导致这种情况的原因很多,跳过 n 个字节之前已到达文件末尾只是其中一种可能。返回跳过的实际字节数。如果 n 为负,则不跳过任何字节。此类的 skip 方法创建一个 byte 数组,然后重复将字节读入其中,直到读够 n 个字节或已到达流末尾为止。建议子类提供此方法更为有效的实现。例如,可依赖搜索能力的实现。

        5、public int available() throws IOException {...};

        返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数,即返回此输入流未读取字节数。下一个调用可能是同一个线程,也可能是另一个线程。一次读取或跳过此估计数个字节不会受阻塞,但读取或跳过的字节数可能小于该数。注意,有些 InputStream 的实现将返回流中的字节总数,但也有很多实现不会这样做。试图使用此方法的返回值分配缓冲区,以保存此流所有数据的做法是不正确的。如果已经调用 close() 方法关闭了此输入流,那么此方法的子类实现可以选择抛出 IOExceptionInputStreamavailable 方法总是返回 0。此方法应该由子类重写。

         6、public void close() throws IOException {};

         关闭此输入流并释放与该流关联的所有系统资源。InputStreamclose 方法不执行任何操作。

         7、public synchronized void mark(int readlimit) {};

         在此输入流中标记当前的位置。对 reset 方法的后续调用会在最后标记的位置重新定位此流,以便后续读取重新读取相同的字节。readlimit 参数告知此输入流在标记位置失效之前允许读取的字节数。mark 的常规协定是:如果方法 markSupported 返回 true,那么输入流总是在调用 mark 之后记录所有读取的字节,并时刻准备在调用方法 reset 时(无论何时),再次提供这些相同的字节。但是,如果在调用 reset 之前可以从流中读取多于 readlimit 的字节,则不需要该流记录任何数据。标记已关闭的流对其无效。InputStreammark 方法不执行任何操作。

          8、public synchronized void reset() throws IOException {...};

          将此流重新定位到最后一次对此输入流调用 mark 方法时的位置。除了抛出 IOException 之外,类 InputStream 的方法 reset 不执行任何操作。

           9、public boolean markSupported() {...};

        测试此输入流是否支持 markreset 方法。是否支持 markreset 是特定输入流实例的不变属性。InputStreammarkSupported 方法返回 false。如果此输入流实例支持 mark 和 reset 方法,则返回 true;否则返回 false

          InputStream子类:

          InputStream是一个抽象类,表示字节输入流的所有类的超类,其提供的大部分方法需要子类去实现,子类的所有功能也围绕这几个方法进行扩展实现。InputStream的直接子类有7个,分别是ByteArrayInputStream, FileInputStream, FilterInputStream, ObjectInputStream, PipedInputStream, SequenceInputStream, StringBufferInputStream

          1、ByteArrayInputStream

          包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException

             字段以及构造方法描述,其他方法功能描述与父类InputStream一致,不再说明。截图自JDK API1.6文档(下同):

                            

       2、FileInputStream

        从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader

                           

           3、FilterInputStream

          包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。FilterInputStream 类本身只是简单地重写那些将所有请求传递给所包含输入流的 InputStream 的所有方法。FilterInputStream 的子类可进一步重写这些方法中的一些方法,并且还可以提供一些额外的方法和字段。FilterInputStream子类有BufferedInputStream、DataInputStream、PustbackInputStream。                       

         3.1、BufferedInputStream

         为另一个输入流添加一些功能,即缓冲输入以及支持 markreset 方法的能力。在创建 BufferedInputStream 时,会创建一个内部缓冲区数组。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。mark 操作记录输入流中的某个点,reset 操作使得在从包含的输入流中获取新字节之前,再次读取自最后一次 mark 操作后读取的所有字节。

                          

        3.2、DataInputStream

         数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。DataInputStream 对于多线程访问不一定是安全的。

         3.3、PustbackInputStream

          为另一个输入流添加性能,即“推回 (push back)”或“取消读取 (unread)”一个字节的能力。在代码片段可以很方便地读取由特定字节值分隔的不定数量的数据字节时,这很有用;在读取终止字节后,代码片段可以“取消读取”该字节,这样,输入流上的下一个读取操作将会重新读取被推回的字节。例如,表示构成标识符字符的字节可能由表示操作符字符的字节终止;用于读取一个标识符的方法可以读取到遇到操作符为止,然后将该操作符推回以进行重读。

          ObjectInputStreamPipedInputStreamSequenceInputStreamStringBufferInputStream由于篇幅原因而且并不常用,这里不再阐述,后续如有遇到会逐个介绍。                           

        b、字符流Reader及其子类

            字符流与字节流的方法大体一致,唯一不同的就是,读取的时候字符流是一个一个字符的读或者读字符数组,而字节流是一个一个字节的读或者读字节数组,故关于字符流的类不再详细阐述。

        c、两个demo

            字节流demo:

// 字节流读取文件内容
		// stream.txt文件内容是英文数字
		File file = new File("D:/work/test/stream.txt");
		FileInputStream is = new FileInputStream(file);
		int i;
		StringBuffer sb = new StringBuffer();
		// 一个字节一个字节的读,如有字节返回该字节的int值,读到文件末尾返回-1
		// while ((i = is.read()) != -1) {
		// sb.append((byte) i);
		// }
		byte[] b = new byte[1024];
		// 将数据读到缓存数组中,每次最多读b.length个字节,返回实际读到的字节数,如到文件末尾返回-1
		while ((i = is.read(b)) != -1) {
			sb.append(new String(b, 0, i));
		}
		is.close();

            字符流demo:

// 字符流读取文件内容
		// 文件包含中文
		File file = new File("D:/work/test/reader.txt");
		// 可指定编码创建字符流对象
		InputStreamReader reader = new InputStreamReader(new FileInputStream(file), "GBK");
		StringBuffer sb = new StringBuffer();
		int i;
		// 一个字符一个字符的读,如有字符返回该字节的int值,读到文件末尾返回-1
		// while ((i = reader.read()) != -1) {
		// sb.append((char) i);
		// }
		char[] c = new char[1024];
		// 将数据读到缓存字符数组中,每次最多读b.length个字节,返回实际读到的字符数,如到文件末尾返回-1
		while ((i = reader.read(c)) != -1) {
			sb.append(c, 0, i);
		}
		reader.close();

四、IO流设计模式之适配器模式

        1、适配器模式概念

        所谓适配器模式就是将某个类的接口转换为客户端期望的另一个接口表示,其主要目的是兼容性,让原本不相干的两个类可以协同一起工作。

        2、IO流中适配器模式分析

         IO流中多处使用了适配器模式,比较典型的就是字节流转字符流,目标接口字符流Reader与需要被适配的类InputStream原本是两个不相干的类,为了满足可以读字符功能,有了适配器者InputStreamReader。InputStreamReader的作用就是将字节输入流转换为能读取字符的字符输入流。

         由InputStreamReader构造方法可知,接受一个字节流InputStream,通过InputSream生成一个StreamDecoder对象,后续字符流读写都是靠这个StreamDecoder对象,间接利用了InputSream的功能。

public InputStreamReader(InputStream in) {
        super(in);
        try {
            sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
        } catch (UnsupportedEncodingException e) {
            // The default encoding should always be available
            throw new Error(e);
        }
    }

        五、IO流中设计模式之装饰模式

            1、装饰模式概念

                装饰模式指的是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实对象。装饰模式又叫包装(Wrapper)模式。

            2、装饰模式各角色

                (1)抽象构件(component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。

                (2)具体构件(Concrete Component)角色:定义一个将要接收附加责任的类。

                (3)装饰(Decorator)角色:持有一个构件(Component)对象的实例,并实现一个与抽象构件接口一致的接口。

                (4)具体装饰(Concrete Decorator)角色:负责给构件对象添加上附加的责任。

                  注:责任可理解为功能,附加责任即为扩展的功能

            3、IO流中装饰模式分析

                (1)抽象构件角色为InputStream抽象类,定义了字节输入流的基本方法。如read()方法

public abstract int read() throws IOException;

                (2)具体构件角色ByteArrayInputStream类,继承了InputStream类,重写了相关方法如read()方法。       

public synchronized int read() {
        return (pos < count) ? (buf[pos++] & 0xff) : -1;
    }

               (3) 装饰角色FilterInputStream类,继承了InputSream类,持有InputStream对象,重写了相关方法如read()方法,将方法委派给具体构件角色(ByteArrayInputStream)执行。

//继承抽象构件角色
class FilterInputStream extends InputStream {
 
    //持有抽象构件对象实例
	protected volatile InputStream in;

    protected FilterInputStream(InputStream in) {
        this.in = in;
    }

    public int read() throws IOException {
        //委派给具体构件角色执行
    	return in.read();
    }
}

        (4)具体装饰角色BufferedInputStream类,继承装饰角色FilterInputSream类,添加了相关的属性,重写了read()方法,增强了原有read()方法功能,提高了读效率。

class BufferedInputStream extends FilterInputStream {

    private static int DEFAULT_BUFFER_SIZE = 8192;

    private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;

    protected volatile byte buf[];
 
    protected int count;

    protected int pos;

    protected int markpos = -1;

    protected int marklimit;
    
    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }

    public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }
    
    public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }
} 

        总结:适配器模式是在适配器中,重写旧接口的方法来调用新接口方法,来实现旧接口不改变,同事使用新接口的目的,新接口适配旧接口。而装饰模式,是装饰器和旧接口实现相同的接口,在调用新接口的方法中,会调用旧接口的方法,并对其进行扩展。

        本章节主要以输入流为主,未对输出流详细说明。如有错误,欢迎指正,谢谢!

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值