范文健康探索娱乐情感热点
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

Go语言核心36讲(Go语言实战与应用十七)学习笔记

  39 | bytes包与字节串操作(下)
  在上一篇文章中,我们分享了bytes.Buffer中已读计数的大致功用,并围绕着这个问题做了解析,下面我们来进行相关的知识扩展。 知识扩展问题 1:bytes.Buffer的扩容策略是怎样的?
  Buffer值既可以被手动扩容,也可以进行自动扩容。并且,这两种扩容方式的策略是基本一致的。所以,除非我们完全确定后续内容所需的字节数,否则让Buffer值自动去扩容就好了。
  在扩容的时候,Buffer值中相应的代码(以下简称扩容代码)会 先判断内容容器的剩余容量 ,是否可以满足调用方的要求,或者是否足够容纳新的内容。
  如果可以,那么扩容代码会在当前的内容容器之上,进行长度扩充。
  更具体地说,如果内容容器的容量与其长度的差,大于或等于另需的字节数,那么扩容代码就会通过切片操作对原有的内容容器的长度进行扩充,就像下面这样: b.buf = b.buf[:length+need]
  反之,如果内容容器的剩余容量不够了,那么扩容代码可能就会用新的内容容器去替代原有的内容容器,从而实现扩容。
  不过,这里还有一步优化。
  如果当前内容容器的容量的一半,仍然大于或等于其现有长度(即未读字节数)再加上另需的字节数的和 ,即: cap(b.buf)/2 >= b.Len() + need
  那么,扩容代码就会复用现有的内容容器,并把容器中的未读内容拷贝到它的头部位置。
  这也意味着其中的已读内容,将会全部被未读内容和之后的新内容覆盖掉。
  这样的复用预计可以至少节省掉一次后续的扩容所带来的内存分配,以及若干字节的拷贝。
  若这一步优化未能达成 ,也就是说,当前内容容器的容量小于新长度的二倍。
  那么,扩容代码就只能再创建一个新的内容容器,并把原有容器中的未读内容拷贝进去,最后再用新的容器替换掉原有的容器。这个新容器的容量将会等于原有容量的二倍再加上另需字节数的和。
  新容器的容量 =2* 原有容量 + 所需字节数
  通过上面这些步骤,对内容容器的扩充基本上就完成了。不过,为了内部数据的一致性,以及避免原有的已读内容可能造成的数据混乱,扩容代码还会把已读计数置为0,并再对内容容器做一下切片操作,以掩盖掉原有的已读内容。
  顺便说一下,对于处在零值状态的Buffer值来说,如果第一次扩容时的另需字节数不大于64,那么该值就会基于一个预先定义好的、长度为64的字节数组来创建内容容器。
  在这种情况下,这个内容容器的容量就是64。这样做的目的是为了让Buffer值在刚被真正使用的时候就可以快速地做好准备。 package main  import ( 	"bytes" 	"fmt" )  func main() { 	// 示例1。 	var contents string 	buffer1 := bytes.NewBufferString(contents) 	fmt.Printf("The length of new buffer with contents %q: %d ", 		contents, buffer1.Len()) 	fmt.Printf("The capacity of new buffer with contents %q: %d ", 		contents, buffer1.Cap()) 	fmt.Println()  	contents = "12345" 	fmt.Printf("Write contents %q ... ", contents) 	buffer1.WriteString(contents) 	fmt.Printf("The length of buffer: %d ", buffer1.Len()) 	fmt.Printf("The capacity of buffer: %d ", buffer1.Cap()) 	fmt.Println()  	contents = "67" 	fmt.Printf("Write contents %q ... ", contents) 	buffer1.WriteString(contents) 	fmt.Printf("The length of buffer: %d ", buffer1.Len()) 	fmt.Printf("The capacity of buffer: %d ", buffer1.Cap()) 	fmt.Println()  	contents = "89" 	fmt.Printf("Write contents %q ... ", contents) 	buffer1.WriteString(contents) 	fmt.Printf("The length of buffer: %d ", buffer1.Len()) 	fmt.Printf("The capacity of buffer: %d ", buffer1.Cap()) 	fmt.Print("  ")  	// 示例2。 	contents = "abcdefghijk" 	buffer2 := bytes.NewBufferString(contents) 	fmt.Printf("The length of new buffer with contents %q: %d ", 		contents, buffer2.Len()) 	fmt.Printf("The capacity of new buffer with contents %q: %d ", 		contents, buffer2.Cap()) 	fmt.Println()  	n := 10 	fmt.Printf("Grow the buffer with %d ... ", n) 	buffer2.Grow(n) 	fmt.Printf("The length of buffer: %d ", buffer2.Len()) 	fmt.Printf("The capacity of buffer: %d ", buffer2.Cap()) 	fmt.Print("  ")  	// 示例3。 	var buffer3 bytes.Buffer 	fmt.Printf("The length of new buffer: %d ", buffer3.Len()) 	fmt.Printf("The capacity of new buffer: %d ", buffer3.Cap()) 	fmt.Println()  	contents = "xyz" 	fmt.Printf("Write contents %q ... ", contents) 	buffer3.WriteString(contents) 	fmt.Printf("The length of buffer: %d ", buffer3.Len()) 	fmt.Printf("The capacity of buffer: %d ", buffer3.Cap()) } 问题 2:bytes.Buffer中的哪些方法可能会造成内容的泄露?
  首先明确一点,什么叫内容泄露?这里所说的内容泄露是指,使用Buffer值的一方通过某种非标准的(或者说不正式的)方式,得到了本不该得到的内容。
  比如说,我通过调用Buffer值的某个用于读取内容的方法,得到了一部分未读内容。我应该,也只应该通过这个方法的结果值,拿到在那一时刻Buffer值中的未读内容。
  但是,在这个Buffer值又有了一些新内容之后,我却可以通过当时得到的结果值,直接获得新的内容,而不需要再次调用相应的方法。
  这就是典型的非标准读取方式。这种读取方式是不应该存在的,即使存在,我们也不应该使用。因为它是在无意中(或者说一不小心)暴露出来的,其行为很可能是不稳定的。
  在bytes.Buffer中,Bytes方法和Next方法都可能会造成内容的泄露。原因在于,它们都把基于内容容器的切片直接返回给了方法的调用方。
  我们都知道,通过切片,我们可以直接访问和操纵它的底层数组。不论这个切片是基于某个数组得来的,还是通过对另一个切片做切片操作获得的,都是如此。
  在这里,Bytes方法和Next方法返回的字节切片,都是通过对内容容器做切片操作得到的。也就是说,它们与内容容器共用了同一个底层数组,起码在一段时期之内是这样的。
  以Bytes方法为例。它会返回在调用那一刻其所属值中的所有未读内容。示例代码如下: contents := "ab" buffer1 := bytes.NewBufferString(contents) fmt.Printf("The capacity of new buffer with contents %q: %d ",  contents, buffer1.Cap()) // 内容容器的容量为:8。 unreadBytes := buffer1.Bytes() fmt.Printf("The unread bytes of the buffer: %v ", unreadBytes) // 未读内容为:[97 98]。
  我用字符串值"ab"初始化了一个Buffer值,由变量buffer1代表,并打印了当时该值的一些状态。
  你可能会有疑惑,我只在这个Buffer值中放入了一个长度为2的字符串值,但为什么该值的容量却变为了8。
  虽然这与我们当前的主题无关,但是我可以提示你一下:你可以去阅读runtime包中一个名叫stringtoslicebyte的函数,答案就在其中。
  接着说buffer1。我又向该值写入了字符串值"cdefg",此时,其容量仍然是8。我在前面通过调用buffer1的Bytes方法得到的结果值unreadBytes,包含了在那时其中的所有未读内容。
  但是,由于这个结果值与buffer1的内容容器在此时还共用着同一个底层数组,所以,我只需通过简单的再切片操作,就可以利用这个结果值拿到buffer1在此时的所有未读内容。如此一来,buffer1的新内容就被泄露出来了。 buffer1.WriteString("cdefg") fmt.Printf("The capacity of buffer: %d ", buffer1.Cap()) // 内容容器的容量仍为:8。 unreadBytes = unreadBytes[:cap(unreadBytes)] fmt.Printf("The unread bytes of the buffer: %v ", unreadBytes) // 基于前面获取到的结果值可得,未读内容为:[97 98 99 100 101 102 103 0]。
  如果我当时把unreadBytes的值传到了外界,那么外界就可以通过该值操纵buffer1的内容了,就像下面这样: unreadBytes[len(unreadBytes)-2] = byte("X") // "X"的ASCII编码为88。 fmt.Printf("The unread bytes of the buffer: %v ", buffer1.Bytes()) // 未读内容变为了:[97 98 99 100 101 102 88]。
  现在,你应该能够体会到,这里的内容泄露可能造成的严重后果了吧?
  对于Buffer值的Next方法,也存在相同的问题。不过,如果经过扩容,Buffer值的内容容器或者它的底层数组被重新设定了,那么之前的内容泄露问题就无法再进一步发展了。我在 demo80.go 文件中写了一个比较完整的示例,你可以去看一看,并揣摩一下。 package main  import ( 	"bytes" 	"fmt" )  func main() { 	// 示例1。 	contents := "ab" 	buffer1 := bytes.NewBufferString(contents) 	fmt.Printf("The capacity of new buffer with contents %q: %d ", 		contents, buffer1.Cap()) 	fmt.Println()  	unreadBytes := buffer1.Bytes() 	fmt.Printf("The unread bytes of the buffer: %v ", unreadBytes) 	fmt.Println()  	contents = "cdefg" 	fmt.Printf("Write contents %q ... ", contents) 	buffer1.WriteString(contents) 	fmt.Printf("The capacity of buffer: %d ", buffer1.Cap()) 	fmt.Println()  	// 只要扩充一下之前拿到的未读字节切片unreadBytes, 	// 就可以用它来读取甚至修改buffer中的后续内容。 	unreadBytes = unreadBytes[:cap(unreadBytes)] 	fmt.Printf("The unread bytes of the buffer: %v ", unreadBytes) 	fmt.Println()  	value := byte("X") 	fmt.Printf("Set a byte in the unread bytes to %v ... ", value) 	unreadBytes[len(unreadBytes)-2] = value 	fmt.Printf("The unread bytes of the buffer: %v ", buffer1.Bytes()) 	fmt.Println()  	// 不过,在buffer的内容容器真正扩容之后就无法这么做了。 	contents = "hijklmn" 	fmt.Printf("Write contents %q ... ", contents) 	buffer1.WriteString(contents) 	fmt.Printf("The capacity of buffer: %d ", buffer1.Cap()) 	fmt.Println()  	unreadBytes = unreadBytes[:cap(unreadBytes)] 	fmt.Printf("The unread bytes of the buffer: %v ", unreadBytes) 	fmt.Print("  ")  	// 示例2。 	// Next方法返回的后续字节切片也存在相同的问题。 	contents = "12" 	buffer2 := bytes.NewBufferString(contents) 	fmt.Printf("The capacity of new buffer with contents %q: %d ", 		contents, buffer2.Cap()) 	fmt.Println()  	nextBytes := buffer2.Next(2) 	fmt.Printf("The next bytes of the buffer: %v ", nextBytes) 	fmt.Println()  	contents = "34567" 	fmt.Printf("Write contents %q ... ", contents) 	buffer2.WriteString(contents) 	fmt.Printf("The capacity of buffer: %d ", buffer2.Cap()) 	fmt.Println()  	// 只要扩充一下之前拿到的后续字节切片nextBytes, 	// 就可以用它来读取甚至修改buffer中的后续内容。 	nextBytes = nextBytes[:cap(nextBytes)] 	fmt.Printf("The next bytes of the buffer: %v ", nextBytes) 	fmt.Println()  	value = byte("X") 	fmt.Printf("Set a byte in the next bytes to %v ... ", value) 	nextBytes[len(nextBytes)-2] = value 	fmt.Printf("The unread bytes of the buffer: %v ", buffer2.Bytes()) 	fmt.Println()  	// 不过,在buffer的内容容器真正扩容之后就无法这么做了。 	contents = "89101112" 	fmt.Printf("Write contents %q ... ", contents) 	buffer2.WriteString(contents) 	fmt.Printf("The capacity of buffer: %d ", buffer2.Cap()) 	fmt.Println()  	nextBytes = nextBytes[:cap(nextBytes)] 	fmt.Printf("The next bytes of the buffer: %v ", nextBytes) } 总结
  我们结合两篇内容总结一下。与strings.Builder类型不同,bytes.Buffer不但可以拼接、截断其中的字节序列,以各种形式导出其中的内容,还可以顺序地读取其中的子序列。
  bytes.Buffer类型使用字节切片作为其内容容器,并且会用一个字段实时地记录已读字节的计数。
  虽然我们无法直接计算出这个已读计数,但是由于它在Buffer值中起到的作用非常关键,所以我们很有必要去理解它。
  无论是读取、写入、截断、导出还是重置,已读计数都是功能实现中的重要一环。
  与strings.Builder类型的值一样,Buffer值既可以被手动扩容,也可以进行自动的扩容。除非我们完全确定后续内容所需的字节数,否则让Buffer值自动去扩容就好了。
  Buffer值的扩容方法并不一定会为了获得更大的容量,替换掉现有的内容容器,而是先会本着尽量减少内存分配和内容拷贝的原则,对当前的内容容器进行重用。并且,只有在容量实在无法满足要求的时候,它才会去创建新的内容容器。
  此外,你可能并没有想到,Buffer值的某些方法可能会造成内容的泄露。这主要是由于这些方法返回的结果值,在一段时期内会与其所属值的内容容器共用同一个底层数组。
  如果我们有意或无意地把这些结果值传到了外界,那么外界就有可能通过它们操纵相关联Buffer值的内容。
  这属于很严重的数据安全问题。我们一定要避免这种情况的发生。最彻底的做法是,在传出切片这类值之前要做好隔离。比如,先对它们进行深度拷贝,然后再把副本传出去。 思考题
  今天的思考题是:对比strings.Builder和bytes.Buffer的String方法,并判断哪一个更高效?原因是什么? 笔记源码
  https://github.com/MingsonZheng/go-core-demo

常州市,丹阳市,京口区,还是这个团队,在复制50多年前的奇迹上个月底,骑行南通从通沙汽渡过江往常州,在汽渡船上看到沪通公铁大桥已经合拢,晨雾中大桥横卧在宽阔的江中宛如一条巨龙,明年底苏北方向的火车将直达上海,苏北沿海地级市将很快融入长三角经常州,无锡市的太湖路,七里风光带,十里明珠堤,千波桥上看海鸥2019年最后一个月,12月2日至5日,4天围着太湖骑了一圈,这次是尽量沿着常州。无锡。苏州。湖州市的沿湖路骑行,马山。西山。东山都环了一圈。足足骑了450多公里,比我前两次环太湖遭遇恒大爆雷?非法集资受害者如何利用司法途径维权?非法集资的概念是在1999年1月中国人民银行发布的关于取缔非法金融机构和非法金融业务活动中有关问题的通知中首次明确提出,并给出了以下定义非法集资是指单位或个人未依照法定程序经有关部阿里异议尊付宝商标,这次失败了前阵子,阿里巴巴异议他人注册的蚂蚁借呗商标,取得成功。但是近期,关于一件尊付宝商标的异议,阿里未能如愿。据了解,尊付宝商标由开店宝支付服务有限公司申请,指定使用于第9类的销售终端机腾讯音乐网易云音乐与索尼音乐娱乐达成新版权合作5月18日消息,网易云音乐宣布与索尼音乐娱乐达成全新版权合作,将获得索尼音乐娱乐数年期的海量曲库授权。终于,网易云音乐也有索尼音乐的版权了,歌单列表里的大量灰色歌曲即将被点亮。网易抖婚抖增等商标获准注册,字节跳动很无奈近期,小知查询到国家知识产权局商标局公布了一批商标异议决定,其中包含了多件抖婚抖增抖醒商标准予注册。自抖音爆火以来,抖X系列商标申请层出不穷,根据商标局官网查询,含有抖字的商标数量傍小米名牌,橙米CNMI商标异议案最近小米相关的新闻经常登上热搜话题,为网友提供了许多茶余饭后的谈资。从米线铁蛋商标,再到小米的米车小米汽车商标,以及小米酷似iPhoneX等的小米抄袭风波。近日,国家知识产权局介绍抢注波动星球商标?腾讯发起异议维权熟悉二次元的朋友可能知道,波动星球是腾讯旗下的一个动漫画内容互动社区,有各种类型的精品动漫人气新番等,是二次元爱好者的聚集地。近期,商标局公布多条关于波动星球商标异议决定,对多家主萧敬腾雨神人设不倒!但商标被抢了?昨天武汉暴雨上了热搜榜。原本夏天下暴雨没什么大惊小怪,但有网友发现,被称为雨神的萧敬腾疑似在武汉开演唱会,这下网友就不奇怪武汉为什么下暴雨了。但暴雨那天其实萧敬腾不在武汉。5月10注册蚂蚁借呗商标?阿里巴巴异议马云创造了淘宝之后,又围绕电商打造了支付宝花呗借呗等服务产品。其中,借呗是一款贷款服务,根据芝麻分的多少,用户可以申请几千到几万元的贷款额度。截止到现在,借呗已经上线6年了,拥有很小米官宣要造车!但商标已经被抢了3月30日下午,小米召开春季第二场新品发布会,会上不仅公开了外界关注的小米MIX和澎湃芯片,雷军还在会上正式官宣了小米的造车计划。小米CEO雷军将成立一家全资子公司,负责智能电动汽
一加骁龙865旗舰狂降2000元值得入手吗?120Hz30W无线立体双扬智能手机行业竞争激烈,今年主要的旗舰机搭载的是骁龙870和骁龙888处理器,因此去年搭载骁龙865的机型纷纷降价,今天要说的这款手机来自一加的一加8Pro,这款手机的标配版现在降价网信办通报84款App违法网络借贷类成重灾区今天网信办通报了84款App违法违规收集使用个人信息情况。其中,安全管理类App包括腾讯手机管家猎豹清理大师360手机卫士等36款App网络借贷类成重灾区,包括安逸花平安消费金融等从5999元跌至4999元,库克判断失误,iPhone开卖仅半年售价大跳水库克曾经是不少打工人最羡慕的人,因为苹果公司不仅仅每年会给库克巨额的薪水,而且2020年的时候苹果董事会为了留住库克继续连任苹果CEO,甚至不惜给他一亿多美金的激励,打工能打到库克特斯拉又致人死亡,死者为退休民警,汇总近两年20起典型事故今年以来,特斯拉多款车型因刹车失灵在中国造成众多交通事故,并发生多起致人死亡据央视4月29日报道,今年因闪存卡问题,在中国韩国美国等国合计召回数十万辆汽车。据韩国媒体4月29日报道抢购2个月终于现货,这四款机型才是真性价比,256G大储存真香今年华为荣耀一机难求就算了,就连小米也有不少机型需要抢购,前两个月上市的Redmi新机一直没抢到手,结果五一也没成功换新机。好在小长假结束这两天不少热销机型终于现货,和我一样想要换荣耀手机确认可升级鸿蒙OS已在内测360正式官宣造车下个月,荣耀30系列荣耀V30系列荣耀Play4Pro,都会开始内测鸿蒙OS。1荣耀手机确认可升级鸿蒙OS已在内测今日有数码博主北冥数码鲲曝光了荣耀30Pro内部测试鸿蒙OS的系统在小县城跑滴滴,还有出路吗?现在在小县城跑滴滴是没有任何前景的,不过就算是大城市,你也只能跑一跑兼职了,因为派单模式已经和以前不同了。首先说一说资费问题,我去过很多北方的小城市,起步价在4。5元的偏多,可以这小米599超级新品来了床头省下3根线小趣今天收到了一款相当有趣的产品小米多线圈无线快充板。作为市面上少有支持3设备同时无线充电的充电板,这意味着至少可以帮助你在床头减少3根充电线,对于整理收纳有着极大助益。设计上,这哪些型号手机屏幕对人眼睛伤害最低?文小伊评科技看到这个问题,我觉得有必要讲明白屏幕到底是怎么伤害眼睛的。屏幕对于眼睛的伤害主要集中在三个方面01。长期看屏幕造成的是视觉疲劳,由此会引起诸如干眼症,近视眼,散光等等常使用华为手机长按2秒,还能开启7个实用功能,你还没发现吗使用华为手机这么久,好用的功能,你都了解了吗?这里就来分享华为手机,长按2秒可以开启的7个实用功能,学会就是涨知了哦。不知道你有没有在用呢?1长按开启桌面小组件桌面太单调,不妨试试摩登太空舱,与大疆联名,潮流又时尚,带你看新宝骏KiWiEV各种因素的影响,如今愿意入手新能源汽车的国人越来越多,我国也逐渐成为新能源车大国。从数据来看,在2020年全球新能源汽车累计销量超312。5万辆,而中国市场便贡献了120万辆,可见