字典放进另一个字典:直接用变量和通过大字典取,有什么不同?

« 返回首页

一句话结论:把字典放进另一个字典时放入的是同一个字典(引用),不是副本;通过大字典修改,直接读变量也能看到变化。

有用户问:一个全局变量的字典,又作为值放进了上一级字典。这时直接用变量名访问它,和用上一级字典的键名访问它,本质和效率上有什么不同?

提问时附的代码是这样的:先给「出厂工作模式参数字典集」「出厂硬件参数字典集」两个全局变量各设置一个字典,再用「创建字典」把它们作为 key1、key2 的值,组成「出厂参数大字典」。

结论:两种方式拿到的是同一个字典,不是两份。 「创建字典」放进去的是这个字典本身(引用),不会复制一份。所以:

  • 通过大字典改了它的内容,直接读变量也能看到,反过来也一样;
  • 但如果之后给变量重新赋值一个新字典,大字典里存的还是原来那个,两边就分开了;
  • 效率上几乎没有差别,差的只是多查一次键,微秒级。

下面用三个小实验看清楚。界面只需要一个标签 Label1 和三个按钮 Button1~Button3。

实验一:从大字典里改,变量里也变了

屏幕初始化时,先给小字典赋值,再把它放进大字典。点按钮时,通过大字典把滴速改成 9,然后直接读变量:

global 工作模式参数 = {}
global 参数大字典 = {}

when Screen1.Initialize() {
  工作模式参数 = {"滴速": 6, "时长": 600}
  参数大字典 = {"key1": 工作模式参数}
}

when Button1.Click() {
  dictSet(dictGet(参数大字典, "key1", {}), "滴速", 9)
  Label1.Text = join("直接读变量:", dictGet(工作模式参数, "滴速", 0))
}

跑起来看:点 Button1,标签显示 直接读变量:9。

改的是大字典里 key1 的值,变量也跟着变了,说明它们是同一个字典。可以理解成:变量和大字典的 key1 是贴在同一个盒子上的两张标签,从哪张标签找过去,打开的都是同一个盒子。

实验二:给变量重新赋值,两边就分开了

还是一样的初始化。这次点按钮时,给变量重新赋值一个新字典,再分别读:

global 工作模式参数 = {}
global 参数大字典 = {}

when Screen1.Initialize() {
  工作模式参数 = {"滴速": 6, "时长": 600}
  参数大字典 = {"key1": 工作模式参数}
}

when Button2.Click() {
  工作模式参数 = {"滴速": 7, "时长": 300}
  Label1.Text = join("直接读变量:", dictGet(工作模式参数, "滴速", 0), ",经大字典读:", dictGet(dictGet(参数大字典, "key1", {}), "滴速", 0))
}

跑起来看:点 Button2,标签显示 直接读变量:7,经大字典读:6。

「设置 全局变量 为 创建字典」是把变量这张标签撕下来,贴到一个新盒子上;大字典的 key1 还贴在旧盒子上,不会自动跟过去。

这是最容易踩的坑:一处改了「整个换掉」,另一处还在用旧数据,程序不报错,只是结果不对。规则很简单:

  • 改内容(「设置 键 在字典 值为」「删除键的条目」这类块):两边同步;
  • 重新赋值(设置 全局变量 为 一个新字典):两边分家。想让大字典也用新的,要再执行一次「设置 键 key1 在字典 参数大字典 值为 新字典」。

实验三:想要互不影响,就复制一份

如果你希望大字典里存的是「出厂默认值」,之后怎么改变量都不影响它,就用字典抽屉的「复制字典」先复制一份再放进去:

global 工作模式参数 = {}
global 参数大字典 = {}

when Screen1.Initialize() {
  工作模式参数 = {"滴速": 6, "时长": 600}
  参数大字典 = {"key1": dictCopy(工作模式参数)}
}

when Button3.Click() {
  dictSet(dictGet(参数大字典, "key1", {}), "滴速", 9)
  Label1.Text = join("直接读变量:", dictGet(工作模式参数, "滴速", 0))
}

跑起来看:点 Button3,标签显示 直接读变量:6。大字典里改成了 9,变量不受影响。

注意「复制字典」是浅复制:只复制最外面一层。如果小字典里面还嵌着字典或列表,里面那些仍然是同一个,改深层的内容两边还会一起变。

效率:几乎没区别

  • 直接读变量:取一次变量;
  • 经大字典读:取一次变量,再按键查一次。字典按键查找不用逐个比较,很快。

多出来的这一步是微秒级的,点按钮、处理一条蓝牙数据这种频率完全感觉不到。只有在几千次的循环里反复「从大字典取 key1」,才值得在循环外先取出来存进局部变量,循环里直接用。

推荐写法

两种访问方式都能用,关键是别让同一份数据有两个「主人」:

做法 适合 注意
只保留大字典,用时按键取 参数组多、要整体保存或发送 写一个「取工作模式参数」过程,取值的地方都调它,键名只写一处
只用小字典变量,需要时再临时组装大字典 各组参数分开改、很少整体用 组装放在要用的那一刻(比如保存、发送前),保证拿到最新值
两边都留 不推荐 只能改内容、不能重新赋值,否则就是实验二的坑

另外,用微数据库保存再读回来时,大字典和变量各自读出来的是两个独立的新字典(保存时变成了文本),原来的「同一个盒子」关系就没有了。读回后如果还想两边连着,要重新组装一次大字典。

平台说明

安卓和鸿蒙运行时行为一致:「创建字典」和「设置 键 在字典 值为」都存引用,「复制字典」都是浅复制。本文三个实验的结果已在鸿蒙运行时里实际运行核对。

文档反馈