数据结构
像在不同的Fibonacci实现中展示的那样,好的算法和数据结构是能够快速运行的应用程序的关键因素。你需要对Android和Java定义的许多数据结构比较熟悉,可以快速的针对需求选择合适的数据结构。考虑选择合适的数据结构是开发最的高优先事情之一。
在java.util包中的主要数据结构列在图1.1中。
图1-1 Java.util包中的数据结构
安卓添加了一些独有的数据机构,通常是为了解决常见问题或者提升性能。
(1) LruCache
(2) SparseArray
(3) SparseBooleanArray
(4) SparseIntArray
(5) Pair
NOTE: Java同样定义了Arrays和Collections类。这两个类仅包含静态方法,分别对arrays和collections进行操作。比如,使用Arrays.sort去排序一个array,Arrays.binarySearch在排好序的array中查找一个值。
尽管上面的Fibonacci实现中有一个用到了cache(基于sparse array),那个cache仅仅是临时的,当计算完成后马上变得可回收。可以使用一个LruCache去保存最终结果,如Listing 1-13所示。
Listing 1-13 使用LruCache保存Fibonacci序列值
int maxSize = 4*8*1024*1024; // 32M
LruCache<Integer, BigInteger> cache = new LruCache<Integer, BigInteger>(maxSize) {
protected int sizeOf(Integer key, BigInteger value) {
return value.bitLength(); // 对象大小的近似值,以位为单位
}
};
...
int n = 100;
BigInteger fN = cache.get(n);
if (fN == null) {
fN = Fibonacci.computeRecursivelyWithCache(n);
cache.put(n, fN);
}
任何时候你需要选择一个数据结构去解决问题,都要可以缩小选择到几类,因为每个类通常针对一个特定的目的优化或者提供一个特别的服务。比如,如果你不需要操作同步,可以选择ArrayList而不是Vector。当然,你可能经常创建自己的数据结构类,从头开始创建(继承object)或者继承一个现存的类。
NOTE:你能够解释为什么在Listing 1-11中,LruCache为什么不是computeRecursivelyWithCache一个好的选择么?
如果你使用的数据结构依赖于hashing(比如HashMap),并且key是你创建的类型,保证你重写了equal和hashCode方法。一个不好的hashCode实现可以轻易的消除使用hashing带来的好处。
TIP:参看http://d.android.com/reference/java/lang/Object.html,hashCode()的一个很好的实例。
尽管对许多的嵌入式应用开发者来说,在应用程序的不同部分把一个数据结构转换成另外一个并不常见。在有些情况下,性能的提升可以很容易的超过转换的开销,因为有更好的算法可以应用。一个常见的例子的是collection转换为array,很可能是已经排过序的。这样的一个转换显然需要内存,因为需要创建一个新对象。在内存受限的设备上,这样的内存分配不是经常可行的,会产生OutOfMemoryError异常。Java语言规范指出两件事:
(1) Error类和它的子类是普通的应用程序通常不预期可恢复的异常
(2) 成熟的应用期望可以捕获和从Error异常恢复
如果内存分配只是优化的一部分,而你作为一个熟练的应用开发者,可以提供一个fallback机制(比如,一个算法,尽管慢一些,使用最原始的数据结构),捕获OutOfMemoryError异常,这样允许兼容更多的设备。这些可选的优化使你的代码更难维护,但是可以让你达到更大的目标。
NOTE:与直觉不同的是,不是所有的异常都是Exception的子类。所有的异常是Throwable的子类(Exception和Error是它的直接子类)。
总而言之,你要对java.util和android.util两个包非常熟悉,因为它们是几乎所有组件依赖的工具箱。每当一个新的安卓版本发布的时候,你需要尤其关注这些包的变动(添加的类、改变的类),可以参考http://d.android.com/sdk。更多的数据结构在java.util.concurrent中讨论,它们将在第五章涉及到。