Java IO—缓冲字符流以及IO中的装饰者模式

Posted 刘Java

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java IO—缓冲字符流以及IO中的装饰者模式相关的知识,希望对你有一定的参考价值。

详细介绍了Java IO中的缓冲字符流BufferedWriter、BufferedReader、LineNumberReader的使用方式,以及Java IO中的装饰者模式。

1 BufferedWriter缓冲区字符输出流

public class BufferedWriter
extends Writer

特点:

  1. 自带缓冲区。缓冲区可以实现自动扩容,提高了写的效率。
  2. 特有的newLine(); 方法可以写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义,并且不一定是单个新行 (’\\n’) 符,可以实现跨平台。

1.1 构造器

public BufferedWriter(Writer out);

创建一个使用默认大小输出缓冲区的缓冲字符输出流。Writer类,是一个抽象类,应该传递的是该抽象类的实现类对象。

public BufferedWriter(Writer out,int sz)

创建一个使用给定大小输出缓冲区的新缓冲字符输出流。在大多数情况下,默认值就足够大了,因此该构造器一般不用。sz:输出缓冲区的大小,是一个正整数

为什么要传递一个流对象而不传递file或者路径呢?

因为缓冲区流仅仅多提供一个缓冲区的功能,是为了高效而设计的。真正的读写还是靠基本的流对象的方法实现!(即装饰设计模式)。并且关闭外层流就相当于关闭了内层的流!

1.2 API方法

大多数方法均继承或重写自直接父类Writer。

特有的方法:

public void newLine();

写入一个行分隔符,即提供了一个换行的方法。

2 BufferedReader缓冲区字符输入流

public class BufferedReader
extends Reader

特点:

  1. 自带缓冲区。可以实现自动扩容,提高了读的效率。
  2. 提供了readLine方法,一次可以读取一行数据。

2.1 构造器

public BufferedReader(Reader in)

创建一个使用默认大小输入缓冲区的缓冲字符输入流。传递一个实现Reade实现类。

2.2 API方法

大部分方法都继承和重写自父类Reader。

特有的方法:

public String readLine()

能够一个读取一行数据,提高了读的效率。返回包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回null。

2.2.1 readLine方法实现原理

BufferedReader构造的时候,包装了一个类:FileReader。

其底层是调用了FileReader#read()方法,一次读取一个字符,将读取的字符放在缓冲区,当读取到换行符号的时候,将一行数据返回到内存当中,“\\r’的ASCII码为13,“\\n”的ASCII码为10。

3 案例

3.1 写入文件

/**
 * @author lx
 */
public class BufferedWriterDemo01 {

    public static void main(String[] args) {
        BufferedWriter bw = null;
        try {
            bw = new BufferedWriter(new FileWriter("C:\\\\Users\\\\lx\\\\Desktop\\\\test.txt"));
            bw.write(97);
            bw.newLine();
            bw.write("nishuo");
            bw.newLine();
            bw.write("你说啥你", 1, 2);
            bw.newLine();
            char[] ch = new char[]{'1', '-', '\\t', ' '};
            bw.write(ch);
            bw.write(ch, 1, 2);
            bw.flush();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

3.2 读取文件

/**
 * @author lx
 */
public class BufferedReaderDemo01 {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("C:\\\\Users\\\\lx\\\\Desktop\\\\test.txt"));
        String str;
        //一次读取一行
        while ((str = br.readLine()) != null) {
            System.out.println(str);
        }
        br.close();
    }

}

3.3 拷贝文件

使用带有缓冲区的流,实现文件的copy。

/**
 * @author lx
 */
public class CopyFile {
    public static void main(String[] args) {
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            br = new BufferedReader(new FileReader("C:\\\\Users\\\\lx\\\\Desktop\\\\test.txt"));
            bw = new BufferedWriter(new FileWriter("C:\\\\Users\\\\lx\\\\Desktop\\\\test2.txt"));
            String str;
            //读取  写入
            while ((str = br.readLine()) != null) {
                bw.write(str);
                bw.newLine();
                bw.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4 LinenumberReader跟踪行号缓冲字符输入流

public class LineNumberReader
extends BufferedReader

这是Reader的又一个包装类,其特点是:

  1. 是BufferedReader子类,具备缓冲区,具有有一次读取一行的方法。
  2. 具有获得行号和设置行号的方法
    1. setLineNumber(int num); 设置行号
    2. getLineNumber(); 获得当前行的行号,返回的是int 类型的值

注意:循环之前设置行号后,循环后获得第一行字符串行号是设置的行号+1。

构造器:

LineNumberReader(Reader in);

LineNumberReader(Reader in, int sz);

5 装饰设计模式

作用:对某个类的某个方法进行功能性的增强。

装饰模式的实现步骤:

  1. 定义一个类:装饰类。实现被装饰类的最上层的接口或类,让装饰类和被装饰类有共同的行为,即同一体系。
  2. 引入一个被装饰类,作为全局变量。这里选择reader抽象类,此后reader的子类都能使用。
  3. 将被装饰的类作为参数传递给装饰类的构造器,用于初始化变量。
  4. 对该被装饰类的某个方法进行功能性的增强。

5.1 Java IO中的装饰模式

BufferedReader类对Reader类的read()方法进行包装,它是基于read方法,并对read方法进行了功能的增强,形成readLine()方法。实际上整个IO流系统用的最多的就是装饰设计模式。

LineNumberReader类对BufferedReader类的readLine()方法进行包装,让其在调用时能够对行号计数,添加了获得行号和设置行号的方法。

5.2 装饰设计模式和继承的异同点

相同点:装饰设计模式和继承都是为了对某个类的行为或者属性的扩展(增强)。

不同点:

当我有2个功能差不多 但是操作数据对象不一样的功能的子类 想要扩展一样的功能的时候,继承需要给每个子类进行扩展,并且需要继承每一个方法,而装饰设计模式,只要写一份,把父类传进来就扩展了制定的方法。

这样如果为了某个功能的扩展而使用继承那么可能产生很多子类,那系统体系是非常臃肿的,并且继承的类之间耦合度高,不利于扩展。

装饰模式使用对象的依赖关系代替继承关系,允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。因此装饰设计模式更加灵活,同时避免类型体系的快速膨胀降低了类与类之间的关系(即所谓的耦合性,继承的一个缺点就是耦合性太强)。

装饰模式符合程序设计开闭原则就是说对扩展开放,对修改关闭。在对某个类需要进行拓展的时候,最好不要修改原有的代码,而是要扩展原有代码,使用装饰设计模式就可以做到,使程序的扩展性好,易于维护和升级。

建议:通常使用装饰设计模式,少用继承。

5.3 案例

5.3.1 自定义读取一行的方法

自定义MyBufferedReader类:对FileReader类进行了包装并对FileReader 的read()方法进行功能性的增强,完成readLine方法的效果。

/**
 * @author lx
 */
public class MyBufferedReader extends Reader {
    private Reader reader;

    public MyBufferedReader(Reader reader) {
        this.reader = reader;
    }

    public String MyReaderLine() throws IOException {
        int read;
        StringBuilder sb = new StringBuilder();

        while ((read = reader.read()) != -1) {
            //判断是否等于13,13是\\r的Unicode码,即回车符,回车符不加
            if (read == 13) {
                continue;
            }
            //判断是否是换行符,是换行就返回,不加,否则就添加
            if (read == 10) {
                return sb.toString();
            } else {
                sb.append((char) read);
            }
        }
        //到这说明读到了末尾,但是末尾可能没有\\n,因此这里需要额外判断
        if (sb.length() > 0) {
            return sb.toString();
        }
        //走到这里说明,读到了末尾.并且缓冲数组的内容已经全部返回,读取完毕
        return null;
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        return reader.read(cbuf, off, len);
    }

    @Override
    public void close() throws IOException {
        reader.close();
    }
}

5.3.2 自定义设置和获得行号的方法

自定义MyLineNumberReader类,使用装饰设计模式,设计设置和获得行号并且读取一行的方法。

/**
 * @author lx
 */
public class MyLineNumberReader extends Reader {
    
    private Reader reader;
    private int num;

    public MyLineNumberReader(Reader reader) {
        this.reader = reader;
    }

    public void setMyLineNumber(int num) {
        this.num = num;
    }

    public int getLineNumber() {
        return num;
    }

    public String myReadLine() throws IOException {
        StringBuilder sb = new StringBuilder();
        int read;
        while ((read = reader.read()) != -1) {
            if (read == '\\r') {
                continue;
            }
            if (read == '\\n') {
                num++;
                return sb.toString();
            } else {
                sb.append((char) read);
            }
        }
        if (sb.length() > 0) {
            num++;
            return sb.toString();
        }
        return null;
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        return reader.read(cbuf, off, len);
    }

    @Override
    public void close() throws IOException {
        reader.close();
    }
}

如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!

以上是关于Java IO—缓冲字符流以及IO中的装饰者模式的主要内容,如果未能解决你的问题,请参考以下文章

Java-IO流之BufferedReader 和BufferedWriter的使用和原理

IO流与装饰者模式

字符流缓冲区原理之装饰模式

字符流缓冲区的增强类LineNumberReader之装饰模式

Java-IO知识详解

Java-IO知识详解