JavaIO流03

10.编码解码

编码解码
在前面我们讲述编码表,使用编码表可以把生活中的数据,进行编码后存储在计算机中,同样可以把计算机中存储的数据,进行解码后展示给我们。

简单的说,编码就是把我们能够看得懂,编码成我们看不懂的(字节数据),存储起来。
解码就是把我们看不懂的,解码成我们能够看的懂,展示给我们。
数据经常使用字符串来描述,而数据存储起来是采用字节数据在存储。所以我们需要把String类
型的数据转成byte类型的数据进行存储。
String----->byte[] 编码
byte[]----->String 解码

public class EncodingDemo {
   public static void main(String[] args) throws UnsupportedEncodingException {
       String str = "你好";
     
       //对字符串编码。--->字节数组。
       byte[] buf1 = str.getBytes("utf-8");
     
       for(byte b : buf1){
          System.out.print(b);
       }
     
       //对字节数组解码 。--->字符串。
       //String s1 = new String(buf1,"GBK");这样会出现乱码问题
       String s1 = new String(buf1,"utf-8");
       System.out.println(s1);
   }
}

字符串–编码(getBytes())–>字节数组
字节数组–解码(new String(byte[]))–>字符串
“你好”:
GBK编码 -60 -29 -70 -61
UTF-8编码:-28 -67 -96 -27 -91 -67
当使用getBytes对字符进行编码后,在解码的时候,指定的码表一定要和编码时的码表一致,
否则会发生解码错误的现象,出现乱码。
按照字节截取字符串
需求:对字符串按照字节数截取(默认码表),“abc你好” 有5个字符,有7个字节。按照3个字节截取 abc ,按照四个字节截取 abc和你字的一半,如果出现中文一半舍弃。请定义一个功能解决这个问题。
思路:
你好的gbk编码为:-60 -29 -70 -61

  1. 一个中文gbk两个字节,都是负数。
  2. 在截取时,先看最后一位是负数吗?不是,直接截取就哦了。如果是,不要直接舍弃,最好在看一下该负数之前连续出现了几个负数。
  3. 因为中文两个字节,出现的负数个数是偶数,不需要舍弃的,是奇数,就舍弃最后一个。
public class CutStringTest {
   public static void main(String[] args) throws UnsupportedEncodingException {
       String str = "abc你好cd谢谢";
       byte[] buf = str.getBytes("GBK");
       for (int i = 0; i < buf.length; i++) {
          String s = cutString(str,i+1);
          System.out.println(str+",截取"+(i+1)+"个结果是:"+s);
       }
   }
   public static String cutString(String str,int len) throws
UnsupportedEncodingException {
     
       //1,将字符串编码成字节数组。
       byte[] buf = str.getBytes("GBK");
       int count = 0;
       //2,对数组进行遍历。从截取位开始遍历。往回遍历。
       for(int i = len - 1; i >=0 ; i--){
          //判断最后截取位上是否是负数
          if(buf[i]<0){
             count++;
          }else{
             break;
          }
       }
       //判断奇偶数。
       if(count%2==0){
          return new String(buf,0,len);
       }else{
          return new String(buf,0,len-1);//舍弃最后一个。
       }
   }
}

思考:上述代码可以截取"abc琲琲cd琲琲"字符串吗?
联通编码
当我们把“联通”和“移动”这两字符串分别存放到两个不同的记事本中,然后打开发现,联通的字样不见了,而移动没有问题。这是怎么回事呢?

其原因是因为当我们给记事本中存放数据时,使用的是本地默认码表存放的数据,当我们保存时会把字符串编码后存放记事本中,而在打开时,记事本会根据数据的特点采用相应的码表解码。

由于“联通”存放的时候二进制数据的格式符合了UTF-8数据存储格式。所以在打开的时候,记事本使用了UTF-8对数据进行解码,结果发生了数据的错误。
这里需要给大家介绍UTF-8的编码格式:关于UTF-8的编码格式介绍在DataInput类中描述。

UTF-8编码格式规律:
当在一个字节范围能存放下的数据,就采用一个字节来存放,并且最高位的二进制固定为0。
当在一个字节存放不下,而在两个字节范围正好可以存放下的数据,就采用两个字节存放,并且第一个字节最高3位固定为110,第二个字节最高2位固定为10.
当在两个字节存放不下,而在三个字节范围正好可以存放下的数据,就采用三个字节存放,并且第一个字节最高4位固定为1110,第二个字节最高2位固定为10,第三个字节最高2位固定为10。
因此在使用UTF-8对数据进行解码时,只要符合上述规律,就会根据utf-8的格式对数据进行读取,并解码。
联通两个汉字的编码格式:
11000001
10101010
11001101
10101000
这时这个二进制正好符合UTF-8码表的编码格式,解析的时候到utf-8码表中查询数据,进行解码,而出现了乱码问题。

11.字符流缓冲区原理

字符流缓冲区以及readLine方法原理

public class BufferedStreamDemo {
   public static void main(String[] args) throws IOException {
       BufferedReader bufr = new BufferedReader(new FileReader("d:\\test\\cn.txt"));
     
       String line = null;
    while((line=bufr.readLine())!=null){
          System.out.println(line);
       }
       bufr.close();
   }
}

上述代码在读取文件时,使用BufferedReader对象,BufferedReader是如何读取的呢?
当使用BufferedReader读取文件时,在BufferedReader中维护了一个数组,这个数组中存放的数据是由底层直接读取存放进去的。当我们调用BufferedReader的read方法时,这个read方法会到BufferedReader的缓冲区中读取数据。

由于BufferedReader读取的都是文本数据,文本数据可以按行的方式进行读取的,怎么读取呢?调用BufferedReader的readLine方法,readLine方法中维护了一个容器,当使用readLine读取数据,仍然会使用BufferedReader的read方法到缓冲区中去读数据,并存放到readLine自己的容器中,读取时,如果读取到了’\r’时,会继续往下读,读到’\n’时,表示一行读取结束了。

readLine会把自己容器中的数据变成字符串返回给调用readLine的调用者。

*总结:缓冲区原理:

  1. 使用了底层流对象从具体设备上读取数据,并将读取到的数据存储到缓冲区的数组中
  2. 通过缓冲区的read方法从缓冲区来获取具体的字符数据,这样就提高了效率
  3. 如果read方法读取字符数据,并存储到另外一个容器中,直到读取到了换行符时,另一个容器中临时存储的数据转成字符串返回,就形成了readLine的功能。*

字符缓冲区read()方法实现
自定义一个字符流缓冲区。 用于缓冲字符数据,从而提高操作效率。并提供了更多操作缓冲区数据的方法。 需要使用具体的流对象来完成数据的获取。
分析: 缓冲区应该具备什么? 1. 必须要有数组。 2. 需要对数组进行操作,对数组操作一定要有角标

public class MyBufferedReader {
   private Reader r;
   // 定义一个字符数组,作为缓冲区。
   private char[] buf = new char[1024];
   // 定义了一个索引,用于操作数组中的元素。
   private int index = 0;
   // 定义了一个变量,用于记录读取字符的个数。
   private int count = 0;
   // 需要一初始化就具备一个流对象。
   public MyBufferedReader(Reader r) {// 可以对Reader的所有子类进行高效读取。
       this.r = r;
   }
   //提供一个可以从缓冲区中读取一个字符的方法。 高效方法。
   public int read() throws IOException {
       // 1,需要先通过流对象从底层设备上获取一定数据的数据到缓冲区数组中。 使用流对象
read(char[]);
       //如果count记录字符个数的变量为0,说明缓冲区已经没有字符数据。
       if(count==0){
          //需要从设备上获取一定数量的数据存储到缓冲区中,并用count记录存储字符的个数。
          count = r.read(buf);
          //每取一次新的数据,就需要将角标归0.
          index = 0;
       }
       //如果count小于0,说明到-1,没有数据了,程序直接返回-1.
       if(count<0){
          return -1;
       }
       //从缓冲区中取出一个字符。
       char ch = buf[index];
       //角标自增。
       index ++;
       //计数器要自减。
       count --;  
       return ch;
   }
   // 关闭流资源。
   public void close() throws IOException {
       // 其实内部就是关闭具体的流。
       r.close();
   }
}

readLine方法实现
思路;
从缓冲区中一次获取一个字符,并将这个字符存储到临时容器中。每获取一个字符都要进行判断,只要不是行终止符都进行存储。一旦读取到行终止符,就将临时容器中的数据转成字符串返回。

//基于高效的read方法,建立一个一次可以读取一行的数据的方法。  将行终止符前的数据转成字符
串返回。
   public String readLine() throws IOException{
       //1,定义一个临时容器。
       StringBuilder sb = new StringBuilder();
     
       //2,调用本类中的read方法,从缓冲区中读取一个字符,存储到临时容器中。
       //存的时候要注意:必须判断,如果是行终止符就不要存储了。就将临时容器中的
       //字符转成字符串返回。
     
       int ch = 0;
       while((ch=this.read())!=-1){
          if(ch=='\r'){
             continue;
          }       
          if(ch=='\n'){
             return sb.toString();
          }
          sb.append((char)ch);//将读取到的字符数字转成char类型,存储到sb中。
       }
       //万一文本中最后一行没有行终止符,判断一下sb中是否有内容,如果有则返回。
       if(sb.length()!=0){
          return sb.toString();
       }
       return null;
    }

12.装饰设计模式

装饰设计模式
设计模式在前面我们已经学习过了,当时我们还学习了单例设计模式。大家回想下什么是设计模式呢?就是解决问题的一种行之有效的策略,而把这种策略总结出来,供以后遇到类似问题直接解决的方案。

什么是装饰设计模式呢?顾名思义就是对原有事物进行包装打扮,增强原有事物的功能。但是事物的本质是不变的。比如大家去礼品店买礼品,都会对礼品进行相应的包装,但不管怎么包装,礼品本身是不会发生改变的。但让我们感觉礼品更加具有吸引力和价值的体现。

在Java中也有大量使用装饰这种设计模式,比如前面学习字节流对象或者字符流对象,使用他们就可以完成字节、字符的读取操作,可是直接使用发现不管是读、还是写效率都比较低,于是就对原有的字节流和字符流进行包装,使用Buffered对其进行功能的扩展,发现读写效率都高了很多。

举例说明:
假设读取图片
使用ImageReader可以完成,为了提高效率,可以使用BufferedImageReader继承ImageReader,并增强其读数据功能,即对图片数据读取提高了效率。
假设读取媒体数据使用MediaReader可以完成,为了提高效率,可以使用BufferedMediaReader继承MediaReader,并增强其读数据功能,即对媒体数据读取提高了效率。

总结:
读取文件
Reader
读取图片 ImageReader
提高效率 BufferedImageReader
读取媒体 MediaReader
提高效率 BufferedMediaReader
发现了一个小问题,如果Reader中还有读取其他数据的子类,如果要高效,那岂不是还要给这个子类添加一个高效子类?是的。为了给具体的读取数据的对象增加一些功能,是需要通过子类来完成的。但是这样做,会导致这个继承体系很臃肿!仅仅为了增加一些功能,而进行继承,不建议的。

这些子类无非就是需要高效,而且这些高效的功能实现是一致的。就是提供了一个缓冲区而已。没有必要每一个对象都存在一个功能重复的子类。
干脆,单独定义一个具备这个缓冲功能的对象,哪个子类需要被缓冲,就将哪个子类传递进来。

class BufferedReader extends Reader{
     
   private [];提供数组。
   BufferedReader(Reader r){// 对Reader高效就哦了 。
     
   }
   read(){操作的是数组}//高效的读取动作。
}

继承体系:
Reader
ImageReader
MediaReader
BufferedReader
发现这种设计方式减少了继承体系的臃肿,增减了功能,比继承更为灵活。这种设计方式单独定义一个名称:装饰设计模式。

解决问题:给一组类增加功能, 避免继承的臃肿,提高灵活。
注意:装饰类和被装饰类必须所属于同一个体系,通常装饰类都会提供构造函数接收被装饰类对象。装饰类通常不单独存在。
装饰设计模式举例
举例:
水果超市对水果的包装。蛋糕店对蛋糕包装。
键盘录入
目前我们学习IO的对象基本都是在操作文件和文件中的数据,但是数据来源不全都是文件,也可以使键盘或者其他地方。那么怎么读取键盘数据呢?
思路:

  1. 将数据存储到的文件,没有问题的。
  2. 怎么获取数据来源呢?键盘录入怎么弄呢?键盘录入是输入,系统中应该具备的一部分。在System类找到了标准输入流 属性 in。System.in 对应的类型是InputStream。字节输入流。
public class ReadKeyDemo {
   public static void main(String[] args) throws IOException {
       //获取了键盘录入的输入流对象。可以不用关闭。
       InputStream in = System.in;
       int ch = in.read();
       System.out.println(ch);
   }
}

上述程序只能读取一个字符,当要读取多个字符怎么办呢?
思路:
读取一个字节先不要操作,将其存储起来,转成一个字符串。能不能一次就读取一行字符串呢?readLine();可是readLine()是BufferedReader方法。BufferedReader使用时必须接收字符流对象。键盘录入是字节流。要是将字节流转成字符流是不是就哦了呢?咋转呢?节流—桥梁
InputStreamReader—>字符流

public class ReadKeyDemo2 {
   public static void main(String[] args) throws IOException {
       BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
       String line = null;
   while((line=bufr.readLine())!=null){//键盘录入记住定义结束标记。强制结束。
          if("over".equals(line)){
             break;
          }
          System.out.println(line);
       }
   }
}

记住:以后但凡提到了键盘录入就写这句,一行一行的读取,除非要对读取每一个字节操作。键盘录入数据存储到文件中。
思路:
1、键盘录入。
2、目的是文件。
3、这个示例中既要用到输入流,也要用到输出流。而且操作的数据都是文本数据,可以使用字符流。而且目的是文件可以使用操作文件的字符输出流。

public class KeyDataToFileTest {
   public static void main(String[] args) throws IOException {
       //键盘录入。
       BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
       //目的是文件。
       BufferedWriter bufw = new BufferedWriter(new FileWriter("tempfile\\key.txt"));
       String line = null;
      while((line=bufr.readLine())!=null){
          if("over".equals(line)){
             break;
          }
          bufw.write(line);
          bufw.newLine();
          bufw.flush();
       }
       bufw.close();
   }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值