java基础io流——字符流的变革(深入浅出)

乱码导火索:

在io流里,先诞生了字节流,但是字节流读取数据会有乱码的问题(读中文会乱码)。比如:

FileInputStream fis = new FileInputStream("a.txt");
//        int by = 0;
//        while ((by=fis.read() )!= -1) {
//            System.out.print((char)by);//bcdbcdbcdbcdbcdbcdhello 中�
//            //因为还正常,中文就乱码了,有什么办法解决吗,有,就是有点麻烦
//        }

从文件中读取中文会有乱码,当然字节流有解决措施。

FileInputStream fis = new FileInputStream("a.txt");
byte[] bytes = new byte[1024];
        int len = 0;
        while ((len = fis.read(bytes)) != -1) {
            System.out.println(new String(bytes,0,len));//bcdbcdbcdbcdbcdbcdhello 中国
            //查看new String()的源码,this.value = StringCoding.decode(bytes, offset, length);
            //点进decode,循序渐进发现,默认编码是UTF-8
            //通过源码,也看到了这个方法public String(byte bytes[], int offset, int length, String charsetName)

        }

但是解码这并不是字节流做的,而是String的功能。查看String的源码,构造方法有解码功能,并且默认编码是utf-8。

聊到了乱码,我觉得有必要聊一下编码的知识。

String(byte[] bytes, String charsetName):通过指定的字符集解码字节数组
byte[] getBytes(String charsetName):使用指定的字符集合把字符串编码为字节数组

编码:把看得懂的变成看不懂的
String -- byte[]

解码:把看不懂的变成看得懂的
byte[] -- String

因此字节流读取的数据是编码过的数据,我们解码就行了。

编码问题简单,只要编码解码的格式是一致的。

计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。

     ASCII:美国标准信息交换码。
     用一个字节的7位可以表示。
     ISO8859-1:拉丁码表。欧洲码表
     用一个字节的8位表示。
     GB2312:中国的中文编码表。
     GBK:中国的中文编码表升级,融合了更多的中文文字符号。
     GB18030:GBK的取代版本
     BIG-5码 :通行于台湾、香港地区的一个繁体字编码方案,俗称“大五码”。
     Unicode:国际标准码,融合了多种文字。
     所有文字都用两个字节来表示,Java语言使用的就是unicode
     UTF-8:最多用三个字节来表示一个字符。

     UTF-8不同,它定义了一种“区间规则”,这种规则可以和ASCII编码保持最大程度的兼容:
     它将Unicode编码为00000000-0000007F的字符,用单个字节来表示�
     它将Unicode编码为00000080-000007FF的字符用两个字节表示 
     它将Unicode编码为00000800-0000FFFF的字符用3字节表示 

示例:

        String s = "你好";
        byte[] bytes1 = s.getBytes();
        System.out.println(Arrays.toString(bytes1));//[-28, -67, -96, -27, -91, -67]  默认编码utf-8

        byte[] bytes2 = s.getBytes("GBK");
        System.out.println(Arrays.toString(bytes2));//[-60, -29, -70, -61]
        byte[] bytes3 = s.getBytes("UTF-8");
        System.out.println(Arrays.toString(bytes3));//[-28, -67, -96, -27, -91, -67]

        String s1 = new String(bytes1);
        System.out.println(s1);//你好
        String s2 = new String(bytes2,"GBK");
        System.out.println(s2);//你好

        String s3 = new String(bytes2,"gbk");
        System.out.println(s3);//你好

        String s4 = new String(bytes3);
        System.out.println(s4);//你好

        String s5 = new String(bytes3,"gbk");
        System.out.println(s5);//浣犲ソ

虽然字节流有解决乱码的方案,但并不方便,所以java io流就设计出了转换流,一场乱码引发的变革。
(OutputStreamWriter、InputStreamReader)

OutputStreamWriter(OutputStream out):根据默认编码把字节流的数据转换为字符流

OutputStreamWriter(OutputStream out,String charsetName):根据指定编码把字节流数据转换为字符流
把字节流转换为字符流。

//创造对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("b.txt"));//查看源码,java8 默认utf-8
//写数据
osw.write("中国");
//释放资源
osw.close();//close包含了close和flush的作用

一般默认编码就够了。

查看源码,发现OutputStreamWriter有5个write方法。

/*
 * OutputStreamWriter的方法:
 * public void write(int c):写一个字符
 * public void write(char[] cbuf):写一个字符数组
 * public void write(char[] cbuf,int off,int len):写一个字符数组的一部分
 * public void write(String str):写一个字符串
 * public void write(String str,int off,int len):写一个字符串的一部分
 *
 * 面试题:close()和flush()的区别?
 * A:close()关闭流对象,但是先刷新一次缓冲区。关闭之后,流对象不可以继续再使用了。
 * B:flush()仅仅刷新缓冲区,刷新之后,流对象还可以继续使用。
 */
 
 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("c.txt"));//查看源码,java8 默认utf-8
       //写一个字符
        osw.write('a');
        osw.write(97);
        osw.write('中');
        // 为什么数据没有进去呢?
        // 原因是:字符 = 2字节
        // 文件中数据存储的基本单位是字节。
        // void flush()
        osw.flush();
        //写一个字符数组
        char[] chars = {'a','b','中','国'};
        osw.write(chars);
        osw.flush();
        //写一个字符数组的一部分
        osw.write(chars,2,2);
        osw.flush();
        //写一个字符串
        osw.write("\n\r中国");
        //写一个字符串的一部分
        osw.write("中国你好",2,2);
        osw.close();

InputStreamReader(InputStream is):用默认的编码读取数据,默认utf-8

InputStreamReader(InputStream is,String charsetName):
用指定的编码读取数据

//创建对象
InputStreamReader isr = new InputStreamReader(new FileInputStream("b.txt"));//默认编码utf-8
InputStreamReader isr1 = new InputStreamReader(new FileInputStream("b.txt"),"gbk");//可指定编码

//读数据
int ch = 0;
while ((ch = isr.read()) != -1) {
    System.out.print((char)ch);//中国
}
//释放资源
isr.close();
//只有文档的编码和读取的编码一致才不会乱码。

查看源码知道InputStreamReader有2个read方法。

/*
 * InputStreamReader的方法:
 * int read():一次读取一个字符
 * int read(char[] chs):一次读取一个字符数组
 */

InputStreamReader isr = new InputStreamReader(new FileInputStream("c.txt"));
        //读一个字符
//        int ch = 0;
//        while ((ch = isr.read()) != -1) {
//            System.out.print((char) ch);//9797200139798200132226920013222691020013222692032022909/
//            //aa中ab中国中国
//            //中国你好
//        }
        //isr.close();
        //读一个字符数组
        char[] chars =new char[1024];
        int len = 0;
        while ((len = isr.read(chars)) != -1) {
            System.out.println(chars.length);//1024
            System.out.println(new String(chars,0,len));
            //aa中ab中国中国
            //中国你好
        }
        isr.close();

现在我们可以通过转换流升级字节流复制文件的方式了。

InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("e:\\a.txt"));
char [] chars = new char[1024];
int len  = 0 ;
while ((len = isr.read(chars)) != -1) {
    osw.write(chars,0,len);
    osw.flush();
}
osw.close();
isr.close();

字符流的诞生:

转换流已经是字符流了,但是他们的名字太长了,Java就提供了其子类供我们使用。

FileWriter 、FileReader 继承了其父类的所有方法。

字符流=字节流+编码表

OutputStreamWriter = FileOutputStream + 编码表(GBK)

FileWriter = FileOutputStream + 编码表(GBK)

InputStreamReader = FileInputStream + 编码表(GBK)

FileReader = FileInputStream + 编码表(GBK)

复制改写:

FileWriter fw  = new FileWriter("e:\\b.txt");
FileReader fr = new FileReader("b.txt");
char[] chars =new char[1024];
int len = 0;
while ((len = fr.read(chars)) != -1) {
    fw.write(chars,0,len);
    fw.flush();
    }
fw.close();
fr.close();
   /**
     * 总结,到现在为止,加上字节流,复制文本的方式有8种,但是复制图片视频等文件只有四种字节流的方式,因为不能用字符流复制图片视频mp3等
     */

字符流学习前辈字节流的经验,也设计了字符缓冲流。

BufferedReader、
BufferedWriter

和字节缓冲流的设计基本一样,也有两个构造方法,但是我们只要默认的缓冲大小的构造方法就可以了。

用字符缓冲流改写复制功能:

BufferedReader br = new BufferedReader(new FileReader("c.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("e:\\d.txt"));
char[] chars = new char[1024];
int len = 0 ;
while ((len = br.read(chars)) != -1) {
    bw.write(chars,0,len);
    bw.flush();
}
br.close();
bw.close();

然而字符缓冲流还有自己特殊的读写功能。

BufferedWriter :void newLine() 换行

BufferedReader :String readLine() 读一行数据

public static void main(String args[]) throws IOException {
       // write();
        read();
}

    private static void read() throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("bw.txt"));
        String line = null;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
            //readLine不会读取换行符
            //读到末尾返回null
        }
        br.close();
    }

    private static void write() throws IOException {
        BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
        for (int i = 0; i <10 ; i++) {
            bw.write("你好哈哈哈哈哈");
            bw.newLine();//换行,并且自动检测不同系统的换行符
            bw.flush();
            //一般三个连用
        }
        bw.close();
    }

用字符缓冲流的特殊方式升级复制功能:

BufferedReader br = new BufferedReader(new FileReader("bw.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("e:\\bw.txt"));

String line = null;
while ((line = br.readLine()) != null) {
    bw.write(line);
    bw.newLine();
    bw.flush();
    }
bw.close();
br.close();


    //总结  字符流复制文本有5种方式

字符流的基础基本就聊完了,还有一个比较常用的类LineNumberReader

查看源码可知LineNumberReader继承自BufferedReader,并且增加了行号的操作。

LineNumberReader lnr = new LineNumberReader(new FileReader("a.txt"));
String line = null;
while ((line = lnr.readLine()) != null) {
    System.out.println(lnr.getLineNumber()+":"+line);
}

1:A
2:S
3:F

但是LineNumberWriter。源码很简单,更多的操作请看源码。

io流总结:

image

java 中文api :
http://tool.oschina.net/apidocs/apidoc?api=jdk-zh

查看api源码,深入理解io设计理念。

io流面试题:

题一:复制单级文件夹
/**
 * 封装
 * 新建文件夹
 * 获得源文件夹下文件列表
 * 复制文件到新文件夹
 */
public class copyFolder {
    public static void main(String args[]) throws IOException {
        File file1 = new File("F:\\汤包\\IT时代\\java基础\\day21\\code\\demo");
        File file2 = new File("e:\\demo");
        //判断文件夹是否存在
        if (!file2.exists()){
            file2.mkdir();
        }

        //获取源文件夹下文件列表

        File[] files = file1.listFiles();
        for (File file : files) {
            File newfile = new File(file2,file.getName());
            copyFun(file,newfile);
        }
    }

    private static void copyFun(File file1,File file2) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file1));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file2));
        byte[] bytes = new byte[1024];
        int len = 0;
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes,0,len);
        }
        bis.close();
        bos.close();
    }
}
题二:复制多极文件夹
/*
 * 需求:复制多极文件夹
 *
 * 数据源:F:\汤包\IT时代\java基础\day21\code\demos
 * 目的地:E:\\
 *
 * 分析:
 *      A:封装数据源File
 *      B:封装目的地File
 *      C:判断该File是文件夹还是文件
 *          a:是文件夹
 *              就在目的地目录下创建该文件夹
 *              获取该File对象下的所有文件或者文件夹File对象
 *              遍历得到每一个File对象
 *              回到C
 *          b:是文件
 *              就复制(字节流)
 */

public class copyFolder2 {

    public static void main(String args[]) throws IOException {
        File file1 = new File("F:\\汤包\\IT时代\\java基础\\day21\\code\\demos");
        File file2 = new File("e:\\");
        copyFolder(file1,file2);
    }

    private static void copyFolder(File srcFile, File destFile) throws IOException {
        if (srcFile.isDirectory()){
            File newFolder = new File(destFile, srcFile.getName());
            newFolder.mkdir();
            File[] files = srcFile.listFiles();
            for (File file1 : files) {
                copyFolder(file1,newFolder);
            }
        }else {
            File newFile = new File(destFile,srcFile.getName());
            copyFile(srcFile,newFile);
        }

    }

    private static void copyFile(File file1,File file2) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file1));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file2));
        byte[] bytes = new byte[1024];
        int len = 0;
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes,0,len);
        }
        bis.close();
        bos.close();
    }
}

复制多级文件夹,构思用到了递归,可知递归真的很重要,之后也会总结一下递归的知识。

题三:键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩),按照总分从高到低打印到控制台。

Student.java

public class Student {
    // 姓名
    private String name;
    // 语文成绩
    private int chinese;
    // 数学成绩
    private int math;
    // 英语成绩
    private int english;

    public Student() {
        super();
    }

    public Student(String name, int chinese, int math, int english) {
        super();
        this.name = name;
        this.chinese = chinese;
        this.math = math;
        this.english = english;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getChinese() {
        return chinese;
    }

    public void setChinese(int chinese) {
        this.chinese = chinese;
    }

    public int getMath() {
        return math;
    }

    public void setMath(int math) {
        this.math = math;
    }

    public int getEnglish() {
        return english;
    }

    public void setEnglish(int english) {
        this.english = english;
    }

    public int getSum() {
        return this.chinese + this.math + this.english;
    }
}

StendentDemo.java

/*
 * 键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩),按照总分从高到低存入文本文件
 *
 * 分析:
 *      A:创建学生类
 *      B:创建集合对象
 *          TreeSet<Student>
 *      C:键盘录入学生信息存储到集合
 *      D:遍历集合,把数据写到文本文件
 */
public class StendentDemo {
    public static void main(String args[]) throws IOException {
        TreeSet<Student> students = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                //s1-s2升序,s2-s1降序
                int num = s2.getSum() - s1.getSum();
                int num1 = num == 0 ?s2.getChinese()-s1.getChinese():num;
                int num2 = num1 ==0 ?s2.getMath()-s1.getMath():num1;
                int num3 = num2 ==0 ?s2.getEnglish()-s1.getEnglish():num2;
                int num4 = num3 == 0?s1.getName().compareTo(s2.getName()):num3;//按字母顺序
                return num4;
            }
        });
        for (int i = 0; i <5 ; i++) {
            Scanner sc = new Scanner(System.in);
            System.out.println("录入学生成绩:");
            System.out.println("姓名:");
            String name = sc.nextLine();
            System.out.println("语文成绩:");
            int chinese = sc.nextInt();
            System.out.println("数学成绩:");
            int math = sc.nextInt();
            System.out.println("英语成绩:");
            int english = sc.nextInt();
            Student student = new Student(name,chinese,math,english);
            students.add(student);
        }
        BufferedWriter bw = new BufferedWriter(new FileWriter("grade.txt"));
        bw.write("学生信息如下:");
        bw.newLine();
        bw.flush();
        bw.write("姓名,语文成绩,数学成绩,英语成绩");
        bw.newLine();
        bw.flush();
        for (Student student : students) {
            StringBuilder sb = new StringBuilder();
            sb.append(student.getName()).append(student.getChinese()).append(student.getMath()).append(student.getEnglish());
            bw.write(sb.toString());
            bw.newLine();
            bw.flush();
        }
        bw.close();






    }
}
题四:已知s.txt文件中有这样的一个字符串:“hcexfgijkamdnoqrzstuvwybpl”,请编写程序读取数据内容,把数据排序后写入ss.txt中。
/*
 * 已知s.txt文件中有这样的一个字符串:“hcexfgijkamdnoqrzstuvwybpl”
 * 请编写程序读取数据内容,把数据排序后写入ss.txt中。
 * 
 * 分析:
 *      A:把s.txt这个文件给做出来
 *      B:读取该文件的内容,存储到一个字符串中
 *      C:把字符串转换为字符数组
 *      D:对字符数组进行排序
 *      E:把排序后的字符数组转换为字符串
 *      F:把字符串再次写入ss.txt中
 */
public class StringDemo {
    public static void main(String[] args) throws IOException {
        // 读取该文件的内容,存储到一个字符串中
        BufferedReader br = new BufferedReader(new FileReader("s.txt"));
        String line = br.readLine();
        br.close();

        // 把字符串转换为字符数组
        char[] chs = line.toCharArray();

        // 对字符数组进行排序
        Arrays.sort(chs);

        // 把排序后的字符数组转换为字符串
        String s = new String(chs);

        // 把字符串再次写入ss.txt中
        BufferedWriter bw = new BufferedWriter(new FileWriter("ss.txt"));
        bw.write(s);
        bw.newLine();
        bw.flush();

        bw.close();
    }
}
题五:用Reader模拟BufferedReader的readLine()功能
/*
 * 用Reader模拟BufferedReader的readLine()功能
 *
 * readLine():一次读取一行,根据换行符判断是否结束,只返回内容,不返回换行符
 */
public class MyBufferedReader {
    private Reader reader;

    public MyBufferedReader(Reader reader){
        this.reader=reader;
    }
    public String readLine() throws IOException {
        StringBuilder sb = new StringBuilder();
        int ch = 0;
        while((ch=reader.read())!=-1) {
            if (ch=='\r'){
                continue;
            }
            if (ch=='\n'){
                return  sb.toString();
            }else {
                sb.append((char)ch);
            }
        }

        if (sb.length()>0){
            return sb.toString();
        }
        return null;
    }

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

测试:

MyBufferedReader mbr = new MyBufferedReader(new FileReader("a.txt"));

        String line = null;
        while ((line = mbr.readLine()) != null) {
            System.out.println((line));
        }

        mbr.close();

通过自己实现readLine()方法,并且查看源码可知,字符缓冲流的readLine()也用到了StringBuilder,并且也要判断\n和\r,最后关闭的流就是Reader。

以上是本人学习笔记整理,重温java经典,欢迎各位同道中人批评指正。

学习心得:
再次学习io流,看了好多源码,体验了其架构思想的强大,逻辑的缜密。发现io流的读和写都是线程安全的。所以线程得再次研究下,设计模式得了解下。

源码码云地址:
https://gitee.com/stefanpy/java

梦回io流完整目录:

java基础io流——File告白(重温经典)

java基础io流——OutputStream和InputStream的故事(温故知新)

java基础io流——字符流的变革(深入浅出)

java基础io流——配角也风流(不求甚解)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容