资讯详情

Golang ToLower和ToLowerSpecial源码探究

本文简要探索ToLower方法及SpecialCase如果只关注使用方法和实现,SpecialCase学生可以直接跳到分割线以下

问题的原因是使用strings包中的ToLower小写转换结果与预期不符

a := "ADASD$%^*@%3Ω" fmt.Println(strings.ToLower(a))   //adasd$%^*@%3ω

本只想将A-Z但是strings.ToLower却把Ω转换成了ω,带着这个问题,我追了下源码

func ToLower(s string) string {     //isASCII是否只有ASCII     //hasUpper是否只包含ASCII小写  isASCII, hasUpper := true, false  for i := 0; i < len(s); i   {   c := s[i]   if c >= utf8.RuneSelf {    isASCII = false    break   }   hasUpper = hasUpper || ('A' <= c && c <= 'Z')  }  if isASCII { // optimize for ASCII-only strings.         //只有ASCII不包括小写直接返回   if !hasUpper {    return s   }         //只有ASCII且包含小写,按A-Z进行转换   var b Builder   b.Grow(len(s))   for i := 0; i < len(s); i   {    c := s[i]    if 'A' <= c && c <= 'Z' {     c  = 'a' - 'A'    }    b.WriteByte(c)   }   return b.String()  }     //包含unicode码,使用Map(unicode.ToLower, s)方法进行转换  return Map(unicode.ToLower, s) }  func Map(mapping func(rune) rune, s string) string {     //部分代码省略...     该方法的核心在于使用参数方法mapping解析字符串s,现在关键在于unicode.ToLower方法     for _, c := range s {         r := mapping(c)     //部分代码省略...     } }   func ToLower(r rune) rune {     ///验证是否是基础ASCII  if r <= MaxASCII {   if 'A' <= r && r <= 'Z' {    r  = 'a' - 'A'   }   return r  }     //非基础ASCII走To(LowerCase, r)  return To(LowerCase, r) }   func To(_case int, r rune) rune {     //根据传参可知r, _ = to(LowerCase, r, CaseRanges),CaseRanges为转换表     //var CaseRanges = _CaseRanges     //var _CaseRanges = []CaseRange{     
      {0x0041, 0x005A, d{0, 32, 0}},{0x0061, 0x007A, d{-32, 0, -32}},{0x00B5, 0x00B5, d{743, 0, 743}},{0x00C0, 0x00D6, d{0, 32, 0}},...}  r, _ = to(_case, r, CaseRanges)  return r }   func to(_case int, r rune, caseRange []CaseRange) (mappedRune rune, foundMapping bool) {     //判断_case是否在0-2之间,查看和MaxCase同时定义的UpperCase、LowerCase、TitleCase可知,转大写UpperCase=0,转小写LowerCase=1,获取title TitleCase=2,即_case参数应该为UpperCase、LowerCase、TitleCase三者之一  if _case < 0 || MaxCase <= _case {   return ReplacementChar, false // as reasonable an error as any  }  // binary search over ranges  lo := 0  hi := len(caseRange)  for lo < hi {         //更混乱的代码,仔细分析下面的内容if r < rune(cr.Lo) {hi = m} else {lo = m   1}可知,该方法采用二分法配置CaseRange的选取   m := lo   (hi-lo)/2   cr := caseRange[m]         ///字符r在配置范围内配置和转换名称CaseRange中Lo范围下限,Hi为范围上限   if rune(cr.Lo) <= r && r <= rune(cr.Hi) {             ///根据所选配置和转换方法进行转换CaseRange中Delta例如,每种转换方式d{743, 0, 743},转大写UpperCase为r   delta[0],转小写LowerCase为r   delta[1],转title TitleCase为r   delta[2]    delta := cr.Delta[_case]    if delta > MaxRune {     // In an Upper-Lower sequence, which always starts with     // an UpperCase letter, the real deltas always look like:     // {0, 1, 0}    UpperCase (Lower is next)     // {-1, 0, -1}  LowerCase (Upper, Title are previous)     // The characters at even offsets from the beginning of the     // sequence are upper case; the ones at odd offsets are lower.     // The correct mapping can be done by clearing or setting the low     // bit in the sequence offset.     // The constants UpperCase and TitleCase are even while LowerCase     // is odd so we take the low bit from _case.     return rune(cr.Lo)   ((r-rune(cr.Lo))&^1 | rune(_case&1)), true    }    return r   delta, true   }   if r < rune(cr.Lo) {    hi = m   } else {    lo = m   1   }  }     ///当配置失败时,请注意返回false,下文要考  return r, false } 

至此终于走完了strings.ToLower()源代码。然后我们通过源代码来验证我们的理解是否正确。

//首先检查字符Ω对应的unicode码 fmt.Println('Ω') //937 -> 转换为16进制为3a9 //反向检查unicode代码937对应的字符 fmt.Println(string(937)) //Ω 看上去没毛病 //根据CaseRanges转换表查询3a位置为{0x0391, 0x03A1, d{0, 32, 0}} //那么根据源码,转大写为937 0,转小写为937 32,转title为937 0,分别试一试 //转大写 fmt.Println(strings.ToUpper("Ω")) // Ω fmt.Println(string(937 0)) // Ω //转小写 fmt.Println(strings.ToLower("Ω")) // ω fmt.Println(string(937 32)) // ω //转title fmt.Println(strings.ToTitle("Ω")) // Ω fmt.Println(string(937 0)) // Ω 

终于明白了golang为什么?A-Z转换和转换字符的方法是什么?

当然,我们可以重写一个A-Z大小写转换的方法,但感觉不那么优雅,在调试过程中发现strings包中存在ToLowerSpecial这种方法,于是又展开了新一轮的探索


type SpecialCase []CaseRange  //以上对ToLower源码的探究我们发现unicode.SpecialCase和CaseRanges同一类型,Map简单分析了上述方法,核心为c.ToLower方法 func ToLowerSpecial(c unicode.SpecialCase, s string) string {  return Map(c.ToLower, s) }   func (special SpecialCase) ToLower(r rune) rune {     //核心to方法,上面也做了简单的分析,传输的参数是[]CaseRange(special),我们是否根据理解定义了一个SpecialCase转换规则可以实现自定义字符的转换  r1, hadMapping := to(LowerCase, r, []CaseRange(special))     ///在获得转换配置失败时使用默认CaseRanges进行转换  if r1 = r && !hadMapping {
		r1 = ToLower(r)
	}
	return r1
}

看起来找到了解决方案,我们来测试一下

a := "ADabacsASD$%^*@%3Ω"
//注意事项,[]unicode.CaseRange中的Lo和Hi一定要从小到大进行,不然二分法获取配置时可能获取失败,ToLowerSpecial在配置获取失败时会默认使用CaseRanges

var specialCase = unicode.SpecialCase{
    //0x0041 - 0x005A :A-Z,转大写不变转小写+32,转title不变
	unicode.CaseRange{Lo: 0x0041, Hi: 0x005A, Delta: [unicode.MaxCase]rune{0, 32, 0}},
    //0x005B - 0x0060 :所有转换不变
	unicode.CaseRange{Lo: 0x005B, Hi: 0x0060, Delta: [unicode.MaxCase]rune{0, 0, 0}},
    //0x0061 - 0x007A :a-z转大写-32,转小写时不变,转title-32
	unicode.CaseRange{Lo: 0x0061, Hi: 0x007A, Delta: [unicode.MaxCase]rune{-32, 0, -32}},
    //0x007B - 0xFFFF :所有转换不变
	unicode.CaseRange{Lo: 0x007B, Hi: 0xFFFF, Delta: [unicode.MaxCase]rune{0, 0, 0}},
}
fmt.Println(strings.ToUpperSpecial(specialCase,a)) // ADABACSASD$%^*@%3Ω
fmt.Println(strings.ToLowerSpecial(specialCase,a)) // adabacsasd$%^*@%3Ω
fmt.Println(strings.ToTitleSpecial(specialCase,a)) // ADABACSASD$%^*@%3Ω

这下好了,不仅实现了小写转换还实现了大写转换,看起来也还算优雅

那么考题来了,如果配置顺序错误了或者配置不存在会有什么后果呢?

标签: 007b2ln传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台