GO语言

GO语言

#英文技术文章翻译# Nitro框架(原Go Micro)介绍文档

GoLangzkbhj 发表了文章 • 0 个评论 • 245 次浏览 • 2020-11-26 11:37 • 来自相关话题

原文地址:https://github.com/asim/nitro 






Nitro (formerly known as Go Micro) is a future framework for distributed app development, IoT, edge and p2p.
Nitro(原来以Go Micro为大家所熟知)是一个面向未来的适用于分布式应用、IoT(物联网)边缘计算和P2P的框架。
 
Overview 综述
 
Nitro will provide the core requirements for distributed app development, IoT, edge and p2p including RPC and Event driven communication. The Nitro mantra is in-memory defaults with a pluggable architecture. Blaze with pure in-memory development and swap out as needed to go multi-process or multi-host.
Nitro将会为分布式应用、IoT(物联网)边缘计算和P2P的应用开发提供核心所需要的功能包括RPC和事件驱动通信。Nitro是一个默认内存式的可插拔的架构。纯内存式编程并且可以随时根据需要切换到go 的多进程或多线程模式上。

Note: Nitro is currently undergoing a complete rewrite and is considered unstable for use.
提示:Nitro目前正在经历全面的重写并且在真正使用到正式项目中时需要深思熟虑它的安全性问题。
 
Features 特性
 
Now focusing on dapps, IoT, edge and p2p. Features include:
现在(框架)只专注于分布式应用、物联网、边缘计算和P2P。特性包括:
Lightweight RPC based communicationsEvent broadcasting and notificationsCRDT Data synchronisation and storageConsensus protocol and execution engineWebAssembly target compilation supportUnique randomized token generation aka BLSP2P gossip networking defined in user space
 
基于轻量级的RPC通信事件驱动和消息通知CRDT数据同步和存储(CRDT是Conflict-free Replicated Data Type的简称,也称为a passive synchronisation,即免冲突的可复制的数据类型)一致的协议和执行引擎实验性的编译器支持唯一token生成器BLS用户空间自定义P2P网络
 
Future 未来
 
In the future there's the potential to launch a live network based on Nitro. More on that soon.
在未来开启一个基于Nitro的在线网络服务是可能的。很快就会实现了。

FAQ QA环节

What happened to Go Micro?
Go Micro发生了什么?

Go Micro has now been renamed to Nitro. Go Micro moved back to being a personal project and no longer lives under the organisation github.com/micro. The company is now doubling down on Micro itself and has pulled in the needed interfaces to consolidate a Server, Framework and CLI into one tool. Go Micro is now no longer maintained by a company. Yet it continued to create confusion even as a personal repo. So for that reason, we're renaming to Nitro. Go Micro V2 has been archived at microhq/go-micro and the plugins at microhq/plugins.
Go Micro现在已经被重新命名为Nitro。Go Micro项目已经变成了一个个人项目并且不再是一个团队(github.com/micro.)负责的项目。公司正在加倍使用Micro并且引入了必要的接口使之成为一个服务、框架和CLI的工具(上云提供商业服务,见 m3o.com)。Go Micro现在已经不再被任何公司维护。但是使用Go Micro作为名字的话依然会产生困惑,即使作为一个个人回收的项目。基于上面的原因,我们考虑把它重新命名为Nitro。Go Micro V2版本已经被归档在 microhq/go-micro ,插件归档在microhq/plugins。What's the new direction of Nitro?Nitro新的方向是什么?

Nitro will now focus on distributed app development using the Go standard library. It will continue to define abstractions for distributed systems but will only do so without external dependencies. In this manner the hope is Nitro can be picked up with minimal overhead for all sorts of new applications that have a low memory or low resource footprint. The assumption is there are places which would like to use distributed apps just as embedded systems or web assembly, unikernels, and related targets that would benefit from a framework that defined these as primitives for such use.
Nitro从现在开始将聚焦在用Go标准库来开发分布式应用。框架会持续为分布式系统定义抽象但是仅限于在没有外部依赖的情况下。通过这种方式,我们希望Nitro可以以最小的开销为那些内存或资源占有率低的程序使用。假设有些场景下更喜欢用分布式应用,像是嵌入式系统或者实验性系统,unikernels,和其他目标系统,他们将会从这个被定义为专为这些应用而生的基础框架中受益。
 
How do Nitro and Micro now differ?
现在Nitro和Micro有什么不同?

Micro is a platform for cloud native development. A complete experience that includes a server, framework and multi-language clients. Beyond that it also include environments, multi-tenancy and many more features which push it towards being a hosted Micro as a Service offering. It is a complete platform.
Micro是一个为云原生应用开发的平台框架。是一个完整的经验包括服务端,框架和多种语言客户端。除了上面提到的它也包括环境,多客端和未来更多的特性,这些特性把它推向成为一个提供服务的Micro托管产品。它是一个完整的平台框架。

Nitro is more of a embeddable framework for distributed app development and now once again a purely personal project maintained by me and perhaps others who still find use for it commercially or noncommercially. It's of sentimental value and something I'd like to carry on for personal projects such as things related to edge, IoT, embedded systems, p2p, web assembly, etc.
Nitro是一个为分布式应用提供的可嵌入式的框架,并且再一次成为一个完全的个人项目被我维护,也许会有人会发现把它并把它用于商业用途或非商业用途。这个框架充满了感情色彩以及我可能希望和喜欢的一些东西带入到这个个人项目中,比如像是一些跟边缘计算、物联网、嵌入式系统、P2P和实验性的东西等等。
 
I used Go Micro to build microservices. What should I do now?
我曾经用Go Micro来构建微服务应用。我现在应该做些什么?

You should quite honestly go look at Micro and then consider the hosted offering at m3o.com which starts as a purely free dev environment in the cloud. Micro continues to address many of the concerns and requirements you had if not more. It is likely you managed metrics, tracing, logging and much other boilerplate that needed to be plugged in. Micro will now take this complete platform story approach and help you in that journey e.gyou're probably running managed kubernetes on a major cloud provider with many other things. We're doing that for you instead as a company and platform team.
你应该非常诚恳的看一看 Micro这个项目然后考虑m3o.com提供的托管产品,这个托管产品开始会在云上提供一个完全免费的开发环境。Micro将会继续设法解决你有的问题和需求如果不太多的话。就像你管理的应用性能,链路追踪,日志和许多其他的组件包被引入的。Micro会采用这种完整的平台托管方式来帮助你解决迁移过程中你可能遇到的问题,比如管理在一个核心的云服务提供上运行的项目或者其他一些事情。我们代替公司或者平台团队为你做这些事情。
 
I want to use Go Micro version 2.0 for my company. Can I still do that?
我想使用Go Micro 2.0版本为我们公司的项目。我可以一直这么做吗?

Yes. Go Micro 2.0 is still Apache 2.0 licensed which means you can still freely use it for everything you were using before. If you're a new user you can do the same. These things are using go modules so you're import path is simply github.com/micro/go-micro/v2 as it was before. Because GitHub handles redirects this should not break. Please continue to use it if you like, but my own support for 2.0 is now end of life.
当然。Go Micro 2.0 将一直是一个Apache 2.0 授权的,这意味着你可以一直免费使用它来做任何事情就像你以前用它一样。如果你是一个新用户,你也可以这么做。可以用go modules来做这些事,所以你可以通过import 这个路径  github.com/micro/go-micro/v2 就像以前一样。因为GitHub上面的链接不会被失效掉。如果你喜欢,请继续使用它。但是我从现在开始不会再继续支持和维护这个2.0版本了。
 
Why has the license changed from Apache 2.0 to Polyform Noncommercial?
为什么授权许可从Apache 2.0变成了Polyform Noncommercia?

Go Micro was largely a solo maintained effort for the entirety of its lifetime. It has enabled the creation of a company called Micro Services, Inc. which now focuses on Micro as a Service and has consolidated any interfaces here into a service library in that project. For the most part, Go Micro was underfunded and in some ways under appreciated. In version 3.0, going back to something of a personal project of more than 6 years I have made the hard decision to relicense as a noncommercial project.
Go Micro 很大程度上在它的整个生命周期中都是一个单独维护的项目。它使一家名为Micro Services的公司成立成为可能,这家公司目前主要聚焦在Micro作为一个服务来提供,然后把所有的接口都整合成一个服务库。在很大程度上,Go Micro项目资金不足且在一定程度上没有得到重视。在3.0版本中,追忆这个超过6年的个人项目的一些事情的情况下我做了一个艰难的决定就是重新作为一个非商业项目获得授权。
 
  查看全部
原文地址:https://github.com/asim/nitro 

73709577.png


Nitro (formerly known as Go Micro) is a future framework for distributed app development, IoT, edge and p2p.
Nitro(原来以Go Micro为大家所熟知)是一个面向未来的适用于分布式应用、IoT(物联网)边缘计算和P2P的框架。
 
Overview 综述
 
Nitro will provide the core requirements for distributed app development, IoT, edge and p2p including RPC and Event driven communication. The Nitro mantra is in-memory defaults with a pluggable architecture. Blaze with pure in-memory development and swap out as needed to go multi-process or multi-host.
Nitro将会为分布式应用、IoT(物联网)边缘计算和P2P的应用开发提供核心所需要的功能包括RPC和事件驱动通信。Nitro是一个默认内存式的可插拔的架构。纯内存式编程并且可以随时根据需要切换到go 的多进程或多线程模式上。

Note: Nitro is currently undergoing a complete rewrite and is considered unstable for use.
提示:Nitro目前正在经历全面的重写并且在真正使用到正式项目中时需要深思熟虑它的安全性问题。
 
Features 特性
 
Now focusing on dapps, IoT, edge and p2p. Features include:
现在(框架)只专注于分布式应用、物联网、边缘计算和P2P。特性包括:
  • Lightweight RPC based communications
  • Event broadcasting and notifications
  • CRDT Data synchronisation and storage
  • Consensus protocol and execution engine
  • WebAssembly target compilation support
  • Unique randomized token generation aka BLS
  • P2P gossip networking defined in user space

 
  • 基于轻量级的RPC通信
  • 事件驱动和消息通知
  • CRDT数据同步和存储(CRDT是Conflict-free Replicated Data Type的简称,也称为a passive synchronisation,即免冲突的可复制的数据类型)
  • 一致的协议和执行引擎
  • 实验性的编译器支持
  • 唯一token生成器BLS
  • 用户空间自定义P2P网络

 
Future 未来
 
In the future there's the potential to launch a live network based on Nitro. More on that soon.
在未来开启一个基于Nitro的在线网络服务是可能的。很快就会实现了。

FAQ QA环节

What happened to Go Micro?
Go Micro发生了什么?

Go Micro has now been renamed to Nitro. Go Micro moved back to being a personal project and no longer lives under the organisation github.com/micro. The company is now doubling down on Micro itself and has pulled in the needed interfaces to consolidate a Server, Framework and CLI into one tool. Go Micro is now no longer maintained by a company. Yet it continued to create confusion even as a personal repo. So for that reason, we're renaming to Nitro. Go Micro V2 has been archived at microhq/go-micro and the plugins at microhq/plugins.
Go Micro现在已经被重新命名为Nitro。Go Micro项目已经变成了一个个人项目并且不再是一个团队(github.com/micro.)负责的项目。公司正在加倍使用Micro并且引入了必要的接口使之成为一个服务、框架和CLI的工具(上云提供商业服务,见 m3o.com)。Go Micro现在已经不再被任何公司维护。但是使用Go Micro作为名字的话依然会产生困惑,即使作为一个个人回收的项目。基于上面的原因,我们考虑把它重新命名为Nitro。Go Micro V2版本已经被归档在 microhq/go-micro ,插件归档在microhq/plugins。What's the new direction of Nitro?Nitro新的方向是什么?

Nitro will now focus on distributed app development using the Go standard library. It will continue to define abstractions for distributed systems but will only do so without external dependencies. In this manner the hope is Nitro can be picked up with minimal overhead for all sorts of new applications that have a low memory or low resource footprint. The assumption is there are places which would like to use distributed apps just as embedded systems or web assembly, unikernels, and related targets that would benefit from a framework that defined these as primitives for such use.
Nitro从现在开始将聚焦在用Go标准库来开发分布式应用。框架会持续为分布式系统定义抽象但是仅限于在没有外部依赖的情况下。通过这种方式,我们希望Nitro可以以最小的开销为那些内存或资源占有率低的程序使用。假设有些场景下更喜欢用分布式应用,像是嵌入式系统或者实验性系统,unikernels,和其他目标系统,他们将会从这个被定义为专为这些应用而生的基础框架中受益。
 
How do Nitro and Micro now differ?
现在Nitro和Micro有什么不同?

Micro is a platform for cloud native development. A complete experience that includes a server, framework and multi-language clients. Beyond that it also include environments, multi-tenancy and many more features which push it towards being a hosted Micro as a Service offering. It is a complete platform.
Micro是一个为云原生应用开发的平台框架。是一个完整的经验包括服务端,框架和多种语言客户端。除了上面提到的它也包括环境,多客端和未来更多的特性,这些特性把它推向成为一个提供服务的Micro托管产品。它是一个完整的平台框架。

Nitro is more of a embeddable framework for distributed app development and now once again a purely personal project maintained by me and perhaps others who still find use for it commercially or noncommercially. It's of sentimental value and something I'd like to carry on for personal projects such as things related to edge, IoT, embedded systems, p2p, web assembly, etc.
Nitro是一个为分布式应用提供的可嵌入式的框架,并且再一次成为一个完全的个人项目被我维护,也许会有人会发现把它并把它用于商业用途或非商业用途。这个框架充满了感情色彩以及我可能希望和喜欢的一些东西带入到这个个人项目中,比如像是一些跟边缘计算、物联网、嵌入式系统、P2P和实验性的东西等等。
 
I used Go Micro to build microservices. What should I do now?
我曾经用Go Micro来构建微服务应用。我现在应该做些什么?

You should quite honestly go look at Micro and then consider the hosted offering at m3o.com which starts as a purely free dev environment in the cloud. Micro continues to address many of the concerns and requirements you had if not more. It is likely you managed metrics, tracing, logging and much other boilerplate that needed to be plugged in. Micro will now take this complete platform story approach and help you in that journey e.gyou're probably running managed kubernetes on a major cloud provider with many other things. We're doing that for you instead as a company and platform team.
你应该非常诚恳的看一看 Micro这个项目然后考虑m3o.com提供的托管产品,这个托管产品开始会在云上提供一个完全免费的开发环境。Micro将会继续设法解决你有的问题和需求如果不太多的话。就像你管理的应用性能,链路追踪,日志和许多其他的组件包被引入的。Micro会采用这种完整的平台托管方式来帮助你解决迁移过程中你可能遇到的问题,比如管理在一个核心的云服务提供上运行的项目或者其他一些事情。我们代替公司或者平台团队为你做这些事情。
 
I want to use Go Micro version 2.0 for my company. Can I still do that?
我想使用Go Micro 2.0版本为我们公司的项目。我可以一直这么做吗?

Yes. Go Micro 2.0 is still Apache 2.0 licensed which means you can still freely use it for everything you were using before. If you're a new user you can do the same. These things are using go modules so you're import path is simply github.com/micro/go-micro/v2 as it was before. Because GitHub handles redirects this should not break. Please continue to use it if you like, but my own support for 2.0 is now end of life.
当然。Go Micro 2.0 将一直是一个Apache 2.0 授权的,这意味着你可以一直免费使用它来做任何事情就像你以前用它一样。如果你是一个新用户,你也可以这么做。可以用go modules来做这些事,所以你可以通过import 这个路径  github.com/micro/go-micro/v2 就像以前一样。因为GitHub上面的链接不会被失效掉。如果你喜欢,请继续使用它。但是我从现在开始不会再继续支持和维护这个2.0版本了。
 
Why has the license changed from Apache 2.0 to Polyform Noncommercial?
为什么授权许可从Apache 2.0变成了Polyform Noncommercia?

Go Micro was largely a solo maintained effort for the entirety of its lifetime. It has enabled the creation of a company called Micro Services, Inc. which now focuses on Micro as a Service and has consolidated any interfaces here into a service library in that project. For the most part, Go Micro was underfunded and in some ways under appreciated. In version 3.0, going back to something of a personal project of more than 6 years I have made the hard decision to relicense as a noncommercial project.
Go Micro 很大程度上在它的整个生命周期中都是一个单独维护的项目。它使一家名为Micro Services的公司成立成为可能,这家公司目前主要聚焦在Micro作为一个服务来提供,然后把所有的接口都整合成一个服务库。在很大程度上,Go Micro项目资金不足且在一定程度上没有得到重视。在3.0版本中,追忆这个超过6年的个人项目的一些事情的情况下我做了一个艰难的决定就是重新作为一个非商业项目获得授权。
 
 

LeetCode刷题Day2:两数相加

LeetCodezkbhj 发表了文章 • 0 个评论 • 272 次浏览 • 2020-09-22 20:57 • 来自相关话题

两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。 

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) 
输出:7 -> 0 -> 8
原因:342 + 465 = 807LeetCode

 首先发一下我的思路已经给出的最初答案(大部分情况是正确的,但是超出数字范围就会和预期结果不一致),总之先把分析思路分享出来。


思路一:常规思路

分别循环两个链表,因为高位是个位,所以不断乘以10的n次方,n代表循环次数,从0开始,直到循环结束,就得到了两个无符号整数。所以,这就要求对输入数字有范围限制的要求,否则数字太大,超出整数的最大范围,得到的结果会因为发生溢出而不符合预期。

然后将两个整数进行加法运算,得到最终结果对应的int数字。

然后再将这个整数转化成字符串,循环字符串,得到字符对应的ASCII码点,然后减去48即可“间接“得到对应的数字。并按序生成链表。
 
func myPow(a,n int) int {
result := int(1)
for i:= n; i >0 ; i >>= 1 {
if i&1 != 0 {
result *=a
}
a *=a
}
return result
}

func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
var a,b,c,i,j int
p1 := l1
for p1!= nil {
a = a + p1.Val * myPow(10,i)
i++
p1 = p1.Next
}
p2 := l2
for p2!= nil {
b = b + p2.Val * myPow(10,j)
j++
p2 = p2.Next
}
c = a + b

str := strconv.Itoa(c)
headNode := new(ListNode)
current := headNode
current.Next = nil
for _,one := range str {
preNode := current
current.Val = int(one-48)
current = new(ListNode)
current.Next = preNode

}

return current.Next
}
思路一:高级思路(学习)
对应位置的数字相加,跟10取模,即可得到该位置上十进制数相加之后的数字,然后和10进行整除,如果两个数相加小于10,则整除之后是0,否则是1,即要进位的1,所以直到两个链表都循环完,或者最后没有进位时,停止循环,最终返回。
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
headList := new(ListNode)
head := headList
num := 0

for (l1 != nil || l2 != nil || num > 0) {
headList.Next = new(ListNode)
headList = headList.Next
if l1 != nil {
num = num + l1.Val
l1 = l1.Next
}
if l2 != nil {
num = num + l2.Val
l2 = l2.Next
}
headList.Val = (num) % 10
num = num / 10
}

return head.Next
}
  查看全部


两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。 

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) 
输出:7 -> 0 -> 8
原因:342 + 465 = 807LeetCode


 首先发一下我的思路已经给出的最初答案(大部分情况是正确的,但是超出数字范围就会和预期结果不一致),总之先把分析思路分享出来。


思路一:常规思路

分别循环两个链表,因为高位是个位,所以不断乘以10的n次方,n代表循环次数,从0开始,直到循环结束,就得到了两个无符号整数。所以,这就要求对输入数字有范围限制的要求,否则数字太大,超出整数的最大范围,得到的结果会因为发生溢出而不符合预期。

然后将两个整数进行加法运算,得到最终结果对应的int数字。

然后再将这个整数转化成字符串,循环字符串,得到字符对应的ASCII码点,然后减去48即可“间接“得到对应的数字。并按序生成链表。
 
func myPow(a,n int) int {
result := int(1)
for i:= n; i >0 ; i >>= 1 {
if i&1 != 0 {
result *=a
}
a *=a
}
return result
}

func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
var a,b,c,i,j int
p1 := l1
for p1!= nil {
a = a + p1.Val * myPow(10,i)
i++
p1 = p1.Next
}
p2 := l2
for p2!= nil {
b = b + p2.Val * myPow(10,j)
j++
p2 = p2.Next
}
c = a + b

str := strconv.Itoa(c)
headNode := new(ListNode)
current := headNode
current.Next = nil
for _,one := range str {
preNode := current
current.Val = int(one-48)
current = new(ListNode)
current.Next = preNode

}

return current.Next
}

思路一:高级思路(学习)
对应位置的数字相加,跟10取模,即可得到该位置上十进制数相加之后的数字,然后和10进行整除,如果两个数相加小于10,则整除之后是0,否则是1,即要进位的1,所以直到两个链表都循环完,或者最后没有进位时,停止循环,最终返回。

func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
headList := new(ListNode)
head := headList
num := 0

for (l1 != nil || l2 != nil || num > 0) {
headList.Next = new(ListNode)
headList = headList.Next
if l1 != nil {
num = num + l1.Val
l1 = l1.Next
}
if l2 != nil {
num = num + l2.Val
l2 = l2.Next
}
headList.Val = (num) % 10
num = num / 10
}

return head.Next
}

 

LeetCode刷题Day1:两数之和

LeetCodezkbhj 发表了文章 • 0 个评论 • 277 次浏览 • 2020-09-22 20:55 • 来自相关话题

从今天开始,把LeetCode刷题作为每天日常计划的一项。尽早做准备,同时也每天对算法和相关的技能知识通过刷题的形式进行潜移默化的提升和加深理解。其实身边已经有很多程序猿朋友都在LeetCode刷题了,再不参与进来,都要出圈儿了呀。

今天是Day1,从第一道题目开始:
 

两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 

示例: 

给定 nums = [2, 7, 11, 15], target = 9 

因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]LeetCode
 

 
思路一:暴力遍历

也是最简单最原始的未经优化的方案思路。

首先明确题目要求,可以假设每种输入只会对应一个答案,即一定会有且只有一对符合要求的数据。

所以得到数组的长度,然后从第一个开始进行循环,将目标值target减去当前被循环的值,得到与之可以配对的值,然后在数组中进行寻找,如果找到,就返回2个符合要求的值的索引,否则继续下一次寻找,直到找到。

最外层循环的条件中 x&y异或之后如果为1,说明已经找到对应的结果,就不再循环。时间复杂度是O(n²),空间复杂度是O(1)。
func twoSum(nums []int, target int) []int {
len := len(nums)
var x,y int

for i:=0; i < len && x|y == 0; i++ {
findNum := target - nums[i]
for j:=0; j < len; j++ {
if findNum == nums[j] {
x = i;
y = j;
}
}

}

return []int{x,y}
}
思路二:进一步提升效率,可以利用哈希表
从第一个思路中,可以看到其实在判断一个值在一个“表”中是否存在的时候,我们采用了遍历的方式,这样的话时间复杂度就是O(n),但是大家肯定都知道,哈希表是最擅长做这个事情的。它可以把时间复杂度降到O(1)。所以,这样思路就很清晰了,我们需要先把传过来的数组生成对应的哈希结构,也就是Go里面的map字典,这样我们就可以快速的找到某值在还是不在了。我们把数组的value作为map的key,index作为map的value。代码实现如下。
 
func twoSum(nums []int, target int) []int {
var hashMap map[int]int
len := len(nums)
var x,y,found int

//初始化字典
hashMap = make(map[int]int)
for i:=0; i < len; i++ {
hashMap[nums[i]] = i
}

for j:=0; j < len && found == 0; j++ {
findNum := target - nums[j]
v, ok := hashMap[findNum]
if ok && v != j {
found = 1
x = j
y = v
}

}

return []int{x,y}
}
今天这道题的难度级别是简单,但是带我回顾了Go语言的数组、map的相关知识和用法,以及哈希表这一知识点。 查看全部
从今天开始,把LeetCode刷题作为每天日常计划的一项。尽早做准备,同时也每天对算法和相关的技能知识通过刷题的形式进行潜移默化的提升和加深理解。其实身边已经有很多程序猿朋友都在LeetCode刷题了,再不参与进来,都要出圈儿了呀。

今天是Day1,从第一道题目开始:
 


两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 

示例: 

给定 nums = [2, 7, 11, 15], target = 9 

因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]LeetCode
 


 
思路一:暴力遍历

也是最简单最原始的未经优化的方案思路。

首先明确题目要求,可以假设每种输入只会对应一个答案,即一定会有且只有一对符合要求的数据。

所以得到数组的长度,然后从第一个开始进行循环,将目标值target减去当前被循环的值,得到与之可以配对的值,然后在数组中进行寻找,如果找到,就返回2个符合要求的值的索引,否则继续下一次寻找,直到找到。

最外层循环的条件中 x&y异或之后如果为1,说明已经找到对应的结果,就不再循环。时间复杂度是O(n²),空间复杂度是O(1)。

func twoSum(nums []int, target int) []int {
len := len(nums)
var x,y int

for i:=0; i < len && x|y == 0; i++ {
findNum := target - nums[i]
for j:=0; j < len; j++ {
if findNum == nums[j] {
x = i;
y = j;
}
}

}

return []int{x,y}
}

思路二:进一步提升效率,可以利用哈希表
从第一个思路中,可以看到其实在判断一个值在一个“表”中是否存在的时候,我们采用了遍历的方式,这样的话时间复杂度就是O(n),但是大家肯定都知道,哈希表是最擅长做这个事情的。它可以把时间复杂度降到O(1)。所以,这样思路就很清晰了,我们需要先把传过来的数组生成对应的哈希结构,也就是Go里面的map字典,这样我们就可以快速的找到某值在还是不在了。我们把数组的value作为map的key,index作为map的value。代码实现如下。
 

func twoSum(nums []int, target int) []int {
var hashMap map[int]int
len := len(nums)
var x,y,found int

//初始化字典
hashMap = make(map[int]int)
for i:=0; i < len; i++ {
hashMap[nums[i]] = i
}

for j:=0; j < len && found == 0; j++ {
findNum := target - nums[j]
v, ok := hashMap[findNum]
if ok && v != j {
found = 1
x = j
y = v
}

}

return []int{x,y}
}

今天这道题的难度级别是简单,但是带我回顾了Go语言的数组、map的相关知识和用法,以及哈希表这一知识点。

使用了Go mod管理的项目里如何添加新的第三方包?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 1173 次浏览 • 2020-09-17 11:32 • 来自相关话题

如何快速提升Go程序性能?

GoLangzkbhj 发表了文章 • 0 个评论 • 263 次浏览 • 2020-09-15 18:39 • 来自相关话题

这篇文章中列出了一些不需要太多精力就能显著提高性能的技巧,并不包含那些需要太多精力或需要大幅度修改程序结构的技巧。
 
开始优化之前
 
开始优化之前,首先应该花些时间找出一个合适的基准线,以便稍后比较。如果没有基准,那就等于摸着石头过河,根本不知道自己的优化有没有效果。首先要编写性能测试程序,然后生成能用于pprof的profile文件。最好可以编写Go的性能测试脚本(https://dave.cheney.net/2013/0 ... in-go),这样可以很容易地使用pprof,还可以评测内存分配情况。还可以使用benchcmp,这个工具可以帮助比较两次性能测试之间的性能差异。

如果代码很难做性能测试,那就从你能测量时间的部分开始。可以利用runtime/pprof手工测量代码。

现在开始吧!
 
使用sync.Pool重用之前分配过的对象
 
sync.Pool实现了一个空闲列表(free-list)。这样可以重新使用之前分配过的对象。这样做可以将对象分配的代价平摊到多次使用上,减少垃圾回收器的工作。API非常简单:只需实现一个函数,用来分配新的对象即可。它会返回指针类型。
 
var bufpool = sync.Pool{
New: func() interface{} {
buf := make([]byte, 512)
return &buf
}}之后,可以用Get()从池中获取对象,用完之后用Put()将对象放回。

不过要注意一些陷阱。在Go 1.13之前,每次发生垃圾回收时该池都会被清空。对于需要分配大量对象的程序来说,这可能会造成性能的影响。在1.13版本中似乎GC后能保留更多对象了(https://go-review.googlesource.com/c/go/+/162919/)。

你可能需要在将对象放回池中之前将其结构的字段清空。

如果不这样做,就可能从池中获得一个“脏”的对象,它包含之前使用过的数据。这可能会造成严重的安全问题!
 
安全的做法就是明确清空内存:// reset resets all fields of the AuthenticationResponse before pooling it.
func (a* AuthenticationResponse) reset() {
a.Token = ""
a.UserID = ""
}

rsp := authPool.Get().(*AuthenticationResponse)
defer func() {
rsp.reset()
authPool.Put(rsp)
}()
在大map中避免使用包含指针的结构作为map的键
 
关于Go中大型堆的性能问题已经有很多人讨论过了。在垃圾回收过程中,运行时会扫描包含指针的对象并遍历其指针。如果你有非常大的map[string]int,那么垃圾回收器就不得不在每次垃圾回收过程中检查map中的每个字符串,因为字符串包含指针。

这个例子中我们向一个map[string]int中写入了一千万个元素,然后测量垃圾回收的时间。map是在包的作用域中分配的,以保证它被分配到堆上。
import (
"fmt"
"runtime"
"strconv"
"time"
)

const (
numElements = 10000000
)

var foo = map[string]int{}

func timeGC() {
t := time.Now()
runtime.GC()
fmt.Printf("gc took: %s\n", time.Since(t))
}

func main() {
for i := 0; i < numElements; i++ {
foo[strconv.Itoa(i)] = i
}

for {
timeGC()
time.Sleep(1 * time.Second)
}
}运行后可以得到以下结果:gc took: 98.726321ms
gc took: 105.524633ms
gc took: 102.829451ms
gc took: 102.71908ms
gc took: 103.084104ms
gc took: 104.821989ms对于计算机来说这花得时间太多了!

怎样可以改进呢?最好是能尽量去掉指针,这样能减少垃圾回收器需要遍历的指针数量。由于字符串包含指针,因此我们可以用map[int]int来实现:
 
import (
"fmt"
"runtime"
"time"
)

const (
numElements = 10000000
)

var foo = map[int]int{}

func timeGC() {
t := time.Now()
runtime.GC()
fmt.Printf("gc took: %s\n", time.Since(t))
}

func main() {
for i := 0; i < numElements; i++ {
foo[i] = i
}

for {
timeGC()
time.Sleep(1 * time.Second)
}
}重新运行程序,结果如下:
 
gc took: 3.608993ms
gc took: 3.926913ms
gc took: 3.955706ms
gc took: 4.063795ms
gc took: 3.91519ms
gc took: 3.75226ms好多了。垃圾回收的时间减少了97%。在生产环境下,字符串需要进行hash之后再插入到map中。
 
生成marshalling代码以避免运行时反射
 
将数据结构marshalh或unmarshal成JSON等各种序列化格式是个很常见的操作,特别是在构建微服务的时候。实际上,大部分微服务做的唯一工作就是序列化。像json.Marshal和json.Unmarshal需要依赖运行时反射才能将结构体的字段序列化成字节,反之亦然。这个操作很慢,反射的性能完全无法与显式的代码相比。

但我们不必这么做。marshalling JSON的原理大致如下:
package json

// Marshal take an object and returns its representation in JSON.
func Marshal(obj interface{}) ([]byte, error) {
// Check if this object knows how to marshal itself to JSON
// by satisfying the Marshaller interface.
if m, is := obj.(json.Marshaller); is {
return m.MarshalJSON()
}

// It doesn't know how to marshal itself. Do default reflection based marshallling.
return marshal(obj)
}如果我们知道怎样将对象marshal成JSON,就应该避免运行时反射。但我们不想手工marshal所有代码,怎么办呢?可以让计算机替我们写程序!像easyjson等代码生成器会检查结构体,然后生成高度优化且与json.Marshaller等接口完全兼容的代码。

下载这个包,然后在包含结构体的$file.go上运行下面的命令:
easyjson -all $file.go$file.go
这个命令会生成$file_easyjson.go。由于easyjson为我们实现了json.Marshaller接口,因此序列化时不会调用默认的反射,而是会使用生成的函数。祝贺你!你已经将JSON marshalling的代码的速度提高了三倍。还有许多其他技巧可以进一步提升性能。
 
使用strings.Builder来构建字符串
 
Go语言的字符串是不可修改的,可以认为它们是只读的字节切片。这就是说,每次创建字符串都要分配新的内存,可能还会给垃圾回收器造成更多工作。

Go 1.10引入了strings.Builder作为高效率构建字符串的方式。它内部会将字符串写入到字节缓冲区。只有在builder上调用String()时才会真正生成字符串。它依赖一些unsafe的技巧将底层的字节作为字符串返回,而不实际进行内存非配。这篇文章(https://syslog.ravelin.com/byt ... ca7ff)介绍了更多其工作原理。pkg: github.com/sjwhitworth/perfblog/strbuild
BenchmarkStringBuildNaive-8 5000000 255 ns/op 216 B/op 8 allocs/op
BenchmarkStringBuildBuilder-8 20000000 54.9 ns/op 64 B/op 1 allocs/op可见,strings.Builder要快4.7倍,它的内存分配次只有前者的1/8,内存使用只有前者的1/4。

所以,在性能重要的时候应该使用strings.Builder。一般来说,除非是非常不重要的情况,否则我建议永远使用strings.Builder来构建字符串。
 
使用strconv代替fmt

fmt是Go中最著名的包之一。估计你从第一个Go程序——输出“hello, world”——的时候就开始用它了。但是,如果需要将整数和浮点数转换成字符串,它就不如更底层的strconv有效率了。strconv只需要在API中进行很小改动,就能带来不错的性能提升。

大多数情况下fmt会接受一个interface{}作为参数。这样做有两个弊端:

失去了类型安全。对于我来说这是个很大的问题。

会增加内存分配次数。将非指针类型作为interface{}传递通常会导致堆分配的问题。进一步的内容可以阅读这篇文章(https://www.darkcoding.net/sof ... face/)。
BenchmarkStrconv-8 30000000 39.5 ns/op 32 B/op 1 allocs/op
BenchmarkFmt-8 10000000 143 ns/op 72 B/op 3 allocs/op可以看到,strconv版本要快3.5倍,内存分配次数是1/3,内存分配量是1/2。

在make中指定分配的容量来避免重新分配

在讨论性能改善之前,我们先来迅速看一下切片。切片是Go语言中一个非常有用的概念。它提供了可改变大小的数组,还可以用不同的方式表示同一片底层内存区域,而不需要重新进行内存分配。slice的内部结构由三个元素组成:

data:切片中指向底层数据的指针
len:切片中的当前元素数目
cap:在不重新分配内存的前提下,切片能够增长到的元素数目

我经常看到类似于下面的代码,尽管在切片容量可以预先得知的情况下依然生成一个容量为零的切片:var userIDs []string
for _, bar := range rsp.Users {
userIDs = append(userIDs, bar.ID)
}这段代码中,切片的初始长度和容量都为零。在收到响应后,我们将用户添加到切片。这样做就会达到切片的容量上限,从而导致底层分配两倍容量的新数组,然后将旧切片中的数据拷贝过来。如果有8个用户,就会造成5次内存分配。

更有效的方式是这样:
userIDs := make([]string, 0, len(rsp.Users)
for _, bar := range rsp.Users {
userIDs = append(userIDs, bar.ID)
}使用make显式声明切片的容量。接下来可以向切片添加元素,而不会触发内存重新分配和拷贝。
 
这条建议也适用于map:适用make(map[string]string, len(foo))可以分配足够的底层内存以避免内存重新分配。
 
使用可以接受字节切片的方法
 
在使用包时,寻找那些接受字节切片作为参数的方法,这些方法通常给你更多控制内存分配的自由。

一个很好的例子就是time.Format和time.AppendFormat。time.Format返回字符串。内部会分配一个新的字节切片,然后在其上调用time.AppendFormat。而time.AppendFormat接受一个字节缓冲区,将格式化后的时间写入缓冲区,然后返回扩展后的字节切片。标准库中这种做法非常常见,如strconv.AppendFloat或bytes.NewBuffer。

为什么这样能提高性能?因为你可以传递从sync.Poolh获得的字节切片,而不需要每次都分配新的缓冲区。或者可以初始化一个足够大的缓冲区,来减少切片拷贝。
 
这些建议只是某些具体情况下的建议,而不是真理。一定要自己测量性能。

要知道何时该停止优化。提高系统性能会让工程师感觉非常满足:问题本身很有趣,也有立竿见影的效果。但是,提高性能带来的效果非常依赖于具体情况。如果服务的响应时间只有10毫秒,而网络访问需要90毫秒,那么将10毫秒优化到5毫秒就完全不值得,因为你依然需要95毫秒。就算你将响应时间优化到1毫秒,最后结果还是91毫秒。你应该去做其他更有价值的事情。
 
原文:
https://stephen.sh/posts/quick-go-performance-improvements
译文:
https://blog.csdn.net/csdnnews/article/details/93987866
  查看全部
这篇文章中列出了一些不需要太多精力就能显著提高性能的技巧,并不包含那些需要太多精力或需要大幅度修改程序结构的技巧。
 
开始优化之前
 
开始优化之前,首先应该花些时间找出一个合适的基准线,以便稍后比较。如果没有基准,那就等于摸着石头过河,根本不知道自己的优化有没有效果。首先要编写性能测试程序,然后生成能用于pprof的profile文件。最好可以编写Go的性能测试脚本(https://dave.cheney.net/2013/0 ... in-go),这样可以很容易地使用pprof,还可以评测内存分配情况。还可以使用benchcmp,这个工具可以帮助比较两次性能测试之间的性能差异。

如果代码很难做性能测试,那就从你能测量时间的部分开始。可以利用runtime/pprof手工测量代码。

现在开始吧!
 
使用sync.Pool重用之前分配过的对象
 
sync.Pool实现了一个空闲列表(free-list)。这样可以重新使用之前分配过的对象。这样做可以将对象分配的代价平摊到多次使用上,减少垃圾回收器的工作。API非常简单:只需实现一个函数,用来分配新的对象即可。它会返回指针类型。
 
var bufpool = sync.Pool{
New: func() interface{} {
buf := make([]byte, 512)
return &buf
}}
之后,可以用Get()从池中获取对象,用完之后用Put()将对象放回。

不过要注意一些陷阱。在Go 1.13之前,每次发生垃圾回收时该池都会被清空。对于需要分配大量对象的程序来说,这可能会造成性能的影响。在1.13版本中似乎GC后能保留更多对象了(https://go-review.googlesource.com/c/go/+/162919/)。

你可能需要在将对象放回池中之前将其结构的字段清空。

如果不这样做,就可能从池中获得一个“脏”的对象,它包含之前使用过的数据。这可能会造成严重的安全问题!
 
安全的做法就是明确清空内存:
// reset resets all fields of the AuthenticationResponse before pooling it.
func (a* AuthenticationResponse) reset() {
a.Token = ""
a.UserID = ""
}

rsp := authPool.Get().(*AuthenticationResponse)
defer func() {
rsp.reset()
authPool.Put(rsp)
}()

在大map中避免使用包含指针的结构作为map的键
 
关于Go中大型堆的性能问题已经有很多人讨论过了。在垃圾回收过程中,运行时会扫描包含指针的对象并遍历其指针。如果你有非常大的map[string]int,那么垃圾回收器就不得不在每次垃圾回收过程中检查map中的每个字符串,因为字符串包含指针。

这个例子中我们向一个map[string]int中写入了一千万个元素,然后测量垃圾回收的时间。map是在包的作用域中分配的,以保证它被分配到堆上。
import (
"fmt"
"runtime"
"strconv"
"time"
)

const (
numElements = 10000000
)

var foo = map[string]int{}

func timeGC() {
t := time.Now()
runtime.GC()
fmt.Printf("gc took: %s\n", time.Since(t))
}

func main() {
for i := 0; i < numElements; i++ {
foo[strconv.Itoa(i)] = i
}

for {
timeGC()
time.Sleep(1 * time.Second)
}
}
运行后可以得到以下结果:
gc took: 98.726321ms
gc took: 105.524633ms
gc took: 102.829451ms
gc took: 102.71908ms
gc took: 103.084104ms
gc took: 104.821989ms
对于计算机来说这花得时间太多了!

怎样可以改进呢?最好是能尽量去掉指针,这样能减少垃圾回收器需要遍历的指针数量。由于字符串包含指针,因此我们可以用map[int]int来实现:
 
import (
"fmt"
"runtime"
"time"
)

const (
numElements = 10000000
)

var foo = map[int]int{}

func timeGC() {
t := time.Now()
runtime.GC()
fmt.Printf("gc took: %s\n", time.Since(t))
}

func main() {
for i := 0; i < numElements; i++ {
foo[i] = i
}

for {
timeGC()
time.Sleep(1 * time.Second)
}
}
重新运行程序,结果如下:
 
gc took: 3.608993ms
gc took: 3.926913ms
gc took: 3.955706ms
gc took: 4.063795ms
gc took: 3.91519ms
gc took: 3.75226ms
好多了。垃圾回收的时间减少了97%。在生产环境下,字符串需要进行hash之后再插入到map中。
 
生成marshalling代码以避免运行时反射
 
将数据结构marshalh或unmarshal成JSON等各种序列化格式是个很常见的操作,特别是在构建微服务的时候。实际上,大部分微服务做的唯一工作就是序列化。像json.Marshal和json.Unmarshal需要依赖运行时反射才能将结构体的字段序列化成字节,反之亦然。这个操作很慢,反射的性能完全无法与显式的代码相比。

但我们不必这么做。marshalling JSON的原理大致如下:
package json

// Marshal take an object and returns its representation in JSON.
func Marshal(obj interface{}) ([]byte, error) {
// Check if this object knows how to marshal itself to JSON
// by satisfying the Marshaller interface.
if m, is := obj.(json.Marshaller); is {
return m.MarshalJSON()
}

// It doesn't know how to marshal itself. Do default reflection based marshallling.
return marshal(obj)
}
如果我们知道怎样将对象marshal成JSON,就应该避免运行时反射。但我们不想手工marshal所有代码,怎么办呢?可以让计算机替我们写程序!像easyjson等代码生成器会检查结构体,然后生成高度优化且与json.Marshaller等接口完全兼容的代码。

下载这个包,然后在包含结构体的$file.go上运行下面的命令:
easyjson -all $file.go$file.go

这个命令会生成$file_easyjson.go。由于easyjson为我们实现了json.Marshaller接口,因此序列化时不会调用默认的反射,而是会使用生成的函数。祝贺你!你已经将JSON marshalling的代码的速度提高了三倍。还有许多其他技巧可以进一步提升性能。
 
使用strings.Builder来构建字符串
 
Go语言的字符串是不可修改的,可以认为它们是只读的字节切片。这就是说,每次创建字符串都要分配新的内存,可能还会给垃圾回收器造成更多工作。

Go 1.10引入了strings.Builder作为高效率构建字符串的方式。它内部会将字符串写入到字节缓冲区。只有在builder上调用String()时才会真正生成字符串。它依赖一些unsafe的技巧将底层的字节作为字符串返回,而不实际进行内存非配。这篇文章(https://syslog.ravelin.com/byt ... ca7ff)介绍了更多其工作原理。
pkg: github.com/sjwhitworth/perfblog/strbuild
BenchmarkStringBuildNaive-8 5000000 255 ns/op 216 B/op 8 allocs/op
BenchmarkStringBuildBuilder-8 20000000 54.9 ns/op 64 B/op 1 allocs/op
可见,strings.Builder要快4.7倍,它的内存分配次只有前者的1/8,内存使用只有前者的1/4。

所以,在性能重要的时候应该使用strings.Builder。一般来说,除非是非常不重要的情况,否则我建议永远使用strings.Builder来构建字符串。
 
使用strconv代替fmt

fmt是Go中最著名的包之一。估计你从第一个Go程序——输出“hello, world”——的时候就开始用它了。但是,如果需要将整数和浮点数转换成字符串,它就不如更底层的strconv有效率了。strconv只需要在API中进行很小改动,就能带来不错的性能提升。

大多数情况下fmt会接受一个interface{}作为参数。这样做有两个弊端:

失去了类型安全。对于我来说这是个很大的问题。

会增加内存分配次数。将非指针类型作为interface{}传递通常会导致堆分配的问题。进一步的内容可以阅读这篇文章(https://www.darkcoding.net/sof ... face/)。
BenchmarkStrconv-8      30000000            39.5 ns/op        32 B/op          1 allocs/op
BenchmarkFmt-8 10000000 143 ns/op 72 B/op 3 allocs/op
可以看到,strconv版本要快3.5倍,内存分配次数是1/3,内存分配量是1/2。

在make中指定分配的容量来避免重新分配

在讨论性能改善之前,我们先来迅速看一下切片。切片是Go语言中一个非常有用的概念。它提供了可改变大小的数组,还可以用不同的方式表示同一片底层内存区域,而不需要重新进行内存分配。slice的内部结构由三个元素组成:


data:切片中指向底层数据的指针
len:切片中的当前元素数目
cap:在不重新分配内存的前提下,切片能够增长到的元素数目


我经常看到类似于下面的代码,尽管在切片容量可以预先得知的情况下依然生成一个容量为零的切片:
var userIDs []string
for _, bar := range rsp.Users {
userIDs = append(userIDs, bar.ID)
}
这段代码中,切片的初始长度和容量都为零。在收到响应后,我们将用户添加到切片。这样做就会达到切片的容量上限,从而导致底层分配两倍容量的新数组,然后将旧切片中的数据拷贝过来。如果有8个用户,就会造成5次内存分配。

更有效的方式是这样:
userIDs := make([]string, 0, len(rsp.Users)
for _, bar := range rsp.Users {
userIDs = append(userIDs, bar.ID)
}
使用make显式声明切片的容量。接下来可以向切片添加元素,而不会触发内存重新分配和拷贝。
 
这条建议也适用于map:适用make(map[string]string, len(foo))可以分配足够的底层内存以避免内存重新分配。
 
使用可以接受字节切片的方法
 
在使用包时,寻找那些接受字节切片作为参数的方法,这些方法通常给你更多控制内存分配的自由。

一个很好的例子就是time.Format和time.AppendFormat。time.Format返回字符串。内部会分配一个新的字节切片,然后在其上调用time.AppendFormat。而time.AppendFormat接受一个字节缓冲区,将格式化后的时间写入缓冲区,然后返回扩展后的字节切片。标准库中这种做法非常常见,如strconv.AppendFloat或bytes.NewBuffer。

为什么这样能提高性能?因为你可以传递从sync.Poolh获得的字节切片,而不需要每次都分配新的缓冲区。或者可以初始化一个足够大的缓冲区,来减少切片拷贝。
 
这些建议只是某些具体情况下的建议,而不是真理。一定要自己测量性能。

要知道何时该停止优化。提高系统性能会让工程师感觉非常满足:问题本身很有趣,也有立竿见影的效果。但是,提高性能带来的效果非常依赖于具体情况。如果服务的响应时间只有10毫秒,而网络访问需要90毫秒,那么将10毫秒优化到5毫秒就完全不值得,因为你依然需要95毫秒。就算你将响应时间优化到1毫秒,最后结果还是91毫秒。你应该去做其他更有价值的事情。
 
原文:
https://stephen.sh/posts/quick-go-performance-improvements
译文:
https://blog.csdn.net/csdnnews/article/details/93987866
 

#每日精进#2020年8月4日

总结zkbhj 发表了文章 • 0 个评论 • 375 次浏览 • 2020-08-04 14:51 • 来自相关话题

【早读:《深入理解计算机系统》】
 
第二章 信息的表示和处理

大多数计算机使用8位的块,或字节,作为最小的可寻址内存单位。

机器级程序将内存视为一个非常大的字节数组,称为虚拟内存。

内存中每个字节由一个唯一的数字来标识,称为它的地址。所有可能的地址的集合就称为虚拟地址空间。

所以,这个虚拟地址空间只是一个展现给机器级程序的概念性映像。实际上,它将DRAM、闪存、磁盘存储器等和操作系统软件结合起来,封装了复杂性,为程序提供一个看上去统一的字节数组。


C语言中的一个指针的值,都是某个存储块的第一个字节的虚拟地址。每个程序对象可以简单地视为一个字节块,而程序本身就是一个字节序列。

十六进制表示法

由于二进制和十进制对于描述位模式来说都非常不方便:二进制太冗长,十进制和位模式的互相转化很麻烦,替代的方法就是引入16进制。

以0x或者0X开头,0~9、A~F,不区分大小写且大小写不敏感。

重要的是二进制、十进制和十六进制之间的互相转换方法,详细的可以进入凯冰科技知识共享中心搜索相关问题或文章查看。

对于x=2的n次方这个公式,转换十六进制,可以转化为n=i+4j,然后得到的十六进制就是:0x + 2的i次方 + j个0,比如512,是2的9次方,9=1+4*2,所以十六进制就是0x200。

字数据大小

每台计算机都有一个字长,指明指针数据的标称大小。字长决定虚拟地址空间的最大大小。32位字长限制的虚拟地址空间位4千兆字节(约4GB),而现在比较普及的64位字长的虚拟空间位16EB。

大多数64位机器也可以运行32位机器编译的程序,这是一种向后兼容。//该编译后的程序可以在32或64位机器上运行
linux> gcc -m32 prog.c

//该编译后,只能在64位机器上运行
linux> gcc -m64 prog.c我们将程序称为32位程序或64位程序,区别在于该程序是如何编译的,而不是其运行的机器类型。

ISO C99引入了确定大小的数据类型,int32_t 和int64_t,其数据大小是固定的,分别为4个字节和8个字节。使用确切大小的整数类型是程序员准确控制数据表示的最佳途径。

程序员应该力图使他们的程序可以在不同的机器和编译器上可移植,可移植的一方面就是说程序对不同数据类型的确切大小不敏感。

比如许多程序员假设一个声明为int类型的程序对象能被用来存储一个指针,这在大多数32位的机器上能够正常工作,但是在一台64位的机器上却会导致问题。所以,1980到2010年期间(32位机器是主流)编写的程序,之后64位机器陆续普及之后,迁移过来的程序就暴露出来许多隐藏的对字长的依赖性问题,导致错误。

【垂直行业如电商如何衡量搜索引擎的优劣】

在电商行业中,无论是2B还是2C,最终的业务目的就是交易成单,众所周知搜索服务旨在让消费者能够更快的定位到自己想要的产品。

一般电商搜索的核心是搜索精度和搜索广度,精度就是搜索的精确性,广度就是搜索结果的范围,其关键结果肯定是“为用户找到想要的商品”,但过于追求搜索的精确度就会导致出现搜索的结果比较少或结果为0的情况,用户搜不到商品势必会引发流失,因此在搜索服务里面还可以做的就是给用户提供一些相关性搜索结果。那么搜索做的好不好,其实就是在搜索精度和搜索广度二者之间做一个比较好的平衡点。

搜索过程中遇到的问题:

1.随机性发现的Bad case
2.KPI或者OKR考核
3.业务方诉求

核心指标
 
“搜索PV”:指访问搜索页面的次数;“搜索UV”:访问过搜索结果页的用户数;“无结果率”:空结果PV/搜索PV,无结果率越低,代表客户搜索需求解决情况越好;“TOP5 PV—CTR”:指该query search结果中,排在前五位的item有被点击的搜索PV/该query搜索PV该指标能一定程度反应排序效果;“人均搜索PV”:搜索PV/搜索UV;该指标的含义比较复杂,一方面人均pv大的话可能代表用户对搜索比较感兴趣,但另一方面人均pv大也可能代表搜索召回的结果较差,导致用户无法使用较少的点击找到满足需求的结果;“有点击搜索PV占比”:有点击搜索PV/搜索PV数;“PV-CTR”:搜索结果页item点击数/搜索PV数;“UV-CTR”:点击的uv / 曝光的uv;“Item-CTR”:搜索结果页item点击数/搜索结果页item总曝光PV数;
 
搜索技术等级分类





 
https://developer.aliyun.com/article/769492
 
  查看全部

【早读:《深入理解计算机系统》】
 
第二章 信息的表示和处理

大多数计算机使用8位的块,或字节,作为最小的可寻址内存单位。

机器级程序将内存视为一个非常大的字节数组,称为虚拟内存。

内存中每个字节由一个唯一的数字来标识,称为它的地址。所有可能的地址的集合就称为虚拟地址空间。

所以,这个虚拟地址空间只是一个展现给机器级程序的概念性映像。实际上,它将DRAM、闪存、磁盘存储器等和操作系统软件结合起来,封装了复杂性,为程序提供一个看上去统一的字节数组。


C语言中的一个指针的值,都是某个存储块的第一个字节的虚拟地址。每个程序对象可以简单地视为一个字节块,而程序本身就是一个字节序列。

十六进制表示法

由于二进制和十进制对于描述位模式来说都非常不方便:二进制太冗长,十进制和位模式的互相转化很麻烦,替代的方法就是引入16进制。

以0x或者0X开头,0~9、A~F,不区分大小写且大小写不敏感。

重要的是二进制、十进制和十六进制之间的互相转换方法,详细的可以进入凯冰科技知识共享中心搜索相关问题或文章查看。

对于x=2的n次方这个公式,转换十六进制,可以转化为n=i+4j,然后得到的十六进制就是:0x + 2的i次方 + j个0,比如512,是2的9次方,9=1+4*2,所以十六进制就是0x200。

字数据大小

每台计算机都有一个字长,指明指针数据的标称大小。字长决定虚拟地址空间的最大大小。32位字长限制的虚拟地址空间位4千兆字节(约4GB),而现在比较普及的64位字长的虚拟空间位16EB。

大多数64位机器也可以运行32位机器编译的程序,这是一种向后兼容。
//该编译后的程序可以在32或64位机器上运行
linux> gcc -m32 prog.c

//该编译后,只能在64位机器上运行
linux> gcc -m64 prog.c
我们将程序称为32位程序或64位程序,区别在于该程序是如何编译的,而不是其运行的机器类型。

ISO C99引入了确定大小的数据类型,int32_t 和int64_t,其数据大小是固定的,分别为4个字节和8个字节。使用确切大小的整数类型是程序员准确控制数据表示的最佳途径。

程序员应该力图使他们的程序可以在不同的机器和编译器上可移植,可移植的一方面就是说程序对不同数据类型的确切大小不敏感。

比如许多程序员假设一个声明为int类型的程序对象能被用来存储一个指针,这在大多数32位的机器上能够正常工作,但是在一台64位的机器上却会导致问题。所以,1980到2010年期间(32位机器是主流)编写的程序,之后64位机器陆续普及之后,迁移过来的程序就暴露出来许多隐藏的对字长的依赖性问题,导致错误。

【垂直行业如电商如何衡量搜索引擎的优劣】

在电商行业中,无论是2B还是2C,最终的业务目的就是交易成单,众所周知搜索服务旨在让消费者能够更快的定位到自己想要的产品。

一般电商搜索的核心是搜索精度和搜索广度,精度就是搜索的精确性,广度就是搜索结果的范围,其关键结果肯定是“为用户找到想要的商品”,但过于追求搜索的精确度就会导致出现搜索的结果比较少或结果为0的情况,用户搜不到商品势必会引发流失,因此在搜索服务里面还可以做的就是给用户提供一些相关性搜索结果。那么搜索做的好不好,其实就是在搜索精度和搜索广度二者之间做一个比较好的平衡点。

搜索过程中遇到的问题:


1.随机性发现的Bad case
2.KPI或者OKR考核
3.业务方诉求


核心指标
 
  • “搜索PV”:指访问搜索页面的次数;
  • “搜索UV”:访问过搜索结果页的用户数;
  • “无结果率”:空结果PV/搜索PV,无结果率越低,代表客户搜索需求解决情况越好;
  • “TOP5 PV—CTR”:指该query search结果中,排在前五位的item有被点击的搜索PV/该query搜索PV该指标能一定程度反应排序效果;
  • “人均搜索PV”:搜索PV/搜索UV;该指标的含义比较复杂,一方面人均pv大的话可能代表用户对搜索比较感兴趣,但另一方面人均pv大也可能代表搜索召回的结果较差,导致用户无法使用较少的点击找到满足需求的结果;
  • “有点击搜索PV占比”:有点击搜索PV/搜索PV数;
  • “PV-CTR”:搜索结果页item点击数/搜索PV数;
  • “UV-CTR”:点击的uv / 曝光的uv;
  • “Item-CTR”:搜索结果页item点击数/搜索结果页item总曝光PV数;

 
搜索技术等级分类

13cfe86b5b2f4aacba814f4fb2f080e5.png

 
https://developer.aliyun.com/article/769492
 
 

#每日精进#2020年8月3日

总结zkbhj 发表了文章 • 0 个评论 • 387 次浏览 • 2020-08-03 20:45 • 来自相关话题

【早读:《深入理解计算机系统》】

第二章 信息的表示和处理

现代计算机存储和处理信息以二值信号表示。二值信号能够很容易的被表示、存储和传输,且用二值信号进行存储和执行计算的电子电路非常简单可靠。

三种重要的数字表示:
 

无符号编码:基于传统的二进制表示法,表示大于或者等于0的数字;
补码编码:表示有符号整数的最常见方式;
浮点数编码:表示实数的科学计数法的以2为基数的版本;


计算机的表示法是以有限数量的位来对一个数字进行编码,所以一旦超出界限,某些运算就会溢出,导致令人吃惊的后果。

整数的计算机运算满足人们所熟知的真正整数运算的许多性质。但是浮点数不一样。

整数的表示虽然只能编码一个相对较小的数值范围,但是这种表示是精确的;

浮点数虽然可以编码一个比较大的数值范围,但是这种标示只是近似的;

通过如下命令,可以在gcc编译C程序时指定C语言版本:
linux> gcc -std=c11 zkbhj.c
//其他版本参数
//GNU 89 无,-std=gnu89
//ANSI ,ISO C90 -ansi,-std=c89
//ISO C99 -std=c99
//ISO C11 -std=c11
【Go核心36讲:第11节 通道的高级玩法】

单向通道

所谓单向通道就是,只能发不能收,或者只能收不能发的通道。

声明一个只能发(向通道发送)不能收(从通道接收),容量为1的单向通道:
var uselessChan = make(chan<- int, 1)声明一个只能收(从通道接收)不能发(向通道发送),容量为1的单向通道:
var uselessChan = make(<-chan int, 1)
与发送操作和接收操作对应,这里的“发”和“收”都是站在操作通道的代码的角度上说的。

单向通道有什么应用价值?

概括地说,单向通道最主要的用途就是约束其他代码的行为。
//参数定义中就约束 ch只能进行发送操作,不能接收
//可以限制方法函数内对参数的操作行为做限定
func SendInt(ch chan<- int) {
ch <- rand.Intn(1000)
}//这段接口声明中,就约定了所以要实现这个接口的实现类型
//都约定了这些方法的参数类型
type Notifier interface {
SendInt(ch chan<- int)
}在实际调用的时候,传递一个双向通道即可,因为Go 语言在这种情况下会自动地把双向通道转换为函数所需的单向通道。

一种专门为了操作通道而存在的语句:select语句

select语句只能与通道联用,它一般由若干个分支组成。每次执行这种语句的时候,一般只有一个分支中的代码会被运行。分支分为两种,一种叫做候选分支,另一种叫做默认分支。每个case表达式中都只能包含操作通道的表达式。
select {
case <-intChannels[0]:
fmt.Println("The first candidate case is selected.")
case <-intChannels[1]:
fmt.Println("The second candidate case is selected.")
case elem := <-intChannels[2]: fmt.Printf("The third candidate case is selected, the element is %d.\n", elem)
default: fmt.Println("No candidate case is selected!")
}select语句只能对其中的每一个case表达式各求值一次。所以,如果我们想连续或定时地操作其中的通道的话,就往往需要通过在for语句中嵌入select语句的方式实现。但这时要注意,简单地在select语句的分支中使用break语句,只能结束当前的select语句的执行,而并不会对外层的for语句产生作用。这种错误的用法可能会让这个for语句无休止地运行下去。

select语句的分支选择规则总结:

1、对于每一个case表达式,都至少会包含一个代表发送操作的发送表达式或者一个代表接收操作的接收表达式;
2、select语句包含的候选分支中的case表达式都会在该语句执行开始时先被求值,并且求值的顺序是依从代码编写的顺序从上到下的;
3、对于每一个case表达式,如果其中的发送表达式或者接收表达式在被求值时,相应的操作正处于阻塞状态,那么对该case表达式的求值就是不成功的;
4、仅当select语句中的所有case表达式都被求值完毕后,它才会开始选择候选分支。这时候,它只会挑选满足选择条件的候选分支执行。所有的都不满足,执行default;
5、如果select语句发现同时有多个候选分支满足选择条件,那么它就会用一种伪随机的算法在这些分支中选择一个并执行;

 
【关于730政治局会议的总结】

15大要点(内循环 + 持久战)
 
​中国发展仍处于战略机遇期从持久战角度认识中长期问题以国内大循环为主题建立中长期协调机制牢牢把握扩大内需这个战略基点确保宏观政策落地见效保持货币供应量合理增长毫不放松抓好常态化疫情防控扩大最终消费加快新基建以新型城镇化带动投资和消费产业链补短板和锻长板从严打击证券违法活动住房不炒缓解疫情对年轻人就业影响

我们遇到的很多问题是中长期的,必须从持久战的角度加以认识

“在泡沫中狂欢的日志不多了,做好潮水退却后的准备,是每个国家,每个人都要面对的现实”

——吴晓灵 前央行副行长 查看全部
【早读:《深入理解计算机系统》】

第二章 信息的表示和处理

现代计算机存储和处理信息以二值信号表示。二值信号能够很容易的被表示、存储和传输,且用二值信号进行存储和执行计算的电子电路非常简单可靠。

三种重要的数字表示:
 


无符号编码:基于传统的二进制表示法,表示大于或者等于0的数字;
补码编码:表示有符号整数的最常见方式;
浮点数编码:表示实数的科学计数法的以2为基数的版本;



计算机的表示法是以有限数量的位来对一个数字进行编码,所以一旦超出界限,某些运算就会溢出,导致令人吃惊的后果。

整数的计算机运算满足人们所熟知的真正整数运算的许多性质。但是浮点数不一样。

整数的表示虽然只能编码一个相对较小的数值范围,但是这种表示是精确的;

浮点数虽然可以编码一个比较大的数值范围,但是这种标示只是近似的;

通过如下命令,可以在gcc编译C程序时指定C语言版本:
linux> gcc -std=c11 zkbhj.c
//其他版本参数
//GNU 89 无,-std=gnu89
//ANSI ,ISO C90 -ansi,-std=c89
//ISO C99 -std=c99
//ISO C11 -std=c11

【Go核心36讲:第11节 通道的高级玩法】

单向通道

所谓单向通道就是,只能发不能收,或者只能收不能发的通道。

声明一个只能发(向通道发送)不能收(从通道接收),容量为1的单向通道:
var uselessChan = make(chan<- int, 1)
声明一个只能收(从通道接收)不能发(向通道发送),容量为1的单向通道:
var uselessChan = make(<-chan int, 1)
与发送操作和接收操作对应,这里的“发”和“收”都是站在操作通道的代码的角度上说的。

单向通道有什么应用价值?

概括地说,单向通道最主要的用途就是约束其他代码的行为。
//参数定义中就约束 ch只能进行发送操作,不能接收
//可以限制方法函数内对参数的操作行为做限定
func SendInt(ch chan<- int) {
ch <- rand.Intn(1000)
}
//这段接口声明中,就约定了所以要实现这个接口的实现类型
//都约定了这些方法的参数类型
type Notifier interface {
SendInt(ch chan<- int)
}
在实际调用的时候,传递一个双向通道即可,因为Go 语言在这种情况下会自动地把双向通道转换为函数所需的单向通道。

一种专门为了操作通道而存在的语句:select语句

select语句只能与通道联用,它一般由若干个分支组成。每次执行这种语句的时候,一般只有一个分支中的代码会被运行。分支分为两种,一种叫做候选分支,另一种叫做默认分支。每个case表达式中都只能包含操作通道的表达式。
select {
case <-intChannels[0]:
fmt.Println("The first candidate case is selected.")
case <-intChannels[1]:
fmt.Println("The second candidate case is selected.")
case elem := <-intChannels[2]: fmt.Printf("The third candidate case is selected, the element is %d.\n", elem)
default: fmt.Println("No candidate case is selected!")
}
select语句只能对其中的每一个case表达式各求值一次。所以,如果我们想连续或定时地操作其中的通道的话,就往往需要通过在for语句中嵌入select语句的方式实现。但这时要注意,简单地在select语句的分支中使用break语句,只能结束当前的select语句的执行,而并不会对外层的for语句产生作用。这种错误的用法可能会让这个for语句无休止地运行下去。

select语句的分支选择规则总结:


1、对于每一个case表达式,都至少会包含一个代表发送操作的发送表达式或者一个代表接收操作的接收表达式;
2、select语句包含的候选分支中的case表达式都会在该语句执行开始时先被求值,并且求值的顺序是依从代码编写的顺序从上到下的;
3、对于每一个case表达式,如果其中的发送表达式或者接收表达式在被求值时,相应的操作正处于阻塞状态,那么对该case表达式的求值就是不成功的;
4、仅当select语句中的所有case表达式都被求值完毕后,它才会开始选择候选分支。这时候,它只会挑选满足选择条件的候选分支执行。所有的都不满足,执行default;
5、如果select语句发现同时有多个候选分支满足选择条件,那么它就会用一种伪随机的算法在这些分支中选择一个并执行;


 
【关于730政治局会议的总结】

15大要点(内循环 + 持久战)
 
  1. ​中国发展仍处于战略机遇期
  2. 从持久战角度认识中长期问题
  3. 以国内大循环为主题
  4. 建立中长期协调机制
  5. 牢牢把握扩大内需这个战略基点
  6. 确保宏观政策落地见效
  7. 保持货币供应量合理增长
  8. 毫不放松抓好常态化疫情防控
  9. 扩大最终消费
  10. 加快新基建
  11. 以新型城镇化带动投资和消费
  12. 产业链补短板和锻长板
  13. 从严打击证券违法活动
  14. 住房不炒
  15. 缓解疫情对年轻人就业影响


我们遇到的很多问题是中长期的,必须从持久战的角度加以认识


“在泡沫中狂欢的日志不多了,做好潮水退却后的准备,是每个国家,每个人都要面对的现实”

——吴晓灵 前央行副行长


#每日精进#2020年08月02日

总结zkbhj 发表了文章 • 0 个评论 • 353 次浏览 • 2020-08-02 19:46 • 来自相关话题

【午读:《深入理解计算机系统》】

第一章 计算机系统漫游

现代系统之间利用网络通信,和其他系统连接在一起。从一个单独的系统来看,网络可以视为一个I/O设备。

系统不仅仅只是硬件,而是硬件和软件互相交织的结合体,他们之间共同协作已达到运行应用程序的最终目的。

Amdahl(安达尔定律)定律

主要思想是:当对系统的某个部分加速时,其对系统整体性能的影响取决于该部分的重要性和加速程度。
主要观点是:想要显著加速整个系统,必须提升全系统中相当大的部分的速度。

 
Amdahl定律描述了改善任何过程的一般原则。

并发和并行

整个计算机发展历史中,我们一直在做两件事:一是让计算机做得更多,二是让计算机运行的更快。
并发:是一个通用概念,指一个同时具有多个活动的系统;
并行:指的是用并发来使一个系统运行得更快。

进程级并发
并发构建在进程整个抽象上,就能够设计出同时有多个程序执行的系统。
单处理器系统:只有一个处理器的系统;
多处理器系统:一个由单操作系统内核控制的多处理器组成的系统。
超线程,有时称为同时多线程,是一项允许一个CPU执行多个控制流的技术。

指令级并行
CPU可以同时执行多条指令的属性称为指令级并行。
超标量处理器:处理器能够达到比一个时钟周期一条指令更快的执行速率。

计算机系统中抽象的重要性
计算机系统提供的一些抽象,它提供不同层次的抽象表示来隐藏实际实现的复杂性。
比如上一节中:文件是对I/O设备的抽象,虚拟内存是对主存和磁盘的抽象,进程则是对一个正在运行的程序的抽象(处理器、主存和I/O的抽象)。
虚拟机,则提供对整个计算机的抽象。

第一章总结
计算机系统是由硬件和系统软件组成的,它们共同协作以运行应用程序。计算机内部的信息被表示为一组组的位,它们根据上下文有不同的解释方式。程序被其他程序翻译成不同的形式,开始是ASCII文本,然后被编译器和链接器翻译成二进制可执行文件。

处理器读取并解释存储在主存里的二进制指令。因为计算机花了大把时间用于存储器、I/O设备和CPU寄存器之间复制数据,所以讲系统中的存储设备划分成层次结构——CPU寄存器在顶部,接着是多层的硬件高速缓存存储器、DRAM主存和磁盘存储器。

在层次模型中,位于更高层的存储设备比低层的存储设备要更快,单位比特造价也更高。层次结构中较高层次存储设备可以作为较低层次存储设备的高速缓存。通过理解和运用这种存储层次结构的知识,程序员可以优化C程序的性能。

操作系统内核是应用程序和硬件之间的媒介。它提供三个基本的抽象:

(1)文件是对I/O设备的抽象

(2)虚拟存储器是对主存和I/O设备的抽象

(3)进程是对处理器、主存和I/O设备的抽象。

另外,虚拟机提供了对整个计算机的抽象。

最后,网络提供了计算机系统之间通信的手段。从特殊系统的角度来看,网络就是一种I/O设备。
 

一周的早读,完成了《深入理解计算机系统》这本又厚又重的计算机最底层原理书籍第一章的阅读和理解。
好好利用碎片时间,以及找到自己的高效时间段及学习方式很重要。
积少成多,积流成河!
明天开启第二章:程序结构和执行!


PS广而告之时间:

正在筹划一个作为技术人员角度的个人分享网站,以很程序员的视角,将学习过的一门语言、一本技术书籍,以“手册”的形式输出出来。
根据能量守恒定律,有输入就要有输出,既然自己有这样的渠道和能力,打算把体系学习过的一些内容,沉淀成一本本技术手册分享出来,可以给更多有同样需求的人提供一些有意义的帮助和指导。
还是凯冰科技10多年来坚持的一句slogan:
代码改变世界,技术改变生活
Code changes the world, technology changes life
doc.zkbhj.com
第一本就是这本《深入理解计算机系统》。嘿嘿~

【关于如何在美团里继续使用支付宝支付的方法】

额。。由于一些你懂的的原因,大部分美团用户已经无法再美团APP上用支付宝进行支付了。这里暂时不评论这件事情的对与错好与坏,只是从技术的角度,帮你找到了一个怎么继续在美团里使用支付宝支付的方法,操作步骤如下:

1、打开美团APP,点击进入个人中心页面,点击右上角的在线客服,进入客服聊天界面;

2、输入问题:支付宝无法使用;

3、在返回的结果里选择:放弃优惠,恢复支付宝。






经过上面的几步“骚操作”,就可以继续在美团里使用支付宝进行支付了!
 
​【Go语言核心36讲:第10节 通道的基本操作 】

☆ 通道(Channel)

通道是Go 语言最有特色的数据类型,是不同Goroutine之间通信的“桥梁”。
 

Don’t communicate by sharing memory; share memory by communicating. (不要通过共享内存来通信,而应该通过通信来共享内存。)


通道类型的值是 Go 语言自带的、唯一一个可以满足并发安全性的类型。

当容量为0时,我们可以称通道为非缓冲通道,也就是不带缓冲的通道。而当容量大于0时,我们可以称为缓冲通道,也就是带有缓冲的通道。

一个通道相当于一个先进先出(FIFO)的队列。也就是说,通道中的各个元素值都是严格地按照发送的顺序排列的,先被发送通道的元素值一定会先被接收。元素值的发送和接收都需要用到操作符<-。我们也可以叫它接送操作符。一个左尖括号紧接着一个减号形象地代表了元素值的传输方向。

☆ 对通道的发送和接收操作都有哪些基本的特性?

1、对于同一个通道,发送操作之间是互斥的,接收操作之间也是互斥的。

元素值从外界进入通道时会被复制。更具体地说,进入通道的并不是在接收操作符右边的那个元素值,而是它的副本。另一方面,元素值从通道进入外界时会被移动。这个移动操作实际上包含了两步,第一步是生成正在通道中的这个元素值的副本,并准备给到接收方,第二步是删除在通道中的这个元素值。

2、发送操作和接收操作中对元素值的处理都是不可分割的。

这里的“不可分割”的意思是,它们处理元素值时都是一气呵成的,绝不会被打断。

发送时,“复制元素值”和“放置副本到通道内部”这两个步骤不会被打断;

接收时,“复制通道内元素值”“放置副本到接收方”“删掉原值”三个步骤不会被打断。

3、发送操作在完全完成之前会被阻塞。接收操作也是如此。

以上各步骤执行期间,其他操作都会被阻塞。如此阻塞代码其实就是为了实现操作的互斥和元素值的完整。

☆ 发送操作和接收操作在什么时候可能被长时间的阻塞?

有缓冲的,如果通道已满,对它的所有发送操作都会被阻塞,直到通道中有元素值被接收走。如果通道已空,对它的所有接收操作都会被阻塞,直到通道中有新的元素值出现。所有被阻塞的goroutine都是按FIFO的策略执行的。

非缓冲通道,情况要简单一些。无论是发送操作还是接收操作,一开始执行就会被阻塞,直到配对的操作也开始执行,才会继续传递。由此可见,非缓冲通道是在用同步的方式传递数据。

对于值为nil的通道,不论它的具体类型是什么,对它的发送操作和接收操作都会永久地处于阻塞状态。它们所属的 goroutine 中的任何代码,都不再会被执行。所以我们一定不要忘记初始化通道!

☆ 发送操作和接收操作在什么时候会引发 panic?

通道一旦关闭,再对它进行发送操作,就会引发 panic。

如果我们试图关闭一个已经关闭了的通道,也会引发 panic。

通过接收表达式的第二个结果值,可以来判断通道是否关闭,但是可能有延时的。即如果通道已经关闭,但还有值未被取出,则这个时候,返回的仍然是true!

所以最佳实践告诉我们,千万不要让接收方关闭通道,而应当让发送方做这件事。 查看全部

【午读:《深入理解计算机系统》】

第一章 计算机系统漫游

现代系统之间利用网络通信,和其他系统连接在一起。从一个单独的系统来看,网络可以视为一个I/O设备。

系统不仅仅只是硬件,而是硬件和软件互相交织的结合体,他们之间共同协作已达到运行应用程序的最终目的。

Amdahl(安达尔定律)定律


主要思想是:当对系统的某个部分加速时,其对系统整体性能的影响取决于该部分的重要性和加速程度。
主要观点是:想要显著加速整个系统,必须提升全系统中相当大的部分的速度。


 
Amdahl定律描述了改善任何过程的一般原则。

并发和并行

整个计算机发展历史中,我们一直在做两件事:一是让计算机做得更多,二是让计算机运行的更快。
并发:是一个通用概念,指一个同时具有多个活动的系统;
并行:指的是用并发来使一个系统运行得更快。

进程级并发
并发构建在进程整个抽象上,就能够设计出同时有多个程序执行的系统。
单处理器系统:只有一个处理器的系统;
多处理器系统:一个由单操作系统内核控制的多处理器组成的系统。
超线程,有时称为同时多线程,是一项允许一个CPU执行多个控制流的技术。

指令级并行
CPU可以同时执行多条指令的属性称为指令级并行。
超标量处理器:处理器能够达到比一个时钟周期一条指令更快的执行速率。

计算机系统中抽象的重要性
计算机系统提供的一些抽象,它提供不同层次的抽象表示来隐藏实际实现的复杂性。
比如上一节中:文件是对I/O设备的抽象,虚拟内存是对主存和磁盘的抽象,进程则是对一个正在运行的程序的抽象(处理器、主存和I/O的抽象)。
虚拟机,则提供对整个计算机的抽象。

第一章总结
计算机系统是由硬件和系统软件组成的,它们共同协作以运行应用程序。计算机内部的信息被表示为一组组的位,它们根据上下文有不同的解释方式。程序被其他程序翻译成不同的形式,开始是ASCII文本,然后被编译器和链接器翻译成二进制可执行文件。

处理器读取并解释存储在主存里的二进制指令。因为计算机花了大把时间用于存储器、I/O设备和CPU寄存器之间复制数据,所以讲系统中的存储设备划分成层次结构——CPU寄存器在顶部,接着是多层的硬件高速缓存存储器、DRAM主存和磁盘存储器。

在层次模型中,位于更高层的存储设备比低层的存储设备要更快,单位比特造价也更高。层次结构中较高层次存储设备可以作为较低层次存储设备的高速缓存。通过理解和运用这种存储层次结构的知识,程序员可以优化C程序的性能。

操作系统内核是应用程序和硬件之间的媒介。它提供三个基本的抽象:

(1)文件是对I/O设备的抽象

(2)虚拟存储器是对主存和I/O设备的抽象

(3)进程是对处理器、主存和I/O设备的抽象。

另外,虚拟机提供了对整个计算机的抽象。

最后,网络提供了计算机系统之间通信的手段。从特殊系统的角度来看,网络就是一种I/O设备。
 


一周的早读,完成了《深入理解计算机系统》这本又厚又重的计算机最底层原理书籍第一章的阅读和理解。
好好利用碎片时间,以及找到自己的高效时间段及学习方式很重要。
积少成多,积流成河!
明天开启第二章:程序结构和执行!



PS广而告之时间

正在筹划一个作为技术人员角度的个人分享网站,以很程序员的视角,将学习过的一门语言、一本技术书籍,以“手册”的形式输出出来。
根据能量守恒定律,有输入就要有输出,既然自己有这样的渠道和能力,打算把体系学习过的一些内容,沉淀成一本本技术手册分享出来,可以给更多有同样需求的人提供一些有意义的帮助和指导。
还是凯冰科技10多年来坚持的一句slogan:
代码改变世界,技术改变生活
Code changes the world, technology changes life
doc.zkbhj.com
第一本就是这本《深入理解计算机系统》。嘿嘿~

【关于如何在美团里继续使用支付宝支付的方法】

额。。由于一些你懂的的原因,大部分美团用户已经无法再美团APP上用支付宝进行支付了。这里暂时不评论这件事情的对与错好与坏,只是从技术的角度,帮你找到了一个怎么继续在美团里使用支付宝支付的方法,操作步骤如下:


1、打开美团APP,点击进入个人中心页面,点击右上角的在线客服,进入客服聊天界面;

2、输入问题:支付宝无法使用;

3、在返回的结果里选择:放弃优惠,恢复支付宝。



WechatIMG147.jpeg

经过上面的几步“骚操作”,就可以继续在美团里使用支付宝进行支付了!
 
​【Go语言核心36讲:第10节 通道的基本操作 】

☆ 通道(Channel)

通道是Go 语言最有特色的数据类型,是不同Goroutine之间通信的“桥梁”。
 


Don’t communicate by sharing memory; share memory by communicating. (不要通过共享内存来通信,而应该通过通信来共享内存。)



通道类型的值是 Go 语言自带的、唯一一个可以满足并发安全性的类型。

当容量为0时,我们可以称通道为非缓冲通道,也就是不带缓冲的通道。而当容量大于0时,我们可以称为缓冲通道,也就是带有缓冲的通道。

一个通道相当于一个先进先出(FIFO)的队列。也就是说,通道中的各个元素值都是严格地按照发送的顺序排列的,先被发送通道的元素值一定会先被接收。元素值的发送和接收都需要用到操作符<-。我们也可以叫它接送操作符。一个左尖括号紧接着一个减号形象地代表了元素值的传输方向。

☆ 对通道的发送和接收操作都有哪些基本的特性?

1、对于同一个通道,发送操作之间是互斥的,接收操作之间也是互斥的。

元素值从外界进入通道时会被复制。更具体地说,进入通道的并不是在接收操作符右边的那个元素值,而是它的副本。另一方面,元素值从通道进入外界时会被移动。这个移动操作实际上包含了两步,第一步是生成正在通道中的这个元素值的副本,并准备给到接收方,第二步是删除在通道中的这个元素值。

2、发送操作和接收操作中对元素值的处理都是不可分割的。

这里的“不可分割”的意思是,它们处理元素值时都是一气呵成的,绝不会被打断。

发送时,“复制元素值”和“放置副本到通道内部”这两个步骤不会被打断;

接收时,“复制通道内元素值”“放置副本到接收方”“删掉原值”三个步骤不会被打断。

3、发送操作在完全完成之前会被阻塞。接收操作也是如此。

以上各步骤执行期间,其他操作都会被阻塞。如此阻塞代码其实就是为了实现操作的互斥和元素值的完整。

☆ 发送操作和接收操作在什么时候可能被长时间的阻塞?

有缓冲的,如果通道已满,对它的所有发送操作都会被阻塞,直到通道中有元素值被接收走。如果通道已空,对它的所有接收操作都会被阻塞,直到通道中有新的元素值出现。所有被阻塞的goroutine都是按FIFO的策略执行的。

非缓冲通道,情况要简单一些。无论是发送操作还是接收操作,一开始执行就会被阻塞,直到配对的操作也开始执行,才会继续传递。由此可见,非缓冲通道是在用同步的方式传递数据。

对于值为nil的通道,不论它的具体类型是什么,对它的发送操作和接收操作都会永久地处于阻塞状态。它们所属的 goroutine 中的任何代码,都不再会被执行。所以我们一定不要忘记初始化通道!

☆ 发送操作和接收操作在什么时候会引发 panic?

通道一旦关闭,再对它进行发送操作,就会引发 panic。

如果我们试图关闭一个已经关闭了的通道,也会引发 panic。

通过接收表达式的第二个结果值,可以来判断通道是否关闭,但是可能有延时的。即如果通道已经关闭,但还有值未被取出,则这个时候,返回的仍然是true!

所以最佳实践告诉我们,千万不要让接收方关闭通道,而应当让发送方做这件事。

#每日精进#2020年07月30日

总结zkbhj 发表了文章 • 0 个评论 • 277 次浏览 • 2020-07-30 08:34 • 来自相关话题

 
【早读:《深入理解计算机系统》】

第一章 计算机系统漫游

hello程序运行过程:

1、键盘输入"./hello"后,shell程序将字符逐一读入寄存器,再把它们存放到主存中;
2、敲击回车,shell就知道我们已经输入结束了,然后shell会执行一系列指令,将hello程序中的代码和数据从磁盘复制到主存。这其中利用了直接存储器存取(DMA),数据直接通过总线从磁盘进入主存中,不会经过处理器;
3、代码和数据加载完毕之后,处理器就开始执行hello程序的main程序中的机器语言指令,将数据(hello,world\n)字符串中的字符从主存中复制到寄存器文件中,再从寄存器文件中复制到显示器设备,最终显示出结果。


高速缓存的重要性:上面的执行过程,我们就可以发现,系统花费了大量的时间把信息从一个地方移动到另一个地方!这些复制都是巨大的开销,减慢了程序”真正“的工作。因此,系统设计者通过让”存储设备形成层次结构”来提供速度,尽可能让这些复制操作能够尽快完成。






存储器层次结构的主要思想是上一层的存储器作为低一层存储器的高速缓存。

L1、L2、L3这些更小更快的存储设备,称为高速缓存存储器(cache memory,简称cache或高速缓存)。高速缓存是用一种叫做静态随机访问存储器(SRAM)的硬件技术实现的。

高速缓存之所以能加快访问速度,从而提高系统整体的效率,是利用了高速缓存的局部性原理:即程序具有访问局部区域里的数据和代码的趋势。通过让高速缓存里存放可能经常访问的数据,大部分的内存操作都能在高速缓存中快速完成。

操作系统管理硬件

应用程序并不能直接跟硬件“打交道”,而是要通过“操作系统”这一“中间商”提供的服务来和硬件进行交流。

操作系统有2个基本功能:
防止硬件被失控的应用程序滥用;向应用程序提供简单一致的机制来控制复杂而又通常大不相同的低级硬件设备。
 
操作系统通过以下三个抽象概念来实现上述2个功能:
文件:是对I/O设备的抽象表示;虚拟内存:是对主存和磁盘I/O设备的抽象表示;进程:是对处理器、主存和磁盘I/O设备的抽象表示。Posix标准是IEEE组织为了组织Unix越来越混乱,越来越不标准化而制定的标准化Unix开发提出来的一套标准规范。
 
 
【爱英语Show】
 
开电脑,关电脑不能用Open the computer 和 Close the computer!这两个表达方式的意思是 拆开/关上的意思。
正确的表达方式应该是 Turn on the computer 和 Turn off the computer!
另外,打开/关闭电脑中的文件,可以说 Open the file 或者 Close the file。
 

【高次方程】
 
今天中午在读紫金陈的《无证之罪》时,严良给赵铁民分析案情时提到,这个案子不能用常规的方法。普通的案子,可以根据线索通过“推论”算出来“嫌疑人”,就像是方程,可以通过公式计算出结果。但是这个案子,要用对应“高次方程”的求解方法来分析:那就是先确定结果,再代入方程中进行论证。也就是先确定嫌疑人,然后代入案情中推论。
 
严良提到的“高次方程”其实指的是次数大于等于5的方程,这类方程有一个特点: 高于5次(大于等于5)的方程没有精确代数解(这个定理已经被证明)。 这种方程只能通过数值计算的方法,用数值逼近求近似解。
 
埃瓦里斯特·伽罗瓦,用群论系统化地阐述了五次及五次以上方程不能用公式求解。
 
【今日放学别走】

今天的放学别走部门同学一起Review了近期完成的筛选项后台管理系统的项目代码,项目使用了Gin框架,其中有一个配置文件格式是以前没有接触过的,是Toml。之前一直接触过的有yaml、json、ini之类的,这个今天还是第一次听,大概了解了下一个配置文件的相关内容。

GitHub觉得 YAML 不够简洁优雅,因此捣鼓出了一个TOML。TOML的全称是 Tom’s Obvious, Minimal Language,因为它的作者是 GitHub联合创始人Tom Preston-Werner 。

TOML 的目标是成为一个极简的配置文件格式。TOML 被设计成可以无歧义地被映射为哈希表,从而被多种语言解析。

是大小写敏感的。

可能是目前最好的配置文件格式。详细可以点击了解:
https://blog.csdn.net/john_f_lau/article/details/55803069
 
【Go语言核心36讲:第9节 字典的操作和约束】

Go 语言的字典类型(Map)其实是一个哈希表(hash table)的特定实现,在这个实现中,键和元素的最大不同在于,键的类型是受限的,而元素却可以是任意类型的。之所以会受限,是因为哈希表中的一个重要过程:映射。

映射过程的第一步就是:把键值转换为哈希值。

Go 语言规范规定,键类型的值必须要支持判等操作,所以字典的键类型不可以是函数类型、字典类型和切片类型。

应该优先考虑哪些类型作为字典的键类型?求哈希和判等操作的速度越快,对应的类型就越适合作为键类型。优先选用数值类型和指针类型,通常情况下类型的宽度越小越好。如果非要选择字符串类型的话,最好对键值的长度进行额外的约束。

除了添加键 - 元素对,我们在一个值为nil的字典上做任何操作都不会引起错误。当我们试图在一个值为nil的字典中添加键 - 元素对的时候,Go 语言的运行时系统就会立即抛出一个 panic。

永远要注意那些可能引发 panic 的操作,比如像一个值为nil的字典添加键 - 元素对。 查看全部

 
【早读:《深入理解计算机系统》】

第一章 计算机系统漫游

hello程序运行过程:


1、键盘输入"./hello"后,shell程序将字符逐一读入寄存器,再把它们存放到主存中;
2、敲击回车,shell就知道我们已经输入结束了,然后shell会执行一系列指令,将hello程序中的代码和数据从磁盘复制到主存。这其中利用了直接存储器存取(DMA),数据直接通过总线从磁盘进入主存中,不会经过处理器;
3、代码和数据加载完毕之后,处理器就开始执行hello程序的main程序中的机器语言指令,将数据(hello,world\n)字符串中的字符从主存中复制到寄存器文件中,再从寄存器文件中复制到显示器设备,最终显示出结果。



高速缓存的重要性:上面的执行过程,我们就可以发现,系统花费了大量的时间把信息从一个地方移动到另一个地方!这些复制都是巨大的开销,减慢了程序”真正“的工作。因此,系统设计者通过让”存储设备形成层次结构”来提供速度,尽可能让这些复制操作能够尽快完成。

timg.jpeg


存储器层次结构的主要思想是上一层的存储器作为低一层存储器的高速缓存。

L1、L2、L3这些更小更快的存储设备,称为高速缓存存储器(cache memory,简称cache或高速缓存)。高速缓存是用一种叫做静态随机访问存储器(SRAM)的硬件技术实现的。

高速缓存之所以能加快访问速度,从而提高系统整体的效率,是利用了高速缓存的局部性原理:即程序具有访问局部区域里的数据和代码的趋势。通过让高速缓存里存放可能经常访问的数据,大部分的内存操作都能在高速缓存中快速完成。

操作系统管理硬件

应用程序并不能直接跟硬件“打交道”,而是要通过“操作系统”这一“中间商”提供的服务来和硬件进行交流。

操作系统有2个基本功能:
  1. 防止硬件被失控的应用程序滥用;
  2. 向应用程序提供简单一致的机制来控制复杂而又通常大不相同的低级硬件设备。

 
操作系统通过以下三个抽象概念来实现上述2个功能:
  • 文件:是对I/O设备的抽象表示;
  • 虚拟内存:是对主存和磁盘I/O设备的抽象表示;
  • 进程:是对处理器、主存和磁盘I/O设备的抽象表示。Posix标准是IEEE组织为了组织Unix越来越混乱,越来越不标准化而制定的标准化Unix开发提出来的一套标准规范。

 
 
【爱英语Show】
 
开电脑,关电脑不能用Open the computer 和 Close the computer!这两个表达方式的意思是 拆开/关上的意思。
正确的表达方式应该是 Turn on the computer 和 Turn off the computer!
另外,打开/关闭电脑中的文件,可以说 Open the file 或者 Close the file。
 

【高次方程】
 
今天中午在读紫金陈的《无证之罪》时,严良给赵铁民分析案情时提到,这个案子不能用常规的方法。普通的案子,可以根据线索通过“推论”算出来“嫌疑人”,就像是方程,可以通过公式计算出结果。但是这个案子,要用对应“高次方程”的求解方法来分析:那就是先确定结果,再代入方程中进行论证。也就是先确定嫌疑人,然后代入案情中推论。
 
严良提到的“高次方程”其实指的是次数大于等于5的方程,这类方程有一个特点: 高于5次(大于等于5)的方程没有精确代数解(这个定理已经被证明)。 这种方程只能通过数值计算的方法,用数值逼近求近似解
 
埃瓦里斯特·伽罗瓦,用群论系统化地阐述了五次及五次以上方程不能用公式求解
 
【今日放学别走】

今天的放学别走部门同学一起Review了近期完成的筛选项后台管理系统的项目代码,项目使用了Gin框架,其中有一个配置文件格式是以前没有接触过的,是Toml。之前一直接触过的有yaml、json、ini之类的,这个今天还是第一次听,大概了解了下一个配置文件的相关内容。

GitHub觉得 YAML 不够简洁优雅,因此捣鼓出了一个TOML。TOML的全称是 Tom’s Obvious, Minimal Language,因为它的作者是 GitHub联合创始人Tom Preston-Werner 。

TOML 的目标是成为一个极简的配置文件格式。TOML 被设计成可以无歧义地被映射为哈希表,从而被多种语言解析。

是大小写敏感的。

可能是目前最好的配置文件格式。详细可以点击了解:
https://blog.csdn.net/john_f_lau/article/details/55803069
 
【Go语言核心36讲:第9节 字典的操作和约束】

Go 语言的字典类型(Map)其实是一个哈希表(hash table)的特定实现,在这个实现中,键和元素的最大不同在于,键的类型是受限的,而元素却可以是任意类型的。之所以会受限,是因为哈希表中的一个重要过程:映射。

映射过程的第一步就是:把键值转换为哈希值。

Go 语言规范规定,键类型的值必须要支持判等操作,所以字典的键类型不可以是函数类型、字典类型和切片类型。

应该优先考虑哪些类型作为字典的键类型?求哈希和判等操作的速度越快,对应的类型就越适合作为键类型。优先选用数值类型和指针类型,通常情况下类型的宽度越小越好。如果非要选择字符串类型的话,最好对键值的长度进行额外的约束。

除了添加键 - 元素对,我们在一个值为nil的字典上做任何操作都不会引起错误。当我们试图在一个值为nil的字典中添加键 - 元素对的时候,Go 语言的运行时系统就会立即抛出一个 panic。

永远要注意那些可能引发 panic 的操作,比如像一个值为nil的字典添加键 - 元素对。

什么是变量的深拷贝和浅拷贝?

专业名词zkbhj 发表了文章 • 0 个评论 • 376 次浏览 • 2020-07-24 10:41 • 来自相关话题

通用深拷贝和浅拷贝的区别
 

深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。

 假设B复制了A,修改A的时候,看B是否发生变化:

如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)

 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
 
Go语言中的深浅拷贝
 
1、深拷贝(Deep Copy):

拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。

值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。

2、浅拷贝(Shallow Copy):

拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。

引用类型的数据,默认全部都是浅复制,Slice,Map。
 
深拷贝示例:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {
fmt.Println("深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

fmt.Println("修改Robot1的Name属性值")
robot1.Name = "小白-X型-V1.1"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

}深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。
Robot 1:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
修改Robot1的Name属性值
Robot 1:{小白-X型-V1.1 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360浅拷贝示例2:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 使用new方式")
//new方式返回的是一个指针,是引用类型,所以会触发浅拷贝
robot1 := new(Robot)
robot1.Name = "小白-X型-V1.0"
robot1.Color = "白色"
robot1.Model = "小型"

robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小蓝-X型-V1.2"
robot1.Color = "蓝色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
}浅拷贝 使用new方式
Robot 1:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
Robot 2:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
在这里面修改Robot1的Name和Color属性
Robot 1:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
Robot 2:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330另外一种浅拷贝方式:&取指针赋值
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 内容和内存地址一样,改变其中一个对象的值时,另一个同时变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := &robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小黑-X型-V1.1"
robot1.Color = "黑色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

}
参考文档:
https://www.cnblogs.com/mikeCao/p/8710837.html
https://www.cnblogs.com/guichenglin/p/12736203.html
  查看全部
通用深拷贝和浅拷贝的区别
 


深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用


 假设B复制了A,修改A的时候,看B是否发生变化:


如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)


 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
 
Go语言中的深浅拷贝
 
1、深拷贝(Deep Copy):

拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。

值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。

2、浅拷贝(Shallow Copy):

拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。

引用类型的数据,默认全部都是浅复制,Slice,Map。
 
深拷贝示例:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {
fmt.Println("深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

fmt.Println("修改Robot1的Name属性值")
robot1.Name = "小白-X型-V1.1"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

}
深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。
Robot 1:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
修改Robot1的Name属性值
Robot 1:{小白-X型-V1.1 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
浅拷贝示例2:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 使用new方式")
//new方式返回的是一个指针,是引用类型,所以会触发浅拷贝
robot1 := new(Robot)
robot1.Name = "小白-X型-V1.0"
robot1.Color = "白色"
robot1.Model = "小型"

robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小蓝-X型-V1.2"
robot1.Color = "蓝色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
}
浅拷贝 使用new方式
Robot 1:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
Robot 2:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
在这里面修改Robot1的Name和Color属性
Robot 1:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
Robot 2:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
另外一种浅拷贝方式:&取指针赋值
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 内容和内存地址一样,改变其中一个对象的值时,另一个同时变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := &robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小黑-X型-V1.1"
robot1.Color = "黑色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

}

参考文档:
https://www.cnblogs.com/mikeCao/p/8710837.html
https://www.cnblogs.com/guichenglin/p/12736203.html
 

使用了Go mod管理的项目里如何添加新的第三方包?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 1173 次浏览 • 2020-09-17 11:32 • 来自相关话题

如何用守护进程启动Beego应用?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 770 次浏览 • 2020-07-09 20:04 • 来自相关话题

#2020学习打卡##Go语言高级编程# 什么是第一类对象?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 634 次浏览 • 2020-06-09 17:23 • 来自相关话题

#2020学习打卡##Go语言高级编程# Go语言中的Nil到底是什么?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 591 次浏览 • 2020-06-08 17:26 • 来自相关话题

#2020学习打卡##Go语言高级编程# 什么是鸭子对象模型?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 576 次浏览 • 2020-06-08 16:01 • 来自相关话题

#2020学习打卡##Go语言高级编程# 数组初始化时,索引初始化怎么理解?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 605 次浏览 • 2020-06-07 15:34 • 来自相关话题

如何在go项目中使用go mod?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 824 次浏览 • 2020-05-22 17:09 • 来自相关话题

GO语言如何格式化时间?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 1273 次浏览 • 2019-04-15 09:55 • 来自相关话题

GO语言如何实现JSON和结构体之间的相互转换?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 1367 次浏览 • 2019-04-15 09:52 • 来自相关话题

#英文技术文章翻译# Nitro框架(原Go Micro)介绍文档

GoLangzkbhj 发表了文章 • 0 个评论 • 245 次浏览 • 2020-11-26 11:37 • 来自相关话题

原文地址:https://github.com/asim/nitro 






Nitro (formerly known as Go Micro) is a future framework for distributed app development, IoT, edge and p2p.
Nitro(原来以Go Micro为大家所熟知)是一个面向未来的适用于分布式应用、IoT(物联网)边缘计算和P2P的框架。
 
Overview 综述
 
Nitro will provide the core requirements for distributed app development, IoT, edge and p2p including RPC and Event driven communication. The Nitro mantra is in-memory defaults with a pluggable architecture. Blaze with pure in-memory development and swap out as needed to go multi-process or multi-host.
Nitro将会为分布式应用、IoT(物联网)边缘计算和P2P的应用开发提供核心所需要的功能包括RPC和事件驱动通信。Nitro是一个默认内存式的可插拔的架构。纯内存式编程并且可以随时根据需要切换到go 的多进程或多线程模式上。

Note: Nitro is currently undergoing a complete rewrite and is considered unstable for use.
提示:Nitro目前正在经历全面的重写并且在真正使用到正式项目中时需要深思熟虑它的安全性问题。
 
Features 特性
 
Now focusing on dapps, IoT, edge and p2p. Features include:
现在(框架)只专注于分布式应用、物联网、边缘计算和P2P。特性包括:
Lightweight RPC based communicationsEvent broadcasting and notificationsCRDT Data synchronisation and storageConsensus protocol and execution engineWebAssembly target compilation supportUnique randomized token generation aka BLSP2P gossip networking defined in user space
 
基于轻量级的RPC通信事件驱动和消息通知CRDT数据同步和存储(CRDT是Conflict-free Replicated Data Type的简称,也称为a passive synchronisation,即免冲突的可复制的数据类型)一致的协议和执行引擎实验性的编译器支持唯一token生成器BLS用户空间自定义P2P网络
 
Future 未来
 
In the future there's the potential to launch a live network based on Nitro. More on that soon.
在未来开启一个基于Nitro的在线网络服务是可能的。很快就会实现了。

FAQ QA环节

What happened to Go Micro?
Go Micro发生了什么?

Go Micro has now been renamed to Nitro. Go Micro moved back to being a personal project and no longer lives under the organisation github.com/micro. The company is now doubling down on Micro itself and has pulled in the needed interfaces to consolidate a Server, Framework and CLI into one tool. Go Micro is now no longer maintained by a company. Yet it continued to create confusion even as a personal repo. So for that reason, we're renaming to Nitro. Go Micro V2 has been archived at microhq/go-micro and the plugins at microhq/plugins.
Go Micro现在已经被重新命名为Nitro。Go Micro项目已经变成了一个个人项目并且不再是一个团队(github.com/micro.)负责的项目。公司正在加倍使用Micro并且引入了必要的接口使之成为一个服务、框架和CLI的工具(上云提供商业服务,见 m3o.com)。Go Micro现在已经不再被任何公司维护。但是使用Go Micro作为名字的话依然会产生困惑,即使作为一个个人回收的项目。基于上面的原因,我们考虑把它重新命名为Nitro。Go Micro V2版本已经被归档在 microhq/go-micro ,插件归档在microhq/plugins。What's the new direction of Nitro?Nitro新的方向是什么?

Nitro will now focus on distributed app development using the Go standard library. It will continue to define abstractions for distributed systems but will only do so without external dependencies. In this manner the hope is Nitro can be picked up with minimal overhead for all sorts of new applications that have a low memory or low resource footprint. The assumption is there are places which would like to use distributed apps just as embedded systems or web assembly, unikernels, and related targets that would benefit from a framework that defined these as primitives for such use.
Nitro从现在开始将聚焦在用Go标准库来开发分布式应用。框架会持续为分布式系统定义抽象但是仅限于在没有外部依赖的情况下。通过这种方式,我们希望Nitro可以以最小的开销为那些内存或资源占有率低的程序使用。假设有些场景下更喜欢用分布式应用,像是嵌入式系统或者实验性系统,unikernels,和其他目标系统,他们将会从这个被定义为专为这些应用而生的基础框架中受益。
 
How do Nitro and Micro now differ?
现在Nitro和Micro有什么不同?

Micro is a platform for cloud native development. A complete experience that includes a server, framework and multi-language clients. Beyond that it also include environments, multi-tenancy and many more features which push it towards being a hosted Micro as a Service offering. It is a complete platform.
Micro是一个为云原生应用开发的平台框架。是一个完整的经验包括服务端,框架和多种语言客户端。除了上面提到的它也包括环境,多客端和未来更多的特性,这些特性把它推向成为一个提供服务的Micro托管产品。它是一个完整的平台框架。

Nitro is more of a embeddable framework for distributed app development and now once again a purely personal project maintained by me and perhaps others who still find use for it commercially or noncommercially. It's of sentimental value and something I'd like to carry on for personal projects such as things related to edge, IoT, embedded systems, p2p, web assembly, etc.
Nitro是一个为分布式应用提供的可嵌入式的框架,并且再一次成为一个完全的个人项目被我维护,也许会有人会发现把它并把它用于商业用途或非商业用途。这个框架充满了感情色彩以及我可能希望和喜欢的一些东西带入到这个个人项目中,比如像是一些跟边缘计算、物联网、嵌入式系统、P2P和实验性的东西等等。
 
I used Go Micro to build microservices. What should I do now?
我曾经用Go Micro来构建微服务应用。我现在应该做些什么?

You should quite honestly go look at Micro and then consider the hosted offering at m3o.com which starts as a purely free dev environment in the cloud. Micro continues to address many of the concerns and requirements you had if not more. It is likely you managed metrics, tracing, logging and much other boilerplate that needed to be plugged in. Micro will now take this complete platform story approach and help you in that journey e.gyou're probably running managed kubernetes on a major cloud provider with many other things. We're doing that for you instead as a company and platform team.
你应该非常诚恳的看一看 Micro这个项目然后考虑m3o.com提供的托管产品,这个托管产品开始会在云上提供一个完全免费的开发环境。Micro将会继续设法解决你有的问题和需求如果不太多的话。就像你管理的应用性能,链路追踪,日志和许多其他的组件包被引入的。Micro会采用这种完整的平台托管方式来帮助你解决迁移过程中你可能遇到的问题,比如管理在一个核心的云服务提供上运行的项目或者其他一些事情。我们代替公司或者平台团队为你做这些事情。
 
I want to use Go Micro version 2.0 for my company. Can I still do that?
我想使用Go Micro 2.0版本为我们公司的项目。我可以一直这么做吗?

Yes. Go Micro 2.0 is still Apache 2.0 licensed which means you can still freely use it for everything you were using before. If you're a new user you can do the same. These things are using go modules so you're import path is simply github.com/micro/go-micro/v2 as it was before. Because GitHub handles redirects this should not break. Please continue to use it if you like, but my own support for 2.0 is now end of life.
当然。Go Micro 2.0 将一直是一个Apache 2.0 授权的,这意味着你可以一直免费使用它来做任何事情就像你以前用它一样。如果你是一个新用户,你也可以这么做。可以用go modules来做这些事,所以你可以通过import 这个路径  github.com/micro/go-micro/v2 就像以前一样。因为GitHub上面的链接不会被失效掉。如果你喜欢,请继续使用它。但是我从现在开始不会再继续支持和维护这个2.0版本了。
 
Why has the license changed from Apache 2.0 to Polyform Noncommercial?
为什么授权许可从Apache 2.0变成了Polyform Noncommercia?

Go Micro was largely a solo maintained effort for the entirety of its lifetime. It has enabled the creation of a company called Micro Services, Inc. which now focuses on Micro as a Service and has consolidated any interfaces here into a service library in that project. For the most part, Go Micro was underfunded and in some ways under appreciated. In version 3.0, going back to something of a personal project of more than 6 years I have made the hard decision to relicense as a noncommercial project.
Go Micro 很大程度上在它的整个生命周期中都是一个单独维护的项目。它使一家名为Micro Services的公司成立成为可能,这家公司目前主要聚焦在Micro作为一个服务来提供,然后把所有的接口都整合成一个服务库。在很大程度上,Go Micro项目资金不足且在一定程度上没有得到重视。在3.0版本中,追忆这个超过6年的个人项目的一些事情的情况下我做了一个艰难的决定就是重新作为一个非商业项目获得授权。
 
  查看全部
原文地址:https://github.com/asim/nitro 

73709577.png


Nitro (formerly known as Go Micro) is a future framework for distributed app development, IoT, edge and p2p.
Nitro(原来以Go Micro为大家所熟知)是一个面向未来的适用于分布式应用、IoT(物联网)边缘计算和P2P的框架。
 
Overview 综述
 
Nitro will provide the core requirements for distributed app development, IoT, edge and p2p including RPC and Event driven communication. The Nitro mantra is in-memory defaults with a pluggable architecture. Blaze with pure in-memory development and swap out as needed to go multi-process or multi-host.
Nitro将会为分布式应用、IoT(物联网)边缘计算和P2P的应用开发提供核心所需要的功能包括RPC和事件驱动通信。Nitro是一个默认内存式的可插拔的架构。纯内存式编程并且可以随时根据需要切换到go 的多进程或多线程模式上。

Note: Nitro is currently undergoing a complete rewrite and is considered unstable for use.
提示:Nitro目前正在经历全面的重写并且在真正使用到正式项目中时需要深思熟虑它的安全性问题。
 
Features 特性
 
Now focusing on dapps, IoT, edge and p2p. Features include:
现在(框架)只专注于分布式应用、物联网、边缘计算和P2P。特性包括:
  • Lightweight RPC based communications
  • Event broadcasting and notifications
  • CRDT Data synchronisation and storage
  • Consensus protocol and execution engine
  • WebAssembly target compilation support
  • Unique randomized token generation aka BLS
  • P2P gossip networking defined in user space

 
  • 基于轻量级的RPC通信
  • 事件驱动和消息通知
  • CRDT数据同步和存储(CRDT是Conflict-free Replicated Data Type的简称,也称为a passive synchronisation,即免冲突的可复制的数据类型)
  • 一致的协议和执行引擎
  • 实验性的编译器支持
  • 唯一token生成器BLS
  • 用户空间自定义P2P网络

 
Future 未来
 
In the future there's the potential to launch a live network based on Nitro. More on that soon.
在未来开启一个基于Nitro的在线网络服务是可能的。很快就会实现了。

FAQ QA环节

What happened to Go Micro?
Go Micro发生了什么?

Go Micro has now been renamed to Nitro. Go Micro moved back to being a personal project and no longer lives under the organisation github.com/micro. The company is now doubling down on Micro itself and has pulled in the needed interfaces to consolidate a Server, Framework and CLI into one tool. Go Micro is now no longer maintained by a company. Yet it continued to create confusion even as a personal repo. So for that reason, we're renaming to Nitro. Go Micro V2 has been archived at microhq/go-micro and the plugins at microhq/plugins.
Go Micro现在已经被重新命名为Nitro。Go Micro项目已经变成了一个个人项目并且不再是一个团队(github.com/micro.)负责的项目。公司正在加倍使用Micro并且引入了必要的接口使之成为一个服务、框架和CLI的工具(上云提供商业服务,见 m3o.com)。Go Micro现在已经不再被任何公司维护。但是使用Go Micro作为名字的话依然会产生困惑,即使作为一个个人回收的项目。基于上面的原因,我们考虑把它重新命名为Nitro。Go Micro V2版本已经被归档在 microhq/go-micro ,插件归档在microhq/plugins。What's the new direction of Nitro?Nitro新的方向是什么?

Nitro will now focus on distributed app development using the Go standard library. It will continue to define abstractions for distributed systems but will only do so without external dependencies. In this manner the hope is Nitro can be picked up with minimal overhead for all sorts of new applications that have a low memory or low resource footprint. The assumption is there are places which would like to use distributed apps just as embedded systems or web assembly, unikernels, and related targets that would benefit from a framework that defined these as primitives for such use.
Nitro从现在开始将聚焦在用Go标准库来开发分布式应用。框架会持续为分布式系统定义抽象但是仅限于在没有外部依赖的情况下。通过这种方式,我们希望Nitro可以以最小的开销为那些内存或资源占有率低的程序使用。假设有些场景下更喜欢用分布式应用,像是嵌入式系统或者实验性系统,unikernels,和其他目标系统,他们将会从这个被定义为专为这些应用而生的基础框架中受益。
 
How do Nitro and Micro now differ?
现在Nitro和Micro有什么不同?

Micro is a platform for cloud native development. A complete experience that includes a server, framework and multi-language clients. Beyond that it also include environments, multi-tenancy and many more features which push it towards being a hosted Micro as a Service offering. It is a complete platform.
Micro是一个为云原生应用开发的平台框架。是一个完整的经验包括服务端,框架和多种语言客户端。除了上面提到的它也包括环境,多客端和未来更多的特性,这些特性把它推向成为一个提供服务的Micro托管产品。它是一个完整的平台框架。

Nitro is more of a embeddable framework for distributed app development and now once again a purely personal project maintained by me and perhaps others who still find use for it commercially or noncommercially. It's of sentimental value and something I'd like to carry on for personal projects such as things related to edge, IoT, embedded systems, p2p, web assembly, etc.
Nitro是一个为分布式应用提供的可嵌入式的框架,并且再一次成为一个完全的个人项目被我维护,也许会有人会发现把它并把它用于商业用途或非商业用途。这个框架充满了感情色彩以及我可能希望和喜欢的一些东西带入到这个个人项目中,比如像是一些跟边缘计算、物联网、嵌入式系统、P2P和实验性的东西等等。
 
I used Go Micro to build microservices. What should I do now?
我曾经用Go Micro来构建微服务应用。我现在应该做些什么?

You should quite honestly go look at Micro and then consider the hosted offering at m3o.com which starts as a purely free dev environment in the cloud. Micro continues to address many of the concerns and requirements you had if not more. It is likely you managed metrics, tracing, logging and much other boilerplate that needed to be plugged in. Micro will now take this complete platform story approach and help you in that journey e.gyou're probably running managed kubernetes on a major cloud provider with many other things. We're doing that for you instead as a company and platform team.
你应该非常诚恳的看一看 Micro这个项目然后考虑m3o.com提供的托管产品,这个托管产品开始会在云上提供一个完全免费的开发环境。Micro将会继续设法解决你有的问题和需求如果不太多的话。就像你管理的应用性能,链路追踪,日志和许多其他的组件包被引入的。Micro会采用这种完整的平台托管方式来帮助你解决迁移过程中你可能遇到的问题,比如管理在一个核心的云服务提供上运行的项目或者其他一些事情。我们代替公司或者平台团队为你做这些事情。
 
I want to use Go Micro version 2.0 for my company. Can I still do that?
我想使用Go Micro 2.0版本为我们公司的项目。我可以一直这么做吗?

Yes. Go Micro 2.0 is still Apache 2.0 licensed which means you can still freely use it for everything you were using before. If you're a new user you can do the same. These things are using go modules so you're import path is simply github.com/micro/go-micro/v2 as it was before. Because GitHub handles redirects this should not break. Please continue to use it if you like, but my own support for 2.0 is now end of life.
当然。Go Micro 2.0 将一直是一个Apache 2.0 授权的,这意味着你可以一直免费使用它来做任何事情就像你以前用它一样。如果你是一个新用户,你也可以这么做。可以用go modules来做这些事,所以你可以通过import 这个路径  github.com/micro/go-micro/v2 就像以前一样。因为GitHub上面的链接不会被失效掉。如果你喜欢,请继续使用它。但是我从现在开始不会再继续支持和维护这个2.0版本了。
 
Why has the license changed from Apache 2.0 to Polyform Noncommercial?
为什么授权许可从Apache 2.0变成了Polyform Noncommercia?

Go Micro was largely a solo maintained effort for the entirety of its lifetime. It has enabled the creation of a company called Micro Services, Inc. which now focuses on Micro as a Service and has consolidated any interfaces here into a service library in that project. For the most part, Go Micro was underfunded and in some ways under appreciated. In version 3.0, going back to something of a personal project of more than 6 years I have made the hard decision to relicense as a noncommercial project.
Go Micro 很大程度上在它的整个生命周期中都是一个单独维护的项目。它使一家名为Micro Services的公司成立成为可能,这家公司目前主要聚焦在Micro作为一个服务来提供,然后把所有的接口都整合成一个服务库。在很大程度上,Go Micro项目资金不足且在一定程度上没有得到重视。在3.0版本中,追忆这个超过6年的个人项目的一些事情的情况下我做了一个艰难的决定就是重新作为一个非商业项目获得授权。
 
 

LeetCode刷题Day2:两数相加

LeetCodezkbhj 发表了文章 • 0 个评论 • 272 次浏览 • 2020-09-22 20:57 • 来自相关话题

两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。 

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) 
输出:7 -> 0 -> 8
原因:342 + 465 = 807LeetCode

 首先发一下我的思路已经给出的最初答案(大部分情况是正确的,但是超出数字范围就会和预期结果不一致),总之先把分析思路分享出来。


思路一:常规思路

分别循环两个链表,因为高位是个位,所以不断乘以10的n次方,n代表循环次数,从0开始,直到循环结束,就得到了两个无符号整数。所以,这就要求对输入数字有范围限制的要求,否则数字太大,超出整数的最大范围,得到的结果会因为发生溢出而不符合预期。

然后将两个整数进行加法运算,得到最终结果对应的int数字。

然后再将这个整数转化成字符串,循环字符串,得到字符对应的ASCII码点,然后减去48即可“间接“得到对应的数字。并按序生成链表。
 
func myPow(a,n int) int {
result := int(1)
for i:= n; i >0 ; i >>= 1 {
if i&1 != 0 {
result *=a
}
a *=a
}
return result
}

func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
var a,b,c,i,j int
p1 := l1
for p1!= nil {
a = a + p1.Val * myPow(10,i)
i++
p1 = p1.Next
}
p2 := l2
for p2!= nil {
b = b + p2.Val * myPow(10,j)
j++
p2 = p2.Next
}
c = a + b

str := strconv.Itoa(c)
headNode := new(ListNode)
current := headNode
current.Next = nil
for _,one := range str {
preNode := current
current.Val = int(one-48)
current = new(ListNode)
current.Next = preNode

}

return current.Next
}
思路一:高级思路(学习)
对应位置的数字相加,跟10取模,即可得到该位置上十进制数相加之后的数字,然后和10进行整除,如果两个数相加小于10,则整除之后是0,否则是1,即要进位的1,所以直到两个链表都循环完,或者最后没有进位时,停止循环,最终返回。
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
headList := new(ListNode)
head := headList
num := 0

for (l1 != nil || l2 != nil || num > 0) {
headList.Next = new(ListNode)
headList = headList.Next
if l1 != nil {
num = num + l1.Val
l1 = l1.Next
}
if l2 != nil {
num = num + l2.Val
l2 = l2.Next
}
headList.Val = (num) % 10
num = num / 10
}

return head.Next
}
  查看全部


两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。 

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) 
输出:7 -> 0 -> 8
原因:342 + 465 = 807LeetCode


 首先发一下我的思路已经给出的最初答案(大部分情况是正确的,但是超出数字范围就会和预期结果不一致),总之先把分析思路分享出来。


思路一:常规思路

分别循环两个链表,因为高位是个位,所以不断乘以10的n次方,n代表循环次数,从0开始,直到循环结束,就得到了两个无符号整数。所以,这就要求对输入数字有范围限制的要求,否则数字太大,超出整数的最大范围,得到的结果会因为发生溢出而不符合预期。

然后将两个整数进行加法运算,得到最终结果对应的int数字。

然后再将这个整数转化成字符串,循环字符串,得到字符对应的ASCII码点,然后减去48即可“间接“得到对应的数字。并按序生成链表。
 
func myPow(a,n int) int {
result := int(1)
for i:= n; i >0 ; i >>= 1 {
if i&1 != 0 {
result *=a
}
a *=a
}
return result
}

func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
var a,b,c,i,j int
p1 := l1
for p1!= nil {
a = a + p1.Val * myPow(10,i)
i++
p1 = p1.Next
}
p2 := l2
for p2!= nil {
b = b + p2.Val * myPow(10,j)
j++
p2 = p2.Next
}
c = a + b

str := strconv.Itoa(c)
headNode := new(ListNode)
current := headNode
current.Next = nil
for _,one := range str {
preNode := current
current.Val = int(one-48)
current = new(ListNode)
current.Next = preNode

}

return current.Next
}

思路一:高级思路(学习)
对应位置的数字相加,跟10取模,即可得到该位置上十进制数相加之后的数字,然后和10进行整除,如果两个数相加小于10,则整除之后是0,否则是1,即要进位的1,所以直到两个链表都循环完,或者最后没有进位时,停止循环,最终返回。

func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
headList := new(ListNode)
head := headList
num := 0

for (l1 != nil || l2 != nil || num > 0) {
headList.Next = new(ListNode)
headList = headList.Next
if l1 != nil {
num = num + l1.Val
l1 = l1.Next
}
if l2 != nil {
num = num + l2.Val
l2 = l2.Next
}
headList.Val = (num) % 10
num = num / 10
}

return head.Next
}

 

LeetCode刷题Day1:两数之和

LeetCodezkbhj 发表了文章 • 0 个评论 • 277 次浏览 • 2020-09-22 20:55 • 来自相关话题

从今天开始,把LeetCode刷题作为每天日常计划的一项。尽早做准备,同时也每天对算法和相关的技能知识通过刷题的形式进行潜移默化的提升和加深理解。其实身边已经有很多程序猿朋友都在LeetCode刷题了,再不参与进来,都要出圈儿了呀。

今天是Day1,从第一道题目开始:
 

两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 

示例: 

给定 nums = [2, 7, 11, 15], target = 9 

因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]LeetCode
 

 
思路一:暴力遍历

也是最简单最原始的未经优化的方案思路。

首先明确题目要求,可以假设每种输入只会对应一个答案,即一定会有且只有一对符合要求的数据。

所以得到数组的长度,然后从第一个开始进行循环,将目标值target减去当前被循环的值,得到与之可以配对的值,然后在数组中进行寻找,如果找到,就返回2个符合要求的值的索引,否则继续下一次寻找,直到找到。

最外层循环的条件中 x&y异或之后如果为1,说明已经找到对应的结果,就不再循环。时间复杂度是O(n²),空间复杂度是O(1)。
func twoSum(nums []int, target int) []int {
len := len(nums)
var x,y int

for i:=0; i < len && x|y == 0; i++ {
findNum := target - nums[i]
for j:=0; j < len; j++ {
if findNum == nums[j] {
x = i;
y = j;
}
}

}

return []int{x,y}
}
思路二:进一步提升效率,可以利用哈希表
从第一个思路中,可以看到其实在判断一个值在一个“表”中是否存在的时候,我们采用了遍历的方式,这样的话时间复杂度就是O(n),但是大家肯定都知道,哈希表是最擅长做这个事情的。它可以把时间复杂度降到O(1)。所以,这样思路就很清晰了,我们需要先把传过来的数组生成对应的哈希结构,也就是Go里面的map字典,这样我们就可以快速的找到某值在还是不在了。我们把数组的value作为map的key,index作为map的value。代码实现如下。
 
func twoSum(nums []int, target int) []int {
var hashMap map[int]int
len := len(nums)
var x,y,found int

//初始化字典
hashMap = make(map[int]int)
for i:=0; i < len; i++ {
hashMap[nums[i]] = i
}

for j:=0; j < len && found == 0; j++ {
findNum := target - nums[j]
v, ok := hashMap[findNum]
if ok && v != j {
found = 1
x = j
y = v
}

}

return []int{x,y}
}
今天这道题的难度级别是简单,但是带我回顾了Go语言的数组、map的相关知识和用法,以及哈希表这一知识点。 查看全部
从今天开始,把LeetCode刷题作为每天日常计划的一项。尽早做准备,同时也每天对算法和相关的技能知识通过刷题的形式进行潜移默化的提升和加深理解。其实身边已经有很多程序猿朋友都在LeetCode刷题了,再不参与进来,都要出圈儿了呀。

今天是Day1,从第一道题目开始:
 


两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 

示例: 

给定 nums = [2, 7, 11, 15], target = 9 

因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]LeetCode
 


 
思路一:暴力遍历

也是最简单最原始的未经优化的方案思路。

首先明确题目要求,可以假设每种输入只会对应一个答案,即一定会有且只有一对符合要求的数据。

所以得到数组的长度,然后从第一个开始进行循环,将目标值target减去当前被循环的值,得到与之可以配对的值,然后在数组中进行寻找,如果找到,就返回2个符合要求的值的索引,否则继续下一次寻找,直到找到。

最外层循环的条件中 x&y异或之后如果为1,说明已经找到对应的结果,就不再循环。时间复杂度是O(n²),空间复杂度是O(1)。

func twoSum(nums []int, target int) []int {
len := len(nums)
var x,y int

for i:=0; i < len && x|y == 0; i++ {
findNum := target - nums[i]
for j:=0; j < len; j++ {
if findNum == nums[j] {
x = i;
y = j;
}
}

}

return []int{x,y}
}

思路二:进一步提升效率,可以利用哈希表
从第一个思路中,可以看到其实在判断一个值在一个“表”中是否存在的时候,我们采用了遍历的方式,这样的话时间复杂度就是O(n),但是大家肯定都知道,哈希表是最擅长做这个事情的。它可以把时间复杂度降到O(1)。所以,这样思路就很清晰了,我们需要先把传过来的数组生成对应的哈希结构,也就是Go里面的map字典,这样我们就可以快速的找到某值在还是不在了。我们把数组的value作为map的key,index作为map的value。代码实现如下。
 

func twoSum(nums []int, target int) []int {
var hashMap map[int]int
len := len(nums)
var x,y,found int

//初始化字典
hashMap = make(map[int]int)
for i:=0; i < len; i++ {
hashMap[nums[i]] = i
}

for j:=0; j < len && found == 0; j++ {
findNum := target - nums[j]
v, ok := hashMap[findNum]
if ok && v != j {
found = 1
x = j
y = v
}

}

return []int{x,y}
}

今天这道题的难度级别是简单,但是带我回顾了Go语言的数组、map的相关知识和用法,以及哈希表这一知识点。

如何快速提升Go程序性能?

GoLangzkbhj 发表了文章 • 0 个评论 • 263 次浏览 • 2020-09-15 18:39 • 来自相关话题

这篇文章中列出了一些不需要太多精力就能显著提高性能的技巧,并不包含那些需要太多精力或需要大幅度修改程序结构的技巧。
 
开始优化之前
 
开始优化之前,首先应该花些时间找出一个合适的基准线,以便稍后比较。如果没有基准,那就等于摸着石头过河,根本不知道自己的优化有没有效果。首先要编写性能测试程序,然后生成能用于pprof的profile文件。最好可以编写Go的性能测试脚本(https://dave.cheney.net/2013/0 ... in-go),这样可以很容易地使用pprof,还可以评测内存分配情况。还可以使用benchcmp,这个工具可以帮助比较两次性能测试之间的性能差异。

如果代码很难做性能测试,那就从你能测量时间的部分开始。可以利用runtime/pprof手工测量代码。

现在开始吧!
 
使用sync.Pool重用之前分配过的对象
 
sync.Pool实现了一个空闲列表(free-list)。这样可以重新使用之前分配过的对象。这样做可以将对象分配的代价平摊到多次使用上,减少垃圾回收器的工作。API非常简单:只需实现一个函数,用来分配新的对象即可。它会返回指针类型。
 
var bufpool = sync.Pool{
New: func() interface{} {
buf := make([]byte, 512)
return &buf
}}之后,可以用Get()从池中获取对象,用完之后用Put()将对象放回。

不过要注意一些陷阱。在Go 1.13之前,每次发生垃圾回收时该池都会被清空。对于需要分配大量对象的程序来说,这可能会造成性能的影响。在1.13版本中似乎GC后能保留更多对象了(https://go-review.googlesource.com/c/go/+/162919/)。

你可能需要在将对象放回池中之前将其结构的字段清空。

如果不这样做,就可能从池中获得一个“脏”的对象,它包含之前使用过的数据。这可能会造成严重的安全问题!
 
安全的做法就是明确清空内存:// reset resets all fields of the AuthenticationResponse before pooling it.
func (a* AuthenticationResponse) reset() {
a.Token = ""
a.UserID = ""
}

rsp := authPool.Get().(*AuthenticationResponse)
defer func() {
rsp.reset()
authPool.Put(rsp)
}()
在大map中避免使用包含指针的结构作为map的键
 
关于Go中大型堆的性能问题已经有很多人讨论过了。在垃圾回收过程中,运行时会扫描包含指针的对象并遍历其指针。如果你有非常大的map[string]int,那么垃圾回收器就不得不在每次垃圾回收过程中检查map中的每个字符串,因为字符串包含指针。

这个例子中我们向一个map[string]int中写入了一千万个元素,然后测量垃圾回收的时间。map是在包的作用域中分配的,以保证它被分配到堆上。
import (
"fmt"
"runtime"
"strconv"
"time"
)

const (
numElements = 10000000
)

var foo = map[string]int{}

func timeGC() {
t := time.Now()
runtime.GC()
fmt.Printf("gc took: %s\n", time.Since(t))
}

func main() {
for i := 0; i < numElements; i++ {
foo[strconv.Itoa(i)] = i
}

for {
timeGC()
time.Sleep(1 * time.Second)
}
}运行后可以得到以下结果:gc took: 98.726321ms
gc took: 105.524633ms
gc took: 102.829451ms
gc took: 102.71908ms
gc took: 103.084104ms
gc took: 104.821989ms对于计算机来说这花得时间太多了!

怎样可以改进呢?最好是能尽量去掉指针,这样能减少垃圾回收器需要遍历的指针数量。由于字符串包含指针,因此我们可以用map[int]int来实现:
 
import (
"fmt"
"runtime"
"time"
)

const (
numElements = 10000000
)

var foo = map[int]int{}

func timeGC() {
t := time.Now()
runtime.GC()
fmt.Printf("gc took: %s\n", time.Since(t))
}

func main() {
for i := 0; i < numElements; i++ {
foo[i] = i
}

for {
timeGC()
time.Sleep(1 * time.Second)
}
}重新运行程序,结果如下:
 
gc took: 3.608993ms
gc took: 3.926913ms
gc took: 3.955706ms
gc took: 4.063795ms
gc took: 3.91519ms
gc took: 3.75226ms好多了。垃圾回收的时间减少了97%。在生产环境下,字符串需要进行hash之后再插入到map中。
 
生成marshalling代码以避免运行时反射
 
将数据结构marshalh或unmarshal成JSON等各种序列化格式是个很常见的操作,特别是在构建微服务的时候。实际上,大部分微服务做的唯一工作就是序列化。像json.Marshal和json.Unmarshal需要依赖运行时反射才能将结构体的字段序列化成字节,反之亦然。这个操作很慢,反射的性能完全无法与显式的代码相比。

但我们不必这么做。marshalling JSON的原理大致如下:
package json

// Marshal take an object and returns its representation in JSON.
func Marshal(obj interface{}) ([]byte, error) {
// Check if this object knows how to marshal itself to JSON
// by satisfying the Marshaller interface.
if m, is := obj.(json.Marshaller); is {
return m.MarshalJSON()
}

// It doesn't know how to marshal itself. Do default reflection based marshallling.
return marshal(obj)
}如果我们知道怎样将对象marshal成JSON,就应该避免运行时反射。但我们不想手工marshal所有代码,怎么办呢?可以让计算机替我们写程序!像easyjson等代码生成器会检查结构体,然后生成高度优化且与json.Marshaller等接口完全兼容的代码。

下载这个包,然后在包含结构体的$file.go上运行下面的命令:
easyjson -all $file.go$file.go
这个命令会生成$file_easyjson.go。由于easyjson为我们实现了json.Marshaller接口,因此序列化时不会调用默认的反射,而是会使用生成的函数。祝贺你!你已经将JSON marshalling的代码的速度提高了三倍。还有许多其他技巧可以进一步提升性能。
 
使用strings.Builder来构建字符串
 
Go语言的字符串是不可修改的,可以认为它们是只读的字节切片。这就是说,每次创建字符串都要分配新的内存,可能还会给垃圾回收器造成更多工作。

Go 1.10引入了strings.Builder作为高效率构建字符串的方式。它内部会将字符串写入到字节缓冲区。只有在builder上调用String()时才会真正生成字符串。它依赖一些unsafe的技巧将底层的字节作为字符串返回,而不实际进行内存非配。这篇文章(https://syslog.ravelin.com/byt ... ca7ff)介绍了更多其工作原理。pkg: github.com/sjwhitworth/perfblog/strbuild
BenchmarkStringBuildNaive-8 5000000 255 ns/op 216 B/op 8 allocs/op
BenchmarkStringBuildBuilder-8 20000000 54.9 ns/op 64 B/op 1 allocs/op可见,strings.Builder要快4.7倍,它的内存分配次只有前者的1/8,内存使用只有前者的1/4。

所以,在性能重要的时候应该使用strings.Builder。一般来说,除非是非常不重要的情况,否则我建议永远使用strings.Builder来构建字符串。
 
使用strconv代替fmt

fmt是Go中最著名的包之一。估计你从第一个Go程序——输出“hello, world”——的时候就开始用它了。但是,如果需要将整数和浮点数转换成字符串,它就不如更底层的strconv有效率了。strconv只需要在API中进行很小改动,就能带来不错的性能提升。

大多数情况下fmt会接受一个interface{}作为参数。这样做有两个弊端:

失去了类型安全。对于我来说这是个很大的问题。

会增加内存分配次数。将非指针类型作为interface{}传递通常会导致堆分配的问题。进一步的内容可以阅读这篇文章(https://www.darkcoding.net/sof ... face/)。
BenchmarkStrconv-8 30000000 39.5 ns/op 32 B/op 1 allocs/op
BenchmarkFmt-8 10000000 143 ns/op 72 B/op 3 allocs/op可以看到,strconv版本要快3.5倍,内存分配次数是1/3,内存分配量是1/2。

在make中指定分配的容量来避免重新分配

在讨论性能改善之前,我们先来迅速看一下切片。切片是Go语言中一个非常有用的概念。它提供了可改变大小的数组,还可以用不同的方式表示同一片底层内存区域,而不需要重新进行内存分配。slice的内部结构由三个元素组成:

data:切片中指向底层数据的指针
len:切片中的当前元素数目
cap:在不重新分配内存的前提下,切片能够增长到的元素数目

我经常看到类似于下面的代码,尽管在切片容量可以预先得知的情况下依然生成一个容量为零的切片:var userIDs []string
for _, bar := range rsp.Users {
userIDs = append(userIDs, bar.ID)
}这段代码中,切片的初始长度和容量都为零。在收到响应后,我们将用户添加到切片。这样做就会达到切片的容量上限,从而导致底层分配两倍容量的新数组,然后将旧切片中的数据拷贝过来。如果有8个用户,就会造成5次内存分配。

更有效的方式是这样:
userIDs := make([]string, 0, len(rsp.Users)
for _, bar := range rsp.Users {
userIDs = append(userIDs, bar.ID)
}使用make显式声明切片的容量。接下来可以向切片添加元素,而不会触发内存重新分配和拷贝。
 
这条建议也适用于map:适用make(map[string]string, len(foo))可以分配足够的底层内存以避免内存重新分配。
 
使用可以接受字节切片的方法
 
在使用包时,寻找那些接受字节切片作为参数的方法,这些方法通常给你更多控制内存分配的自由。

一个很好的例子就是time.Format和time.AppendFormat。time.Format返回字符串。内部会分配一个新的字节切片,然后在其上调用time.AppendFormat。而time.AppendFormat接受一个字节缓冲区,将格式化后的时间写入缓冲区,然后返回扩展后的字节切片。标准库中这种做法非常常见,如strconv.AppendFloat或bytes.NewBuffer。

为什么这样能提高性能?因为你可以传递从sync.Poolh获得的字节切片,而不需要每次都分配新的缓冲区。或者可以初始化一个足够大的缓冲区,来减少切片拷贝。
 
这些建议只是某些具体情况下的建议,而不是真理。一定要自己测量性能。

要知道何时该停止优化。提高系统性能会让工程师感觉非常满足:问题本身很有趣,也有立竿见影的效果。但是,提高性能带来的效果非常依赖于具体情况。如果服务的响应时间只有10毫秒,而网络访问需要90毫秒,那么将10毫秒优化到5毫秒就完全不值得,因为你依然需要95毫秒。就算你将响应时间优化到1毫秒,最后结果还是91毫秒。你应该去做其他更有价值的事情。
 
原文:
https://stephen.sh/posts/quick-go-performance-improvements
译文:
https://blog.csdn.net/csdnnews/article/details/93987866
  查看全部
这篇文章中列出了一些不需要太多精力就能显著提高性能的技巧,并不包含那些需要太多精力或需要大幅度修改程序结构的技巧。
 
开始优化之前
 
开始优化之前,首先应该花些时间找出一个合适的基准线,以便稍后比较。如果没有基准,那就等于摸着石头过河,根本不知道自己的优化有没有效果。首先要编写性能测试程序,然后生成能用于pprof的profile文件。最好可以编写Go的性能测试脚本(https://dave.cheney.net/2013/0 ... in-go),这样可以很容易地使用pprof,还可以评测内存分配情况。还可以使用benchcmp,这个工具可以帮助比较两次性能测试之间的性能差异。

如果代码很难做性能测试,那就从你能测量时间的部分开始。可以利用runtime/pprof手工测量代码。

现在开始吧!
 
使用sync.Pool重用之前分配过的对象
 
sync.Pool实现了一个空闲列表(free-list)。这样可以重新使用之前分配过的对象。这样做可以将对象分配的代价平摊到多次使用上,减少垃圾回收器的工作。API非常简单:只需实现一个函数,用来分配新的对象即可。它会返回指针类型。
 
var bufpool = sync.Pool{
New: func() interface{} {
buf := make([]byte, 512)
return &buf
}}
之后,可以用Get()从池中获取对象,用完之后用Put()将对象放回。

不过要注意一些陷阱。在Go 1.13之前,每次发生垃圾回收时该池都会被清空。对于需要分配大量对象的程序来说,这可能会造成性能的影响。在1.13版本中似乎GC后能保留更多对象了(https://go-review.googlesource.com/c/go/+/162919/)。

你可能需要在将对象放回池中之前将其结构的字段清空。

如果不这样做,就可能从池中获得一个“脏”的对象,它包含之前使用过的数据。这可能会造成严重的安全问题!
 
安全的做法就是明确清空内存:
// reset resets all fields of the AuthenticationResponse before pooling it.
func (a* AuthenticationResponse) reset() {
a.Token = ""
a.UserID = ""
}

rsp := authPool.Get().(*AuthenticationResponse)
defer func() {
rsp.reset()
authPool.Put(rsp)
}()

在大map中避免使用包含指针的结构作为map的键
 
关于Go中大型堆的性能问题已经有很多人讨论过了。在垃圾回收过程中,运行时会扫描包含指针的对象并遍历其指针。如果你有非常大的map[string]int,那么垃圾回收器就不得不在每次垃圾回收过程中检查map中的每个字符串,因为字符串包含指针。

这个例子中我们向一个map[string]int中写入了一千万个元素,然后测量垃圾回收的时间。map是在包的作用域中分配的,以保证它被分配到堆上。
import (
"fmt"
"runtime"
"strconv"
"time"
)

const (
numElements = 10000000
)

var foo = map[string]int{}

func timeGC() {
t := time.Now()
runtime.GC()
fmt.Printf("gc took: %s\n", time.Since(t))
}

func main() {
for i := 0; i < numElements; i++ {
foo[strconv.Itoa(i)] = i
}

for {
timeGC()
time.Sleep(1 * time.Second)
}
}
运行后可以得到以下结果:
gc took: 98.726321ms
gc took: 105.524633ms
gc took: 102.829451ms
gc took: 102.71908ms
gc took: 103.084104ms
gc took: 104.821989ms
对于计算机来说这花得时间太多了!

怎样可以改进呢?最好是能尽量去掉指针,这样能减少垃圾回收器需要遍历的指针数量。由于字符串包含指针,因此我们可以用map[int]int来实现:
 
import (
"fmt"
"runtime"
"time"
)

const (
numElements = 10000000
)

var foo = map[int]int{}

func timeGC() {
t := time.Now()
runtime.GC()
fmt.Printf("gc took: %s\n", time.Since(t))
}

func main() {
for i := 0; i < numElements; i++ {
foo[i] = i
}

for {
timeGC()
time.Sleep(1 * time.Second)
}
}
重新运行程序,结果如下:
 
gc took: 3.608993ms
gc took: 3.926913ms
gc took: 3.955706ms
gc took: 4.063795ms
gc took: 3.91519ms
gc took: 3.75226ms
好多了。垃圾回收的时间减少了97%。在生产环境下,字符串需要进行hash之后再插入到map中。
 
生成marshalling代码以避免运行时反射
 
将数据结构marshalh或unmarshal成JSON等各种序列化格式是个很常见的操作,特别是在构建微服务的时候。实际上,大部分微服务做的唯一工作就是序列化。像json.Marshal和json.Unmarshal需要依赖运行时反射才能将结构体的字段序列化成字节,反之亦然。这个操作很慢,反射的性能完全无法与显式的代码相比。

但我们不必这么做。marshalling JSON的原理大致如下:
package json

// Marshal take an object and returns its representation in JSON.
func Marshal(obj interface{}) ([]byte, error) {
// Check if this object knows how to marshal itself to JSON
// by satisfying the Marshaller interface.
if m, is := obj.(json.Marshaller); is {
return m.MarshalJSON()
}

// It doesn't know how to marshal itself. Do default reflection based marshallling.
return marshal(obj)
}
如果我们知道怎样将对象marshal成JSON,就应该避免运行时反射。但我们不想手工marshal所有代码,怎么办呢?可以让计算机替我们写程序!像easyjson等代码生成器会检查结构体,然后生成高度优化且与json.Marshaller等接口完全兼容的代码。

下载这个包,然后在包含结构体的$file.go上运行下面的命令:
easyjson -all $file.go$file.go

这个命令会生成$file_easyjson.go。由于easyjson为我们实现了json.Marshaller接口,因此序列化时不会调用默认的反射,而是会使用生成的函数。祝贺你!你已经将JSON marshalling的代码的速度提高了三倍。还有许多其他技巧可以进一步提升性能。
 
使用strings.Builder来构建字符串
 
Go语言的字符串是不可修改的,可以认为它们是只读的字节切片。这就是说,每次创建字符串都要分配新的内存,可能还会给垃圾回收器造成更多工作。

Go 1.10引入了strings.Builder作为高效率构建字符串的方式。它内部会将字符串写入到字节缓冲区。只有在builder上调用String()时才会真正生成字符串。它依赖一些unsafe的技巧将底层的字节作为字符串返回,而不实际进行内存非配。这篇文章(https://syslog.ravelin.com/byt ... ca7ff)介绍了更多其工作原理。
pkg: github.com/sjwhitworth/perfblog/strbuild
BenchmarkStringBuildNaive-8 5000000 255 ns/op 216 B/op 8 allocs/op
BenchmarkStringBuildBuilder-8 20000000 54.9 ns/op 64 B/op 1 allocs/op
可见,strings.Builder要快4.7倍,它的内存分配次只有前者的1/8,内存使用只有前者的1/4。

所以,在性能重要的时候应该使用strings.Builder。一般来说,除非是非常不重要的情况,否则我建议永远使用strings.Builder来构建字符串。
 
使用strconv代替fmt

fmt是Go中最著名的包之一。估计你从第一个Go程序——输出“hello, world”——的时候就开始用它了。但是,如果需要将整数和浮点数转换成字符串,它就不如更底层的strconv有效率了。strconv只需要在API中进行很小改动,就能带来不错的性能提升。

大多数情况下fmt会接受一个interface{}作为参数。这样做有两个弊端:

失去了类型安全。对于我来说这是个很大的问题。

会增加内存分配次数。将非指针类型作为interface{}传递通常会导致堆分配的问题。进一步的内容可以阅读这篇文章(https://www.darkcoding.net/sof ... face/)。
BenchmarkStrconv-8      30000000            39.5 ns/op        32 B/op          1 allocs/op
BenchmarkFmt-8 10000000 143 ns/op 72 B/op 3 allocs/op
可以看到,strconv版本要快3.5倍,内存分配次数是1/3,内存分配量是1/2。

在make中指定分配的容量来避免重新分配

在讨论性能改善之前,我们先来迅速看一下切片。切片是Go语言中一个非常有用的概念。它提供了可改变大小的数组,还可以用不同的方式表示同一片底层内存区域,而不需要重新进行内存分配。slice的内部结构由三个元素组成:


data:切片中指向底层数据的指针
len:切片中的当前元素数目
cap:在不重新分配内存的前提下,切片能够增长到的元素数目


我经常看到类似于下面的代码,尽管在切片容量可以预先得知的情况下依然生成一个容量为零的切片:
var userIDs []string
for _, bar := range rsp.Users {
userIDs = append(userIDs, bar.ID)
}
这段代码中,切片的初始长度和容量都为零。在收到响应后,我们将用户添加到切片。这样做就会达到切片的容量上限,从而导致底层分配两倍容量的新数组,然后将旧切片中的数据拷贝过来。如果有8个用户,就会造成5次内存分配。

更有效的方式是这样:
userIDs := make([]string, 0, len(rsp.Users)
for _, bar := range rsp.Users {
userIDs = append(userIDs, bar.ID)
}
使用make显式声明切片的容量。接下来可以向切片添加元素,而不会触发内存重新分配和拷贝。
 
这条建议也适用于map:适用make(map[string]string, len(foo))可以分配足够的底层内存以避免内存重新分配。
 
使用可以接受字节切片的方法
 
在使用包时,寻找那些接受字节切片作为参数的方法,这些方法通常给你更多控制内存分配的自由。

一个很好的例子就是time.Format和time.AppendFormat。time.Format返回字符串。内部会分配一个新的字节切片,然后在其上调用time.AppendFormat。而time.AppendFormat接受一个字节缓冲区,将格式化后的时间写入缓冲区,然后返回扩展后的字节切片。标准库中这种做法非常常见,如strconv.AppendFloat或bytes.NewBuffer。

为什么这样能提高性能?因为你可以传递从sync.Poolh获得的字节切片,而不需要每次都分配新的缓冲区。或者可以初始化一个足够大的缓冲区,来减少切片拷贝。
 
这些建议只是某些具体情况下的建议,而不是真理。一定要自己测量性能。

要知道何时该停止优化。提高系统性能会让工程师感觉非常满足:问题本身很有趣,也有立竿见影的效果。但是,提高性能带来的效果非常依赖于具体情况。如果服务的响应时间只有10毫秒,而网络访问需要90毫秒,那么将10毫秒优化到5毫秒就完全不值得,因为你依然需要95毫秒。就算你将响应时间优化到1毫秒,最后结果还是91毫秒。你应该去做其他更有价值的事情。
 
原文:
https://stephen.sh/posts/quick-go-performance-improvements
译文:
https://blog.csdn.net/csdnnews/article/details/93987866
 

#每日精进#2020年8月4日

总结zkbhj 发表了文章 • 0 个评论 • 375 次浏览 • 2020-08-04 14:51 • 来自相关话题

【早读:《深入理解计算机系统》】
 
第二章 信息的表示和处理

大多数计算机使用8位的块,或字节,作为最小的可寻址内存单位。

机器级程序将内存视为一个非常大的字节数组,称为虚拟内存。

内存中每个字节由一个唯一的数字来标识,称为它的地址。所有可能的地址的集合就称为虚拟地址空间。

所以,这个虚拟地址空间只是一个展现给机器级程序的概念性映像。实际上,它将DRAM、闪存、磁盘存储器等和操作系统软件结合起来,封装了复杂性,为程序提供一个看上去统一的字节数组。


C语言中的一个指针的值,都是某个存储块的第一个字节的虚拟地址。每个程序对象可以简单地视为一个字节块,而程序本身就是一个字节序列。

十六进制表示法

由于二进制和十进制对于描述位模式来说都非常不方便:二进制太冗长,十进制和位模式的互相转化很麻烦,替代的方法就是引入16进制。

以0x或者0X开头,0~9、A~F,不区分大小写且大小写不敏感。

重要的是二进制、十进制和十六进制之间的互相转换方法,详细的可以进入凯冰科技知识共享中心搜索相关问题或文章查看。

对于x=2的n次方这个公式,转换十六进制,可以转化为n=i+4j,然后得到的十六进制就是:0x + 2的i次方 + j个0,比如512,是2的9次方,9=1+4*2,所以十六进制就是0x200。

字数据大小

每台计算机都有一个字长,指明指针数据的标称大小。字长决定虚拟地址空间的最大大小。32位字长限制的虚拟地址空间位4千兆字节(约4GB),而现在比较普及的64位字长的虚拟空间位16EB。

大多数64位机器也可以运行32位机器编译的程序,这是一种向后兼容。//该编译后的程序可以在32或64位机器上运行
linux> gcc -m32 prog.c

//该编译后,只能在64位机器上运行
linux> gcc -m64 prog.c我们将程序称为32位程序或64位程序,区别在于该程序是如何编译的,而不是其运行的机器类型。

ISO C99引入了确定大小的数据类型,int32_t 和int64_t,其数据大小是固定的,分别为4个字节和8个字节。使用确切大小的整数类型是程序员准确控制数据表示的最佳途径。

程序员应该力图使他们的程序可以在不同的机器和编译器上可移植,可移植的一方面就是说程序对不同数据类型的确切大小不敏感。

比如许多程序员假设一个声明为int类型的程序对象能被用来存储一个指针,这在大多数32位的机器上能够正常工作,但是在一台64位的机器上却会导致问题。所以,1980到2010年期间(32位机器是主流)编写的程序,之后64位机器陆续普及之后,迁移过来的程序就暴露出来许多隐藏的对字长的依赖性问题,导致错误。

【垂直行业如电商如何衡量搜索引擎的优劣】

在电商行业中,无论是2B还是2C,最终的业务目的就是交易成单,众所周知搜索服务旨在让消费者能够更快的定位到自己想要的产品。

一般电商搜索的核心是搜索精度和搜索广度,精度就是搜索的精确性,广度就是搜索结果的范围,其关键结果肯定是“为用户找到想要的商品”,但过于追求搜索的精确度就会导致出现搜索的结果比较少或结果为0的情况,用户搜不到商品势必会引发流失,因此在搜索服务里面还可以做的就是给用户提供一些相关性搜索结果。那么搜索做的好不好,其实就是在搜索精度和搜索广度二者之间做一个比较好的平衡点。

搜索过程中遇到的问题:

1.随机性发现的Bad case
2.KPI或者OKR考核
3.业务方诉求

核心指标
 
“搜索PV”:指访问搜索页面的次数;“搜索UV”:访问过搜索结果页的用户数;“无结果率”:空结果PV/搜索PV,无结果率越低,代表客户搜索需求解决情况越好;“TOP5 PV—CTR”:指该query search结果中,排在前五位的item有被点击的搜索PV/该query搜索PV该指标能一定程度反应排序效果;“人均搜索PV”:搜索PV/搜索UV;该指标的含义比较复杂,一方面人均pv大的话可能代表用户对搜索比较感兴趣,但另一方面人均pv大也可能代表搜索召回的结果较差,导致用户无法使用较少的点击找到满足需求的结果;“有点击搜索PV占比”:有点击搜索PV/搜索PV数;“PV-CTR”:搜索结果页item点击数/搜索PV数;“UV-CTR”:点击的uv / 曝光的uv;“Item-CTR”:搜索结果页item点击数/搜索结果页item总曝光PV数;
 
搜索技术等级分类





 
https://developer.aliyun.com/article/769492
 
  查看全部

【早读:《深入理解计算机系统》】
 
第二章 信息的表示和处理

大多数计算机使用8位的块,或字节,作为最小的可寻址内存单位。

机器级程序将内存视为一个非常大的字节数组,称为虚拟内存。

内存中每个字节由一个唯一的数字来标识,称为它的地址。所有可能的地址的集合就称为虚拟地址空间。

所以,这个虚拟地址空间只是一个展现给机器级程序的概念性映像。实际上,它将DRAM、闪存、磁盘存储器等和操作系统软件结合起来,封装了复杂性,为程序提供一个看上去统一的字节数组。


C语言中的一个指针的值,都是某个存储块的第一个字节的虚拟地址。每个程序对象可以简单地视为一个字节块,而程序本身就是一个字节序列。

十六进制表示法

由于二进制和十进制对于描述位模式来说都非常不方便:二进制太冗长,十进制和位模式的互相转化很麻烦,替代的方法就是引入16进制。

以0x或者0X开头,0~9、A~F,不区分大小写且大小写不敏感。

重要的是二进制、十进制和十六进制之间的互相转换方法,详细的可以进入凯冰科技知识共享中心搜索相关问题或文章查看。

对于x=2的n次方这个公式,转换十六进制,可以转化为n=i+4j,然后得到的十六进制就是:0x + 2的i次方 + j个0,比如512,是2的9次方,9=1+4*2,所以十六进制就是0x200。

字数据大小

每台计算机都有一个字长,指明指针数据的标称大小。字长决定虚拟地址空间的最大大小。32位字长限制的虚拟地址空间位4千兆字节(约4GB),而现在比较普及的64位字长的虚拟空间位16EB。

大多数64位机器也可以运行32位机器编译的程序,这是一种向后兼容。
//该编译后的程序可以在32或64位机器上运行
linux> gcc -m32 prog.c

//该编译后,只能在64位机器上运行
linux> gcc -m64 prog.c
我们将程序称为32位程序或64位程序,区别在于该程序是如何编译的,而不是其运行的机器类型。

ISO C99引入了确定大小的数据类型,int32_t 和int64_t,其数据大小是固定的,分别为4个字节和8个字节。使用确切大小的整数类型是程序员准确控制数据表示的最佳途径。

程序员应该力图使他们的程序可以在不同的机器和编译器上可移植,可移植的一方面就是说程序对不同数据类型的确切大小不敏感。

比如许多程序员假设一个声明为int类型的程序对象能被用来存储一个指针,这在大多数32位的机器上能够正常工作,但是在一台64位的机器上却会导致问题。所以,1980到2010年期间(32位机器是主流)编写的程序,之后64位机器陆续普及之后,迁移过来的程序就暴露出来许多隐藏的对字长的依赖性问题,导致错误。

【垂直行业如电商如何衡量搜索引擎的优劣】

在电商行业中,无论是2B还是2C,最终的业务目的就是交易成单,众所周知搜索服务旨在让消费者能够更快的定位到自己想要的产品。

一般电商搜索的核心是搜索精度和搜索广度,精度就是搜索的精确性,广度就是搜索结果的范围,其关键结果肯定是“为用户找到想要的商品”,但过于追求搜索的精确度就会导致出现搜索的结果比较少或结果为0的情况,用户搜不到商品势必会引发流失,因此在搜索服务里面还可以做的就是给用户提供一些相关性搜索结果。那么搜索做的好不好,其实就是在搜索精度和搜索广度二者之间做一个比较好的平衡点。

搜索过程中遇到的问题:


1.随机性发现的Bad case
2.KPI或者OKR考核
3.业务方诉求


核心指标
 
  • “搜索PV”:指访问搜索页面的次数;
  • “搜索UV”:访问过搜索结果页的用户数;
  • “无结果率”:空结果PV/搜索PV,无结果率越低,代表客户搜索需求解决情况越好;
  • “TOP5 PV—CTR”:指该query search结果中,排在前五位的item有被点击的搜索PV/该query搜索PV该指标能一定程度反应排序效果;
  • “人均搜索PV”:搜索PV/搜索UV;该指标的含义比较复杂,一方面人均pv大的话可能代表用户对搜索比较感兴趣,但另一方面人均pv大也可能代表搜索召回的结果较差,导致用户无法使用较少的点击找到满足需求的结果;
  • “有点击搜索PV占比”:有点击搜索PV/搜索PV数;
  • “PV-CTR”:搜索结果页item点击数/搜索PV数;
  • “UV-CTR”:点击的uv / 曝光的uv;
  • “Item-CTR”:搜索结果页item点击数/搜索结果页item总曝光PV数;

 
搜索技术等级分类

13cfe86b5b2f4aacba814f4fb2f080e5.png

 
https://developer.aliyun.com/article/769492
 
 

#每日精进#2020年8月3日

总结zkbhj 发表了文章 • 0 个评论 • 387 次浏览 • 2020-08-03 20:45 • 来自相关话题

【早读:《深入理解计算机系统》】

第二章 信息的表示和处理

现代计算机存储和处理信息以二值信号表示。二值信号能够很容易的被表示、存储和传输,且用二值信号进行存储和执行计算的电子电路非常简单可靠。

三种重要的数字表示:
 

无符号编码:基于传统的二进制表示法,表示大于或者等于0的数字;
补码编码:表示有符号整数的最常见方式;
浮点数编码:表示实数的科学计数法的以2为基数的版本;


计算机的表示法是以有限数量的位来对一个数字进行编码,所以一旦超出界限,某些运算就会溢出,导致令人吃惊的后果。

整数的计算机运算满足人们所熟知的真正整数运算的许多性质。但是浮点数不一样。

整数的表示虽然只能编码一个相对较小的数值范围,但是这种表示是精确的;

浮点数虽然可以编码一个比较大的数值范围,但是这种标示只是近似的;

通过如下命令,可以在gcc编译C程序时指定C语言版本:
linux> gcc -std=c11 zkbhj.c
//其他版本参数
//GNU 89 无,-std=gnu89
//ANSI ,ISO C90 -ansi,-std=c89
//ISO C99 -std=c99
//ISO C11 -std=c11
【Go核心36讲:第11节 通道的高级玩法】

单向通道

所谓单向通道就是,只能发不能收,或者只能收不能发的通道。

声明一个只能发(向通道发送)不能收(从通道接收),容量为1的单向通道:
var uselessChan = make(chan<- int, 1)声明一个只能收(从通道接收)不能发(向通道发送),容量为1的单向通道:
var uselessChan = make(<-chan int, 1)
与发送操作和接收操作对应,这里的“发”和“收”都是站在操作通道的代码的角度上说的。

单向通道有什么应用价值?

概括地说,单向通道最主要的用途就是约束其他代码的行为。
//参数定义中就约束 ch只能进行发送操作,不能接收
//可以限制方法函数内对参数的操作行为做限定
func SendInt(ch chan<- int) {
ch <- rand.Intn(1000)
}//这段接口声明中,就约定了所以要实现这个接口的实现类型
//都约定了这些方法的参数类型
type Notifier interface {
SendInt(ch chan<- int)
}在实际调用的时候,传递一个双向通道即可,因为Go 语言在这种情况下会自动地把双向通道转换为函数所需的单向通道。

一种专门为了操作通道而存在的语句:select语句

select语句只能与通道联用,它一般由若干个分支组成。每次执行这种语句的时候,一般只有一个分支中的代码会被运行。分支分为两种,一种叫做候选分支,另一种叫做默认分支。每个case表达式中都只能包含操作通道的表达式。
select {
case <-intChannels[0]:
fmt.Println("The first candidate case is selected.")
case <-intChannels[1]:
fmt.Println("The second candidate case is selected.")
case elem := <-intChannels[2]: fmt.Printf("The third candidate case is selected, the element is %d.\n", elem)
default: fmt.Println("No candidate case is selected!")
}select语句只能对其中的每一个case表达式各求值一次。所以,如果我们想连续或定时地操作其中的通道的话,就往往需要通过在for语句中嵌入select语句的方式实现。但这时要注意,简单地在select语句的分支中使用break语句,只能结束当前的select语句的执行,而并不会对外层的for语句产生作用。这种错误的用法可能会让这个for语句无休止地运行下去。

select语句的分支选择规则总结:

1、对于每一个case表达式,都至少会包含一个代表发送操作的发送表达式或者一个代表接收操作的接收表达式;
2、select语句包含的候选分支中的case表达式都会在该语句执行开始时先被求值,并且求值的顺序是依从代码编写的顺序从上到下的;
3、对于每一个case表达式,如果其中的发送表达式或者接收表达式在被求值时,相应的操作正处于阻塞状态,那么对该case表达式的求值就是不成功的;
4、仅当select语句中的所有case表达式都被求值完毕后,它才会开始选择候选分支。这时候,它只会挑选满足选择条件的候选分支执行。所有的都不满足,执行default;
5、如果select语句发现同时有多个候选分支满足选择条件,那么它就会用一种伪随机的算法在这些分支中选择一个并执行;

 
【关于730政治局会议的总结】

15大要点(内循环 + 持久战)
 
​中国发展仍处于战略机遇期从持久战角度认识中长期问题以国内大循环为主题建立中长期协调机制牢牢把握扩大内需这个战略基点确保宏观政策落地见效保持货币供应量合理增长毫不放松抓好常态化疫情防控扩大最终消费加快新基建以新型城镇化带动投资和消费产业链补短板和锻长板从严打击证券违法活动住房不炒缓解疫情对年轻人就业影响

我们遇到的很多问题是中长期的,必须从持久战的角度加以认识

“在泡沫中狂欢的日志不多了,做好潮水退却后的准备,是每个国家,每个人都要面对的现实”

——吴晓灵 前央行副行长 查看全部
【早读:《深入理解计算机系统》】

第二章 信息的表示和处理

现代计算机存储和处理信息以二值信号表示。二值信号能够很容易的被表示、存储和传输,且用二值信号进行存储和执行计算的电子电路非常简单可靠。

三种重要的数字表示:
 


无符号编码:基于传统的二进制表示法,表示大于或者等于0的数字;
补码编码:表示有符号整数的最常见方式;
浮点数编码:表示实数的科学计数法的以2为基数的版本;



计算机的表示法是以有限数量的位来对一个数字进行编码,所以一旦超出界限,某些运算就会溢出,导致令人吃惊的后果。

整数的计算机运算满足人们所熟知的真正整数运算的许多性质。但是浮点数不一样。

整数的表示虽然只能编码一个相对较小的数值范围,但是这种表示是精确的;

浮点数虽然可以编码一个比较大的数值范围,但是这种标示只是近似的;

通过如下命令,可以在gcc编译C程序时指定C语言版本:
linux> gcc -std=c11 zkbhj.c
//其他版本参数
//GNU 89 无,-std=gnu89
//ANSI ,ISO C90 -ansi,-std=c89
//ISO C99 -std=c99
//ISO C11 -std=c11

【Go核心36讲:第11节 通道的高级玩法】

单向通道

所谓单向通道就是,只能发不能收,或者只能收不能发的通道。

声明一个只能发(向通道发送)不能收(从通道接收),容量为1的单向通道:
var uselessChan = make(chan<- int, 1)
声明一个只能收(从通道接收)不能发(向通道发送),容量为1的单向通道:
var uselessChan = make(<-chan int, 1)
与发送操作和接收操作对应,这里的“发”和“收”都是站在操作通道的代码的角度上说的。

单向通道有什么应用价值?

概括地说,单向通道最主要的用途就是约束其他代码的行为。
//参数定义中就约束 ch只能进行发送操作,不能接收
//可以限制方法函数内对参数的操作行为做限定
func SendInt(ch chan<- int) {
ch <- rand.Intn(1000)
}
//这段接口声明中,就约定了所以要实现这个接口的实现类型
//都约定了这些方法的参数类型
type Notifier interface {
SendInt(ch chan<- int)
}
在实际调用的时候,传递一个双向通道即可,因为Go 语言在这种情况下会自动地把双向通道转换为函数所需的单向通道。

一种专门为了操作通道而存在的语句:select语句

select语句只能与通道联用,它一般由若干个分支组成。每次执行这种语句的时候,一般只有一个分支中的代码会被运行。分支分为两种,一种叫做候选分支,另一种叫做默认分支。每个case表达式中都只能包含操作通道的表达式。
select {
case <-intChannels[0]:
fmt.Println("The first candidate case is selected.")
case <-intChannels[1]:
fmt.Println("The second candidate case is selected.")
case elem := <-intChannels[2]: fmt.Printf("The third candidate case is selected, the element is %d.\n", elem)
default: fmt.Println("No candidate case is selected!")
}
select语句只能对其中的每一个case表达式各求值一次。所以,如果我们想连续或定时地操作其中的通道的话,就往往需要通过在for语句中嵌入select语句的方式实现。但这时要注意,简单地在select语句的分支中使用break语句,只能结束当前的select语句的执行,而并不会对外层的for语句产生作用。这种错误的用法可能会让这个for语句无休止地运行下去。

select语句的分支选择规则总结:


1、对于每一个case表达式,都至少会包含一个代表发送操作的发送表达式或者一个代表接收操作的接收表达式;
2、select语句包含的候选分支中的case表达式都会在该语句执行开始时先被求值,并且求值的顺序是依从代码编写的顺序从上到下的;
3、对于每一个case表达式,如果其中的发送表达式或者接收表达式在被求值时,相应的操作正处于阻塞状态,那么对该case表达式的求值就是不成功的;
4、仅当select语句中的所有case表达式都被求值完毕后,它才会开始选择候选分支。这时候,它只会挑选满足选择条件的候选分支执行。所有的都不满足,执行default;
5、如果select语句发现同时有多个候选分支满足选择条件,那么它就会用一种伪随机的算法在这些分支中选择一个并执行;


 
【关于730政治局会议的总结】

15大要点(内循环 + 持久战)
 
  1. ​中国发展仍处于战略机遇期
  2. 从持久战角度认识中长期问题
  3. 以国内大循环为主题
  4. 建立中长期协调机制
  5. 牢牢把握扩大内需这个战略基点
  6. 确保宏观政策落地见效
  7. 保持货币供应量合理增长
  8. 毫不放松抓好常态化疫情防控
  9. 扩大最终消费
  10. 加快新基建
  11. 以新型城镇化带动投资和消费
  12. 产业链补短板和锻长板
  13. 从严打击证券违法活动
  14. 住房不炒
  15. 缓解疫情对年轻人就业影响


我们遇到的很多问题是中长期的,必须从持久战的角度加以认识


“在泡沫中狂欢的日志不多了,做好潮水退却后的准备,是每个国家,每个人都要面对的现实”

——吴晓灵 前央行副行长


#每日精进#2020年08月02日

总结zkbhj 发表了文章 • 0 个评论 • 353 次浏览 • 2020-08-02 19:46 • 来自相关话题

【午读:《深入理解计算机系统》】

第一章 计算机系统漫游

现代系统之间利用网络通信,和其他系统连接在一起。从一个单独的系统来看,网络可以视为一个I/O设备。

系统不仅仅只是硬件,而是硬件和软件互相交织的结合体,他们之间共同协作已达到运行应用程序的最终目的。

Amdahl(安达尔定律)定律

主要思想是:当对系统的某个部分加速时,其对系统整体性能的影响取决于该部分的重要性和加速程度。
主要观点是:想要显著加速整个系统,必须提升全系统中相当大的部分的速度。

 
Amdahl定律描述了改善任何过程的一般原则。

并发和并行

整个计算机发展历史中,我们一直在做两件事:一是让计算机做得更多,二是让计算机运行的更快。
并发:是一个通用概念,指一个同时具有多个活动的系统;
并行:指的是用并发来使一个系统运行得更快。

进程级并发
并发构建在进程整个抽象上,就能够设计出同时有多个程序执行的系统。
单处理器系统:只有一个处理器的系统;
多处理器系统:一个由单操作系统内核控制的多处理器组成的系统。
超线程,有时称为同时多线程,是一项允许一个CPU执行多个控制流的技术。

指令级并行
CPU可以同时执行多条指令的属性称为指令级并行。
超标量处理器:处理器能够达到比一个时钟周期一条指令更快的执行速率。

计算机系统中抽象的重要性
计算机系统提供的一些抽象,它提供不同层次的抽象表示来隐藏实际实现的复杂性。
比如上一节中:文件是对I/O设备的抽象,虚拟内存是对主存和磁盘的抽象,进程则是对一个正在运行的程序的抽象(处理器、主存和I/O的抽象)。
虚拟机,则提供对整个计算机的抽象。

第一章总结
计算机系统是由硬件和系统软件组成的,它们共同协作以运行应用程序。计算机内部的信息被表示为一组组的位,它们根据上下文有不同的解释方式。程序被其他程序翻译成不同的形式,开始是ASCII文本,然后被编译器和链接器翻译成二进制可执行文件。

处理器读取并解释存储在主存里的二进制指令。因为计算机花了大把时间用于存储器、I/O设备和CPU寄存器之间复制数据,所以讲系统中的存储设备划分成层次结构——CPU寄存器在顶部,接着是多层的硬件高速缓存存储器、DRAM主存和磁盘存储器。

在层次模型中,位于更高层的存储设备比低层的存储设备要更快,单位比特造价也更高。层次结构中较高层次存储设备可以作为较低层次存储设备的高速缓存。通过理解和运用这种存储层次结构的知识,程序员可以优化C程序的性能。

操作系统内核是应用程序和硬件之间的媒介。它提供三个基本的抽象:

(1)文件是对I/O设备的抽象

(2)虚拟存储器是对主存和I/O设备的抽象

(3)进程是对处理器、主存和I/O设备的抽象。

另外,虚拟机提供了对整个计算机的抽象。

最后,网络提供了计算机系统之间通信的手段。从特殊系统的角度来看,网络就是一种I/O设备。
 

一周的早读,完成了《深入理解计算机系统》这本又厚又重的计算机最底层原理书籍第一章的阅读和理解。
好好利用碎片时间,以及找到自己的高效时间段及学习方式很重要。
积少成多,积流成河!
明天开启第二章:程序结构和执行!


PS广而告之时间:

正在筹划一个作为技术人员角度的个人分享网站,以很程序员的视角,将学习过的一门语言、一本技术书籍,以“手册”的形式输出出来。
根据能量守恒定律,有输入就要有输出,既然自己有这样的渠道和能力,打算把体系学习过的一些内容,沉淀成一本本技术手册分享出来,可以给更多有同样需求的人提供一些有意义的帮助和指导。
还是凯冰科技10多年来坚持的一句slogan:
代码改变世界,技术改变生活
Code changes the world, technology changes life
doc.zkbhj.com
第一本就是这本《深入理解计算机系统》。嘿嘿~

【关于如何在美团里继续使用支付宝支付的方法】

额。。由于一些你懂的的原因,大部分美团用户已经无法再美团APP上用支付宝进行支付了。这里暂时不评论这件事情的对与错好与坏,只是从技术的角度,帮你找到了一个怎么继续在美团里使用支付宝支付的方法,操作步骤如下:

1、打开美团APP,点击进入个人中心页面,点击右上角的在线客服,进入客服聊天界面;

2、输入问题:支付宝无法使用;

3、在返回的结果里选择:放弃优惠,恢复支付宝。






经过上面的几步“骚操作”,就可以继续在美团里使用支付宝进行支付了!
 
​【Go语言核心36讲:第10节 通道的基本操作 】

☆ 通道(Channel)

通道是Go 语言最有特色的数据类型,是不同Goroutine之间通信的“桥梁”。
 

Don’t communicate by sharing memory; share memory by communicating. (不要通过共享内存来通信,而应该通过通信来共享内存。)


通道类型的值是 Go 语言自带的、唯一一个可以满足并发安全性的类型。

当容量为0时,我们可以称通道为非缓冲通道,也就是不带缓冲的通道。而当容量大于0时,我们可以称为缓冲通道,也就是带有缓冲的通道。

一个通道相当于一个先进先出(FIFO)的队列。也就是说,通道中的各个元素值都是严格地按照发送的顺序排列的,先被发送通道的元素值一定会先被接收。元素值的发送和接收都需要用到操作符<-。我们也可以叫它接送操作符。一个左尖括号紧接着一个减号形象地代表了元素值的传输方向。

☆ 对通道的发送和接收操作都有哪些基本的特性?

1、对于同一个通道,发送操作之间是互斥的,接收操作之间也是互斥的。

元素值从外界进入通道时会被复制。更具体地说,进入通道的并不是在接收操作符右边的那个元素值,而是它的副本。另一方面,元素值从通道进入外界时会被移动。这个移动操作实际上包含了两步,第一步是生成正在通道中的这个元素值的副本,并准备给到接收方,第二步是删除在通道中的这个元素值。

2、发送操作和接收操作中对元素值的处理都是不可分割的。

这里的“不可分割”的意思是,它们处理元素值时都是一气呵成的,绝不会被打断。

发送时,“复制元素值”和“放置副本到通道内部”这两个步骤不会被打断;

接收时,“复制通道内元素值”“放置副本到接收方”“删掉原值”三个步骤不会被打断。

3、发送操作在完全完成之前会被阻塞。接收操作也是如此。

以上各步骤执行期间,其他操作都会被阻塞。如此阻塞代码其实就是为了实现操作的互斥和元素值的完整。

☆ 发送操作和接收操作在什么时候可能被长时间的阻塞?

有缓冲的,如果通道已满,对它的所有发送操作都会被阻塞,直到通道中有元素值被接收走。如果通道已空,对它的所有接收操作都会被阻塞,直到通道中有新的元素值出现。所有被阻塞的goroutine都是按FIFO的策略执行的。

非缓冲通道,情况要简单一些。无论是发送操作还是接收操作,一开始执行就会被阻塞,直到配对的操作也开始执行,才会继续传递。由此可见,非缓冲通道是在用同步的方式传递数据。

对于值为nil的通道,不论它的具体类型是什么,对它的发送操作和接收操作都会永久地处于阻塞状态。它们所属的 goroutine 中的任何代码,都不再会被执行。所以我们一定不要忘记初始化通道!

☆ 发送操作和接收操作在什么时候会引发 panic?

通道一旦关闭,再对它进行发送操作,就会引发 panic。

如果我们试图关闭一个已经关闭了的通道,也会引发 panic。

通过接收表达式的第二个结果值,可以来判断通道是否关闭,但是可能有延时的。即如果通道已经关闭,但还有值未被取出,则这个时候,返回的仍然是true!

所以最佳实践告诉我们,千万不要让接收方关闭通道,而应当让发送方做这件事。 查看全部

【午读:《深入理解计算机系统》】

第一章 计算机系统漫游

现代系统之间利用网络通信,和其他系统连接在一起。从一个单独的系统来看,网络可以视为一个I/O设备。

系统不仅仅只是硬件,而是硬件和软件互相交织的结合体,他们之间共同协作已达到运行应用程序的最终目的。

Amdahl(安达尔定律)定律


主要思想是:当对系统的某个部分加速时,其对系统整体性能的影响取决于该部分的重要性和加速程度。
主要观点是:想要显著加速整个系统,必须提升全系统中相当大的部分的速度。


 
Amdahl定律描述了改善任何过程的一般原则。

并发和并行

整个计算机发展历史中,我们一直在做两件事:一是让计算机做得更多,二是让计算机运行的更快。
并发:是一个通用概念,指一个同时具有多个活动的系统;
并行:指的是用并发来使一个系统运行得更快。

进程级并发
并发构建在进程整个抽象上,就能够设计出同时有多个程序执行的系统。
单处理器系统:只有一个处理器的系统;
多处理器系统:一个由单操作系统内核控制的多处理器组成的系统。
超线程,有时称为同时多线程,是一项允许一个CPU执行多个控制流的技术。

指令级并行
CPU可以同时执行多条指令的属性称为指令级并行。
超标量处理器:处理器能够达到比一个时钟周期一条指令更快的执行速率。

计算机系统中抽象的重要性
计算机系统提供的一些抽象,它提供不同层次的抽象表示来隐藏实际实现的复杂性。
比如上一节中:文件是对I/O设备的抽象,虚拟内存是对主存和磁盘的抽象,进程则是对一个正在运行的程序的抽象(处理器、主存和I/O的抽象)。
虚拟机,则提供对整个计算机的抽象。

第一章总结
计算机系统是由硬件和系统软件组成的,它们共同协作以运行应用程序。计算机内部的信息被表示为一组组的位,它们根据上下文有不同的解释方式。程序被其他程序翻译成不同的形式,开始是ASCII文本,然后被编译器和链接器翻译成二进制可执行文件。

处理器读取并解释存储在主存里的二进制指令。因为计算机花了大把时间用于存储器、I/O设备和CPU寄存器之间复制数据,所以讲系统中的存储设备划分成层次结构——CPU寄存器在顶部,接着是多层的硬件高速缓存存储器、DRAM主存和磁盘存储器。

在层次模型中,位于更高层的存储设备比低层的存储设备要更快,单位比特造价也更高。层次结构中较高层次存储设备可以作为较低层次存储设备的高速缓存。通过理解和运用这种存储层次结构的知识,程序员可以优化C程序的性能。

操作系统内核是应用程序和硬件之间的媒介。它提供三个基本的抽象:

(1)文件是对I/O设备的抽象

(2)虚拟存储器是对主存和I/O设备的抽象

(3)进程是对处理器、主存和I/O设备的抽象。

另外,虚拟机提供了对整个计算机的抽象。

最后,网络提供了计算机系统之间通信的手段。从特殊系统的角度来看,网络就是一种I/O设备。
 


一周的早读,完成了《深入理解计算机系统》这本又厚又重的计算机最底层原理书籍第一章的阅读和理解。
好好利用碎片时间,以及找到自己的高效时间段及学习方式很重要。
积少成多,积流成河!
明天开启第二章:程序结构和执行!



PS广而告之时间

正在筹划一个作为技术人员角度的个人分享网站,以很程序员的视角,将学习过的一门语言、一本技术书籍,以“手册”的形式输出出来。
根据能量守恒定律,有输入就要有输出,既然自己有这样的渠道和能力,打算把体系学习过的一些内容,沉淀成一本本技术手册分享出来,可以给更多有同样需求的人提供一些有意义的帮助和指导。
还是凯冰科技10多年来坚持的一句slogan:
代码改变世界,技术改变生活
Code changes the world, technology changes life
doc.zkbhj.com
第一本就是这本《深入理解计算机系统》。嘿嘿~

【关于如何在美团里继续使用支付宝支付的方法】

额。。由于一些你懂的的原因,大部分美团用户已经无法再美团APP上用支付宝进行支付了。这里暂时不评论这件事情的对与错好与坏,只是从技术的角度,帮你找到了一个怎么继续在美团里使用支付宝支付的方法,操作步骤如下:


1、打开美团APP,点击进入个人中心页面,点击右上角的在线客服,进入客服聊天界面;

2、输入问题:支付宝无法使用;

3、在返回的结果里选择:放弃优惠,恢复支付宝。



WechatIMG147.jpeg

经过上面的几步“骚操作”,就可以继续在美团里使用支付宝进行支付了!
 
​【Go语言核心36讲:第10节 通道的基本操作 】

☆ 通道(Channel)

通道是Go 语言最有特色的数据类型,是不同Goroutine之间通信的“桥梁”。
 


Don’t communicate by sharing memory; share memory by communicating. (不要通过共享内存来通信,而应该通过通信来共享内存。)



通道类型的值是 Go 语言自带的、唯一一个可以满足并发安全性的类型。

当容量为0时,我们可以称通道为非缓冲通道,也就是不带缓冲的通道。而当容量大于0时,我们可以称为缓冲通道,也就是带有缓冲的通道。

一个通道相当于一个先进先出(FIFO)的队列。也就是说,通道中的各个元素值都是严格地按照发送的顺序排列的,先被发送通道的元素值一定会先被接收。元素值的发送和接收都需要用到操作符<-。我们也可以叫它接送操作符。一个左尖括号紧接着一个减号形象地代表了元素值的传输方向。

☆ 对通道的发送和接收操作都有哪些基本的特性?

1、对于同一个通道,发送操作之间是互斥的,接收操作之间也是互斥的。

元素值从外界进入通道时会被复制。更具体地说,进入通道的并不是在接收操作符右边的那个元素值,而是它的副本。另一方面,元素值从通道进入外界时会被移动。这个移动操作实际上包含了两步,第一步是生成正在通道中的这个元素值的副本,并准备给到接收方,第二步是删除在通道中的这个元素值。

2、发送操作和接收操作中对元素值的处理都是不可分割的。

这里的“不可分割”的意思是,它们处理元素值时都是一气呵成的,绝不会被打断。

发送时,“复制元素值”和“放置副本到通道内部”这两个步骤不会被打断;

接收时,“复制通道内元素值”“放置副本到接收方”“删掉原值”三个步骤不会被打断。

3、发送操作在完全完成之前会被阻塞。接收操作也是如此。

以上各步骤执行期间,其他操作都会被阻塞。如此阻塞代码其实就是为了实现操作的互斥和元素值的完整。

☆ 发送操作和接收操作在什么时候可能被长时间的阻塞?

有缓冲的,如果通道已满,对它的所有发送操作都会被阻塞,直到通道中有元素值被接收走。如果通道已空,对它的所有接收操作都会被阻塞,直到通道中有新的元素值出现。所有被阻塞的goroutine都是按FIFO的策略执行的。

非缓冲通道,情况要简单一些。无论是发送操作还是接收操作,一开始执行就会被阻塞,直到配对的操作也开始执行,才会继续传递。由此可见,非缓冲通道是在用同步的方式传递数据。

对于值为nil的通道,不论它的具体类型是什么,对它的发送操作和接收操作都会永久地处于阻塞状态。它们所属的 goroutine 中的任何代码,都不再会被执行。所以我们一定不要忘记初始化通道!

☆ 发送操作和接收操作在什么时候会引发 panic?

通道一旦关闭,再对它进行发送操作,就会引发 panic。

如果我们试图关闭一个已经关闭了的通道,也会引发 panic。

通过接收表达式的第二个结果值,可以来判断通道是否关闭,但是可能有延时的。即如果通道已经关闭,但还有值未被取出,则这个时候,返回的仍然是true!

所以最佳实践告诉我们,千万不要让接收方关闭通道,而应当让发送方做这件事。

#每日精进#2020年07月30日

总结zkbhj 发表了文章 • 0 个评论 • 277 次浏览 • 2020-07-30 08:34 • 来自相关话题

 
【早读:《深入理解计算机系统》】

第一章 计算机系统漫游

hello程序运行过程:

1、键盘输入"./hello"后,shell程序将字符逐一读入寄存器,再把它们存放到主存中;
2、敲击回车,shell就知道我们已经输入结束了,然后shell会执行一系列指令,将hello程序中的代码和数据从磁盘复制到主存。这其中利用了直接存储器存取(DMA),数据直接通过总线从磁盘进入主存中,不会经过处理器;
3、代码和数据加载完毕之后,处理器就开始执行hello程序的main程序中的机器语言指令,将数据(hello,world\n)字符串中的字符从主存中复制到寄存器文件中,再从寄存器文件中复制到显示器设备,最终显示出结果。


高速缓存的重要性:上面的执行过程,我们就可以发现,系统花费了大量的时间把信息从一个地方移动到另一个地方!这些复制都是巨大的开销,减慢了程序”真正“的工作。因此,系统设计者通过让”存储设备形成层次结构”来提供速度,尽可能让这些复制操作能够尽快完成。






存储器层次结构的主要思想是上一层的存储器作为低一层存储器的高速缓存。

L1、L2、L3这些更小更快的存储设备,称为高速缓存存储器(cache memory,简称cache或高速缓存)。高速缓存是用一种叫做静态随机访问存储器(SRAM)的硬件技术实现的。

高速缓存之所以能加快访问速度,从而提高系统整体的效率,是利用了高速缓存的局部性原理:即程序具有访问局部区域里的数据和代码的趋势。通过让高速缓存里存放可能经常访问的数据,大部分的内存操作都能在高速缓存中快速完成。

操作系统管理硬件

应用程序并不能直接跟硬件“打交道”,而是要通过“操作系统”这一“中间商”提供的服务来和硬件进行交流。

操作系统有2个基本功能:
防止硬件被失控的应用程序滥用;向应用程序提供简单一致的机制来控制复杂而又通常大不相同的低级硬件设备。
 
操作系统通过以下三个抽象概念来实现上述2个功能:
文件:是对I/O设备的抽象表示;虚拟内存:是对主存和磁盘I/O设备的抽象表示;进程:是对处理器、主存和磁盘I/O设备的抽象表示。Posix标准是IEEE组织为了组织Unix越来越混乱,越来越不标准化而制定的标准化Unix开发提出来的一套标准规范。
 
 
【爱英语Show】
 
开电脑,关电脑不能用Open the computer 和 Close the computer!这两个表达方式的意思是 拆开/关上的意思。
正确的表达方式应该是 Turn on the computer 和 Turn off the computer!
另外,打开/关闭电脑中的文件,可以说 Open the file 或者 Close the file。
 

【高次方程】
 
今天中午在读紫金陈的《无证之罪》时,严良给赵铁民分析案情时提到,这个案子不能用常规的方法。普通的案子,可以根据线索通过“推论”算出来“嫌疑人”,就像是方程,可以通过公式计算出结果。但是这个案子,要用对应“高次方程”的求解方法来分析:那就是先确定结果,再代入方程中进行论证。也就是先确定嫌疑人,然后代入案情中推论。
 
严良提到的“高次方程”其实指的是次数大于等于5的方程,这类方程有一个特点: 高于5次(大于等于5)的方程没有精确代数解(这个定理已经被证明)。 这种方程只能通过数值计算的方法,用数值逼近求近似解。
 
埃瓦里斯特·伽罗瓦,用群论系统化地阐述了五次及五次以上方程不能用公式求解。
 
【今日放学别走】

今天的放学别走部门同学一起Review了近期完成的筛选项后台管理系统的项目代码,项目使用了Gin框架,其中有一个配置文件格式是以前没有接触过的,是Toml。之前一直接触过的有yaml、json、ini之类的,这个今天还是第一次听,大概了解了下一个配置文件的相关内容。

GitHub觉得 YAML 不够简洁优雅,因此捣鼓出了一个TOML。TOML的全称是 Tom’s Obvious, Minimal Language,因为它的作者是 GitHub联合创始人Tom Preston-Werner 。

TOML 的目标是成为一个极简的配置文件格式。TOML 被设计成可以无歧义地被映射为哈希表,从而被多种语言解析。

是大小写敏感的。

可能是目前最好的配置文件格式。详细可以点击了解:
https://blog.csdn.net/john_f_lau/article/details/55803069
 
【Go语言核心36讲:第9节 字典的操作和约束】

Go 语言的字典类型(Map)其实是一个哈希表(hash table)的特定实现,在这个实现中,键和元素的最大不同在于,键的类型是受限的,而元素却可以是任意类型的。之所以会受限,是因为哈希表中的一个重要过程:映射。

映射过程的第一步就是:把键值转换为哈希值。

Go 语言规范规定,键类型的值必须要支持判等操作,所以字典的键类型不可以是函数类型、字典类型和切片类型。

应该优先考虑哪些类型作为字典的键类型?求哈希和判等操作的速度越快,对应的类型就越适合作为键类型。优先选用数值类型和指针类型,通常情况下类型的宽度越小越好。如果非要选择字符串类型的话,最好对键值的长度进行额外的约束。

除了添加键 - 元素对,我们在一个值为nil的字典上做任何操作都不会引起错误。当我们试图在一个值为nil的字典中添加键 - 元素对的时候,Go 语言的运行时系统就会立即抛出一个 panic。

永远要注意那些可能引发 panic 的操作,比如像一个值为nil的字典添加键 - 元素对。 查看全部

 
【早读:《深入理解计算机系统》】

第一章 计算机系统漫游

hello程序运行过程:


1、键盘输入"./hello"后,shell程序将字符逐一读入寄存器,再把它们存放到主存中;
2、敲击回车,shell就知道我们已经输入结束了,然后shell会执行一系列指令,将hello程序中的代码和数据从磁盘复制到主存。这其中利用了直接存储器存取(DMA),数据直接通过总线从磁盘进入主存中,不会经过处理器;
3、代码和数据加载完毕之后,处理器就开始执行hello程序的main程序中的机器语言指令,将数据(hello,world\n)字符串中的字符从主存中复制到寄存器文件中,再从寄存器文件中复制到显示器设备,最终显示出结果。



高速缓存的重要性:上面的执行过程,我们就可以发现,系统花费了大量的时间把信息从一个地方移动到另一个地方!这些复制都是巨大的开销,减慢了程序”真正“的工作。因此,系统设计者通过让”存储设备形成层次结构”来提供速度,尽可能让这些复制操作能够尽快完成。

timg.jpeg


存储器层次结构的主要思想是上一层的存储器作为低一层存储器的高速缓存。

L1、L2、L3这些更小更快的存储设备,称为高速缓存存储器(cache memory,简称cache或高速缓存)。高速缓存是用一种叫做静态随机访问存储器(SRAM)的硬件技术实现的。

高速缓存之所以能加快访问速度,从而提高系统整体的效率,是利用了高速缓存的局部性原理:即程序具有访问局部区域里的数据和代码的趋势。通过让高速缓存里存放可能经常访问的数据,大部分的内存操作都能在高速缓存中快速完成。

操作系统管理硬件

应用程序并不能直接跟硬件“打交道”,而是要通过“操作系统”这一“中间商”提供的服务来和硬件进行交流。

操作系统有2个基本功能:
  1. 防止硬件被失控的应用程序滥用;
  2. 向应用程序提供简单一致的机制来控制复杂而又通常大不相同的低级硬件设备。

 
操作系统通过以下三个抽象概念来实现上述2个功能:
  • 文件:是对I/O设备的抽象表示;
  • 虚拟内存:是对主存和磁盘I/O设备的抽象表示;
  • 进程:是对处理器、主存和磁盘I/O设备的抽象表示。Posix标准是IEEE组织为了组织Unix越来越混乱,越来越不标准化而制定的标准化Unix开发提出来的一套标准规范。

 
 
【爱英语Show】
 
开电脑,关电脑不能用Open the computer 和 Close the computer!这两个表达方式的意思是 拆开/关上的意思。
正确的表达方式应该是 Turn on the computer 和 Turn off the computer!
另外,打开/关闭电脑中的文件,可以说 Open the file 或者 Close the file。
 

【高次方程】
 
今天中午在读紫金陈的《无证之罪》时,严良给赵铁民分析案情时提到,这个案子不能用常规的方法。普通的案子,可以根据线索通过“推论”算出来“嫌疑人”,就像是方程,可以通过公式计算出结果。但是这个案子,要用对应“高次方程”的求解方法来分析:那就是先确定结果,再代入方程中进行论证。也就是先确定嫌疑人,然后代入案情中推论。
 
严良提到的“高次方程”其实指的是次数大于等于5的方程,这类方程有一个特点: 高于5次(大于等于5)的方程没有精确代数解(这个定理已经被证明)。 这种方程只能通过数值计算的方法,用数值逼近求近似解
 
埃瓦里斯特·伽罗瓦,用群论系统化地阐述了五次及五次以上方程不能用公式求解
 
【今日放学别走】

今天的放学别走部门同学一起Review了近期完成的筛选项后台管理系统的项目代码,项目使用了Gin框架,其中有一个配置文件格式是以前没有接触过的,是Toml。之前一直接触过的有yaml、json、ini之类的,这个今天还是第一次听,大概了解了下一个配置文件的相关内容。

GitHub觉得 YAML 不够简洁优雅,因此捣鼓出了一个TOML。TOML的全称是 Tom’s Obvious, Minimal Language,因为它的作者是 GitHub联合创始人Tom Preston-Werner 。

TOML 的目标是成为一个极简的配置文件格式。TOML 被设计成可以无歧义地被映射为哈希表,从而被多种语言解析。

是大小写敏感的。

可能是目前最好的配置文件格式。详细可以点击了解:
https://blog.csdn.net/john_f_lau/article/details/55803069
 
【Go语言核心36讲:第9节 字典的操作和约束】

Go 语言的字典类型(Map)其实是一个哈希表(hash table)的特定实现,在这个实现中,键和元素的最大不同在于,键的类型是受限的,而元素却可以是任意类型的。之所以会受限,是因为哈希表中的一个重要过程:映射。

映射过程的第一步就是:把键值转换为哈希值。

Go 语言规范规定,键类型的值必须要支持判等操作,所以字典的键类型不可以是函数类型、字典类型和切片类型。

应该优先考虑哪些类型作为字典的键类型?求哈希和判等操作的速度越快,对应的类型就越适合作为键类型。优先选用数值类型和指针类型,通常情况下类型的宽度越小越好。如果非要选择字符串类型的话,最好对键值的长度进行额外的约束。

除了添加键 - 元素对,我们在一个值为nil的字典上做任何操作都不会引起错误。当我们试图在一个值为nil的字典中添加键 - 元素对的时候,Go 语言的运行时系统就会立即抛出一个 panic。

永远要注意那些可能引发 panic 的操作,比如像一个值为nil的字典添加键 - 元素对。

什么是变量的深拷贝和浅拷贝?

专业名词zkbhj 发表了文章 • 0 个评论 • 376 次浏览 • 2020-07-24 10:41 • 来自相关话题

通用深拷贝和浅拷贝的区别
 

深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。

 假设B复制了A,修改A的时候,看B是否发生变化:

如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)

 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
 
Go语言中的深浅拷贝
 
1、深拷贝(Deep Copy):

拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。

值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。

2、浅拷贝(Shallow Copy):

拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。

引用类型的数据,默认全部都是浅复制,Slice,Map。
 
深拷贝示例:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {
fmt.Println("深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

fmt.Println("修改Robot1的Name属性值")
robot1.Name = "小白-X型-V1.1"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

}深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。
Robot 1:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
修改Robot1的Name属性值
Robot 1:{小白-X型-V1.1 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360浅拷贝示例2:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 使用new方式")
//new方式返回的是一个指针,是引用类型,所以会触发浅拷贝
robot1 := new(Robot)
robot1.Name = "小白-X型-V1.0"
robot1.Color = "白色"
robot1.Model = "小型"

robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小蓝-X型-V1.2"
robot1.Color = "蓝色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
}浅拷贝 使用new方式
Robot 1:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
Robot 2:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
在这里面修改Robot1的Name和Color属性
Robot 1:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
Robot 2:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330另外一种浅拷贝方式:&取指针赋值
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 内容和内存地址一样,改变其中一个对象的值时,另一个同时变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := &robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小黑-X型-V1.1"
robot1.Color = "黑色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

}
参考文档:
https://www.cnblogs.com/mikeCao/p/8710837.html
https://www.cnblogs.com/guichenglin/p/12736203.html
  查看全部
通用深拷贝和浅拷贝的区别
 


深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用


 假设B复制了A,修改A的时候,看B是否发生变化:


如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)


 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
 
Go语言中的深浅拷贝
 
1、深拷贝(Deep Copy):

拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。

值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。

2、浅拷贝(Shallow Copy):

拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。

引用类型的数据,默认全部都是浅复制,Slice,Map。
 
深拷贝示例:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {
fmt.Println("深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

fmt.Println("修改Robot1的Name属性值")
robot1.Name = "小白-X型-V1.1"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

}
深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。
Robot 1:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
修改Robot1的Name属性值
Robot 1:{小白-X型-V1.1 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
浅拷贝示例2:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 使用new方式")
//new方式返回的是一个指针,是引用类型,所以会触发浅拷贝
robot1 := new(Robot)
robot1.Name = "小白-X型-V1.0"
robot1.Color = "白色"
robot1.Model = "小型"

robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小蓝-X型-V1.2"
robot1.Color = "蓝色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
}
浅拷贝 使用new方式
Robot 1:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
Robot 2:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
在这里面修改Robot1的Name和Color属性
Robot 1:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
Robot 2:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
另外一种浅拷贝方式:&取指针赋值
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 内容和内存地址一样,改变其中一个对象的值时,另一个同时变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := &robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小黑-X型-V1.1"
robot1.Color = "黑色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

}

参考文档:
https://www.cnblogs.com/mikeCao/p/8710837.html
https://www.cnblogs.com/guichenglin/p/12736203.html
 

理解Golang中的函数、方法、闭包的本质

GoLangzkbhj 发表了文章 • 0 个评论 • 209 次浏览 • 2020-07-24 10:27 • 来自相关话题

函数的本质

在go的世界中,函数是一等公民,可以给变量赋值,可以作为参数传递,也可以直接赋值。
在go语言中将这样的变量、参数、返回值,即在堆空间和栈空间中绑定函数的值,称为function value。
函数的指令在编译期间生成,使用go tool compile -S main.go可以获取汇编代码, 以OSX 10.15.6,go 1.14为例,将看到下述汇编代码(下面只引用部分)...
"".B STEXT nosplit size=1 args=0x8 locals=0x0
0x0000 00000 (main.go:9) TEXT "".B(SB), NOSPLIT|ABIInternal, $0-8
0x0000 00000 (main.go:9) PCDATA $0, $-2
0x0000 00000 (main.go:9) PCDATA $1, $-2
0x0000 00000 (main.go:9) FUNCDATA $0, gclocals·2a5305abe05176240e61b8620e19a815(SB)
0x0000 00000 (main.go:9) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:9) FUNCDATA $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:11) PCDATA $0, $-1
0x0000 00000 (main.go:11) PCDATA $1, $-1
0x0000 00000 (main.go:11) RET
0x0000 c3
...运行时将存放在__TEXT段中,也就是存放在代码段中,读写权限为rx/rwx, 通过vmmap [pid]可以获取运行时的内存分布

==== Non-writable regions for process 13443
REGION TYPE START - END [ VSIZE RSDNT DIRTY SWAP] PRT/MAX SHRMOD PURGE REGION DETAIL
__TEXT 0000000001000000-0000000001167000 [ 1436K 1436K 0K 0K] r-x/rwx SM=COW .../test

使用otool -v -l [file]可以看到下述内容(下面只引用了一部分)...
Load command 1
cmd LC_SEGMENT_64
cmdsize 632
segname __TEXT
vmaddr 0x0000000001000000
vmsize 0x0000000000167000
fileoff 0
filesize 1470464
maxprot rwx
initprot r-x
nsects 7
flags (none)
Section
sectname __text
segname __TEXT
addr 0x0000000001001000
size 0x000000000009c365
offset 4096
align 2^4 (16)
reloff 0
nreloc 0
type S_REGULAR
attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS
reserved1 0
reserved2 0
...所以如果要问函数在go语言里的本质是什么,那么其实就是指向__TEXT段内存地址的一个指针。
 
函数调用的过程

在go语言中,每一个goroutine持有一个连续栈,栈基础大小为2kb,当栈大小超过预分配大小后,会触发栈扩容,也就是分配一个大小为当前栈2倍的新栈,并且将原来的栈拷贝到新的栈上。使用连续栈而不是分段栈的目的是,利用局部性优势提升执行速度,原理是CPU读取地址时会将相邻的内存读取到访问速度比内存快的多级cache中,地址连续性越好,L1、L2、L3 cache命中率越高,速度也就越快。

在go中,和其他一些语言有所不同,函数的返回值、参数都是由被caller保存。每次函数调用时,会在caller的栈中压入函数返回值列表、参数列表、函数返回时的PC地址,然后更改bp和pc为新函数,执行新函数,执行完之后将变量存到caller的栈空间中,利用栈空间中保存的返回地址和caller的栈基地址,恢复pc和sp回到caller的执行过程。

对于栈变量的访问是通过bp+offset的方式来访问,而对于在堆上分配的变量来说,就是通过地址来访问。在go中,变量被分配到堆上还是被分配到栈上是由编译器在编译时根据逃逸分析决定的,不可以更改,只能利用规则尽量让变量被分配到栈上,因为局部性优势,栈空间的内存访问速度快于堆空间访问。
 方法的本质

go里面其实方法就是语法糖,请看下述代码,两个Println打印的结果是一样的,实际上Method就是将receiver作为函数的第一个参数输入的语法糖而已,本质上和函数没有区别。type T struct {
name string
}

func (t T) Name() string {
return "Hi! " + t.name
}

func main() {
t := T{name: "test"}
fmt.Println(t.Name()) // Hi! test
fmt.Println(T.Name(t)) // Hi! test
}
 闭包的本质

前面已经提到在go语言中将这在堆空间和栈空间中绑定函数的值,称为function value。这也就是闭包在go语言中的实体。一个最简单的funcval实际上是通过二级指针指向__TEXT代码段上函数的结构体。

那我们来看下面这个闭包,也就是main函数中的变量ffunc getFunc() func() int {
a := 0
return func() int {
a++
return a
}
}

func main() {
f := getFunc()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}上面这段代码执行完后会输出1~10,也就是说f在执行的时候所使用的a会累计,但是a并不是一个全局变量,为什么f就变成了一个有状态的函数呢?其实这也就是go里面的闭包了。那我们来看go是如何实现闭包的。

首先来解释一下闭包的含义,闭包在实现上是一个结构体,需要存储函数入口和关联环境,关联环境包含约束变量(函数内部变量)和自由变量(函数外部变量,在函数外被定义,但是在函数内被引用,如例子中的变量a),和函数不同的是,在捕获闭包时才能确定自由变量,当脱离了捕捉变量的上下文时,也能照常运行。基于闭包可以很容易的定义异步调用的回调函数。

在 go 语言中,闭包的状态是通过捕获列表实现的。具体来说,有自由变量的闭包funcval的分配都在堆上,(没有自由变量的funcval在__DATA数据段上,和常量一样),funcval中除了包含地址以外,还会包含所引用的自由变量,所有自由变量构成捕获列表。对于会被修改的值,捕获的是值的指针,对于不会被修改的值,捕获的是值拷贝。
 
https://mp.weixin.qq.com/s/96Df9YGykE6daDREli9W1A
  查看全部
函数的本质

在go的世界中,函数是一等公民,可以给变量赋值,可以作为参数传递,也可以直接赋值。
在go语言中将这样的变量、参数、返回值,即在堆空间和栈空间中绑定函数的值,称为function value。
函数的指令在编译期间生成,使用go tool compile -S main.go可以获取汇编代码, 以OSX 10.15.6,go 1.14为例,将看到下述汇编代码(下面只引用部分)
...
"".B STEXT nosplit size=1 args=0x8 locals=0x0
0x0000 00000 (main.go:9) TEXT "".B(SB), NOSPLIT|ABIInternal, $0-8
0x0000 00000 (main.go:9) PCDATA $0, $-2
0x0000 00000 (main.go:9) PCDATA $1, $-2
0x0000 00000 (main.go:9) FUNCDATA $0, gclocals·2a5305abe05176240e61b8620e19a815(SB)
0x0000 00000 (main.go:9) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:9) FUNCDATA $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:11) PCDATA $0, $-1
0x0000 00000 (main.go:11) PCDATA $1, $-1
0x0000 00000 (main.go:11) RET
0x0000 c3
...
运行时将存放在__TEXT段中,也就是存放在代码段中,读写权限为rx/rwx, 通过vmmap [pid]可以获取运行时的内存分布

==== Non-writable regions for process 13443
REGION TYPE START - END [ VSIZE RSDNT DIRTY SWAP] PRT/MAX SHRMOD PURGE REGION DETAIL
__TEXT 0000000001000000-0000000001167000 [ 1436K 1436K 0K 0K] r-x/rwx SM=COW .../test

使用otool -v -l [file]可以看到下述内容(下面只引用了一部分)
...
Load command 1
cmd LC_SEGMENT_64
cmdsize 632
segname __TEXT
vmaddr 0x0000000001000000
vmsize 0x0000000000167000
fileoff 0
filesize 1470464
maxprot rwx
initprot r-x
nsects 7
flags (none)
Section
sectname __text
segname __TEXT
addr 0x0000000001001000
size 0x000000000009c365
offset 4096
align 2^4 (16)
reloff 0
nreloc 0
type S_REGULAR
attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS
reserved1 0
reserved2 0
...
所以如果要问函数在go语言里的本质是什么,那么其实就是指向__TEXT段内存地址的一个指针。
 
函数调用的过程

在go语言中,每一个goroutine持有一个连续栈,栈基础大小为2kb,当栈大小超过预分配大小后,会触发栈扩容,也就是分配一个大小为当前栈2倍的新栈,并且将原来的栈拷贝到新的栈上。使用连续栈而不是分段栈的目的是,利用局部性优势提升执行速度,原理是CPU读取地址时会将相邻的内存读取到访问速度比内存快的多级cache中,地址连续性越好,L1、L2、L3 cache命中率越高,速度也就越快。

在go中,和其他一些语言有所不同,函数的返回值、参数都是由被caller保存。每次函数调用时,会在caller的栈中压入函数返回值列表、参数列表、函数返回时的PC地址,然后更改bp和pc为新函数,执行新函数,执行完之后将变量存到caller的栈空间中,利用栈空间中保存的返回地址和caller的栈基地址,恢复pc和sp回到caller的执行过程。

对于栈变量的访问是通过bp+offset的方式来访问,而对于在堆上分配的变量来说,就是通过地址来访问。在go中,变量被分配到堆上还是被分配到栈上是由编译器在编译时根据逃逸分析决定的,不可以更改,只能利用规则尽量让变量被分配到栈上,因为局部性优势,栈空间的内存访问速度快于堆空间访问
 方法的本质

go里面其实方法就是语法糖,请看下述代码,两个Println打印的结果是一样的,实际上Method就是将receiver作为函数的第一个参数输入的语法糖而已,本质上和函数没有区别。
type T struct {
name string
}

func (t T) Name() string {
return "Hi! " + t.name
}

func main() {
t := T{name: "test"}
fmt.Println(t.Name()) // Hi! test
fmt.Println(T.Name(t)) // Hi! test
}

 闭包的本质

前面已经提到在go语言中将这在堆空间和栈空间中绑定函数的值,称为function value。这也就是闭包在go语言中的实体。一个最简单的funcval实际上是通过二级指针指向__TEXT代码段上函数的结构体。

那我们来看下面这个闭包,也就是main函数中的变量f
func getFunc() func() int {
a := 0
return func() int {
a++
return a
}
}

func main() {
f := getFunc()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
上面这段代码执行完后会输出1~10,也就是说f在执行的时候所使用的a会累计,但是a并不是一个全局变量,为什么f就变成了一个有状态的函数呢?其实这也就是go里面的闭包了。那我们来看go是如何实现闭包的。

首先来解释一下闭包的含义,闭包在实现上是一个结构体,需要存储函数入口和关联环境,关联环境包含约束变量(函数内部变量)和自由变量(函数外部变量,在函数外被定义,但是在函数内被引用,如例子中的变量a),和函数不同的是,在捕获闭包时才能确定自由变量,当脱离了捕捉变量的上下文时,也能照常运行。基于闭包可以很容易的定义异步调用的回调函数。

在 go 语言中,闭包的状态是通过捕获列表实现的。具体来说,有自由变量的闭包funcval的分配都在堆上,(没有自由变量的funcval在__DATA数据段上,和常量一样),funcval中除了包含地址以外,还会包含所引用的自由变量,所有自由变量构成捕获列表。对于会被修改的值,捕获的是值的指针,对于不会被修改的值,捕获的是值拷贝
 
https://mp.weixin.qq.com/s/96Df9YGykE6daDREli9W1A
 
   Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。