7、数据结构与集合优化:ArrayList vs LinkedList、HashMap vs SparseArray、ArrayMap原理、避免自动装箱

说实话,做Android开发这么多年,我见过太多因为集合选型不当导致的性能问题。内存抖动、卡顿、ANR,很多时候根源就在你用的那个集合类上。今天咱们就把这几个高频集合掰开揉碎了讲清楚。

核心观点:没有最好的集合,只有最合适的场景。选错了,就是性能灾难。

7.1 ArrayList vs LinkedList:谁才是真正的"快"?

很多人一看到"链表"就觉得它插入快,其实这是个误区。我项目中就遇到过,用LinkedList存了上千条日志,结果遍历时卡得不行。

底层原理对比

操作 ArrayList LinkedList
尾部插入 O(1) 均摊 O(1)
头部插入 O(n) 需要移动元素 O(1)
随机访问 O(1) O(n) 需要遍历
内存占用 连续内存,较小 每个节点额外存储前后指针,较大

你看,ArrayList底层是数组,连续内存,CPU缓存友好。LinkedList每个节点都是独立对象,还带两个指针,内存开销大不说,遍历时还会频繁触发GC。

我的建议:90%的场景用ArrayList就够了。真需要频繁头部插入,考虑ArrayDeque,它比LinkedList快得多。

实战代码对比

// 千万别这么写
LinkedList<String> list = new LinkedList<>();
for (int i = 0; i < 100000; i++) {
    list.add("item" + i);  // 尾部插入还行
}
// 但如果你要随机访问
for (int i = 0; i < list.size(); i++) {
    String s = list.get(i);  // 每次都是O(n),总复杂度O(n²)!
}

// 正确做法
ArrayList<String> arrayList = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
    arrayList.add("item" + i);
}
for (int i = 0; i < arrayList.size(); i++) {
    String s = arrayList.get(i);  // O(1),总复杂度O(n)
}

我曾经在一个列表页面上,就因为用了LinkedList做数据源,导致滑动时一卡一卡的。换成ArrayList后,流畅度直接起飞。

7.2 HashMap vs SparseArray:内存敏感场景的抉择

HashMap好用,但它在Android上有个致命问题——内存开销大。每个Entry都是一个独立对象,还有大量的空桶。你想想看,如果你的key是Integer,那HashMap还要额外存储Integer对象,这又是一笔开销。

SparseArray的优势

SparseArray是Android专门为key为int的场景设计的。它内部用两个数组,一个存key(int[]),一个存value(Object[]),通过二分查找定位。

// HashMap写法
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");

// SparseArray写法
SparseArray<String> sparseArray = new SparseArray<>();
sparseArray.put(1, "one");
sparseArray.put(2, "two");

代码看起来差不多,但内存差距巨大。我测过,存1000条数据,SparseArray比HashMap节省约30%的内存。

注意:SparseArray适合数据量在几百到几千的场景。如果数据量上万,二分查找的O(log n)性能就不如HashMap的O(1)了。

其他变体

  • LongSparseArray:key为long类型时使用
  • ArrayMap:key为其他对象时,比HashMap更省内存
  • SimpleArrayMap:ArrayMap的轻量版,不涉及反射

7.3 ArrayMap原理:内存与性能的平衡艺术

ArrayMap是Android Support库提供的集合类,它的设计思路很有意思——用时间换空间。

内部结构

ArrayMap内部维护了两个数组:一个存key的hash值(int[]),一个存key-value对(Object[])。数据是按hash值排序的,查找时用二分查找。

// ArrayMap的put操作简化示意
public V put(K key, V value) {
    int hash = key.hashCode();
    int index = indexOfKey(key, hash);  // 二分查找
    
    if (index >= 0) {
        // key已存在,替换value
        return setValueAt(index, value);
    }
    
    // 插入新数据
    int insertIndex = ~index;  // 取反得到插入位置
    ensureCapacity(mSize + 1);
    // 移动数组,腾出位置
    System.arraycopy(mHashes, insertIndex, mHashes, insertIndex + 1, mSize - insertIndex);
    System.arraycopy(mArray, insertIndex * 2, mArray, (insertIndex + 1) * 2, (mSize - insertIndex) * 2);
    
    mHashes[insertIndex] = hash;
    mArray[insertIndex * 2] = key;
    mArray[insertIndex * 2 + 1] = value;
    mSize++;
    return null;
}

你看,每次插入都要移动数组,这就是"时间换空间"的代价。但好处是,它没有HashMap那么多空桶,内存利用率极高。

使用场景:如果你的Map数据量在1000以内,且对内存敏感(比如ListView的Adapter里),用ArrayMap准没错。

7.4 避免自动装箱:一个容易被忽视的性能杀手

自动装箱,说白了就是Java自动把基本类型包装成对象。比如int变成Integer。这个过程看似方便,但每次装箱都会创建一个新对象,频繁触发GC。

// 糟糕的写法
HashMap<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10000; i++) {
    map.put(i, "value" + i);  // i被自动装箱成Integer
}

// 好的写法
SparseArray<String> sparseArray = new SparseArray<>();
for (int i = 0; i < 10000; i++) {
    sparseArray.put(i, "value" + i);  // 直接使用int,无需装箱
}

我曾经优化过一个列表页面,里面有个HashMap存了上千条数据。用SparseArray替换后,内存占用从80MB降到了50MB,GC次数也明显减少。

其他装箱陷阱

  • 集合遍历:for (Integer i : list) 这种写法,每次循环都会装箱
  • 比较操作:Integer a = 100; Integer b = 100; a == b 可能为true,但超过127就不行了
  • 数学运算:Integer a = 1; a++; 这背后有拆箱、运算、再装箱三步

避坑指南:我曾经在循环里用Integer做计数器,结果内存抖动特别厉害。换成int后,问题就解决了。记住,能用基本类型就别用包装类。

知识体系总览

下面这张图总结了本章的核心知识点,帮你快速建立数据结构选型的全局观:

数据结构与集合优化知识体系 ArrayList vs LinkedList • ArrayList:连续内存,随机访问O(1) • LinkedList:节点分散,随机访问O(n) • 90%场景选ArrayList • 头部插入多考虑ArrayDeque HashMap vs SparseArray • HashMap:O(1)查找,内存开销大 • SparseArray:二分查找,省内存30% • 适合数据量几百到几千 • 还有LongSparseArray等变体 ArrayMap原理 • 内部两个数组:hash数组+对象数组 • 二分查找,插入需移动数组 • 时间换空间,内存利用率高 • 适合1000以内数据量 避免自动装箱 • 装箱创建对象,触发GC • 用SparseArray替代HashMap<Integer> • 循环中用int而非Integer • 比较操作注意缓存范围 集合

嗯,到这里,数据结构与集合优化的核心内容就讲完了。记住一句话:选对集合,性能优化就成功了一半。下次写代码前,先想想你的数据量有多大,访问模式是怎样的,再决定用哪个集合类。

一句话总结:ArrayList + SparseArray + ArrayMap 是Android开发的黄金组合,用好它们,你的App会感谢你。


公众号:蓝海资料掘金营,微信deep3321