arraylist的remove(int)删索引、remove(object)删值,易误用;遍历时删元素须用iterator.remove()或removeif();hashmap容量应按n/0.75向上取2的幂;abstractlist中未重写方法抛unsupportedoperationexception。

为什么 ArrayList 的 remove(int) 和 remove(Object) 容易误用
因为两个重载方法行为完全不同,但编译器不会报错,运行时才暴露问题。比如你本想删值 3,却写了 list.remove(3)——它删的是索引为 3 的元素,不是值为 3 的元素。
常见错误现象:IndexOutOfBoundsException(删越界索引),或删错元素(本想删第一个 "abc",结果删了第 4 个位置的任意对象)。
- 查文档确认:整数参数走的是
remove(int index);非null引用参数走的是remove(Object o) - 如果目标是按值删除,确保传入的是包装类型或引用对象,例如
list.remove(Integer.valueOf(3)),而不是裸写3 - 在泛型为
Integer的列表里,remove(3)仍会被解析为删索引——JVM 优先匹配基本类型参数的重载
迭代中修改集合时,ConcurrentModificationException 怎么绕开又不踩坑
这不是线程安全问题,而是 fail-fast 机制在单线程下也会触发。只要用 for-each 或显式 Iterator 遍历,同时调用集合自身的 add()/remove(),就炸。
使用场景:过滤列表、批量清理满足条件的元素。
- 正确做法:用
Iterator.remove(),它是唯一被允许的“边遍历边删”方式 - 别用
list.remove()配合for-each,也别以为转成数组再操作就能躲过——ArrayList的iterator()持有 modCount 快照,一动就校验 - Java 8+ 可用
removeIf(Predicate),它内部封装了安全的迭代逻辑,语义清晰且高效
HashMap 初始化容量设多少才不浪费内存又不频繁扩容
默认初始容量是 16,负载因子 0.75,意味着放 12 个键值对就触发第一次扩容。但扩容要 rehash,代价不小;而盲目设大容量,又浪费空间——尤其当 key 是大对象时。
性能影响:小容量高频扩容 → CPU 时间涨;大容量低填充率 → 堆内存虚高,GC 压力隐性上升。
- 估算元素数量
n后,按公式capacity = (int) Math.ceil(n / 0.75)向上取最近的 2 的幂(如n=20→Math.ceil(20/0.75)=27→ 实际设 32) - 别直接写
new HashMap(100):构造函数传的不是容量而是“阈值”,HashMap 会把它向上规整为 128(因为内部用 table.length * loadFactor 算扩容点) - 如果 key 是字符串且长度固定、散列质量差(比如都以相同前缀开头),即使容量够,也可能因哈希碰撞多导致链表变长,此时得考虑自定义
hashCode()或换LinkedHashMap观察访问模式
从 Collection 接口使用者,突然要看 AbstractList 源码,该盯哪几行
不是为了背代码,而是理解“为什么 subList() 返回的对象修改会影响原列表”“为什么 Arrays.asList() 返回的 list 不支持 add()”。这些行为差异,全藏在抽象基类对模板方法的实现里。
容易被忽略的地方:很多“不可变”或“视图”行为,不是靠 final 修饰,而是靠抛 UnsupportedOperationException 实现的——它根本没被重写,只是留空或直接 throw。
- 重点看
AbstractList.set()、add()、remove()这几个方法体:它们多数直接 throw,说明子类必须重写才能支持对应操作 -
SubList类里找parent字段和offset字段:它没拷贝数据,所有读写最终都委托给原始ArrayList,所以是强一致性视图 -
Arrays$ArrayList(即Arrays.asList()返回类型)继承自AbstractList,但没重写add(),所以一调就崩——这不是 bug,是设计选择
底层设计者的思维转变,本质是把“API 怎么用”切换成“这个接口契约下,谁负责保证什么,谁可以偷懒不实现什么”。一旦开始问“这里为什么不 new 一个新集合”,你就已经进阶了。










