java IO之 字符流 (字符流 = 字节流 + 编码表) 装饰器模式

简介:

字符流

  计算机并不区分二进制文件与文本文件。所有的文件都是以二进制形式来存储的,因此,

从本质上说,所有的文件都是二进制文件。所以字符流是建立在字节流之上的,它能够提供字符

层次的编码和解码。列如,在写入一个字符时,Java虚拟机会将字符转为文件指定的编码(默认

是系统默认编码),在读取字符时,再将文件指定的编码转化为字符。

常见的码表如下:

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

ISO8859-1:   拉丁码表。欧洲码表,用一个字节的8位表示。又称Latin-1(拉丁编码)或“西欧语言”。ASCII码是包含的仅仅是英文字母,并且没有完全占满256个编码位置,所以它以ASCII为基础,在空置的0xA0-0xFF的范围内,加入192个字母及符号,

藉以供使用变音符号的拉丁字母语言使用。从而支持德文,法文等。因而它依然是一个单字节编码,只是比ASCII更全面。

GB2312:   英文占一个字节,中文占两个字节.中国的中文编码表。

GBK:      中国的中文编码表升级,融合了更多的中文文字符号。

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

UTF-8:    最多用三个字节来表示一个字符。

(我们以后接触最多的是iso8859-1、gbk、utf-8)

查看上述码表后,很显然中文的‘中’在iso8859-1中是没有对映的编码的。或者一个字符在2中码表中对应的编码不同,例如有一些字在不同的编码中是有交集的,例如bjg5 和gbk 中的汉字简体和繁体可能是一样的,就是有交集,但是在各自码表中的数字不一样。

例如

使用gbk 将中文保存在计算机中,

    中  国

对映  100  200   如果使用big5 打开

可能   ?  ...  

不同的编码对映的是不一样的。

很显然,我们使用什么样的编码写数据,就需要使用什么样的编码来对数据。

ISO8859-1:一个字节

GBK: 两个字节包含了英文字符和扩展的中文   ISO8859-1+中文字符

UTF-8 万国码,推行的。是1~3个字节不等长。英文存的是1个字节,中文存的是3个字节,是为了节省空间。

那么我们之前学习的流称之为字节流,以字节为单位进行操作之情的操作全是英文,如果想要操作中文呢?

测试:将指定位置的文件通过字节流读取到控制台

复制代码
public class TestIo {
    public static void main(String[] args) throws IOException {
        String path = "c:\\a.txt";
        writFileTest();
        readFileByInputStream(path);
    }

    private static void readFileByInputStream(String path) throws IOException {
        FileInputStream fis = new FileInputStream(path);
        int len = 0;
        while ((len = fis.read()) != -1) {
            System.out.print((char) len);
        }
    }
    private static void writFileTest() throws FileNotFoundException,
            IOException {
        // 创建文件对象
        File file = new File("c:\\a.txt");
        // 创建文件输出流
        FileOutputStream fos = new FileOutputStream(file);
        fos.write("中国".getBytes());
        fos.close();
    }
}
复制代码

 

发现控制台输出的信息:

???ú  是这样的东西,打开a.txt 文本发现汉字”中国”确实写入成功。

那么说明使用字节流处理中文有问题。

仔细分析,我们的FileInputStream输入流的read() 一次是读一个字节的,返回的是一个int显然进行了自动类型提升。那么我们来验证一下“中国”对应的字节是什么

使用:"中国".getBytes() 即可得到字符串对应的字节数组。是[-42, -48, -71, -6]

同样,将read方法返回值直接强转为byte ,发现结果也是-42, -48, -71, -6 。

复制代码
public class TestIo {
    public static void main(String[] args) throws IOException {
        String path = "c:\\a.txt";
        writFileTest();
        readFileByInputStream(path);
        //查看中国对应的编码
        System.out.println(Arrays.toString("中国".getBytes()));
    }

    private static void readFileByInputStream(String path) throws IOException {
        FileInputStream fis = new FileInputStream(path);
        int len = 0;
        while ((len = fis.read()) != -1) {
            System.out.println((byte)len);
        }
    }

    private static void writFileTest() throws FileNotFoundException,
            IOException {
        // 创建文件对象
        File file = new File("c:\\a.txt");
        // 创建文件输出流
        FileOutputStream fos = new FileOutputStream(file);
        fos.write("中国\r\n".getBytes());
        fos.close();
    }

}
复制代码

 

那么中国 对应的是-42, -48, -71, -6是4个字节。 那就是一个中文占2个字节,(这个和编码是有关系的)

很显然,我们的中文就不能够再一个字节一个字节的读了。所以字节流处理字符信息时并不方便那么就出现了字符流。

  字节流是 字符流是以字符为单位。

  体验字符流:

复制代码
public static void main(String[] args) throws IOException {
        
        String path = "c:\\a.txt";
        readFileByReader(path);
    }
private static void readFileByReader(String path) throws IOException {
        FileReader fr = new FileReader(path);
        int len = 0;
        while ((len = fr.read()) != -1) {
            System.out.print((char) len);
        }
    }
复制代码

 

总结:字符流就是:字节流 + 编码表,为了更便于操作文字数据。字符流的抽象基类:

Reader , Writer。

由这些类派生出来的子类名称都是以其父类名作为子类名的后缀,如FileReader、FileWriter。

 Reader:

方法:

1,int read():

读取一个字符。返回的是读到的那个字符。如果读到流的末尾,返回-1.

2,int read(char[]):

将读到的字符存入指定的数组中,返回的是读到的字符个数,也就是往数组里装的元素的个数。如果读到流的末尾,返回-1.

3,close()

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

复制代码
public class IoTest1_Reader {

    public static void main(String[] args) throws Exception {
        String path = "c:/a.txt";
        // readFileByInputStream(path);
        readFileByReader(path);
    }

    /**
     * 使用字节流读取文件内容
     * 
     * @param path
     */
    public static void readFileByInputStream(String path) throws Exception {
        InputStream in = new FileInputStream(path);

        int len = 0;
        while ((len = in.read()) != -1) {
            System.out.print((char) len);
        }

        in.close();
    }

    /**
     * 使用字符流读取文件内容
     */
    public static void readFileByReader(String path) throws Exception {
        Reader reader = new FileReader(path);
        int len = 0;
        while ((len = reader.read()) != -1) {
            System.out.print((char) len);
        }

        reader.close();
    }

}
复制代码

 

 Writer

Writer中的常见的方法:

1,write(ch): 将一个字符写入到流中。

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

3,write(String): 将一个字符串写入到流中。

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

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

复制代码
public class IoTest2_Writer {

    public static void main(String[] args) throws Exception {
        String path = "c:/ab.txt";

        writeToFile(path);
    }

    /**
     * 写指定数据到指定文件中
     * 
     */
    public static void writeToFile(String path) throws Exception {
        Writer writer = new FileWriter(path);
        writer.write('中');
        writer.write("世界".toCharArray());
        writer.write("中国");

        writer.close();
    }
}
复制代码

 

2:追加文件:

  默认的FileWriter方法新值会覆盖旧值,想要实现追加功能需要

  使用如下构造函数创建输出流 append值为true即可。

  FileWriter(String fileName, boolean append)

  FileWriter(File file, boolean append)

3:flush方法

    如果使用字符输出流,没有调用close方法,会发生什么?

复制代码
private static void writeFileByWriter(File file) throws IOException {
        FileWriter fw = new FileWriter(file);
        fw.write('新');
fw.flush();
        fw.write("中国".toCharArray());
        fw.write("世界你好!!!".toCharArray());
        fw.write("明天");    
        // 关闭流资源
        //fw.close();
    }
复制代码

 

程序执行完毕打开文件,发现没有内容写入.原来需要使用flush方法. 刷新该流的缓冲。

为什么只要指定close方法就不用再flush方法,因为close也调用了flush方法.

字符流拷贝文件

一个文本文件中有中文有英文字母,有数字。想要把这个文件拷贝到别的目录中。

我们可以使用字节流进行拷贝,使用字符流呢?肯定也是可以的。

字符流拷贝文件实现1

复制代码
public static void main(String[] args) throws Exception {
        String path1 = "c:/a.txt";
        String path2 = "c:/b.txt";

        copyFile(path1, path2);
    }

/**
     * 使用字符流拷贝文件
     */
    public static void copyFile(String path1, String path2) throws Exception {
        Reader reader = new FileReader(path1);
        Writer writer = new FileWriter(path2);

        int ch = -1;
        while ((ch = reader.read()) != -1) {
            writer.write(ch);
        }

        reader.close();
        writer.close();
    }
复制代码

 

但是这个一次读一个字符就写一个字符,效率不高。把读到的字符放到字符数组中,再一次性的写出。

字符流拷贝文件实现2

复制代码
public static void main(String[] args) throws Exception {
        String path1 = "c:/a.txt";
        String path2 = "c:/b.txt";
        copyFile(path1, path2);
    }

public static void copyFile3(String path1, String path2) throws Exception {
        Reader reader = new FileReader(path1);
        Writer writer = new FileWriter(path2);

        int ch = -1;
        char [] arr=new char[1024];
        while ((ch = reader.read(arr)) != -1) {
            writer.write(arr,0,ch);
        }

        reader.close();
        writer.close();
    }
复制代码

 

字节流可以拷贝视频和音频等文件,那么字符流可以拷贝这些吗?

  经过验证拷贝图片是不行的。发现丢失了信息,为什么呢?

计算机中的所有信息都是以二进制形式进行的存储(1010)图片中的也都是二进制

在读取文件的时候字符流自动对这些二进制按照码表进行了编码处理,但是图片本来就是二进制文件,不需要进行编码。有一些巧合在码表中有对应,就可以处理,并不是所有的二进制都可以找到对应的。信息就会丢失。所以字符流只能拷贝以字符为单位的文本文件

(以ASCII码为例是127个,并不是所有的二进制都可以找到对应的ASCII,有些对不上的,就会丢失信息。)

 字符流的异常处理

复制代码
public static void main(String[] args) throws Exception {
        String path1 = "c:/a.txt";
        String path2 = "c:/b.txt";

        copyFile2(path1, path2);
    }

/**
     * 使用字符流拷贝文件,有完善的异常处理
     */
    public static void copyFile2(String path1, String path2) {
        Reader reader = null;
        Writer writer = null;
        try {
            // 打开流
            reader = new FileReader(path1);
            writer = new FileWriter(path2);
            // 进行拷贝
            int ch = -1;
            while ((ch = reader.read()) != -1) {
                writer.write(ch);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 关闭流,注意一定要能执行到close()方法,所以都要放到finally代码块中
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                try {
                    if (writer != null) {
                        writer.close();
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
复制代码

 

字符流的缓冲区

  查看Reader 发现Reader,操作的是字符,我们就不需要进行编码解码操作,由字符流读到二进制,自动进行解码得到字符,写入字符自动编码成二进制.

Reader有一个子类BufferedReader。子类继承父类显然子类可以重写父类的方法,也可以增加自己的新方法。例如一次读一行就是常用的操作.那么BufferedReader 类就提供了这个方法,可以查看readLine()方法具备 一次读取一个文本行的功能。很显然,该子类可以对功能进行增强。

体验BufferedReader

复制代码
public class IoTest_BufferedReader {
    public static void main(String[] args) throws IOException {
        readFile("c:\\a.txt");
    }

    private static void readFile(String path) throws IOException {
        Reader read = new FileReader(path);

        BufferedReader br = new BufferedReader(read);
        
        String line = null;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }

    }
}
复制代码

 

注意:

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

缓冲区的出现提高了对流的操作效率。原理:其实就是将数组进行封装。

使用字符流缓冲区拷贝文本文件.

复制代码
public class Demo7 {
    public static void main(String[] args) throws IOException {
        // 关联源文件
        File srcFile = new File("c:\\linux大纲.txt");
        // 关联目标文件
        File destFile = new File("d:\\linux大纲.txt");
        // 实现拷贝
        copyFile(srcFile, destFile);

    }

    private static void copyFile(File srcFile, File destFile)
            throws IOException {
        // 创建字符输入流
        FileReader fr = new FileReader(srcFile);
        // 创建字符输出流
        FileWriter fw = new FileWriter(destFile);

        // 字符输入流的缓冲流
        BufferedReader br = new BufferedReader(fr);
        // 字符输出流的缓冲流
        BufferedWriter bw = new BufferedWriter(fw);

        String line = null;
        // 一次读取一行
        while ((line = br.readLine()) != null) {
            // 一次写出一行.
            bw.write(line);
            // 刷新缓冲
            bw.flush();
            // 进行换行,由于readLine方法默认没有换行.需要手动换行
            bw.newLine();
        }
        // 关闭流
        br.close();
        bw.close();
    }
}
复制代码

 

 装饰器模式

需求:想要在读取的文件的每一行添加行号。

复制代码
public class IoTest7_BufferedReader {

    public static void main(String[] args) throws IOException {
        readFile("c:\\a.txt");
    }

    private static void readFile(String path) throws IOException {
        Reader read = new FileReader(path);

        BufferedReader br = new BufferedReader(read);
        int count = 0;
        String line = null;
        while ((line = br.readLine()) != null) {
            count++;
            System.out.println(count+":"+line);        
        }

    }
}
复制代码

 

很容易的就可以实现。如果每次使用BufferedReader 输出时都需要显示行号呢? 每次都加? 很显然,我们的BufferedReader继承了Reader 对父类进行了功能的增强,那么我们也可以继承BufferedReader 重写该类的readLine方法,进行功能的增强.

复制代码
public class IoTest_BufferedReader {
    public static void main(String[] args) throws IOException {
        readFile("c:\\a.txt");
    }

    private static void readFile(String path) throws IOException {
        Reader read = new FileReader(path);
        BufferedReader br = new MyBufferedReader(read);
String line
= null; while ((line = br.readLine()) != null) { System.out.println(line); } } } class MyBufferedReader extends BufferedReader { public MyBufferedReader(Reader read) { super(read); } int count; @Override public String readLine() throws IOException { String line = super.readLine(); if (line != null) { count++; return count + ":" + line; } else { return null; } } }
复制代码

 

需求:

要在输出的一行前加上引号

可以再定义一个BufferedReader的子类,继承BufferedReader增强功能.

复制代码
public class IoTest_BufferedReader {
    public static void main(String[] args) throws IOException { readFile("c:\\a.txt"); } private static void readFile(String path) throws IOException { Reader read = new FileReader(path); BufferedReader br = new MyQutoBufferedReader(read); int count = 0; String line = null; while ((line = br.readLine()) != null) { System.out.println(line); count++; } } } // quotation 引号 class MyQutoBufferedReader extends BufferedReader { public MyQutoBufferedReader(Reader reader) { super(reader); } public String readLine() throws IOException { String line = super.readLine(); if (line != null) { return "\"" + line + "\""; } else { return null; } } }
复制代码

既想要显示行号又想要显示引号

发现,就需要再定义子类,发现这样比较麻烦,代码臃肿.而且代码重复.

可以换一种方式.如下:

其实就是一个新类要对原有类进行功能增强.

1. 在增强类中维护一个被增强的父类引用变量

        2. 在增强类的构造函数中初始化1中的变量

        3. 创建需要增强的方法,在刚方法中调用被被增强类的方法,并加以增强。

复制代码
public class IoTest_BufferedReader {
    public static void main(String[] args) throws IOException {
        readFile("c:\\a.txt");
    }

    private static void readFile(String path) throws IOException {
        Reader read = new FileReader(path);
        BufferedReader bufferedReader = new BufferedReader(read);
        BufferedReader br = new MyQutoBufferedReader2(bufferedReader);
        br = new MyLineBufferedReader2(br);
        String line = null;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    }
}

// quotation 引号
class MyQutoBufferedReader2 extends BufferedReader {
    private BufferedReader bufferedReader;
    public MyQutoBufferedReader2(BufferedReader bufferedReader) {
        super(bufferedReader);
        this.bufferedReader = bufferedReader;
    }
    public String readLine() throws IOException {
        String line = super.readLine();
        if (line != null) {
            return "\"" + line + "\"";
        } else {
            return null;
        }
    }
}

class MyLineBufferedReader2 extends BufferedReader {
    private BufferedReader bufferedReader;
    public MyLineBufferedReader2(BufferedReader bufferedReader) {
        super(bufferedReader);
        this.bufferedReader = bufferedReader;
    }
    int count;
    @Override
    public String readLine() throws IOException {
        String line = super.readLine();
        if (line != null) {
            count++;
            return count + ":" + line;
        } else {
            return null;
        }
    }
}
复制代码

这就是装饰器模式

装饰器模式:

    使用分层对象来动态透明的向单个对象中添加责任(功能)。

    装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。

    某些对象是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。

    装饰器必须具有和他所装饰的对象相同的接口。

JavaIO中的应用:

    Java I/O类库需要多种不同的功能组合,所以使用了装饰器模式。

    FilterXxx类是JavaIO提供的装饰器基类,即我们要想实现一个新的装饰器,就要继承这些类。

装饰器与继承:

问题:

    修饰模式做的增强功能按照继承的特点也是可以实现的,为什么还要提出修饰设计模式呢?

继承实现的增强类和修饰模式实现的增强类有何区别?

    继承实现的增强类:

       优点:代码结构清晰,而且实现简单

       缺点:对于每一个的需要增强的类都要创建具体的子类来帮助其增强,这样会导致继承体系过于庞大。

修饰模式实现的增强类:

       优点:内部可以通过多态技术对多个需要增强的类进行增强

       缺点:需要内部通过多态技术维护需要增强的类的实例。进而使得代码稍微复杂。

目录
相关文章
|
6天前
|
设计模式 Java
Java一分钟之-设计模式:装饰器模式与代理模式
【5月更文挑战第17天】本文探讨了装饰器模式和代理模式,两者都是在不改变原有对象基础上添加新功能。装饰器模式用于动态扩展对象功能,但过度使用可能导致类数量过多;代理模式用于控制对象访问,可能引入额外性能开销。文中通过 Java 代码示例展示了两种模式的实现。理解并恰当运用这些模式能提升代码的可扩展性和可维护性。
20 1
|
1天前
|
存储 Java API
【JAVA学习之路 | 进阶篇】IO流及流的分类
【JAVA学习之路 | 进阶篇】IO流及流的分类
|
8天前
|
监控 Java
Java一分钟之-NIO:非阻塞IO操作
【5月更文挑战第14天】Java的NIO(New IO)解决了传统BIO在高并发下的低效问题,通过非阻塞方式提高性能。NIO涉及复杂的选择器和缓冲区管理,易出现线程、内存和中断处理的误区。要避免这些问题,可以使用如Netty的NIO库,谨慎设计并发策略,并建立标准异常处理。示例展示了简单NIO服务器,接收连接并发送欢迎消息。理解NIO工作原理和最佳实践,有助于构建高效网络应用。
14 2
|
8天前
|
Java
java一分钟之-字符流与字节流的区别
【5月更文挑战第11天】Java的输入输出通过流操作,分为字符流和字节流。字节流处理二进制数据,如图片、音频,基类是`InputStream`和`OutputStream`;字符流处理文本,基类是`Reader`和`Writer`。字符流涉及编码转换,字节流不涉及。易错点包括乱码(需指定编码)、混用流类型和忘记关闭流。示例展示了字节流和字符流读文件。理解区别并注意编码和资源管理可提高代码质量。
37 3
|
8天前
|
Java 开发者
Java一分钟之-Java IO流:文件读写基础
【5月更文挑战第10天】本文介绍了Java IO流在文件读写中的应用,包括`FileInputStream`和`FileOutputStream`用于字节流操作,`BufferedReader`和`PrintWriter`用于字符流。通过代码示例展示了如何读取和写入文件,强调了常见问题如未关闭流、文件路径、编码、权限和异常处理,并提供了追加写入与读取的示例。理解这些基础知识和注意事项能帮助开发者编写更可靠的程序。
22 0
|
8天前
|
存储 安全 Java
12条通用编程原则✨全面提升Java编码规范性、可读性及性能表现
12条通用编程原则✨全面提升Java编码规范性、可读性及性能表现
|
8天前
|
存储 缓存 Java
Java IO 流详解
Java IO 流详解
18 1
|
8天前
|
存储 Java
Java的`java.io`包包含多种输入输出类
【5月更文挑战第2天】Java的`java.io`包包含多种输入输出类。此示例展示如何使用`FileInputStream`从`input.txt`读取数据。首先创建`FileInputStream`对象,接着分配一个`byte`数组存储流中的数据。通过`read()`方法读取数据,然后将字节数组转换为字符串打印。最后关闭输入流释放资源。`InputStream`是抽象类,此处使用其子类`FileInputStream`。其他子类如`ByteArrayInputStream`、`ObjectInputStream`和`BufferedInputStream`各有特定用途。
59 1
|
8天前
|
存储 Java
java IO接口(Input)用法
【5月更文挑战第1天】Java的`java.io`包包含多种输入输出类。此示例展示了如何使用`FileInputStream`从`input.txt`读取数据。首先创建`FileInputStream`对象,接着创建一个字节数组存储读取的数据,调用`read()`方法将文件内容填充至数组。然后将字节数组转换为字符串并打印,最后关闭输入流。注意,`InputStream`是抽象类,此处使用其子类`FileInputStream`。其他子类如`ByteArrayInputStream`、`ObjectInputStream`和`BufferedInputStream`各有特定用途。
22 2
|
8天前
|
Java Spring
Java 效率编码 必备插件 Lombok 让代码更优雅
该内容是一个关于Lombok插件的教程摘要:介绍了Lombok用于减少Java开发中的模板代码,提升效率;讲解了如何在IntelliJ IDEA中安装Lombok插件,以及在pom.xml中添加依赖;并提到了@Data注解能自动生成getter/setter、equals、hashCode和toString方法,@Slf4j注解自动处理日志,@Builder用于构建对象,以及@AllArgsConstructor和@NoArgsConstructor注解生成构造函数。还鼓励探索更多Lombok的注解用法。