Java字符流Reader

来源:互联网 发布:南京 知乎 编辑:程序博客网 时间:2024/05/22 13:06

读写的数据如果是英文,那么没有任何问题,但是如果一旦有了中文,乱码横生,惨不忍睹……感觉这么多种编码方式有时候真的是各种坑,但是虽然是坑,跪着也得填啊
首先介绍一下各种编码方式:
ASCII码:仅包含对英文的编码,虽然是用一个字节表示,但是仅仅使用了最后7位,并没有占满2^8=256个编码位置
ISO8859-1:欧洲码表,用一个字节的8位表示,在ASCII码的基础上,在空置的0xA0-0xFF的范围内,加入192个字母及符号,以供使用变音符号的拉丁字母语言使用。从而支持德文,法文等。因而它依然是一个单字节编码,只是比ASCII更全面。
GB2312:英文占一个字节,中文占两个字节。中国的中文编码表。
GBK:中国的中文编码表升级,融合了更多的中文文字符号。但仍然是英文占一个字节,中文占两个字节。
UTF-8:万国码表,1~3个字节不等长。英文存的是1个字节,中文存的是3个字节。
有时候之所以出现乱码就是因为使用的编码方式不一样,不同的编码方式对于英文来说没有任何问题,但是对于中文来说,同一个汉字的不同编码方式的产生的编码是不一样的,如果用一种编码方式编码,然后利用另一种方式解码,那么肯定是不可能得到原本的汉字的!
前一篇博文谈及的是字节流的读取,但是当我们利用字节流对象读中文时就出现了乱码的问题,因为字节流读取的是一个一个的字节,而中文是需要>=2个字节才能表示,所以当我们读取一个字节后利用char强制类型转换并不能得到原本的文字,那么,这时候的字符流遍派上真正的用场了

字符流:字节流是读写一个字节,那么字符流顾名思义就是读写一个字符。字符流是编码的字节流,而字节流就是被解码的字符流,所以可以说是字符=字节+编码表

读数据步骤:
1.建立File类对象
2.建立字符输入流通道
3.读数据
4.关闭字符输入流对象

public static void readerFile()throws IOException{        File file = new File("F:\\1.txt");        FileReader reader = new FileReader(file);        int content;        //read():读取的字符,如果已到达流的末尾,则返回 -1 ,返回读取的数据,我们知道中文编码最多需要3个字节,而int可以存储4个字节,所以中文的存储是没有问题的        while((content = reader.read())!=-1){            System.out.print((char)content);        }        reader.close();}

???在调用上述方法读记事本1.txt中的文件时,文本中有中文,虽然利用了字符流对象,但是读出的仍然是乱码!这是因为什么原因呢?
!!!在Java工程中,设置的编码方式是UTF-8,而在记事本中,默认的编码方式是ANSI,那么也就是由本地语言环境来确定编码方式,在我的简体中文操作系统中编码方式是GBK,所以在读取记事本中的数据时,是按照GBK的编码方式读取的字节,之后又以UTF-8的方式进行了编码,所以肯定是乱码。解决方案无非两种,一种是将笔记本另存为的时候选择编码方式为UTF-8,另一种就是设置Java编译器的编码方式为GBK,那么中文就会正常显示了!
但是,有人还纳闷了,我明明用字符流对象也能读字符,而且没有乱码现象?代码如下:

public static void read()throws IOException{        File file = new File("F:\\2.txt");        FileInputStream inputStream = new FileInputStream(file);        byte[] buff = new byte[1024];        int len;        while((len = inputStream.read(buff))!=-1) {            System.out.println(new String(buff, 0, len));        }}

我们说过之所以出现乱码是因为字节流没有编码的能力,而此处之所以侥幸的没有出现乱码是因为利用了String类对象的解码能力,同时文件中的字符一次性能被buff存储,如果设置的buff很小,或者读写的文件内容很大,那么就会出现乱码!这是不可避免的,为了保险起见,如果要读字符,就用字符流对象!

写数据步骤:
1.建立File类对象
2.建立输出字符流通道
3.写数据
4.刷新缓冲区
5.关闭输出字符流对象

public static void writeFile()throws IOException{        File file = new File("F:\\1.txt");        FileWriter writer = new FileWriter(file);        String content = "Hello,I'm Dream.耿耿";        //字符流可以直接写字符串,本身具备解码的功能        writer.write(content);        //刷新该流的缓冲        writer.flush();        //关闭资源        writer.close();}

注意:
1.我们查看源码发现FileWrite类并没有维护缓冲数组,但是却需要刷新缓冲区才能将数据写出到文件中,这是为什么呢?我们追本溯源,发现其超类的超类即Writer类的底层维护了一个缓冲数组,所以是需要我们调用flush()真正写入数据
2.其实在close方法内部也有调用flush方法,所以无需调用flush方法,在关闭资源的时候就会将数据全部写入文件的。
3.读数据时出现的上述问题,写数据的时候并没有出现,我们发现,Java编辑器设置的UTF-8编码方式,在进行写文件时,无论原先文件是以何种方式编码的,在写数据结束后,文件统一变为UTF-8编码方式,且无乱码出现。

下面利用字符流实现文本的拷贝和图片的拷贝

public static void copyTxt()throws IOException{        File inFile = new File("F:\\1.txt");        File outFile = new File("F:\\2.txt");        FileReader reader = new FileReader(inFile);        FileWriter writer = new FileWriter(outFile);        int length;        char[] buf = new char[1024];        while((length =reader.read(buf))!=-1){            writer.write(buf,0,length);        }        writer.close();        reader.close();    }    public static void copyPic()throws IOException{        File inFile = new File("E:\\1.jpg");        File outFile = new File("F:\\2.jpg");        FileReader reader = new FileReader(inFile);        FileWriter writer = new FileWriter(outFile);        int length;        char[] buff = new char[1024];        while((length = reader.read(buff))!=-1){            writer.write(buff,0,length);        }        writer.close();        reader.close();    }

我们发现,其中的文本读写没有任何问题,但是在拷贝图片的时候,拷贝的图片大小变了,而且图片打不开,这又是什么原因呢?
计算机中的文件都是以二进制形式存储的,图片也是如此,在读取这些文件的时候字符流对这些二进制数据进行了编码处理,但是图片本来是二进制文件,并不需要编码。有一些数据巧合在码表中是有对应的,就可以进行编码处理,但是有些是没有对应的,那么就会返回未知字符对应的数字,未知字符是占一个字节的,所以会有信息的丢失。

字节流有缓冲类对象,同样的,字符流也有缓冲类对象,但是此处的字符流缓冲类对象

缓冲字符流输入类对象:
提高了字符流读取文件字符的侠侣还扩展了FileReader的功能,其底层也维护了一个字符数组,大小为8192个字符
主要扩展功能:readLine()读取一行数据,如果读取数据结束返回null,虽然读取的是一行数据,但是不包括”\r\n”(”\r”相当于回车 “\n”表示新行)
缓冲输出字符流对象:
我们知道字符流对象Reader内部维护了一个可以存放1024个字符的数组,缓冲输出字符流仅仅是将这个缓冲区扩大了,是8192个字符容量大小;不仅可以写出单个字符或者多个字符,还可以通过newLine()方法写入换行,虽然这个方法相当于向文件中写入”\r\n”
使用步骤:
1.找到目标文件,建立File类对象
2.建立数据的输入/输出通道 FileReader/FileWriter
3.建立数据的缓冲输入/输出流
4.读取/写出数据
5.关闭缓冲流对象
下面,利用缓冲输入/输出字符流对象实现用户的注册登录功能,每次用户注册,则将用户名和密码写入文件中,登录的时候读取文件从而取得用户名和密码判断当前的输入是否可以匹配进一步验证用户登录是否成功,代码如下:

package BasicObject.day20;import java.io.*;import java.util.Scanner;/** * Created by Dream on 2017/11/5. */public class BufferedReaderWriterRegLog {    static Scanner scanner = new Scanner(System.in);    public static void main(String[] args)throws IOException{        while(true){            System.out.println("请选择:注册(A)   登录(B)");            String option = scanner.next();            if("a".equalsIgnoreCase(option)){                //注册                reg();            }else if("b".equalsIgnoreCase(option)){                //登录                log();            }else{                System.out.println("输入错误,请重新选择......");            }        }    }    public static void reg()throws IOException{        System.out.println("请输入用户名:");        String username = scanner.next();        System.out.println("请输入密码:");        String password = scanner.next();        File file = new File("F:\\users.txt");        FileWriter writer = new FileWriter(file,true);  //每次向文件追加内容        BufferedWriter bufferedWriter = new BufferedWriter(writer);        String user = username+" "+password;        bufferedWriter.write(user);        bufferedWriter.newLine();        bufferedWriter.close();    }    public static void log()throws IOException{        System.out.println("请输入登录用户名:");        String username = scanner.next();        System.out.println("请输入用户密码:");        String password = scanner.next();        String now = username+" "+password;        File file = new File("F:\\users.txt");        FileReader reader = new FileReader(file);        BufferedReader bufferedReader = new BufferedReader(reader);        String info = null;        boolean flag = false;        while((info = bufferedReader.readLine())!=null){            if(now.equals(info)){                flag = true;            }        }        if(flag == false){            System.out.println("登录失败...");        }else{            System.out.println("登录成功!");        }    }}

装饰者模式:增强一个类的功能,而且可以让这些类相互装饰
让这些类有一个共同的父类的目的是为了让这些装饰类构成一个装饰链,达到互相装饰的效果

通过上面的讲解,我们知道缓冲字符输入流BufferedReader类对象有readLine()方法可以实现按行读入数据,假设现在有如下需求
需求1:编写一个类拓展BufferedReader的功能,增强readLine()方法,使其返回每一行都包含行号
需求2:编写一个类拓展BufferedReader的功能,增强readLine()方法,使其返回每一行都带有分号
需求3:编写一个类拓展BufferedReader的功能,增强readLine()方法,使其返回每一行都带有双引号
既然是功能的增强,那么第一想到的就是写一个类继承BufferedReader,然后重写其readLine()方法即可,对于需求1:

class BufferedLineNum extends BufferedReader{    int count = 1;    public BufferedLineNum(Reader reader){        super(reader);   //注意:必须要去调用父类的含参构造方法,否则会报错。        //因为BufferedReader中只有含参的构造方法,并没有无参的构造方法,必须子类显示调用    }    public String readLine()throws IOException{        String line = super.readLine();        if(line == null){            return null;        }        line = count+" "+line;        count++;        return line;    }}

对于需求2,如法炮制:

class BufferedFen extends BufferedReader{    public BufferedFen(Reader reader){        super(reader);    }    public String readLine()throws IOException{        String line = super.readLine();        if(line == null){            return null;        }        line =line+";";        return line;    }}

需求3也是如此:

class BufferedQuote extends BufferedReader{    public BufferedQuote(Reader reader){        super(reader);    }    public String readLine()throws IOException{        String line = super.readLine();        if(line == null){            return null;        }        line ="\""+line+"\"";        return line;    }}

但是,需求还没有结束,还有如下需求:
需求4:编写一个类拓展BufferedReader的功能,增强readLine()方法,使其返回每一行都带有行号+分号
需求5:编写一个类拓展BufferedReader的功能,增强readLine()方法,使其返回每一行都带有行号+引号
需求6:编写一个类拓展BufferedReader的功能,增强readLine()方法,使其返回每一行都带有引号+分号
需求7:编写一个类拓展BufferedReader的功能,增强readLine()方法,使其返回每一行都带有行号+分号+引号
对于这四个需求我们仍然可以如法炮制,但是如果要实现上述全部需求需要我们写7个类,这样就会使得继承体系非常庞大,那么有没有一种方法能够实现上述需求呢?
答案是肯定的,利用装饰者模式,增强一个类的功能,使得这些类可以互相装饰,对于上述需求,我们写3个类就可以实现!
装饰者设计模式的步骤:
1.在装饰类的内部维护一个被装饰类的引用。
2.在增强类的构造函数中初始化1中的变量
3.创建需要增强的方法,在刚方法中调用被被增强类的方法,并加以增强。

class BufferedNum{    BufferedReader reader;    int count = 1;    public BufferedNum(BufferedReader reader){        this.reader = reader;    }    public String getLine()throws IOException{        String line = reader.readLine();        if(line == null){            return null;        }        line = count+" "+line;        count++;        return line;    }}class BufferedSemiQuote{    BufferedReader reader;    public BufferedSemiQuote(BufferedReader reader){        this.reader = reader;    }    public String getLine()throws IOException{        String line = reader.readLine();        if(line == null){            return null;        }        line = line+";";        return line;    }}class BufferedQuotes{    BufferedReader reader;    public BufferedQuotes(BufferedReader reader){        this.reader=reader;    }    public String getLine()throws IOException{        String line = reader.readLine();        if(line == null){            return null;        }        line = "\""+line+"\"";        return line;    }}

所以,在每个类的内部,维护了一个指向被装饰类,在此示例中被装饰类是BufferedReader,作为装饰类构造参数的参数传递进来,前面是我们通过super去调用父类的readLine方法,在这种情况下,因为内部维护了该类,所以可以直接调用readLine方法即可,这样可以轻而易举地实现前三个需求,对于后四个需求,好像并不能实现。那么我们就想,实现需求4,5,6,7的类如果看作是装饰类的话,其被装饰对象是不是可以看作我们刚刚写的那三个类,那如果我们将刚刚写的那三个类作为参数传递进去,是不是就可以实现后面的需求了?但是,目前内部维护的BufferedReader类和BufferedNum类、BufferedSemiQuote以及BufferedQuotes目前没有任何关系,要想BufferedReader类对象可以指向后面的三类对象,只要其是父子关系就可以了,后面的类继承自BufferedReader类就可以了!
修改后的代码如下:

class BufferedNum extends BufferedReader{    BufferedReader reader;    int count = 1;    public BufferedNum(BufferedReader reader){        super(reader);        this.reader = reader;    }    public String getLine()throws IOException{        String line = reader.readLine();        if(line == null){            return null;        }        line = count+" "+line;        count++;        return line;    }}class BufferedSemiQuote extends BufferedReader{    BufferedReader reader;    public BufferedSemiQuote(BufferedReader reader){        super(reader);        this.reader = reader;    }    public String getLine()throws IOException{        String line = reader.readLine();        if(line == null){            return null;        }        line = line+";";        return line;    }}class BufferedQuotes extends BufferedReader{    BufferedReader reader;    public BufferedQuotes(BufferedReader reader){        super(reader);          //必须要写,否则报错        //原因:BufferedReader类没有无参构造方法,必须子类显示调用其父类方法        this.reader=reader;    }    public String getLine()throws IOException{        String line = reader.readLine();        if(line == null){            return null;        }        line = "\""+line+"\"";        return line;    }}public class DecorationDesign {    public static void main(String[] args)throws IOException{        BufferedReader reader = new BufferedReader(new FileReader("F:\\1.txt"));        BufferedNum bufferedNum = new BufferedNum(reader);  //加行号显示        BufferedQuotes bufferedQuotes = new BufferedQuotes(bufferedNum);  //行号+引号        BufferedSemiQuote bufferedSemiQuote = new BufferedSemiQuote(bufferedQuotes); //行号+引号+分号        String line=null;        while((line = bufferedSemiQuote.readLine())!=null){            System.out.println(line);        }    }}

解释:虽然我们传递的是子类的对象,但是由于多态的特性,其父类对象BufferedReader可以指向子类对象,而在实际调用方法时是看右边,也就是会调用子类的readLine方法而非父类的。



总结:
装饰器模式:
使用分层对象来动态透明的向单个对象中添加责任(功能)。
装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。
某些对象是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。
装饰器必须具有和他所装饰的对象相同的接口。

  • 继承实现的增强类:

优点:代码结构清晰,而且实现简单
缺点:对于每一个的需要增强的类都要创建具体的子类来帮助其增强,这样会导致继承体系过于庞大。

  • 修饰模式实现的增强类:

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

下面实现一个小示例,例如儿子会画画,妈妈不仅会画画也会上涂料,而爸爸在妈妈的基础上还会上画框,那么利用代码实现。
分析:基础功能是儿子画画的功能,爸爸和妈妈的功能都是儿子功能的增强

package BasicObject.day20;/** * Created by Dream on 2017/11/6. */abstract class Worker{    public abstract void work();}class Son extends Worker{    public void work(){        System.out.println("画画");    }}class Mom extends Worker{    Worker worker;    public Mom(Worker worker){        this.worker = worker;    }    public void work(){        worker.work();        System.out.println("上涂料");    }}class Dad extends Worker{    Worker worker;    public Dad(Worker worker){        this.worker = worker;    }    public void work(){        worker.work();        System.out.println("上画框");    }}public class DecorationAfterClass {    public static void main(String[] args){        Son son = new Son();        son.work();        Mom mom = new Mom(son);        mom.work();        Dad dad = new Dad(mom);        dad.work();    }}
原创粉丝点击