乐趣区

关于go:UTF8编码原理及GO语言标准库中编解码的实现

背景

  • 最近在应用 GO 写业务,闲暇时看看三方库和规范库的一些工具的实现。

实践

  • 底层数据都是按字节为单位存储。
  • ASCII 码取值区间 0 -127,二进制示意为 0b00000000-0b01111111。
  • UTF8 编码方式:依据具体字符状况,应用 1 - 4 字节长度存储数据。
  • 1 字节:用 ASCII 能示意的字符,间接应用 ASCII 码示意,占用 1 字节。
  • 多(2-4)字节:首字节数据的高 3 位 - 高 5 位,用于示意以后字符占用的字节数量,残余位存储具体数据。其余字节数据的最高 2 位必须是 1 和 0,残余位存储具体数据。

UTF8 数据存储举例

  • “ 界 ” 字在 UTF8 编码中占用 3 字节,示意形式为:
  • 首字节:0b11100111(高 4 位的 1110 为数据长度示意,前三位的 1 示意占用 3 字节,第 4 位的 0 为分隔符,前面的 0111 为首字节存储的理论数据)
  • 二字节:0b10010101(高 2 位的 10 为非首字符的固定示意形式,用于区别于 ASCII 码数据。前面的 6 位 010101 为存储的理论数据)
  • 三字节:0b10001100(与二字节同理)

编码

  • // EncodeRune writes into p (which must be large enough) the UTF-8 encoding of the rune.
    // If the rune is out of range, it writes the encoding of RuneError.
    // It returns the number of bytes written.
    func EncodeRune(p []byte, r rune) int {
      // Negative values are erroneous. Making it unsigned addresses the problem.
      switch i := uint32(r); {
      // rune1Max 的值为 127,小于等于这个数的字符,能够间接应用 1 字节的 ASCII 码示意
      case i <= rune1Max:
          p[0] = byte(r)
          return 1
      // rune2Max 的值为 2047,小于等于这个数的字符,能够用 2 字节存储
      case i <= rune2Max:
          _ = p[1] // eliminate bounds checks
          // t2 的值为 0b11000000,使得首字符的高 2 位必然为 11
          p[0] = t2 | byte(r>>6)
          p[1] = tx | byte(r)&maskx
          return 2
      case i > MaxRune, surrogateMin <= i && i <= surrogateMax:
          r = RuneError
          fallthrough
      // rune3Max 的值为 65535,小于等于这个数的字符,能够用 3 字节存储
      case i <= rune3Max:
          _ = p[2] // eliminate bounds checks
          // t3 的值为 0b11100000,使得首字符的高 3 位必然为 111
          p[0] = t3 | byte(r>>12)
          // t1 的值为 0b10000000
          p[1] = tx | byte(r>>6)&maskx
          p[2] = tx | byte(r)&maskx
          return 3
      // 其余的用 4 字节存储
      default:
          _ = p[3] // eliminate bounds checks
          // t4 的值为 0b11110000,使得首字符的高 4 位必然为 1111
          p[0] = t4 | byte(r>>18)
          p[1] = tx | byte(r>>12)&maskx
          p[2] = tx | byte(r>>6)&maskx
          p[3] = tx | byte(r)&maskx
          return 4
      }
    }
  • 该办法依据字符数据的不同大小,决定应用多少字节的空间进行编码和存储。编码过程中,首字节数据的高位会依据不同字节长度,设置其高位 1 的个数。因为数据的区间大小及每字节最多存储 6 位理论数据的起因,在高位前面必然有一位位 0,用于示意高位的完结和理论数据的开始。

解码

  • // DecodeRune unpacks the first UTF-8 encoding in p and returns the rune and
    // its width in bytes. If p is empty it returns (RuneError, 0). Otherwise, if
    // the encoding is invalid, it returns (RuneError, 1). Both are impossible
    // results for correct, non-empty UTF-8.
    //
    // An encoding is invalid if it is incorrect UTF-8, encodes a rune that is
    // out of range, or is not the shortest possible UTF-8 encoding for the
    // value. No other validation is performed.
    func DecodeRune(p []byte) (r rune, size int) {n := len(p)
      if n < 1 {return RuneError, 0}
      // 取出首字节
      p0 := p[0]
      // 依据首位的值,取出长度的示意数据
      x := first[p0]
      if x >= as {
          // The following code simulates an additional check for x == xx and
          // handling the ASCII and invalid cases accordingly. This mask-and-or
          // approach prevents an additional branch.
          mask := rune(x) << 31 >> 31 // Create 0x0000 or 0xFFFF.
          return rune(p[0])&^mask | RuneError&mask, 1
      }
      sz := int(x & 7)
      accept := acceptRanges[x>>4]
      if n < sz {return RuneError, 1}
      b1 := p[1]
      if b1 < accept.lo || accept.hi < b1 {return RuneError, 1}
      if sz <= 2 { // <= instead of == to help the compiler eliminate some bounds checks
          return rune(p0&mask2)<<6 | rune(b1&maskx), 2
      }
      b2 := p[2]
      if b2 < locb || hicb < b2 {return RuneError, 1}
      if sz <= 3 {return rune(p0&mask3)<<12 | rune(b1&maskx)<<6 | rune(b2&maskx), 3
      }
      b3 := p[3]
      if b3 < locb || hicb < b3 {return RuneError, 1}
      return rune(p0&mask4)<<18 | rune(b1&maskx)<<12 | rune(b2&maskx)<<6 | rune(b3&maskx), 4
    }
退出移动版