I/O的学习之字符流

I/O的学习之字符流

今天的学习内容

  • 字符流FileReader

  • 字符流FileWriter

  • 字符流的拷贝

  • 带缓冲的字符流

    • BufferedReader中有一个特有的方法:readLine();返回类型为String

    • BufferedWriter中有一个特有的方法:newLine();换行

  • IO流(将文本反转)

    • 将一个文本文档上的文本反转,第一行和倒数第一行交换,第二行和倒数第二行交换
  • IO流(LineNumberReader)

  • IO流(装饰设计模式)

一、IO流(字符流FileReader)

  • 字符流是什么

    • 字符流是可以直接读写字符的IO流

    • 字符流读取字符, 就要先读取到字节数据, 然后转为字符. 如果要写出字符, 需要把字符转为字节再写出.

  • FileReader

    • FileReader类的read()方法可以按照字符大小读取
  • 程序

      public static void main(String[] args) throws IOException{
          FileReader fReader = null;
          try{
              fReader = new FileReader(new File("E:/test/test2.txt"));
              
              int len = 0;
              //char[] cArr = new char[1024];
              
              while((len = fReader.read()) != -1) {       //通过码表一次读取一个字符
                  System.out.print((char)len);
              }
          }finally {
              if(fReader != null) {
                  fReader.close();
              }           
          }
      }
      /*
          输出test2.txt中内容:我爱你
      */
    

二、IO流(字符流FileWriter)

  • FileWriter类的write()方法可以自动把字符转为字节写出

  • 程序

      public static void main(String[] args) throws IOException{
          FileWriter fWriter = null;
          
          try {
              //创建流对象
              fWriter = new FileWriter("E:/test/test2.txt",true);
              
              //将内容吸写入文件
              fWriter.write("我爱你");
          }finally {
              if(fWriter != null) {
                  fWriter.close();
              }           
          }
      }
    

三、IO流(字符流的拷贝)

  • 利用FileReader和FileWriter进行文件拷贝

  • 程序

      public static void main(String[] args) throws IOException{
          FileReader fReader = null;
          FileWriter fWriter = null;
          
          try {
              fReader = new FileReader("E:/test/test2.txt");
              fWriter = new FileWriter("E:/test/test3.txt");
              
              int len = 0;
              //char[] cArr = new char[1024 * 8];
              while((len = fReader.read()) != -1) {
                  fWriter.write(len);             //Writer类中有一个2k的缓冲区,不关流,则有内容滞留在缓冲区
              }
          }finally {
              try {
                  if(fReader != null) {
                      fReader.close();
                  }
              }finally {
                  fWriter.close();                //关流将缓冲区中内容写入文件
              }           
          }       
      }
    

四、IO流(什么情况下使用字符流)

  • 字符流也可以拷贝文本文件, 但不推荐使用. 因为读取时会把字节转为字符, 写出时还要把字符转回字节.

  • 程序需要读取一段文本, 或者需要写出一段文本的时候可以使用字符流

    • 只读和只写的时候推荐使用字符流
  • 读取的时候是按照字符的大小读取的,不会出现半个中文

  • 写出的时候可以直接将字符串写出,不用转换为字节数组

  • 字节流和字符流在拷贝文件时候的区别

    image

五、IO流(字符流是否可以拷贝非纯文本的文件)

  • 不可以拷贝非纯文本的文件

  • 因为在读的时候会将字节转换为字符,在转换过程中,可能找不到对应的字符,就
    会用?代替,写出的时候会将字符转换成字节写出去(此时其实是字符流的工作原理的理解:读的时候将字节转换为字符,写的时候将字符转换为字节写出去)

  • 如果是?,直接写出,这样写出之后的文件就乱了,看不了了

六、IO流(自定义字符数组的拷贝)

  • 程序

      public static void main(String[] args) throws IOException{
          FileReader fReader = null;
          FileWriter fWriter = null;
          
          try {
              fReader = new FileReader("E:/test/test2.txt");
              fWriter = new FileWriter("E:/test/test3.txt");
              
              //进行文件拷贝
              int len = 0;
              char[] cArr = new char[1024 * 3];
              
              while((len = fReader.read(cArr)) != -1) {   //将文件中数据读到字符数组中
                  fWriter.write(cArr,0,len);      //将字符数组中内容写出到文件
              }
          }finally {
              try {
                  if(fReader != null) {
                      fReader.close();
                  }
              }finally {
                  if(fWriter != null) {
                      fWriter.close();
                  }
              }
          }
      }
    

七、IO流(带缓冲的字符流)

  • BufferedReader的read()方法读取字符时会一次读取若干字符到缓冲区, 然后逐个返回给程序, 降低读取文件的次数, 提高效率

  • BufferedWriter的write()方法写出字符时会先写到缓冲区, 缓冲区写满时才会写到文件, 降低写文件的次数, 提高效率

  • 程序

      public static void main(String[] args) throws IOException{
          try(
              //创建流对象
              BufferedReader bReader = new BufferedReader(new FileReader("E:/test/test2.txt"));
              BufferedWriter bWriter = new BufferedWriter(new FileWriter("E:/test/test3.txt"));               
          ){
              //进行文件拷贝
              int len = 0;
              while((len = bReader.read()) != -1) {       //将文件中内容写到缓冲区
                  bWriter.write(len);                     //将缓冲区内容写入到文件
              }
          }
      }
    

八、IO流(readLine()和newLine()方法)——BufferedReader和BufferedWriter特有方法

  • BufferedReader的readLine()方法可以读取一行字符(不包含换行符号)

  • BufferedWriter的newLine()可以输出一个跨平台的换行符号"\r\n"

  • readLine方法测试

      public static void main(String[] args) throws IOException{
          BufferedReader bReader = null;
          
          try {
              bReader = new BufferedReader(new FileReader("E:/test/test2.txt"));
              
              //按行读文件并输出
              String line;
              while((line = bReader.readLine()) != null) {
                  System.out.println(line);
              }
              
          }finally {
              //关闭流
              if(bReader != null) {
                  bReader.close();
              }
          }
      }
    
  • newLine()方法测试

      public static void main(String[] args) throws IOException{
          BufferedWriter bWriter = null;
          
          try {
              bWriter = new BufferedWriter(new FileWriter("E:/test/test.txt"));
              
              //测试nextLine()方法
              bWriter.write("哈哈哈,我要换行了");
              bWriter.newLine();
              bWriter.write("换行成功了");
              
          }finally {
              if(bWriter != null) {
                  bWriter.close();
              }
          }
      }   
    

九、IO流(将文本反转)

  • 将一个文本文档上的文本反转,第一行和倒数第一行交换,第二行和倒数第二行交换

  • 分析:

    1. 创建文件输入输出流对象BufferedReader和BufferedWriter

    2. 创建LinkedList等待存放每一行

    3. BufferedReader获取每一行

    4. 将每一行存入Linkedlist集合中

    5. 将每一行从LinkedList中倒序取出,写入文件中

  • 程序

      public static void main(String[] args) throws IOException{
              //1. 创建文件输入输出流对象BufferedReader和BufferedWriter
              BufferedReader bReader = null;
              BufferedWriter bWriter = null;
              
              try{
                  bReader = new BufferedReader(new FileReader("E:/test/test2.txt"));  
                  
                  
                  //2. 创建LinkedList等待存放每一行
                  ArrayList<String> list = new ArrayList<>();
                  
                  //3. BufferedReader获取每一行
                  String line;
                  while((line = bReader.readLine()) != null) {
                      //4. 将每一行存入Linkedlist集合中
                      list.add(line);
                  }
                  
                  bReader.close();
                  bWriter = new BufferedWriter(new FileWriter("E:/test/test3.txt"));
                  
                  //5. 将每一行从LinkedList中倒序取出,写入文件中
                  for(int i = list.size() - 1;i >= 0;i--) {
                      bWriter.write(list.get(i));
                      bWriter.newLine();
                  }
              }finally {
                  try {
                      if(bReader != null) {
                          bReader.close();
                      }
                  }finally {
                      if(bWriter != null) {
                          bWriter.close();
                      }
                  }               
              }       
          }
    

十、IO流(LineNumberReader)

  • LineNumberReader简述

    • LineNumberReader是BufferedReader的子类, 具有相同的功能, 并且可以统计行号,LineNumberReader属性域中有一个成员变量lineNumber,初始化为0,调用一次readLine方法就+1一次。

    • This class defines methods setLineNumber(int) and getLineNumber() for setting and getting the current line number respectively.

    • By default, line numbering begins at 0. This number increments at every line terminator as the data is read, and can be changed with a call to setLineNumber(int).(默认行号从0开始,每read一次行号就增加1)

    • 调用getLineNumber()方法可以获取当前行号

      • public int getLineNumber()
        Get the current line number.
    • 调用setLineNumber()方法可以设置当前行号

      • public void setLineNumber(int lineNumber)
        Set the current line number.
  • 程序

      public class TestLineNumberReader {
          public static void main(String[] args) throws IOException{
              LineNumberReader lNumberReader = null;
              
              try {
                  lNumberReader  = new LineNumberReader(new FileReader("E:/test/test2.txt"));
                  
                  String line;
                  
                  lNumberReader.setLineNumber(100);
                  while((line = lNumberReader.readLine()) != null) {      //read一次行号就+1
                      System.out.println(lNumberReader.getLineNumber() + " : " + line);
                  }
      
              }finally {
                  if(lNumberReader != null) {
                      lNumberReader.close();
                  }           
              }
          }
      }
      /*
       在JDK1.8中输出结果为:
      -----------------
      101 : 哈哈哈我爱你
      102 : 嘻嘻嘻
      103 : 嘻嘻哈哈
      104 : 哈哈嘻嘻
      105 : 我在写几行
      106 : 啊哈哈哈哈哈哈哈
      ----------------- 
       * */
    

十一、IO流(装饰设计模式)

  • 定义一个超类/接口Coder

      public interface Coder {
          void code();
      }
    
  • 定义被装饰类学生——继承接口Coder

      public class Student implements Coder{
    
          @Override
          public void code() {
              System.out.println("学生会敲代码");   
          }   
      }   
    
  • 定义装饰者类GoodStudent——继承接口Coder并接受学生类引用

      public class GoodStudent implements Coder{
          private Student s;          //获取被装饰类的引用
          
          GoodStudent(Student s){     //在构造方法中传入被装饰类的引用
              this.s = s;
          }
          
      
          @Override
          public void code() {        //对原有功能进行升级
              s.code();
              System.out.println("好学生敲好代码");      
          }   
      }
    
  • 继承于装饰者模式的关系

    • 装饰者模式中用到了继承,但是这里装饰者模式用继承是为了保证装饰者和被装饰者有共同的超类,而不是为了给被装饰的类扩展新的特性

    • 而装饰者模式中新的特性是通过类与类的组合(has-a的关系)而得到的,所以把装饰者模式和继承看做同一类设计思想是不恰当的。

    • 装饰者模式减少了类与类之间的耦合性,当改动被装饰类时不影响装饰者类

    • 一个被装饰类可选择多了装饰者,而继承的耦合性太高

十二、IO流(使用指定的码表读写字符)

  • 基础概念

    • FileReader是使用默认码表读取文件, 如果需要使用指定码表读取, 那么可以使用InputStreamReader(字节流,编码表)

    • FileWriter是使用默认码表写出文件, 如果需要使用指定码表写出, 那么可以使用OutputStreamWriter(字节流,编码表)

  • 程序

      //高效的用指定的编码表读
      BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("UTF-8.txt"), "UTF-8"));
    
      //高效的用指定的编码表写
      BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("GBK.txt"), "GBK"));
      
      int ch;
      
      while((ch = br.read()) != -1) {
          bw.write(ch);
      }
      
      br.close();
      bw.close();
    
    • 字节流来读取:防止乱码

    • 用指定的编码表是为了将字节正常的转换为字符

  • 在实际开发中编码方式

    • GBK 国家编码 包含简繁体中文

    • GB2312 国家编码 只包含简体中文

    • ISO-8859-1 国际编码 世界上大多数国家使用的是字母 只包含字母

    • Unicode 十六进制的编码 描述所有的文字信息

    • UTF 象形文字 字母采用ISO-8859-1的编码

十三、IO流(转换流图解)

  • 两个转换流

    • InputStreamReader

    • OutputStreamWriter

  • 转换流可指定编码类型,避免读取和写入时的乱码

  • 转换流图解

    image
    • InputStreamReader(字节流,编码表)字节通向字符的桥梁

      • 通过指定的编码表将字节转化为字符
    • OutputStreamWriter(字节流,编码表)字节通向字符的桥梁

      • 通过指定的编码表将字符转化为字节

十四、IO流(获取文本上字符出现的次数)

  • 获取一个文本上每个字符出现的次数,将结果写在times.txt上

  • 分析

    1. 创建BufferedReader输入流读取文本中的内容

    2. 将读取到的每一个字符存到HashMap中

    3. 注意:若key重复,则增加value;若不重复,则value = 1;

    4. 遍历输出HashMap键值对

    5. 将键值对结果写入到times.txt中

    6. 关闭流

    7. 注意异常的处理

  • 代码

      public static void main(String[] args) throws IOException{
          //1. 创建BufferedReader输入流读取文本中的内容
          BufferedReader bReader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("E:/test/test2.txt")),"GBK"));
          HashMap<Character, Integer> hashMap = new HashMap<>();
          
          /*2. 将读取到的每一个字符存到HashMap中
          3. 注意:若key重复,则增加value;若不重复,则value = 1;*/        
          int b = 0;
          while((b = bReader.read()) != -1) {
              char ch = ((char)b);
              if(hashMap.containsKey(ch)) {
                  hashMap.put(ch, hashMap.get(ch) + 1);
              }else {
                  hashMap.put(ch, 1);
              }
          }
          bReader.close();
          
          //创建输出流
          BufferedWriter bWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File("E:/test/times.txt"))));
          
          //4. 遍历输出HashMap键值对
          //5. 将键值对结果写入到times.txt中        
          for(Character character : hashMap.keySet()){
              char key = character;
              switch (key) {
              case '\n':
                  bWriter.write("\\n" + " = " + hashMap.get(key));
                  break;
              case '\t':
                  bWriter.write("\\t" + " = " + hashMap.get(key));
                  break;
              case '\r':
                  bWriter.write("\\r" + " = " + hashMap.get(key));
                  break;
    
              default:
                  bWriter.write(key + " = " + hashMap.get(key));              
                  break;
              }
              bWriter.newLine();          
          }
          
          bWriter.close();
      }
    

十五、IO流(试用版软件)

  • 当我们下载一个试用版软件,没有购买正版的时候,每执行一次就会提醒我们还有多少次使用机会用学过的IO流知识,模拟试用版软件,试用10次机会,执行一次就提示一次您还有几次机会,如果次数到了提示请购买正版

  • 分析

    • 由于读取数字的原因,不可hi借用字节流和字符流,在这儿我们选择BufferedReader中的特殊方法readLine()读取整行

      • 创建带缓冲的输入流对象

      • 将读到的字符串转化为int

      • 若int值大于0,则返回int-1,若不大于0,则提示购买正版

      • if判断中要将--结果打印,并输出到文件中

  • 代码

      public static void main(String[] args) throws IOException{
              //1. 创建带缓冲的输入流对象
              BufferedReader bReader = new BufferedReader(new FileReader(new File("Conf.txt")));
              String line = bReader.readLine();       
              bReader.close();
              
              //2. 将读到的字符串转化为int
              int times = Integer.parseInt(line);     
              
              //3. 若int值大于0,则返回int-1,若不大于0,则提示购买正版
              if(times > 0) {
                  System.out.println("您还有" + times-- + "次机会");
                  FileWriter fWriter = new FileWriter("Conf.txt");    //会先清空Conf.txt中的内容
                  fWriter.write(times + "");
                  fWriter.close();    //一定要关流,内容在缓冲区里,并没有写入到文件
              }else {
                  System.out.println("您的试用次数已到,请购买正版");
              }       
          }
    

十六、(递归)

  • 5的阶乘

      public class TestRecursion {
          public static void main(String[] args) {
              System.out.println(fun(5));
          }
      
          private static int fun(int i) {
              // TODO Auto-generated method stub
              if(i == 1){
                  return 1;
              }else {
                  return i * fun(i-1);
              }
          }   
      }   
    
  • 递归的弊端

    • 不能调用太多次,容易导致栈溢出
  • 递归与循坏相比的好处

    • 不用知道循坏次数
  • 递归调用是否必须有返回值

十七、File类(练习)

  • 需求:从键盘输入接收一个文件夹路径,打印出该文件夹下所有的.md文件名

  • 程序

      public static void main(String[] args) {
          //获取键盘输入
          Scanner input = new Scanner(System.in);
          System.out.println("请输入文件夹路径");
          String string;
          File file;
          while(true) {
              string = input.nextLine();
              file = new File(string);
              
              if(file.isDirectory()) 
                  break;
              else
                  System.out.println("您输入的目录不存在,请重新输入");
          }
          
          //递归遍历所有的.md文件
          doFind(file);
      }
    
      private static void doFind(File file) {
          // TODO Auto-generated method stub
          if(file.isDirectory()) {
              File[] files = file.listFiles();
              
              if(files != null) {
                  for(int i = 0;i < files.length;i++)
                  doFind(files[i]);
              }
          }else {
              if(file.getName().contains(".md")) {
                  System.out.println(file);
              }
          }
              
      }
    

十八、File类(练习)

  • 需求:从键盘输入接收一个文件夹路径,打印出该文件夹下所有的.java文件名

  • 程序

      public static void main(String[] args) {
          File file = getDir();
          doFind(file);
      }
    
      private static void doFind(File dir) {
          // TODO Auto-generated method stub
          File[] files = dir.listFiles();
          
          for(File subFile : files) {
              if(subFile.isFile() && subFile.getName().endsWith(".md")) {
                  System.out.println(subFile);
              }else if(subFile.isDirectory()){
                  doFind(subFile);
              }
          }   
      }
    
      private static File getDir() {
          // TODO Auto-generated method stub
          //判断输入的合法性
          Scanner input = new Scanner(System.in);
          System.out.println("请输入文件夹路径:");
          
          String string = null;
          File file = null;
          
          while(true) {
              string = input.nextLine();
              file  = new File(string);
              
              if(!file.exists()) {
                  System.out.println("您输入的路径不存在,请重新输入");
              }else if(file.isFile()) {
                  System.out.println("您输入的是文件路径,请重新输入文件夹路径");
              }else {
                  return file;
              }
          }
      }
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容

  • 一、流的概念和作用。 流是一种有顺序的,有起点和终点的字节集合,是对数据传输的总成或抽象。即数据在两设备之间的传输...
    布鲁斯不吐丝阅读 9,953评论 2 95
  • 概述 java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。java.io ...
    Steven1997阅读 9,105评论 1 25
  • 一、基础知识:1、JVM、JRE和JDK的区别:JVM(Java Virtual Machine):java虚拟机...
    杀小贼阅读 2,334评论 0 4
  • 推开窗子,让雨声清晰透明起来,远山曲线迷蒙,雾霭轻轻。近处水漫路面,玉树葱茏,我深呼吸享受这雨水中的清冽。 但是行...
    猫咪看见秋刀鱼阅读 193评论 0 1
  • 半城烟柳一枝春,空闺红妆湿泪痕。斜月浅照深眠处,犹把思念寄良人。
    欲倚阑干不自由阅读 132评论 2 1