Swift 中数组 Array 的比较
下面我们来尝试理解一下 Swift 中数组的比较。
var array1: [String] = ["1", "2", "3", "4", "5"]
var array2: [String] = ["1", "2", "3", "4", "5"]
// (1) 比较一下 2 个简单的数组
if array1 == array2 {
print("equal")
} else {
print("not equal")
}
// 输出:equal
// (2) 和一个数组的拷贝相比较
// 在 swift 中,向一个参数传值会发生拷贝
func arrayTest(anArray: [String]) -> Bool {
return anArray == array1
}
print("Array test 1 is \(arrayTest(anArray: array1))")
print("Array test 2 is \(arrayTest(anArray: array2))")
// 两个的结果都为 true
array2.append("test")
print("Array test 2 is \(arrayTest(anArray: array2))")
// false (很明显)
array2.removeLast()
print("Array test 2 is \(arrayTest(anArray: array2))")
// true
Apple 会在结数组(结构体)需要被拷贝时进行优化,所以在赋值时,数组(结构体)有时候会被拷贝,有时候不会真的拷贝。
现在有几个问题:
-
在使用
==
对数组进行比较时,是迭代数组中所有的元素进行比较么?如果是这样,在比较超大的数组时,性能和内存的使用情况是怎样的? -
在使用
==
进行数组比较时,如果所有的元素都相等,那数组就是相等的么? -
有没有办法可以检查
array1
和array2
在技术上是否使用了相同的内存地址/指针。
我们来了解一下数组拷贝优化的原理是什么?它背后有什么潜在的注意事项?
看下面一段代码:
struct NeverEqual: Equatable { }
func ==(lhs: NeverEqual, rhs: NeverEqual)->Bool { return false }
let x = [NeverEqual()]
var y = x
x == y // this returns true
[NeverEqual()] == [NeverEqual()] // false
x == [NeverEqual()] // false
let z = [NeverEqual()]
x == z // false
x == y // true
y[0] = NeverEqual()
x == y // now false
看起来让人很困惑。Swift 中的数组并不遵循 Equatable
协议,但是有 ==
的操作符,在标准库中是这样定义的:
func ==<T : Equatable>(lhs: [T], rhs: [T]) -> Bool
这个操作符循环 lhs
和 rhs
中的所有元素,比较相同位置对应的值。它不是按位比较,而是调用每对元素的 ==
操作符。这就意味着,如果给数组中的元素实现了一个自定义的 ==
操作符,那这个操作符将会被调用。
但是还要考虑一种优化——如果两个数组底层的缓冲区相同,它就不会这么麻烦了,而是直接返回 true。两个数组包含相同的元素,它们当然相同了。
上面情况的困惑主要是由 NeverEqual
的 ==
操作符造成的。相等应该具备传递性,对称性和反射性。而 NeverEqual
并不具备反射性(x == x
返回 false)。这会不知不觉的让你感到困惑。
Swift 的数组为 写时拷贝
。所有在写 var x == y
时,它并不会真的对数组进行拷贝,它只是将 x
的存储缓冲区的指针指向 y
的。只有在 x
或 y
被改变时,它才会对存储缓存区进行拷贝,所以没有修改的变量不会受到影响。这就使得看起来像值类型的数组具备了较高的性能。
在早期的 Swift 版本中,可以通过 ===
比较数组。
同时,在早期的版本中,修改的操作有点奇怪,如果你修改了
x
,y
也会随之改变,即使是用let
进行声明的。这让人很不解,所以 Apple 对此进行了修改。
现在可以使用下面的技巧来重现 ===
功能。
very implementation-dependent not to be relied-on except for poking and prodding investigations
let a = [1,2,3]
var b = a
a.withUnsafeBufferPointer { outer in
b.withUnsafeBufferPointer { inner in
print(inner.baseAddress == outer.baseAddress)
}
}