Swift集的实现与字典类似,在“ 探索Swift字典的实现”中进行了很好的描述。特别地,元素存储是“桶”的列表,每个桶都可以被占用或不被占用。将新元素插入集合后,其哈希值将用于确定初始存储桶。如果该存储桶已被占用,则将线性搜索下一个空闲存储桶。类似地,当搜索集合中的元素时,哈希值用于确定初始存储桶,然后进行线性搜索,直到找到该元素(或未占用的存储桶)为止。
(详细信息可以在开源实现中找到,最相关的源文件是 Set.swift,NativeSet.swift,SetStorage.swift和HashTable.swift。)
更改插入元素的哈希值会破坏集合存储实现的不变性:通过元素的初始存储桶定位元素不再起作用。突变影响相等性的其他属性可能导致同一存储桶列表中出现多个“相等”元素。
因此,我认为可以肯定地说
将引用类型的实例插入集合后,不得以影响其哈希值或进行相等性测试的方式修改该实例的属性。
例子
首先,这只是引用类型集的问题。值类型的集合包含该值的独立副本,并且在插入后修改该值的属性不会影响该集合:
struct Foo: Hashable {
var x: Int
}
var set = Set<Foo>()
var foo = Foo(x: 1)
set.insert(foo)
print(set.map { $0.x }) // [1]
foo.x = 2
print(set.map { $0.x }) // [1]
set.insert(foo)
print(set.map { $0.x }) // [1, 2]
Run Code Online (Sandbox Code Playgroud)
引用类型的实例是实际对象存储的“指针”,并且修改该实例的属性不会使引用发生突变。因此,可以在将实例的属性插入集合后对其进行修改:
class Bar: Hashable {
var x : Int
init(x: Int) { self.x = x }
static func == (lhs: Bar, rhs: Bar) -> Bool { return lhs.x == rhs.x }
func hash(into hasher: inout Hasher) { hasher.combine(x) }
}
var set = Set<Bar>()
let bar = Bar(x: 1)
set.insert(bar)
print(set.map { $0.x }) // [1]
bar.x = 2
print(set.map { $0.x }) // [2]
Run Code Online (Sandbox Code Playgroud)
但是,这很容易导致崩溃,例如,如果我们再次插入相同的引用:
set.insert(bar)
Run Code Online (Sandbox Code Playgroud)
致命错误:在“集合”中发现“ Bar”类型的重复元素。 这通常意味着该类型违反了Hashable的要求,或者 这样的集合的成员在插入后发生了变异。
这是另一个示例,其中所有实例的哈希值都相同,但是修改用于相等性测试的属性会导致两个“相等”实例的集合:
class Baz: Hashable {
var x : Int
init(x: Int) { self.x = x }
static func == (lhs: Baz, rhs: Baz) -> Bool { return lhs.x == rhs.x }
func hash(into hasher: inout Hasher) { }
}
var set = Set<Baz>()
let baz1 = Baz(x: 1)
set.insert(baz1)
let baz2 = Baz(x: 2)
set.insert(baz2)
baz1.x = 2
print(set.map { $0.x }) // [2, 2]
print(set.count) // 2
print(Set(Array(set)).count) // 1
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
528 次 |
| 最近记录: |