本文简单探索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, falsefor i := 0; i < len(s); i++ {c := s[i]if c >= utf8.RuneSelf {isASCII = falsebreak}hasUpper = hasUpper || ('A' <= c && c <= 'Z')}if isASCII { // optimize for ASCII-only strings.//只有ASCII且不包含小写直接返回if !hasUpper {return s}//只有ASCII且包含小写,按A-Z进行转换var b Builderb.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 {//校验是否是基础ASCIIif 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 rangeslo := 0hi := len(caseRange)for lo < hi {//较为迷惑的一段代码,仔细分析下文中的if r < rune(cr.Lo) {hi = m} else {lo = m + 1}可知,该方法使用二分法进行配置CaseRange的选取m := lo + (hi-lo)/2cr := 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转换表查询3a9所在位置为{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在配置获取失败时会默认使用CaseRangesvar 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-32unicode.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Ω

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

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

Golang ToLower和ToLowerSpecial源码探究相关推荐

  1. Vue源码探究-全局API

    Vue源码探究-全局API 本篇代码位于vue/src/core/global-api/ Vue暴露了一些全局API来强化功能开发,API的使用示例官网上都有说明,无需多言.这里主要来看一下全局API ...

  2. Mybatis 源码探究 (4) 将sql 语句中的#{id} 替换成 ‘?

    Mybatis 源码探究 (4) 将sql 语句中的#{id} 替换成 '? 出于好奇,然后就有了这篇文章啦. 源码给我的感觉,是一座大山的感觉.曲曲折折的路很多,点进去就有可能出不来. 不过慢慢看下 ...

  3. Mybatis 源码探究 (3)创建 SqlSessionFactory对象 执行sqlSession.getMapper()方法

    Mybatis 源码探究 (3)创建 SqlSessionFactory对象 时隔许久,终于又能接着来搞他啦.Mybatis 一起来探究吧. 先笑会再进入主题吧 开始啦 一.new SqlSessio ...

  4. Mabatis 源码探究(2)Java 获取mybatis-config.xml的输入流 inputStream对象

    关于Mybatis源码探究的专栏. 其我的专业是软件技术这个方向的,mybatis 许久以前就学了,但是心里一直存在一些疑惑.也上网查了,看过各种大佬的博客,对 于Mybatis的理解始终感觉不足.最 ...

  5. Vue源码探究-事件系统

    Vue源码探究-事件系统 本篇代码位于vue/src/core/instance/events.js 紧跟着生命周期之后的就是继续初始化事件相关的属性和方法.整个事件系统的代码相对其他模块来说非常简短 ...

  6. ARouter源码探究

    ARouter源码探究 1. 疑问 如何做到支持直接解析标准URL进行跳转,并自动注入参数到目标页面中? 如何做到支持Multidex.InstantRun? 如何做到映射关系按组分类.多级管理,按需 ...

  7. 浅谈.Net Core DependencyInjection源码探究

    前言 对于IOC和DI,可能每个人都能说出自己的理解.IOC全称是Inversion of Control翻译成中文叫控制反转,简单的说就是把对象的控制权反转到IOC容器中,由IOC管理其生命周期.D ...

  8. vue大括号里接受一个函数_vue源码探究(第四弹)

    vue源码探究(第四弹) 结束了上一part的数据代理,这一部分主要讲讲vue的模板解析,感觉这个有点难理解,而且内容有点多,hhh. 模板解析 废话不多说,先从简单的入手. 按照之前的套路,先举一个 ...

  9. 智能会议系统(14)---Linphone探索:1 . Linphone官方源码探究

    Linphone探索:1 . Linphone官方源码探究 项目地址:https://git.oschina.net/qin_xiao_yu/Linphone.git 1 . org.linphone ...

  10. spring-boot-2.0.3之quartz集成,数据源问题,源码探究

    前言 开心一刻 着火了,他报警说:119吗,我家发生火灾了. 119问:在哪里? 他说:在我家. 119问:具体点. 他说:在我家的厨房里. 119问:我说你现在的位置. 他说:我趴在桌子底下. 11 ...

最新文章

  1. 【转】6 Reasons Why JavaScript’s Async/Await Blows Promises Away (Tutorial)
  2. C++使用JSON的序列化与反序列化
  3. datetimepicker 默认时间_Django项目中如何使用日期时间选择器DateTimePicker
  4. Linux学习总结(35)——CentOS 7.X设置服务开机启动
  5. nginx从0到1之参数配置
  6. 产生式模型和判别式模型
  7. Sql server 数量累计求和
  8. find ctime 加减n时间范围
  9. and design pro实现打印电子面单(菜鸟物流-可批量打印)
  10. 【4-11】读书笔记 |《推荐系统实践》- 个性化推荐系统总结
  11. python alphago_使用 Python 搭建简易版AlphaGo
  12. 另类方法破解管理员密码
  13. python发送邮件发件人_在python电子邮件的from字段中添加发件人的名称
  14. 克劳士比语录(转载)
  15. pdf.js在h5端访问图片服务器地址携带token防盗链无法读取问题,兼容安卓ios在线预览
  16. 重装系统后服务器不提示用户密码,明明没有设置密码,重装系统后开机要求输入帐号密码咋办?...
  17. 下载Google Play上面的应用
  18. 欢迎使通过网页免费下载全网歌曲【转载】
  19. 使用Python读取markdown文件并统计字数
  20. LBP特征学习及实现

热门文章

  1. JVm - Parallel Scavenge 垃圾回收器
  2. 别瞎扯,元宇宙就是没有切实发展?
  3. msp430f149最小核心板和bsl下载器连线
  4. 解决ueditor上传图片报Nginx 502 bad gateway问题
  5. matlab 三角函数 积化和差,三角函数之积化和差、和差化积及推导过程
  6. opencv实战4-图像滤波
  7. 去除XP桌面图标的阴影
  8. ubuntu由于安装搜狗拼音导致的黑屏(或屏幕显示异常)
  9. 如何实现bat一键关机
  10. 重磅资源!PyTorch的福音,用PyTorch 1.0进行教学的免费深度学习课程,来自idiap和瑞士洛桑联邦理工学院...