为什么 Bash 关联数组不保持索引顺序?
Why don't Bash associative arrays maintain index order?
我正在创建关联数组以在 for 循环中处理,但我在索引顺序中得到了一些奇怪的结果。请查看此示例脚本:
#!/bin/bash
declare -A test1=(
[d]=1w45
[e]=2dfg
[m]=3df
[o]=4df
)
declare -A test2=(
[d1]=1w45
[e2]=2dfg
[m3]=3df
[o4]=4df
)
declare -A test3=(
[1d]=1w45
[2e]=2dfg
[3m]=3df
[4o]=4df
)
echo ${!test1[@]}
echo ${!test2[@]}
echo ${!test3[@]}
输出将是
$ ./test
d e m o
o4 m3 e2 d1
3m 4o 1d 2e
为什么项目的顺序会改变?以及如何绕过这种行为?提前致谢!
根据评论,可以绕过此行为。
order=(d1 e2 m3 o4)
declare -A test2=(
[d1]=1w45
[e2]=2dfg
[m3]=3df
[o4]=4df
)
for key in ${order[@]}; { echo $key ${test2[$key]}; }
d1 1w45
e2 2dfg
m3 3df
o4 4df
或者那个
declare -A test3=(
[order]="1d 2e 3m 4o"
[1d]=1w45
[2e]=2dfg
[3m]=3df
[4o]=4df
)
for key in ${test3[order]}; { echo $key ${test3[$key]}; }
1d 1w45
2e 2dfg
3m 3df
4o 4df
有没有更好的方法?
更新,根据公认的答案,如果你需要在 for 循环中严格排序,关联数组不是正确的选择,最好使用这样的东西:
key=(d1 e2 m3 o4 )
val=(1w45 2dfg 3df 4df)
for i in ${!key[@]}; {
echo ${key[$i]} ${val[$i]}
}
或者这个
key_val=(
"d1 1w45"
"e2 2dfg"
"m3 3df"
"o4 4df")
for item in "${key_val[@]}"; {
sub=($item)
echo ${sub[0]} ${sub[1]}
}
或者那个
keys=(d1 e2 m3 o4 )
d1=1w45 e2=2dfg m3=3df o4=4df
for key in ${keys[@]}; {
echo $key ${!key}
}
Why order of items is changing?
因为通常关联数组不会自然地维护插入顺序:基于树的数组使用自然(排序)排序,而哈希映射使用它们的哈希函数登陆键的任何地方(这可以出于安全原因,每个进程甚至每个地图都是随机的。
后者还解释了为什么在您添加新项目时项目的顺序甚至会发生变化:不仅可以在现有项目之间插入新项目,而且当必须调整 hashmap 的大小时,整个序列将 "reshuffled" 因为条目被重新散列并移动到它们的新位置。
有些语言会显式添加排序作为一项功能(通常使用双向链表),或者使用自然排序 hashmap,在这种情况下插入顺序会保持不变,但是你不能假设这个 属性 成立,除非语言保证它。 bash 没有。
Why don't bash associative arrays maintain index order?
因为它们的设计目的不是这样做。
Why order of items is changing?
Bashassociative array implementation uses a hash library and stores hashes of indexes. These hashes are stored in buckets with 128 default number of buckets. The hash is calculated with the function hash_string()
using a simple multiplication and a bitwise XOR. The keys of the associative array are listed in the order buckets appear. Bucket number is calculated通过key的hash值与桶数减1associative array implementation uses a hash library and stores hashes of indexes. These hashes are stored in buckets with 128 default number of buckets. The hash is calculated with the function hash_string()
using a simple multiplication and a bitwise XOR. The keys of the associative array are listed in the order buckets appear. Bucket number is calculated
我编译了 bash commit 6c6454cb18d7cd30b3b26d5ba6479431e599f3ed 并且你的脚本输出对我来说:
$ ./test
o m e d
d1 e2 m3 o4
1d 3m 2e 4o
所以我复制了hash_string()
函数,写了一个小的C程序,输出key的bucket号,编译执行:
#include <stdio.h>
#define FNV_OFFSET 2166136261
#define FNV_PRIME 16777619
unsigned int
hash_string (s)
const char *s;
{
register unsigned int i;
for (i = FNV_OFFSET; *s; s++)
{
i *= FNV_PRIME;
i ^= *s;
}
return i;
}
int main() {
const char *s[] = {
"o", "m", "e", "d",
"d1", "e2", "m3", "o4",
"1d", "3m", "2e", "4",
};
for (int i = 0; i < sizeof(s)/sizeof(*s); ++i) {
printf("%3s %3d\n",
s[i],
hash_string(s[i]) & (128 - 1));
}
}
程序输出两列,键和键的桶号(添加了额外的空行):
o 112
m 114
e 122
d 123
d1 16
e2 60
m3 69
o4 100
1d 14
3m 41
2e 50
4o 94
输出键的顺序是使用它们所在的散列table中的桶顺序排序的,因此它们按该顺序输出。这就是项目顺序发生变化的原因。
也就是说,您应该 不 依赖此行为,因为如果 bash 的作者决定更改散列函数或进行任何其他更改。
And how to bypass this behavior?
没有办法绕过这个。 Bash 数组使用散列 table 来存储散列。键的插入顺序不存储在任何地方。
当然,您可以通过修补 bash
来绕过此行为,以实现您请求的此类功能。
也就是说,我只使用两个数组:
keys=(d1 e2 m3 o4)
elements=(1w45 2dfg 3df 4df)
declare -A test2
for ((i=0;i<${#keys[@]};++i)); do
test2[${keys[$i]}]="${elements[$i]}"
done
# or maybe something along:
declare -A test2=($(paste -zd <(printf "[%s]=[=13=]" "${keys[@]}") <(printf "%q [=13=]" "${elements[@]}"))
这样您就可以按照在单独的 keys
数组中插入键的顺序遍历键。
可以简单地解释为:
- 整数索引数组具有隐式自动递增索引,并允许显式分配索引以创建稀疏数组。
如果将具有显式索引的记录添加到索引数组;索引可能不再反映插入顺序
鉴于:
- 关联数组只有一个明确的字符串索引。
总结一下:
一个隐式增量索引反映了插入顺序;而显式命令式索引则不会。
我正在创建关联数组以在 for 循环中处理,但我在索引顺序中得到了一些奇怪的结果。请查看此示例脚本:
#!/bin/bash
declare -A test1=(
[d]=1w45
[e]=2dfg
[m]=3df
[o]=4df
)
declare -A test2=(
[d1]=1w45
[e2]=2dfg
[m3]=3df
[o4]=4df
)
declare -A test3=(
[1d]=1w45
[2e]=2dfg
[3m]=3df
[4o]=4df
)
echo ${!test1[@]}
echo ${!test2[@]}
echo ${!test3[@]}
输出将是
$ ./test
d e m o
o4 m3 e2 d1
3m 4o 1d 2e
为什么项目的顺序会改变?以及如何绕过这种行为?提前致谢!
根据评论,可以绕过此行为。
order=(d1 e2 m3 o4)
declare -A test2=(
[d1]=1w45
[e2]=2dfg
[m3]=3df
[o4]=4df
)
for key in ${order[@]}; { echo $key ${test2[$key]}; }
d1 1w45
e2 2dfg
m3 3df
o4 4df
或者那个
declare -A test3=(
[order]="1d 2e 3m 4o"
[1d]=1w45
[2e]=2dfg
[3m]=3df
[4o]=4df
)
for key in ${test3[order]}; { echo $key ${test3[$key]}; }
1d 1w45
2e 2dfg
3m 3df
4o 4df
有没有更好的方法?
更新,根据公认的答案,如果你需要在 for 循环中严格排序,关联数组不是正确的选择,最好使用这样的东西:
key=(d1 e2 m3 o4 )
val=(1w45 2dfg 3df 4df)
for i in ${!key[@]}; {
echo ${key[$i]} ${val[$i]}
}
或者这个
key_val=(
"d1 1w45"
"e2 2dfg"
"m3 3df"
"o4 4df")
for item in "${key_val[@]}"; {
sub=($item)
echo ${sub[0]} ${sub[1]}
}
或者那个
keys=(d1 e2 m3 o4 )
d1=1w45 e2=2dfg m3=3df o4=4df
for key in ${keys[@]}; {
echo $key ${!key}
}
Why order of items is changing?
因为通常关联数组不会自然地维护插入顺序:基于树的数组使用自然(排序)排序,而哈希映射使用它们的哈希函数登陆键的任何地方(这可以出于安全原因,每个进程甚至每个地图都是随机的。
后者还解释了为什么在您添加新项目时项目的顺序甚至会发生变化:不仅可以在现有项目之间插入新项目,而且当必须调整 hashmap 的大小时,整个序列将 "reshuffled" 因为条目被重新散列并移动到它们的新位置。
有些语言会显式添加排序作为一项功能(通常使用双向链表),或者使用自然排序 hashmap,在这种情况下插入顺序会保持不变,但是你不能假设这个 属性 成立,除非语言保证它。 bash 没有。
Why don't bash associative arrays maintain index order?
因为它们的设计目的不是这样做。
Why order of items is changing?
Bashassociative array implementation uses a hash library and stores hashes of indexes. These hashes are stored in buckets with 128 default number of buckets. The hash is calculated with the function hash_string()
using a simple multiplication and a bitwise XOR. The keys of the associative array are listed in the order buckets appear. Bucket number is calculated通过key的hash值与桶数减1associative array implementation uses a hash library and stores hashes of indexes. These hashes are stored in buckets with 128 default number of buckets. The hash is calculated with the function hash_string()
using a simple multiplication and a bitwise XOR. The keys of the associative array are listed in the order buckets appear. Bucket number is calculated
我编译了 bash commit 6c6454cb18d7cd30b3b26d5ba6479431e599f3ed 并且你的脚本输出对我来说:
$ ./test
o m e d
d1 e2 m3 o4
1d 3m 2e 4o
所以我复制了hash_string()
函数,写了一个小的C程序,输出key的bucket号,编译执行:
#include <stdio.h>
#define FNV_OFFSET 2166136261
#define FNV_PRIME 16777619
unsigned int
hash_string (s)
const char *s;
{
register unsigned int i;
for (i = FNV_OFFSET; *s; s++)
{
i *= FNV_PRIME;
i ^= *s;
}
return i;
}
int main() {
const char *s[] = {
"o", "m", "e", "d",
"d1", "e2", "m3", "o4",
"1d", "3m", "2e", "4",
};
for (int i = 0; i < sizeof(s)/sizeof(*s); ++i) {
printf("%3s %3d\n",
s[i],
hash_string(s[i]) & (128 - 1));
}
}
程序输出两列,键和键的桶号(添加了额外的空行):
o 112
m 114
e 122
d 123
d1 16
e2 60
m3 69
o4 100
1d 14
3m 41
2e 50
4o 94
输出键的顺序是使用它们所在的散列table中的桶顺序排序的,因此它们按该顺序输出。这就是项目顺序发生变化的原因。
也就是说,您应该 不 依赖此行为,因为如果 bash 的作者决定更改散列函数或进行任何其他更改。
And how to bypass this behavior?
没有办法绕过这个。 Bash 数组使用散列 table 来存储散列。键的插入顺序不存储在任何地方。
当然,您可以通过修补 bash
来绕过此行为,以实现您请求的此类功能。
也就是说,我只使用两个数组:
keys=(d1 e2 m3 o4)
elements=(1w45 2dfg 3df 4df)
declare -A test2
for ((i=0;i<${#keys[@]};++i)); do
test2[${keys[$i]}]="${elements[$i]}"
done
# or maybe something along:
declare -A test2=($(paste -zd <(printf "[%s]=[=13=]" "${keys[@]}") <(printf "%q [=13=]" "${elements[@]}"))
这样您就可以按照在单独的 keys
数组中插入键的顺序遍历键。
可以简单地解释为:
- 整数索引数组具有隐式自动递增索引,并允许显式分配索引以创建稀疏数组。
如果将具有显式索引的记录添加到索引数组;索引可能不再反映插入顺序
鉴于:
- 关联数组只有一个明确的字符串索引。
总结一下:
一个隐式增量索引反映了插入顺序;而显式命令式索引则不会。