一种获取context中keys和values的高效方法 | golang
我们知道,在 golang 中的 context 是一个非常重要的包,保存了代码活动的上下文。我们经常使用 WithValue() 这个方法,来往 context 中 传递一些 key value 数据。
如果我们想拿到 context 中所有的 key and value 或者在不知道 key 的情况想获得value 要怎么做呢?这里提供一个比较hacker的实现给大家参考。
调研
首先,看看WithValue到底发生了什么:
package context
func WithValue(parent Context, key, val interface{}) Context {
return &valueCtx{parent, key, val}
}
// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.
type valueCtx struct {
Context
key, val interface{}
}
WithValue 通过把 key value 塞到了 valueCtx 的 struct 中,将数据保存下来。
通过研究 context 包我们发现,不同的 功能的context有不同的实现
package context
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
// implement Done and Err. It implements cancel by stopping its timer then
// delegating to cancelCtx.cancel.
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done chan struct{} // created lazily, closed by first cancel call
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
但无一例外,全部是私有 struct ,同时是通过链表的形式将各个 context 串在一起的。
这种情况对与我们想要做的事情是比较不好的,我们无法将 context 接口转换成实现来获取其内部保存的 key and value,而且由于有多种实现,我们无法得知下一个 context 是不是 valueCtx,需不需要跳过。
思路
既然这样,我们就只能用一些比较 hacker 的方法了:
- 定义一个自己的 valueCtx 内部数据结构与 context 包中一致
- 通过 unsafe.Pointer() 绕过类型检测,强制将 context.valueCtx 转换成我们的 valueCtx
- 获取内部的值保存在 map 中
实践
首先自定义一个我们自己的 valueCtx ,直接照搬 context 的实现就行:
package main
type valueCtx struct {
context.Context
key, val interface{}
}
然后强转并打印:
package main
func main() {
ctx := context.Background()
ctx = context.WithValue(ctx, "key1", "value1")
valCtx := (*valueCtx)(unsafe.Pointer(&ctx))
fmt.Printf("key: %v, value: %v", valCtx.key, valCtx.val)
// panic: runtime error: invalid memory address or nil pointer dereference
}
事情并没有按我们预想的发生,这在编程的过程中再常见不过了,一定是有什么不对。在这种时候我们就应该去翻阅资料,去搞懂我们还模糊的部分,就会找到原因。不过,既然是文章,我就直接写我的结论了。
这个要从接口类型的实现说起,golang 的接口的概念和实现是比较具体和复杂的,如果想进一步深入,请参阅这篇Stefno的文章,其非常深入和详细的讲解了 golang 中接口的方方面面。好了,回到我们的问题,现在对于 golang 的接口,我们只需要知道:在 golang 的接口里保存两个东西,其一是接口所持有的动态类型,其二是动态类型的值。
结构如下:
type iface struct {
itab, data uintptr
}
也就是说,当我们在转换接口的时候,其实是再把上面的结构强转为 valueCtx ,但其实我们期望的数据应该是保存在接口的动态类型值里,因此我们应该强转的是 iface.data。
要做到这点,我们就需要将 context 先转换成 iface,再获取其中的 data:
package main
type iface struct {
itab, data uintptr
}
type valueCtx struct {
context.Context
key, val interface{}
}
func main() {
ctx := context.Background()
ctx = context.WithValue(ctx, "key1", "value1")
ictx := (*iface)(unsafe.Pointer(&ctx))
valCtx := (*valueCtx)(unsafe.Pointer(ictx.data))
fmt.Printf("key: %v, value: %v", valCtx.key, valCtx.val)
// output:
// key: key1, value: value1
}
这回,我们终于获得其中的数据了。
完善
接下来,我们需要把 context 中的所有 key value 获取出来:
package main
func GetKeyValues(ctx context.Context) map[interface{}]interface{} {
m := make(map[interface{}]interface{})
getKeyValue(ctx, m)
return m
}
func getKeyValue(ctx context.Context, m map[interface{}]interface{}) {
ictx := *(*iface)(unsafe.Pointer(&ctx))
if ictx.data == 0 {
return
}
valCtx := (*valueCtx)(unsafe.Pointer(ictx.data))
if valCtx != nil && valCtx.key != nil && valCtx.val != nil {
m[valCtx.key] = valCtx.val
}
getKeyValue(valCtx.Context, m)
}
通过递归调用,我们可以很轻松的遍历所有 context,同时 golang 中的 nil 在底层其实就是一个int的0,这就是为什么我们把 nil 叫做 零值。所以,递归的退出条件也很简单,就是当 iface 中的 data 为0时,说明我们已经查找到 context 中的最后一个了。
好了,以上就是所有的内容了,如果你想获取文章的具体实现和 demo 示例,可以到我的 github 上找到,谢谢你的阅读。
一种获取context中keys和values的高效方法 | golang的更多相关文章
- Javascript获取数组中的最大值和最小值的方法汇总
比较数组中数值的大小是比较常见的操作,下面同本文给大家分享四种放哪广发获取数组中最大值和最小值,对此感兴趣的朋友一起学习吧 比较数组中数值的大小是比较常见的操作,比较大小的方法有多种,比如可以使用 ...
- 获取session中存储的所有值的方法
记录一个获取系统中session存储的对象都有哪些的方法 HttpSession session = request.getSession(); for ( Enumeration e = sessi ...
- ASP.Net MVC 在控制器中获取View中的form表单值的方法
在网站开发中,我们常常需要用到表单提交的方式,那么在MVC中是如何获取到表单中的数据呢?下面我们来介绍以下几种方式 首先我们先看看View前台页面 添加一个控制器 我们再看看前台页面的代码 @{ La ...
- C#获取url中参数键值对的方法
方法如下: /// <summary> /// 遍历Url中的参数列表 /// </summary> /// <returns>如:(?userName=keley ...
- 一种获取xml文件某个节点内容的shell方法
配置文件 config.xml <xml> <server> <name>srv-01</name> </server> <serve ...
- 三种查看SqlServer中数据物理pge页的方法
1.根据数据记录查看当前记录所在的文件编号.page页.以及在页中的插槽. 示例如下: SELECT top %%physloc%%, sys.fn_physlocFormatter (%%physl ...
- php获取post中的json数据的实现方法
最近用到腾讯微博与PHP交互,腾讯把json数据post给PHP(腾讯推送数据时,每条数据是一个json格式的数据包,作为post的数据体(请注意post数据体没有参数,不是key=value形式,整 ...
- 整理:Javascript获取数组中的最大值和最小值的方法汇总
方法一: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 //最小值 Array.prototype.min = function ...
- android#全局获取Context的技巧
参考<第一行代码>——郭霖 回想这么久以来我们所学的内容,你会发现有很多地方都需要用到Context,弹出Toast的时候需要.启动活动的时候需要.发送广播的时候需要.操作数据库的时候需要 ...
随机推荐
- 关于CAS中的ABA问题存在的隐患
一开始觉得ABA问题确实是个问题,但是具体场景是什么呢,虽然过程改了,但是结果一样,取的值也一样 不明所以,不过呢,这里也有一个小的demo可以说明一下 该例子通过AtomicReference判断对 ...
- Web服务器-服务器开发-返回固定页面的HTTP服务器(3.3.1)
@ 目录 1.注意 2.代码 关于作者 1.注意 浏览器解析的时候偶\r\n才算一个换行符 发送的str要编码,这里使用的是utf8 其他的都和上一篇没有什么区别 这里主要返回的是固定的网址 2.代码 ...
- 设计模式——责任链(结合Tomcat中Filter机制)
设计模式:责任链模式 说责任链之前,先引入一个场景,假如规定学生请假小于或等于 2 天,班主任可以批准:小于或等于 7 天,系主任可以批准:小于或等于 10 天,院长可以批准:其他情况不予批准:以此为 ...
- 从零开始手把手教你使用javascript+canvas开发一个塔防游戏01地图创建
项目演示 项目演示地址: 体验一下 项目源码: 项目源码 代码结构 本节做完效果 游戏主页面 index.html <!DOCTYPE html PUBLIC "-//W3C//DTD ...
- C# AutoMapper:流行的对象映射框架,可减少大量硬编码,很小巧灵活,性能表现也可接受。
AutoMapper 是一个对象-对象映射器,可以将一个对象映射到另一个对象. 官网地址:http://automapper.org/ 官方文档:https://docs.automapper.org ...
- setTimeout 是到了xx ms 就执行吗,了解浏览器的 Event-Loop 机制
要想 JavaScript 玩得溜,还得了解波 JavaScript 执行机制/(ㄒoㄒ)/~~. 个人博客:https://shansan.top 前言 最近看了波 JavaScript 相关的文章 ...
- Proguard结合maven使用
添加插件配置 项目是maven项目,则可以在 pom 的 build 标签下添加插件 <plugin> <groupId>com.github.wvengen</grou ...
- checkBox判断是否选中的方法
这里可以分为两种情况:JQuery对象和DOM对象: 通常我们用JQuery判断元素的属性的时候喜欢用 attr("attrName"); 但是尝试过的同学可能都知道,这种方法判断 ...
- ArrayList之SubList源码解析
subList是ArrayList的内部类, public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck( ...
- GraduateDesign-初试APP编写(去除虚拟按键和禁止状态栏下拉)
为了毕设的要求,需要在Android系统上运行一个app来控制硬件,今天开始这个app的编写. 首先,我们的系统将只运行这个app,也就是我们不需要状态栏,虚拟按键等. 故这里将app设置为全屏模式. ...