3.11 Hash
哈希是一种数据结构, 它维护了一个键值对集合, 而且将每个键都与一个值关联起来. 哈希也被称为映射(map), 有时也叫作关联数组(associative array)
Ruby | Javascript | Javascript ES6 | Go | Lua | |
---|---|---|---|---|---|
实现 | Hash | Object | Map | map | table |
key类型 | 任意 | 仅字符串 | 任意 | 必须是支持相等运算符的类型 | 除nil外任意类型 |
顺序 | 插入顺序 | 无序 | 插入顺序 | 无序 | 无序 |
字符串key点号读取 | N | Y | N | N | Y |
字面量键与值分隔 | => : 仅用于Symbol |
: |
不涉及 | : |
= |
字面量键值对分隔 | , |
, |
不涉及 | , |
, 优选; 通常作为数组和map分隔 |
删除键值对 | Object#delete(key) |
操作符 delete |
Map.prototype.delete(key) |
func delete(m map[Type]Type1, key Type) |
赋值nil |
哈希在底层通常是利用叫做哈希表的数据结构来实现的, 那些作为哈希的键, 通常有方法算得一个哈希码, 如果两个键相同, 算出来的哈希码相等, 但是不同的键算出来的哈希码可能相同, 这种现象叫做哈希冲突.
1. Ruby
Ruby Hash 键可以是任一类型, 在Ruby1.9之后, Hash是有序的, keys()
, values()
都会按照属性插入的顺序返回.
# 字面量
person = {age: 8, name: "fox"} // key 为Symbol
person = {"age" => 8, "name" => "fox"} // key 为String
# 设置
person["age"] = 8
# 读取
person["age"]
# 获取键
person.keys() #["age", "name"]
# 获取值
person.values() # [8, "fox"]
2. Javascript
Object
在ES6 之前, Object是大量用于需要哈希的场景, Javascript 的Object键只能使用字符串.
属性读取时, 有两种形式:
- 方括号+属性字符串: 优点是可以通过变量来访问
- 点号+属性: 在属性确定的情况下, 这种访问更加简洁
// 字面量
var person = {"age": 8, name: "fox"};
var key = "age";
// 设置
person.age = 8;
person[key] = 8; //通常只用于key是变量的情况
// 读取
person.age //8
person[key] //8
// 获取键
Object.keys(person) //[ 'age', 'name' ]
// 获取值没有内置方法
Map
Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。如果你需要“键值对”的数据结构,Map比Object更合适.
var m = new Map();
var o = {p: 'Hello World'};
// Map 的key可以是任意值:
m.set(o, 'content') // 设置
m.get(o) // 获取, 返回 "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
// 可以用数组进行初始化
var map = new Map([
['name', '张三'],
['title', 'Author']
]);
// 提供了长度读取
map.size // 2
primitive 类型数据如果值相同, 后设置的值会覆盖前设置的值; 对象类型的key, 只要内容地址不同, 就视为2个key, 这就解决了同名属性碰撞(clash)的问题:
var map = new Map();
map.set('x', 1) // Map { 'x' => 1 }
map.set('x', 2) // Map { 'x' => 2 } // 覆盖
map.set({}, 3) // Map { 'x' => 2, {} => 3 }
map.set({}, 4) // Map { 'x' => 2, {} => 3, {} => 4 } 不会覆盖
3. Go
- key 的类型必须支持
==
!=
, 比如数字, 字符串, 指针, 数组, 结构体, 以及对应的接口类型, 不支持比较的类型不可以作为map key, 比如slice, function, 以及包含 slice 的 struct 类型也不可以. - len: 返回键值对数量, 无法使用cap
- 读取方式:
value, ok := someMap[key]
ok 表示key的存在性 - 当map为nil的时候,不能添加值, 但是可读
- map的value成员是not addressable, 因此无法直接修改, 只能先返回value, 修改后再赋值到key上
map中的元素不是一个变量,不能对map的元素进行取地址操作,禁止对map进行取地址操作的原因可能是map随着元素的增加map可能会重新分配内存空间,这样会导致原来的地址无效.
4. Lua
Lua table可以用作键值对. 叫做记录(record), 字面量构造方式:
--list 字面量:
a = {10, 20, 'fox'}
--table字面量:
a = {x=10, y=20}
--混合型:
a = {
x = 'm',
y = 'n',
{x = 0}, --a[1]
{['x'..'y'] = 1} --动态计算的key
}
-- 混合型建议使用`;`分隔:
a = {x=10, y=20; "one", "two"}
key可以是除了nil外的其他所有类型的值, key可以是混合不同类型, '1', 1, '01' 都是不同的key.
-- key为字符串类型
a = {x=0, y=0}
-- 使用时key是字符串
a['x'] -- 0
-- 非字符串的key, 需要放到[]里
b = {[5] = true, [a] = false}
b[a] -- false
b[5] -- true
value 是nil的键值对, 实际上在table里是不存在的, 因此可以利用赋值nil 删除键值对.