一、先搞懂:Java 集合到底是啥?
二、常用集合怎么用?
1. ArrayList:最常用的动态数组
是什么?
怎么用?
// 定义一个存用户姓名的ArrayList List<String> userNameList = new ArrayList<>();// 加元素 userNameList.add(张三); userNameList.add(李四); userNameList.add(王五);// 查元素(根据索引,很快) String firstUser = userNameList.get(0); // 拿到张三// 遍历(最常用的for-each) for (String name : userNameList) {System.out.println(name); }// 删元素(根据索引删) userNameList.remove(1); // 删掉李四
注意什么?(我的踩坑记录)
- 坑 1:频繁增删元素别用它!比如我之前做一个购物车功能,用户频繁添加、删除商品,用了 ArrayList,结果操作多了之后,页面反应特别慢。后来换成 LinkedList 就快了 —— 因为 ArrayList 删元素的时候,后面的元素都要往前挪位置,就像排队的时候有人走了,后面的人都得往前补,麻烦。
- 坑 2:初始化的时候可以指定容量!如果知道大概要存多少数据,比如要存 100 个用户,就写成 new ArrayList<>(100),这样它不用频繁扩容,性能会好一点。我之前没指定,存了 200 多个数据,后来看日志才发现它扩容了好几次。
2. LinkedList:适合频繁增删的链表
是什么?
怎么用?
// 定义一个购物车列表 List<String> cartList = new LinkedList<>();// 加元素(和ArrayList用法一样,API通用) cartList.add(苹果); cartList.add(香蕉);// 往指定位置加元素(比ArrayList快) cartList.add(1, 橙子); // 结果:苹果→橙子→香蕉// 删元素(比ArrayList快) cartList.remove(香蕉);
注意什么?
- 坑:查询元素别用它!比如你要查第 100 个元素,它得从第一个元素开始一个个往后数,直到找到第 100 个,比 ArrayList 慢太多。我之前犯过这个错,用 LinkedList 存了 1000 多条日志,查询的时候卡了好几秒。
3. HashMap:键值对存储,查询贼快
是什么?
怎么用?
// 定义一个存用户信息的HashMap,key是用户ID(String),value是年龄(Integer) Map<String, Integer> userAgeMap = new HashMap<>();// 存元素(put方法) userAgeMap.put(1001, 25); userAgeMap.put(1002, 30); userAgeMap.put(1003, 28);// 查元素(get方法,根据key查) Integer age = userAgeMap.get(1002); // 拿到30// 遍历(两种常用方式) // 方式1:遍历key for (String userId : userAgeMap.keySet()) {System.out.println(用户ID: + userId + ,年龄: + userAgeMap.get(userId)); }// 方式2:遍历键值对(推荐,更高效) for (Map.Entry<String, Integer> entry : userAgeMap.entrySet()) {System.out.println(用户ID: + entry.getKey() + ,年龄: + entry.getValue()); }// 删元素(根据key删) userAgeMap.remove(1003);
注意什么?
- 坑 1:key 不能重复!如果重复 put 同一个 key,后面的 value 会覆盖前面的。我之前做统计功能,重复 put 了同一个用户 ID,结果数据不对,查了半天才发现是覆盖了。
- 坑 2:key 可以是 null,但只能有一个!HashMap 允许 key 为 null,但如果你 put 两次 null 作为 key,后面的 value 会覆盖前面的。我之前不小心 put 了两次 null,结果只拿到最后一个 value,还以为是代码出了 bug。
- 坑 3:遍历的时候不能修改集合大小!比如用 for-each 遍历的时候,不能直接 remove 元素,会报 ConcurrentModificationException 异常。要删的话,用迭代器(Iterator)或者 foreachRemaining 方法。
- 坑 4:多线程环境下别用!我之前在多线程里用 HashMap 存数据,结果出现了数据丢失的情况。后来问了老同事,才知道 HashMap 不是线程安全的,多线程要用 ConcurrentHashMap。
4. ConcurrentHashMap:多线程专用的安全 HashMap
是什么?
怎么用?
// 多线程环境下存用户信息 Map<String, Integer> safeUserMap = new ConcurrentHashMap<>();// 存元素(和HashMap一样) safeUserMap.put(1001, 25);// 查元素(和HashMap一样) Integer age = safeUserMap.get(1001);
注意什么?
- 不用自己加锁!它内部已经做好了线程安全,我们直接用就行。我之前傻呵呵地在外面加了个 synchronized 锁,结果性能变得特别差,老同事看到后笑了我半天。
- 适合高并发场景!比如接口被频繁调用,需要缓存数据的时候,用它就对了。