计算机知识

计算机知识

如果电脑访问网站总是被强制跳转到https访问且不通,要怎么解决?

回复

网络zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 189 次浏览 • 2021-05-06 16:47 • 来自相关话题

Yii2怎么在ActivityRecorder中直接生成插入时间?

回复

Yii框架zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 634 次浏览 • 2021-01-19 11:37 • 来自相关话题

如何在git连接上增加账号信息而不是每次操作都输入账号密码?

回复

工具软件zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 828 次浏览 • 2020-12-09 10:51 • 来自相关话题

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

GoLangzkbhj 发表了文章 • 0 个评论 • 594 次浏览 • 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 个评论 • 464 次浏览 • 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 个评论 • 473 次浏览 • 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 个回复 • 2687 次浏览 • 2020-09-17 11:32 • 来自相关话题

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

GoLangzkbhj 发表了文章 • 0 个评论 • 468 次浏览 • 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
 

MySQL如何通过sql查询数据的区间分布情况?

回复

数据库zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 1317 次浏览 • 2020-09-07 10:42 • 来自相关话题

#面试题集锦#实现单链表反转

面试zkbhj 发表了文章 • 0 个评论 • 578 次浏览 • 2020-08-14 17:16 • 来自相关话题

反转一个链表

示例:输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
结构体定义struct ListNode {
int val;
struct ListNode *next;
};
 





 思路一

先对原链表做头删操作,再对新链表做头插
定义一个新head头指针,标记为newHead,将它初始为NULL,并非指向NULL,最后我们选择返回这个newHead指针作为新链表的头指针。
定义一个结点node作为"临时中转站",初始化与否并无大影响。进行循环遍历链表各个结点,判定head指针是否为空,即是否到达了原链表的结尾。如果不为空,说明还没有到达尾部。如果程序第一次运行就没有进入循环,说明传入了一个空链表,此时返回newHead新链表的头指针,同样返回NULL,这样处理也是合理的。以下开始逆序链表逻辑:在当前指针不为NULL时,先对原链表做头删操作,再对新链表做头插操作。即使用循环进行操作:让node指针指向传入函数链表的头指针head,两指针指向保持相同。然后让head指针指向它的next结点,此时旧链表已经完成了头删操作。第一个结点已经被"切割"下来。接下来要做的就是对新链表进行头插操作,使结点放入新链表。让node指针的next下一个结点指向新链表的头指针newHead,完成结点的链接,即头插入新的链表中。然后更新newHead指向为新链表的头结点。进行下一次循环。最终head指针指向了原链表的结尾,即为NULL,退出循环,此时新链表已经反转完毕,情况如图:最终返回新链表头指针newHead即可。





 struct ListNode *reverseList(struct ListNode* head) {
struct ListNode *newHead = NULL;
struct ListNode *node;
while (head != NULL) {
//1. 对之前的链表做头删
node = head;
head = head->next;

//2. 对新链表做头插
node->next = newHead;
newHead = node;
}
return newHead;
}
思路二





利用选择语句,找到空链表的情况,此情况返回NULL空指针,因为空链表不能反转,或者说反转之后还是一个空链表,返回空。利用三个指针p0"前指针"、p1“当前指针”、p2"后指针"来分批处理链表元素,p0置为NULL,它将作为链表的尾结点向前推进处理,p1指向旧链表的头指针head,p2指向旧链表的头指针的next结点。开始遍历链表,循环判定因子为p1,当它为空时到达链表尾部跳出循环。否则在表中执行循环内逻辑:将p1指向的当前结点的下一个结点指向p0,即前一个结点。此时p0为NULL,那么p1的下一个结点就为空了,它现在是最后一个结点。然后将p0指针指向p1,将p1指针指向p2,注意这两步不可以调换顺序,否则正确向后挪移一位。此时完成了三个指针的一轮更迭。判定p2指针是否为空,如果为空说明此时p2到达了链表结尾,当前指针p1的指向为最后一个结点,它的next即为空。如果不为空,将p2更新到下一个结点,进行下一次循环。下一次进行循环时,就会把截断结点链接到新链表的头部,同时更新三个指针。继续循环。循环终止条件为:p1指向了链表尾部的NULL,此时p1的前指针p0即指向了反转后的链表,它就是新链表的head头指针。此时返回p0即可。





 struct ListNode *reverseList(struct ListNode* head) {
if (head == NULL) {
return NULL;
}
struct ListNode *p0 = NULL;
struct ListNode *p1 = head;
struct ListNode *p2 = head->next;
while (p1 != NULL) {
p1->next = p0;

p0 = p1;
p1 = p2;
if (p2 != NULL) {
p2 = p2->next;
}
}
return p0;
}
参考文档:
https://blog.csdn.net/qq_42351880/article/details/88637387
 
  查看全部
反转一个链表

示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

结构体定义
struct ListNode {
int val;
struct ListNode *next;
};

 

QQ截图20200814170600.jpg

 思路一

先对原链表做头删操作,再对新链表做头插
  1. 定义一个新head头指针,标记为newHead,将它初始为NULL,并非指向NULL,最后我们选择返回这个newHead指针作为新链表的头指针。

  1. 定义一个结点node作为"临时中转站",初始化与否并无大影响。
  2. 进行循环遍历链表各个结点,判定head指针是否为空,即是否到达了原链表的结尾。如果不为空,说明还没有到达尾部。如果程序第一次运行就没有进入循环,说明传入了一个空链表,此时返回newHead新链表的头指针,同样返回NULL,这样处理也是合理的。
  3. 以下开始逆序链表逻辑:在当前指针不为NULL时,先对原链表做头删操作,再对新链表做头插操作。即使用循环进行操作:
  4. 让node指针指向传入函数链表的头指针head,两指针指向保持相同。
  5. 然后让head指针指向它的next结点,此时旧链表已经完成了头删操作。第一个结点已经被"切割"下来。接下来要做的就是对新链表进行头插操作,使结点放入新链表。
  6. 让node指针的next下一个结点指向新链表的头指针newHead,完成结点的链接,即头插入新的链表中。然后更新newHead指向为新链表的头结点。进行下一次循环。
  7. 最终head指针指向了原链表的结尾,即为NULL,退出循环,此时新链表已经反转完毕,情况如图:
  8. 最终返回新链表头指针newHead即可。


QQ截图20200814170610.jpg

 
struct ListNode *reverseList(struct ListNode* head) {
struct ListNode *newHead = NULL;
struct ListNode *node;
while (head != NULL) {
//1. 对之前的链表做头删
node = head;
head = head->next;

//2. 对新链表做头插
node->next = newHead;
newHead = node;
}
return newHead;
}

思路二

QQ截图20200814171211.jpg

  1. 利用选择语句,找到空链表的情况,此情况返回NULL空指针,因为空链表不能反转,或者说反转之后还是一个空链表,返回空。
  2. 利用三个指针p0"前指针"、p1“当前指针”、p2"后指针"来分批处理链表元素,p0置为NULL,它将作为链表的尾结点向前推进处理,p1指向旧链表的头指针head,p2指向旧链表的头指针的next结点。
  3. 开始遍历链表,循环判定因子为p1,当它为空时到达链表尾部跳出循环。否则在表中执行循环内逻辑:将p1指向的当前结点的下一个结点指向p0,即前一个结点。此时p0为NULL,那么p1的下一个结点就为空了,它现在是最后一个结点。
  4. 然后将p0指针指向p1,将p1指针指向p2,注意这两步不可以调换顺序,否则正确向后挪移一位。此时完成了三个指针的一轮更迭。
  5. 判定p2指针是否为空,如果为空说明此时p2到达了链表结尾,当前指针p1的指向为最后一个结点,它的next即为空。如果不为空,将p2更新到下一个结点,进行下一次循环。
  6. 下一次进行循环时,就会把截断结点链接到新链表的头部,同时更新三个指针。继续循环。
  7. 循环终止条件为:p1指向了链表尾部的NULL,此时p1的前指针p0即指向了反转后的链表,它就是新链表的head头指针。此时返回p0即可。


QQ截图20200814171221.jpg

 
struct ListNode *reverseList(struct ListNode* head) {
if (head == NULL) {
return NULL;
}
struct ListNode *p0 = NULL;
struct ListNode *p1 = head;
struct ListNode *p2 = head->next;
while (p1 != NULL) {
p1->next = p0;

p0 = p1;
p1 = p2;
if (p2 != NULL) {
p2 = p2->next;
}
}
return p0;
}

参考文档:
https://blog.csdn.net/qq_42351880/article/details/88637387
 
 

如果电脑访问网站总是被强制跳转到https访问且不通,要怎么解决?

回复

网络zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 189 次浏览 • 2021-05-06 16:47 • 来自相关话题

Yii2怎么在ActivityRecorder中直接生成插入时间?

回复

Yii框架zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 634 次浏览 • 2021-01-19 11:37 • 来自相关话题

如何在git连接上增加账号信息而不是每次操作都输入账号密码?

回复

工具软件zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 828 次浏览 • 2020-12-09 10:51 • 来自相关话题

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

回复

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

MySQL如何通过sql查询数据的区间分布情况?

回复

数据库zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 1317 次浏览 • 2020-09-07 10:42 • 来自相关话题

如何给页面增加文字水印?

回复

前端开发zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 1300 次浏览 • 2020-08-06 14:31 • 来自相关话题

怎么理解软件开发中的上游和下游?

回复

专业名词zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 3498 次浏览 • 2020-07-22 09:35 • 来自相关话题

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

回复

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

生产者消费者模型是什么?

回复

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

一段程序,从生成可执行文件,到执行完成,数据在计算机内是怎么存储和分配的?

回复

常识zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 1224 次浏览 • 2020-06-09 17:34 • 来自相关话题

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

GoLangzkbhj 发表了文章 • 0 个评论 • 594 次浏览 • 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 个评论 • 464 次浏览 • 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 个评论 • 473 次浏览 • 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 个评论 • 468 次浏览 • 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
 

#面试题集锦#实现单链表反转

面试zkbhj 发表了文章 • 0 个评论 • 578 次浏览 • 2020-08-14 17:16 • 来自相关话题

反转一个链表

示例:输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
结构体定义struct ListNode {
int val;
struct ListNode *next;
};
 





 思路一

先对原链表做头删操作,再对新链表做头插
定义一个新head头指针,标记为newHead,将它初始为NULL,并非指向NULL,最后我们选择返回这个newHead指针作为新链表的头指针。
定义一个结点node作为"临时中转站",初始化与否并无大影响。进行循环遍历链表各个结点,判定head指针是否为空,即是否到达了原链表的结尾。如果不为空,说明还没有到达尾部。如果程序第一次运行就没有进入循环,说明传入了一个空链表,此时返回newHead新链表的头指针,同样返回NULL,这样处理也是合理的。以下开始逆序链表逻辑:在当前指针不为NULL时,先对原链表做头删操作,再对新链表做头插操作。即使用循环进行操作:让node指针指向传入函数链表的头指针head,两指针指向保持相同。然后让head指针指向它的next结点,此时旧链表已经完成了头删操作。第一个结点已经被"切割"下来。接下来要做的就是对新链表进行头插操作,使结点放入新链表。让node指针的next下一个结点指向新链表的头指针newHead,完成结点的链接,即头插入新的链表中。然后更新newHead指向为新链表的头结点。进行下一次循环。最终head指针指向了原链表的结尾,即为NULL,退出循环,此时新链表已经反转完毕,情况如图:最终返回新链表头指针newHead即可。





 struct ListNode *reverseList(struct ListNode* head) {
struct ListNode *newHead = NULL;
struct ListNode *node;
while (head != NULL) {
//1. 对之前的链表做头删
node = head;
head = head->next;

//2. 对新链表做头插
node->next = newHead;
newHead = node;
}
return newHead;
}
思路二





利用选择语句,找到空链表的情况,此情况返回NULL空指针,因为空链表不能反转,或者说反转之后还是一个空链表,返回空。利用三个指针p0"前指针"、p1“当前指针”、p2"后指针"来分批处理链表元素,p0置为NULL,它将作为链表的尾结点向前推进处理,p1指向旧链表的头指针head,p2指向旧链表的头指针的next结点。开始遍历链表,循环判定因子为p1,当它为空时到达链表尾部跳出循环。否则在表中执行循环内逻辑:将p1指向的当前结点的下一个结点指向p0,即前一个结点。此时p0为NULL,那么p1的下一个结点就为空了,它现在是最后一个结点。然后将p0指针指向p1,将p1指针指向p2,注意这两步不可以调换顺序,否则正确向后挪移一位。此时完成了三个指针的一轮更迭。判定p2指针是否为空,如果为空说明此时p2到达了链表结尾,当前指针p1的指向为最后一个结点,它的next即为空。如果不为空,将p2更新到下一个结点,进行下一次循环。下一次进行循环时,就会把截断结点链接到新链表的头部,同时更新三个指针。继续循环。循环终止条件为:p1指向了链表尾部的NULL,此时p1的前指针p0即指向了反转后的链表,它就是新链表的head头指针。此时返回p0即可。





 struct ListNode *reverseList(struct ListNode* head) {
if (head == NULL) {
return NULL;
}
struct ListNode *p0 = NULL;
struct ListNode *p1 = head;
struct ListNode *p2 = head->next;
while (p1 != NULL) {
p1->next = p0;

p0 = p1;
p1 = p2;
if (p2 != NULL) {
p2 = p2->next;
}
}
return p0;
}
参考文档:
https://blog.csdn.net/qq_42351880/article/details/88637387
 
  查看全部
反转一个链表

示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

结构体定义
struct ListNode {
int val;
struct ListNode *next;
};

 

QQ截图20200814170600.jpg

 思路一

先对原链表做头删操作,再对新链表做头插
  1. 定义一个新head头指针,标记为newHead,将它初始为NULL,并非指向NULL,最后我们选择返回这个newHead指针作为新链表的头指针。

  1. 定义一个结点node作为"临时中转站",初始化与否并无大影响。
  2. 进行循环遍历链表各个结点,判定head指针是否为空,即是否到达了原链表的结尾。如果不为空,说明还没有到达尾部。如果程序第一次运行就没有进入循环,说明传入了一个空链表,此时返回newHead新链表的头指针,同样返回NULL,这样处理也是合理的。
  3. 以下开始逆序链表逻辑:在当前指针不为NULL时,先对原链表做头删操作,再对新链表做头插操作。即使用循环进行操作:
  4. 让node指针指向传入函数链表的头指针head,两指针指向保持相同。
  5. 然后让head指针指向它的next结点,此时旧链表已经完成了头删操作。第一个结点已经被"切割"下来。接下来要做的就是对新链表进行头插操作,使结点放入新链表。
  6. 让node指针的next下一个结点指向新链表的头指针newHead,完成结点的链接,即头插入新的链表中。然后更新newHead指向为新链表的头结点。进行下一次循环。
  7. 最终head指针指向了原链表的结尾,即为NULL,退出循环,此时新链表已经反转完毕,情况如图:
  8. 最终返回新链表头指针newHead即可。


QQ截图20200814170610.jpg

 
struct ListNode *reverseList(struct ListNode* head) {
struct ListNode *newHead = NULL;
struct ListNode *node;
while (head != NULL) {
//1. 对之前的链表做头删
node = head;
head = head->next;

//2. 对新链表做头插
node->next = newHead;
newHead = node;
}
return newHead;
}

思路二

QQ截图20200814171211.jpg

  1. 利用选择语句,找到空链表的情况,此情况返回NULL空指针,因为空链表不能反转,或者说反转之后还是一个空链表,返回空。
  2. 利用三个指针p0"前指针"、p1“当前指针”、p2"后指针"来分批处理链表元素,p0置为NULL,它将作为链表的尾结点向前推进处理,p1指向旧链表的头指针head,p2指向旧链表的头指针的next结点。
  3. 开始遍历链表,循环判定因子为p1,当它为空时到达链表尾部跳出循环。否则在表中执行循环内逻辑:将p1指向的当前结点的下一个结点指向p0,即前一个结点。此时p0为NULL,那么p1的下一个结点就为空了,它现在是最后一个结点。
  4. 然后将p0指针指向p1,将p1指针指向p2,注意这两步不可以调换顺序,否则正确向后挪移一位。此时完成了三个指针的一轮更迭。
  5. 判定p2指针是否为空,如果为空说明此时p2到达了链表结尾,当前指针p1的指向为最后一个结点,它的next即为空。如果不为空,将p2更新到下一个结点,进行下一次循环。
  6. 下一次进行循环时,就会把截断结点链接到新链表的头部,同时更新三个指针。继续循环。
  7. 循环终止条件为:p1指向了链表尾部的NULL,此时p1的前指针p0即指向了反转后的链表,它就是新链表的head头指针。此时返回p0即可。


QQ截图20200814171221.jpg

 
struct ListNode *reverseList(struct ListNode* head) {
if (head == NULL) {
return NULL;
}
struct ListNode *p0 = NULL;
struct ListNode *p1 = head;
struct ListNode *p2 = head->next;
while (p1 != NULL) {
p1->next = p0;

p0 = p1;
p1 = p2;
if (p2 != NULL) {
p2 = p2->next;
}
}
return p0;
}

参考文档:
https://blog.csdn.net/qq_42351880/article/details/88637387
 
 

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

总结zkbhj 发表了文章 • 0 个评论 • 573 次浏览 • 2020-08-06 09:31 • 来自相关话题

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

第二章 信息的表示和处理

表示代码int sum(int x, int y) {
return x + y;
}当我们在不同的机器上编译上面的程序得到的机器代码都不尽相同:

Linux32     55 89 e5 8b 45 0c 03 45 08 c9 c3
Windows  55 89 e5 8b 45 0c 03 45 08 5d c3
Sun           81 c3 e0 08 90 03 00 09


因此,二进制代码是不兼容的,无法在不同机器之间移植。

这得到一个计算机系统的基本概念:从机器的角度来看,程序仅仅只是字节序列,机器没有关于原始程序的任何信息。

布尔代数简介

计算机的核心都是围绕1和0来演化的。对于0和1的起源,要追溯到1850年前后乔治·布尔的工作,所以这个也叫布尔代数。即通过将逻辑值TRUE和FALSE编码为二进制1和0设计出的一种代数,以研究逻辑推理的基本原则。






上面分别列出了~(NOT)、&(AND)、|(OR)和^(EXCLUSIVE-OR)四种基本运算。

后来创立信息论领域的Claude Shannon首先建立了布尔代数和数字逻辑之间的联系。

将上述基础的布尔运算扩展到位向量运算。位向量就是固定长度为w、由0和1组成的串。

假设 w=4,a=[0110],b=[1100]。那么四种运算 a&b、a|b、a^b、~b 结果分别如下:





 
布尔运算&对|满足分配率:a&(b|c) = (a&b)|(a&c);反过来,|也满足对&的分配率,即:a|(b&c) = (a|b) & (a|c)。

位向量的一个有用应用就是表示有限集合,即用位向量来给集合进行编码。

C语言的一个有用特性就是支持按位布尔运算。|、&、~、^这些运算可以用到任何“整型”的数据类型上。void inplace_swap(int *x, int *y){
*y = *x ^ *y;
*x = *x ^ *y;
*y = *x ^ *y;
}
上面这段代码,就是利用了两个事实来实现*x和*y所指向的变量值进行了交换操作。两个事实分别是:

异或运算是可交换和可结合的;

对于任意的a, a ^ a = 0;

所以上述程序的计算过程如下:

初始:*x = a    *y = b
第一步:*x = a   *y = a ^ b
第二步:*x = a ^ (a ^ b) = (a ^ a) ^ b = b   *y = a ^ b
第三部:*x = b   *y = b ^ ( a ^ b) = (b ^ b) ^ a = a


但是注意,这种方式和通常的交换两个数值的技术不一样,当移动一个值时,我们不需要第三个位置来临时存放另外一个值。这种交换方式并没有性能上的优势,它仅仅是一个智力游戏!

位级运算常见的用法就是实现掩码运算:掩码是一个位模式,表示从一个字中选出的位的集合。

比如对于掩码0xFF(最低的8位都是1)表示一个字的低位字节。x&0xFF会得到一个由x的最低有效字节组成的值。 
 
 
【英文中几点钟的说法o'clock是什么的缩写?】

o'clock = of the clock.

在14世纪以前,人类还没有发明出来时钟,都是通过一些其他途径来获取和感知时间,比如日晷、沙漏等。直到 14 世纪,现代意义上的时钟雏形才得以发明。当时的时钟会自己报时“说出”:It's 7 of the clock!后来,随着时钟的普及和大众化,人们开始将 of 的 f 和 the 一带而过,简略地读成了 o'clock。
 https://ask.zkbhj.com/?/article/370 查看全部

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

第二章 信息的表示和处理

表示代码
int sum(int x, int y) {
return x + y;
}
当我们在不同的机器上编译上面的程序得到的机器代码都不尽相同:


Linux32     55 89 e5 8b 45 0c 03 45 08 c9 c3
Windows  55 89 e5 8b 45 0c 03 45 08 5d c3
Sun           81 c3 e0 08 90 03 00 09



因此,二进制代码是不兼容的,无法在不同机器之间移植。

这得到一个计算机系统的基本概念:从机器的角度来看,程序仅仅只是字节序列,机器没有关于原始程序的任何信息。

布尔代数简介

计算机的核心都是围绕1和0来演化的。对于0和1的起源,要追溯到1850年前后乔治·布尔的工作,所以这个也叫布尔代数。即通过将逻辑值TRUE和FALSE编码为二进制1和0设计出的一种代数,以研究逻辑推理的基本原则。

20200806092721.jpg


上面分别列出了~(NOT)、&(AND)、|(OR)和^(EXCLUSIVE-OR)四种基本运算。

后来创立信息论领域的Claude Shannon首先建立了布尔代数和数字逻辑之间的联系。

将上述基础的布尔运算扩展到位向量运算。位向量就是固定长度为w、由0和1组成的串。

假设 w=4,a=[0110],b=[1100]。那么四种运算 a&b、a|b、a^b、~b 结果分别如下:

20200806092735.jpg

 
布尔运算&对|满足分配率:a&(b|c) = (a&b)|(a&c);反过来,|也满足对&的分配率,即:a|(b&c) = (a|b) & (a|c)。

位向量的一个有用应用就是表示有限集合,即用位向量来给集合进行编码。

C语言的一个有用特性就是支持按位布尔运算。|、&、~、^这些运算可以用到任何“整型”的数据类型上。
void inplace_swap(int *x, int *y){
*y = *x ^ *y;
*x = *x ^ *y;
*y = *x ^ *y;
}

上面这段代码,就是利用了两个事实来实现*x和*y所指向的变量值进行了交换操作。两个事实分别是:

异或运算是可交换和可结合的;

对于任意的a, a ^ a = 0;

所以上述程序的计算过程如下:


初始:*x = a    *y = b
第一步:*x = a   *y = a ^ b
第二步:*x = a ^ (a ^ b) = (a ^ a) ^ b = b   *y = a ^ b
第三部:*x = b   *y = b ^ ( a ^ b) = (b ^ b) ^ a = a



但是注意,这种方式和通常的交换两个数值的技术不一样,当移动一个值时,我们不需要第三个位置来临时存放另外一个值。这种交换方式并没有性能上的优势,它仅仅是一个智力游戏!

位级运算常见的用法就是实现掩码运算:掩码是一个位模式,表示从一个字中选出的位的集合。

比如对于掩码0xFF(最低的8位都是1)表示一个字的低位字节。x&0xFF会得到一个由x的最低有效字节组成的值。 
 
 
英文中几点钟的说法o'clock是什么的缩写?】


o'clock = of the clock.


在14世纪以前,人类还没有发明出来时钟,都是通过一些其他途径来获取和感知时间,比如日晷、沙漏等。直到 14 世纪,现代意义上的时钟雏形才得以发明。当时的时钟会自己报时“说出”:It's 7 of the clock!后来,随着时钟的普及和大众化,人们开始将 of 的 f 和 the 一带而过,简略地读成了 o'clock。
 https://ask.zkbhj.com/?/article/370

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

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

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

第二章 信息的表示和处理

寻址和字节顺序

在几乎所有机器上,多字节对象被存储为连续的字节序列,对象的地址为最小的地址。

排列表示一个对象的字节有两个通用的规则:

小端法:在内存中按照从最低有效字节到最高有效字节的顺序存储对象;
大端法:在内存中按照从最高有效字节到最低有效字节的顺序存储对象;一旦选择了特定的操作系统,那么字节顺序也就固定下来。Android和IOS只能够运行于小端模式下。

两种方式没有谁好谁坏之分,对于那种字节排序的选择都是任意的。
大小端的说法来自于Jonathan Swift的《格利弗游记》一书,其中交战的两个派别无法就打开一个半熟的鸡蛋应该从哪一端打开达成一致意见。
不同的端模式下,会有以下影响:
1、通过网络在不同端模式的机器间传递数据时,发送和接收时,需要转换网络标准;
2、在检查机器级程序时,对数据的解读方式;
3、在编写规避正常的类型系统的程序时。​#include <stdio.h>

typedef unsigned char *byte_pointer;
//typedef char *byte_pointer;

void show_bytes(byte_pointer start, size_t len) {
size_t i;
for (i = 0; i < len; i++)
printf("%p\t0x%.2x\n", &start, start);
printf("\n");
}

void show_int(int x) {
show_bytes((byte_pointer) &x, sizeof(int));
}

void show_float(float x) {
show_bytes((byte_pointer) &x, sizeof(float));
}

void show_pointer(void *x) {
show_bytes((byte_pointer) &x, sizeof(void *));
}

​在这段程序中,“byte_pointer start”告诉编译器,应该把这个指针看成指向一个字节序列,而不是指向一个原始数据类型的对象。然后,这个指针会被看成是对象使用的最低字节地址。
这种强制类型转换不会改变真实的指针,它们只是告诉编译器以新的数据类型来看待被指向的数据。
使用ASCII码作为字符码的任何系统上都是将得到相同的结果,与字节顺序和字节大小规则无关。因而,文本数据比二进制数据具有更强的平台独立性。
 
【HTML页面里怎么实现代码包含?】
https://ask.zkbhj.com/?/question/387
 
【Yii2框架中如何区分不同的场景指定赋值字段和检验规则】
 
场景(scenario)

分析上面问题,会发现关键点是批量赋值(massive assignment)和数据校验(validate)两个方法。如果对不同的场景指定赋值字段和检验规则,问题就迎刃而解。

Yii中的scenario有 安全属性 和 活跃属性 两个概念。安全属性用在批量赋值的load方法,只有安全属性才能被赋值;活跃属性用在规则校验的validate方法,在活跃属性集中并且定义了校验规则的属性才会被校验。活跃属性和安全属性的关系是,安全属性是活跃属性的子集。

\yii\base\Model类定义了默认场景:SCENARIO_DEFAULT(值为default)。默认场景下,出现在rules方法中的属性既是活跃属性,又是安全属性(这句话基本正确,看后续解释)。为不同场景指定活跃属性、安全属性以及校验器,可以通过覆盖senarios或rules两个方法实现(几乎每个Model类都会重写rules方法,senarios用得少)。

rules

先看rules方法。默认的属性加校验器定义方式,让每个属性既是安全属性,也是活跃属性。如果想让某个属性不是安全属性(不能通过load批量赋值),在属性名前加感叹号!即可。例如student中的user_id字段:public function rules()
{
return [
["!user_od", "required"],
["!user_id", "integer"],
["!user_od", "unique"],
// other rules
];
}user_id是活跃属性,在写入数据库时会被校验。但它不是安全属性,不能通过load方法进行赋值,解决了安全隐患。

再看rules方法按场景区分校验器规则的做法:定义校验器时on属性指定规则在哪些场景下生效,except属性则排除一些场景(如果不指定on和except,规则对所有场景生效)。例如:public function rules()
{
return [
["password", "string", "length" => [8, 16], "on" => ["signup"]], // 仅在signup场景时才被验证
["status", "integer", "except" => ["signup"], // 除了signup场景,其他情况都校验
// other rules
];
}在原来基础上新增感叹号和on/except属性,非常简便的就定义了非安全属性以及分场景指定校验规则。

scenarios

另外一种更清晰定义安全属性和活跃属性的做法是重写scenarios方法。scenarios方法返回一个数组,数组的键是场景名称,值是活跃属性集合(包饭安全属性)。例如student表的可能实现如下:public function scenarios()
{
return [
self::SCENARIO_DEFAULT => ["!user_id", "grade", "class", xxxx],
"update" => ["grade", "class", xxxx],
];
}默认情形下(学生报名),年级、班级这些信息是安全属性,但user_id不是,只能在程序内部赋值,并在插入数据时被校验;在修改信息时,user_id不是活跃属性,既不能被批量赋值,也不需要校验(事实上它不应该改变)。

scenarios方法只能定义活跃属性和安全属性,无法定义校验规则,需要和rules配合使用。
https://www.cnblogs.com/yangxunwu1992/p/6669380.html

 
【关于未来几年国内经济形势的分析总结】】
 
疫情发生以来,国内外经济形势发生了重大变化。国际局势也从全球化走向了逆全球化的道路,美国通过各种手段(贸易战、“中国病毒”论、打压华为、打压Tik Tok、关闭领事馆、干涉中国内政等)不断打压中国,各大企业也在不断将企业撤出中国。
 
最近最热门的经济词汇就是经济内循环,意思是说,以后要以国内市场为主,国内生产出来的东西,主要在自己国内消化掉,重新转化为生产力。也就是走内需拉动经济增长的路子。这是在疫情发生以及国内外形式发生如此变化之后的无奈之举。
 
我们过去拉动经济的三驾马车是外贸,基建和房地产,内需一向很薄弱,这几年内需份额有所提高,但还不足以成为拉动经济增长的主要动力。
 
最主要体现在居民收入不足,杠杆过高,中产被绑在房地产的战车上面等。
 
从微观层面看,中国消费结构呈现两边高中间低的M字型结构(健康的结构应该是相反的,两边低中间高,中产成为消费的主力)。所以中国目前的情况就是有钱人并不会受到高房价的影响而降低消费,反而还因此提高了消费能力,还在消费升级。然后相对经济收入低的人群消费能力本就低,地摊经济很火就说明这个问题。中产,由于购房等压力,从中端消费跌落到低端消费。
 
所以,国内要实现以经济内循环为主,国际循环为辅的国内国际双循环的新发展格局,有以下几种办法:
1、提高居民购买力
主要就是降房价和提高居民收入,提高居民收入很难,现在经济太差,没有需求就没有工作岗位,居民收入不下降已经很不错,但房价是有可能缓慢下降的

2、发展房地产。
过去十几年就是这么干的,但现在房价太高,已经到了伤害经济的底部,已经不能继续走拉抬房价发展经济的老路。

3、让股市走出慢牛。
目前看也相对可行,上层也一直在强调要通过资本市场服务实体经济,这也是我一向看好A股走牛的重要原因。
 
4、发展高科技和人民币国际化。
 
讲通俗一点,就是:

经济内循环其实就是要过苦日子的另一个代名词。也可以简单理解为好好干活,但不要想着能挣很多钱。在未来很长一段时间里面,物价会相对比较便宜。

 
https://zhuanlan.zhihu.com/p/165347415
 
【Go语言核心36讲:第12节 使用函数的正确姿势(1)】
 
在 Go 语言中,函数可是一等的(first-class)公民,函数类型也是一等的数据类型(函数类型属于引用类型,它的零值为nil)。
这意味着函数不但可以用于封装代码、分割功能、解耦逻辑,还可以化身为普通的值,在其他函数间传递、赋予变量、做类型判断和转换等。“函数是一等的公民”是函数式编程(functional programming)的重要特征。Go 语言在语言层面支持了函数式编程。

package main

import "fmt"

type Printer func(contents string) (n int, err error)

func printToStd(contents string) (bytesNum int, err error) {
return fmt.Println(contents)
}

func main() {
var p Printer
p = printToStd
p("something")
}函数的签名其实就是函数的参数列表和结果列表的统称,它定义了可用来鉴别不同函数的那些特征,同时也定义了我们与函数交互的方式。

各个参数和结果的名称不能算作函数签名的一部分,对于结果声明,名字也可以没有。且函数的名称也不算函数签名的一部分,只是个标识符而已。

高阶函数
 

1. 接受其他的函数作为参数传入;
2. 把其他的函数作为结果返回。

只要满足了其中任意一个特点,我们就可以说这个函数是一个高阶函数。
 
卫述语句
 
卫述语句是指被用来检查关键的先决条件的合法性,并在检查未通过的情况下立即终止当前代码块执行的语句。在 Go 语言中,if 语句常被作为卫述语句。
if op == nil {
return 0, errors.New("invalid operation")
} 查看全部
【早读:《深入理解计算机系统》】

第二章 信息的表示和处理

寻址和字节顺序

在几乎所有机器上,多字节对象被存储为连续的字节序列,对象的地址为最小的地址。

排列表示一个对象的字节有两个通用的规则:


小端法:在内存中按照从最低有效字节到最高有效字节的顺序存储对象;
大端法:在内存中按照从最高有效字节到最低有效字节的顺序存储对象;一旦选择了特定的操作系统,那么字节顺序也就固定下来。Android和IOS只能够运行于小端模式下。


两种方式没有谁好谁坏之分,对于那种字节排序的选择都是任意的。
大小端的说法来自于Jonathan Swift的《格利弗游记》一书,其中交战的两个派别无法就打开一个半熟的鸡蛋应该从哪一端打开达成一致意见。
不同的端模式下,会有以下影响:
1、通过网络在不同端模式的机器间传递数据时,发送和接收时,需要转换网络标准;
2、在检查机器级程序时,对数据的解读方式;
3、在编写规避正常的类型系统的程序时。
​#include <stdio.h>

typedef unsigned char *byte_pointer;
//typedef char *byte_pointer;

void show_bytes(byte_pointer start, size_t len) {
size_t i;
for (i = 0; i < len; i++)
printf("%p\t0x%.2x\n", &start, start);
printf("\n");
}

void show_int(int x) {
show_bytes((byte_pointer) &x, sizeof(int));
}

void show_float(float x) {
show_bytes((byte_pointer) &x, sizeof(float));
}

void show_pointer(void *x) {
show_bytes((byte_pointer) &x, sizeof(void *));
}

​在这段程序中,“byte_pointer start”告诉编译器,应该把这个指针看成指向一个字节序列,而不是指向一个原始数据类型的对象。然后,这个指针会被看成是对象使用的最低字节地址。
这种强制类型转换不会改变真实的指针,它们只是告诉编译器以新的数据类型来看待被指向的数据。
使用ASCII码作为字符码的任何系统上都是将得到相同的结果,与字节顺序和字节大小规则无关。因而,文本数据比二进制数据具有更强的平台独立性。
 
【HTML页面里怎么实现代码包含?】
https://ask.zkbhj.com/?/question/387
 
【Yii2框架中如何区分不同的场景指定赋值字段和检验规则】
 
场景(scenario)

分析上面问题,会发现关键点是批量赋值(massive assignment)和数据校验(validate)两个方法。如果对不同的场景指定赋值字段和检验规则,问题就迎刃而解。

Yii中的scenario有 安全属性 和 活跃属性 两个概念。安全属性用在批量赋值的load方法,只有安全属性才能被赋值;活跃属性用在规则校验的validate方法,在活跃属性集中并且定义了校验规则的属性才会被校验。活跃属性和安全属性的关系是,安全属性是活跃属性的子集。

\yii\base\Model类定义了默认场景:SCENARIO_DEFAULT(值为default)。默认场景下,出现在rules方法中的属性既是活跃属性,又是安全属性(这句话基本正确,看后续解释)。为不同场景指定活跃属性、安全属性以及校验器,可以通过覆盖senarios或rules两个方法实现(几乎每个Model类都会重写rules方法,senarios用得少)。

rules

先看rules方法。默认的属性加校验器定义方式,让每个属性既是安全属性,也是活跃属性。如果想让某个属性不是安全属性(不能通过load批量赋值),在属性名前加感叹号!即可。例如student中的user_id字段:
public function rules()
{
return [
["!user_od", "required"],
["!user_id", "integer"],
["!user_od", "unique"],
// other rules
];
}
user_id是活跃属性,在写入数据库时会被校验。但它不是安全属性,不能通过load方法进行赋值,解决了安全隐患。

再看rules方法按场景区分校验器规则的做法:定义校验器时on属性指定规则在哪些场景下生效,except属性则排除一些场景(如果不指定on和except,规则对所有场景生效)。例如:
public function rules()
{
return [
["password", "string", "length" => [8, 16], "on" => ["signup"]], // 仅在signup场景时才被验证
["status", "integer", "except" => ["signup"], // 除了signup场景,其他情况都校验
// other rules
];
}
在原来基础上新增感叹号和on/except属性,非常简便的就定义了非安全属性以及分场景指定校验规则。

scenarios

另外一种更清晰定义安全属性和活跃属性的做法是重写scenarios方法。scenarios方法返回一个数组,数组的键是场景名称,值是活跃属性集合(包饭安全属性)。例如student表的可能实现如下:
public function scenarios()
{
return [
self::SCENARIO_DEFAULT => ["!user_id", "grade", "class", xxxx],
"update" => ["grade", "class", xxxx],
];
}
默认情形下(学生报名),年级、班级这些信息是安全属性,但user_id不是,只能在程序内部赋值,并在插入数据时被校验;在修改信息时,user_id不是活跃属性,既不能被批量赋值,也不需要校验(事实上它不应该改变)。

scenarios方法只能定义活跃属性和安全属性,无法定义校验规则,需要和rules配合使用。
https://www.cnblogs.com/yangxunwu1992/p/6669380.html

 
【关于未来几年国内经济形势的分析总结】】
 
疫情发生以来,国内外经济形势发生了重大变化。国际局势也从全球化走向了逆全球化的道路,美国通过各种手段(贸易战、“中国病毒”论、打压华为、打压Tik Tok、关闭领事馆、干涉中国内政等)不断打压中国,各大企业也在不断将企业撤出中国。
 
最近最热门的经济词汇就是经济内循环,意思是说,以后要以国内市场为主,国内生产出来的东西,主要在自己国内消化掉,重新转化为生产力。也就是走内需拉动经济增长的路子。这是在疫情发生以及国内外形式发生如此变化之后的无奈之举。
 
我们过去拉动经济的三驾马车是外贸,基建和房地产,内需一向很薄弱,这几年内需份额有所提高,但还不足以成为拉动经济增长的主要动力。
 
最主要体现在居民收入不足杠杆过高中产被绑在房地产的战车上面等。
 
从微观层面看,中国消费结构呈现两边高中间低的M字型结构(健康的结构应该是相反的,两边低中间高,中产成为消费的主力)。所以中国目前的情况就是有钱人并不会受到高房价的影响而降低消费,反而还因此提高了消费能力,还在消费升级。然后相对经济收入低的人群消费能力本就低,地摊经济很火就说明这个问题。中产,由于购房等压力,从中端消费跌落到低端消费。
 
所以,国内要实现以经济内循环为主,国际循环为辅的国内国际双循环的新发展格局,有以下几种办法:
1、提高居民购买力
主要就是降房价和提高居民收入,提高居民收入很难,现在经济太差,没有需求就没有工作岗位,居民收入不下降已经很不错,但房价是有可能缓慢下降的

2、发展房地产。
过去十几年就是这么干的,但现在房价太高,已经到了伤害经济的底部,已经不能继续走拉抬房价发展经济的老路。

3、让股市走出慢牛。
目前看也相对可行,上层也一直在强调要通过资本市场服务实体经济,这也是我一向看好A股走牛的重要原因。
 
4、发展高科技和人民币国际化。
 
讲通俗一点,就是:


经济内循环其实就是要过苦日子的另一个代名词。也可以简单理解为好好干活,但不要想着能挣很多钱。在未来很长一段时间里面,物价会相对比较便宜。


 
https://zhuanlan.zhihu.com/p/165347415
 
【Go语言核心36讲:第12节 使用函数的正确姿势(1)】
 
在 Go 语言中,函数可是一等的(first-class)公民,函数类型也是一等的数据类型(函数类型属于引用类型,它的零值为nil)。
这意味着函数不但可以用于封装代码、分割功能、解耦逻辑,还可以化身为普通的值,在其他函数间传递、赋予变量、做类型判断和转换等。“函数是一等的公民”是函数式编程(functional programming)的重要特征。Go 语言在语言层面支持了函数式编程。

package main

import "fmt"

type Printer func(contents string) (n int, err error)

func printToStd(contents string) (bytesNum int, err error) {
return fmt.Println(contents)
}

func main() {
var p Printer
p = printToStd
p("something")
}
函数的签名其实就是函数的参数列表和结果列表的统称,它定义了可用来鉴别不同函数的那些特征,同时也定义了我们与函数交互的方式。


各个参数和结果的名称不能算作函数签名的一部分,对于结果声明,名字也可以没有。且函数的名称也不算函数签名的一部分,只是个标识符而已。


高阶函数
 


1. 接受其他的函数作为参数传入;
2. 把其他的函数作为结果返回。


只要满足了其中任意一个特点,我们就可以说这个函数是一个高阶函数。
 
卫述语句
 
卫述语句是指被用来检查关键的先决条件的合法性,并在检查未通过的情况下立即终止当前代码块执行的语句。在 Go 语言中,if 语句常被作为卫述语句。
if op == nil { 
return 0, errors.New("invalid operation")
}

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

总结zkbhj 发表了文章 • 0 个评论 • 618 次浏览 • 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 个评论 • 591 次浏览 • 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 个评论 • 547 次浏览 • 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!

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