关于Java集合的小抄

在尽可能短的篇幅里,将所有集合与并发集合的特征,实现方式,性能捋一遍。适合所有”精通Java”其实还不那么自信的人阅读。

不断更新中,请尽量访问博客原文

List

ArrayList

以数组实现。节约空间,但数组有容量限制。超出限制时会增加50%容量,用System.arraycopy()复制到新的数组,因此最好能给出数组大小的预估值。默认第一次插入元素时创建大小为10的数组。

按数组下标访问元素–get(i)/set(i,e) 的性能很高,这是数组的基本优势。

直接在数组末尾加入元素–add(e)的性能也高,但如果按下标插入、删除元素–add(i,e), remove(i), remove(e),则要用System.arraycopy()来移动部分受影响的元素,性能就变差了,这是基本劣势。

LinkedList

以双向链表实现。链表无容量限制,但双向链表本身使用了更多空间,也需要额外的链表指针操作。

按下标访问元素–get(i)/set(i,e) 要悲剧的遍历链表将指针移动到位(如果i>数组大小的一半,会从末尾移起)。

插入、删除元素时修改前后节点的指针即可,但还是要遍历部分链表的指针才能移动到下标所指的位置,只有在链表两头的操作–add(), addFirst(),removeLast()或用iterator()上的remove()能省掉指针的移动。

CopyOnWriteArrayList

并发优化的ArrayList。用CopyOnWrite策略,在修改时先复制一个快照来修改,改完再让内部指针指向新数组。

因为对快照的修改对读操作来说不可见,所以只有写锁没有读锁,加上复制的昂贵成本,典型的适合读多写少的场景。如果更新频率较高,或数组较大时,还是Collections.synchronizedList(list),对所有操作用同一把锁来保证线程安全更好。

增加了addIfAbsent(e)方法,会遍历数组来检查元素是否已存在,性能可想像的不会太好。

补充

无论哪种实现,按值返回下标–contains(e), indexOf(e), remove(e) 都需遍历所有元素进行比较,性能可想像的不会太好。

没有按元素值排序的SortedList,在线程安全类中也没有无锁算法的ConcurrentLinkedList,凑合着用Set与Queue中的等价类时,会缺少一些List特有的方法。

Map

HashMap

以Entry[]数组实现的哈希桶数组,用Key的哈希值取模桶数组的大小可得到数组下标。

插入元素时,如果两条Key落在同一个桶(比如哈希值1和17取模16后都属于第一个哈希桶),Entry用一个next属性实现多个Entry以单向链表存放,后入桶的Entry将next指向桶当前的Entry。

查找哈希值为17的key时,先定位到第一个哈希桶,然后以链表遍历桶里所有元素,逐个比较其key值。

当Entry数量达到桶数量的75%时(很多文章说使用的桶数量达到了75%,但看代码不是),会成倍扩容桶数组,并重新分配所有原来的Entry,所以这里也最好有个预估值。

取模用位运算(hash & (arrayLength-1))会比较快,所以数组的大小永远是2的N次方, 你随便给一个初始值比如17会转为32。默认第一次放入元素时的初始值是16。

iterator()时顺着哈希桶数组来遍历,看起来是个乱序。

在JDK8里,新增默认为8的閥值,当一个桶里的Entry超过閥值,就不以单向链表而以红黑树来存放以加快Key的查找速度。

LinkedHashMap

扩展HashMap增加双向链表的实现,号称是最占内存的数据结构。支持iterator()时按Entry的插入顺序来排序(但是更新不算, 如果设置accessOrder属性为true,则所有读写访问都算)。

实现上是在Entry上再增加属性before/after指针,插入时把自己加到Header Entry的前面去。如果所有读写访问都要排序,还要把前后Entry的before/after拼接起来以在链表中删除掉自己。

TreeMap

以红黑树实现,篇幅所限详见入门教程。支持iterator()时按Key值排序,可按实现了Comparable接口的Key的升序排序,或由传入的Comparator控制。可想象的,在树上插入/删除元素的代价一定比HashMap的大。

支持SortedMap接口,如firstKey(),lastKey()取得最大最小的key,或sub(fromKey, toKey), tailMap(fromKey)剪取Map的某一段。

ConcurrentHashMap

并发优化的HashMap,默认16把写锁(可以设置更多),有效分散了阻塞的概率,而且没有读锁。
数据结构为Segment[],Segment里面才是哈希桶数组,每个Segment一把锁。Key先算出它在哪个Segment里,再算出它在哪个哈希桶里。

支持ConcurrentMap接口,如putIfAbsent(key,value)与相反的replace(key,value)与以及实现CAS的replace(key, oldValue, newValue)。

没有读锁是因为put/remove动作是个原子动作(比如put是一个对数组元素/Entry 指针的赋值操作),读操作不会看到一个更新动作的中间状态。

ConcurrentSkipListMap

JDK6新增的并发优化的SortedMap,以SkipList实现。SkipList是红黑树的一种简化替代方案,是个流行的有序集合算法,篇幅所限见入门教程。Concurrent包选用它是因为它支持基于CAS的无锁算法,而红黑树则没有好的无锁算法。

很特殊的,它的size()不能随便调,会遍历来统计。

补充

关于null,HashMap和LinkedHashMap是随意的,TreeMap没有设置Comparator时key不能为null;ConcurrentHashMap在JDK7里value不能为null(这是为什么呢?),JDK8里key与value都不能为null;ConcurrentSkipListMap是所有JDK里key与value都不能为null。

Set

Set几乎都是内部用一个Map来实现, 因为Map里的KeySet就是一个Set,而value是假值,全部使用同一个Object。Set的特征也继承了那些内部Map实现的特征。

  • HashSet:内部是HashMap。
  • LinkedHashSet:内部是LinkedHashMap。
  • TreeSet:内部是TreeMap的SortedSet。
  • ConcurrentSkipListSet:内部是ConcurrentSkipListMap的并发优化的SortedSet。
  • CopyOnWriteArraySet:内部是CopyOnWriteArrayList的并发优化的Set,利用其addIfAbsent()方法实现元素去重,如前所述该方法的性能很一般。

补充:好像少了个ConcurrentHashSet,本来也该有一个内部用ConcurrentHashMap的简单实现,但JDK偏偏没提供。Jetty就自己封了一个,Guava则直接用java.util.Collections.newSetFromMap(new ConcurrentHashMap()) 实现。

Queue

Queue是在两端出入的List,所以也可以用数组或链表来实现。

–普通队列–

LinkedList

是的,以双向链表实现的LinkedList既是List,也是Queue。它是唯一一个允许放入null的Queue。

ArrayDeque

以循环数组实现的双向Queue。大小是2的倍数,默认是16。

普通数组只能快速在末尾添加元素,为了支持FIFO,从数组头快速取出元素,就需要使用循环数组:有队头队尾两个下标:弹出元素时,队头下标递增;加入元素时,如果已到数组空间的末尾,则将元素循环赋值到数组[0](如果此时队头下标大于0,说明队头弹出过元素,有空位),同时队尾下标指向0,再插入下一个元素则赋值到数组[1],队尾下标指向1。如果队尾的下标追上队头,说明数组所有空间已用完,进行双倍的数组扩容。

PriorityQueue

用二叉堆实现的优先级队列,详见入门教程,不再是FIFO而是按元素实现的Comparable接口或传入Comparator的比较结果来出队,数值越小,优先级越高,越先出队。但是注意其iterator()的返回不会排序。

–线程安全的队列–

ConcurrentLinkedQueue/ConcurrentLinkedDeque

无界的并发优化的Queue,基于链表,实现了依赖于CAS的无锁算法。

ConcurrentLinkedQueue的结构是单向链表和head/tail两个指针,因为入队时需要修改队尾元素的next指针,以及修改tail指向新入队的元素两个CAS动作无法原子,所以需要的特殊的算法,篇幅所限见入门教程

PriorityBlockingQueue

无界的并发优化的PriorityQueue,也是基于二叉堆。使用一把公共的读写锁。虽然实现了BlockingQueue接口,其实没有任何阻塞队列的特征,空间不够时会自动扩容。

DelayQueue

内部包含一个PriorityQueue,同样是无界的。元素需实现Delayed接口,每次调用时需返回当前离触发时间还有多久,小于0表示该触发了。
pull()时会用peek()查看队头的元素,检查是否到达触发时间。ScheduledThreadPoolExecutor用了类似的结构。

–线程安全的阻塞队列–

BlockingQueue的队列长度受限,用以保证生产者与消费者的速度不会相差太远,避免内存耗尽。队列长度设定后不可改变。当入队时队列已满,或出队时队列已空,不同函数的效果见下表:

  可能报异常 返回布尔值 可能阻塞等待 可设定等待时间
入队 add(e) offer(e) put(e) offer(e, timeout, unit)
出队 remove() poll() take() poll(timeout, unit)
查看 element() peek()

ArrayBlockingQueue

定长的并发优化的BlockingQueue,基于循环数组实现。有一把公共的读写锁与notFull、notEmpty两个Condition管理队列满或空时的阻塞状态。

LinkedBlockingQueue/LinkedBlockingDeque

可选定长的并发优化的BlockingQueue,基于链表实现,所以可以把长度设为Integer.MAX_VALUE。利用链表的特征,分离了takeLock与putLock两把锁,继续用notEmpty、notFull管理队列满或空时的阻塞状态。

补充

JDK7有个LinkedTransferQueue,transfer(e)方法保证Producer放入的元素,被Consumer取走了再返回,比SynchronousQueue更好,有空要学习下。

<think>我们正在为Java面试准备常见问题及答案总结。根据引用内容,我们可以整理出一些常见的Java面试问题。引用[2]中提供了一个包含40道Java基础面试题的列表,我们可以以此为基础进行扩展和整理。同时,引用[1]提到了一些重要的话题,如并发概念、JVM内部、32位与64位JVM的区别、单元测试及整洁代码等。引用[3]则提到了单例模式的应用场景和线程状态等问题。 因此,我们可以将问题分为几个大类:Java基础、面向对象、集合框架、并发、JVM、设计模式等。 下面我们将按照类别整理常见问题及答案。注意:由于答案需要详细,但这里我们只给出问题列表,并在每个问题后提供简要答案要点(实际准备时需详细回答)。同时,根据要求,我们使用中文,并且数学表达式使用$...$或$$...$$格式(虽然Java问题中数学表达式较少,但如果有则遵守规则)。 ### 一、Java基础 1. **八种基本数据类型的大小,以及他们的封装类** - 答案要点:byte(1)、short(2)、int(4)、long(8)、float(4)、double(8)、char(2)、boolean(未定);对应的封装类:Byte, Short, Integer, Long, Float, Double, Character, Boolean。 2. **引用数据类型** - 答案要点:类、接口、数组。 3. **Switch能否用string做参数** - 答案要点:Java 7开始支持。 4. **equals与==的区别** - 答案要点:==比较两个对象的地址(引用类型)或值(基本类型),equals是Object类的方法,默认比较地址,可重写比较内容。 5. **自动装箱,常量池** - 答案要点:自动装箱是基本类型和其包装类之间的自动转换;常量池(如字符串常量池、整型常量池)用于共享常用值。 6. **Object有哪些公用方法** - 答案要点:toString(), equals(), hashCode(), getClass(), clone(), finalize(), wait(), notify(), notifyAll()。 7. **Java的四种引用,强弱软虚,用到的场景** - 答案要点:强引用(不会被回收)、软引用(内存不足时回收)、弱引用(GC时回收)、虚引用(用于跟踪对象被回收的状态)。 8. **Hashcode的作用** - 答案要点:用于快速确定对象在哈希表中的位置。 9. **HashMap的hashcode的作用** - 答案要点:确定键值对在数组中的索引位置。 10. **为什么重载hashCode方法?** - 答案要点:为了保持和equals方法的一致性,即两个对象equals相等,则hashCode必须相等。 11. **ArrayList、LinkedList、Vector的区别** - 答案要点:ArrayList基于动态数组,随机访问快,插入删除慢;LinkedList基于双向链表,插入删除快,随机访问慢;Vector是线程安全的ArrayList。 12. **String、StringBuffer与StringBuilder的区别** - 答案要点:String不可变;StringBuffer和StringBuilder可变,StringBuffer线程安全(方法加synchronized),StringBuilder非线程安全。 13. **Map、Set、List、Queue、Stack的特点与用法** - 答案要点:Map键值对;Set无序不重复;List有序可重复;Queue队列;Stack栈(Vector子类)。 14. **HashMap和HashTable的区别** - 答案要点:HashMap非线程安全,允许null键值;HashTable线程安全(方法加synchronized),不允许null键值。 15. **JDK7与JDK8中HashMap的实现** - 答案要点:JDK7:数组+链表;JDK8:数组+链表/红黑树(当链表长度大于阈值时转换为红黑树)。 16. **HashMap和ConcurrentHashMap的区别,HashMap的底层源码** - 答案要点:ConcurrentHashMap线程安全(使用分段锁或CAS+synchronized),HashMap非线程安全。 17. **ConcurrentHashMap能完全替代HashTable吗** - 答案要点:可以,因为ConcurrentHashMap性能更好。 18. **为什么HashMap是线程不安全的** - 答案要点:多线程同时扩容可能导致死循环(JDK7)或数据覆盖(JDK8)。 19. **如何线程安全的使用HashMap** - 答案要点:使用Collections.synchronizedMap或ConcurrentHashMap。 20. **多并发情况下HashMap是否还会产生死循环** - 答案要点:JDK7中可能,JDK8已解决死循环问题,但仍有数据覆盖问题。 21. **TreeMap、HashMap、LinkedHashMap的区别** - 答案要点:TreeMap基于红黑树,有序;HashMap无序;LinkedHashMap保持插入顺序或访问顺序。 22. **Collection包结构,与Collections的区别** - 答案要点:Collection是集合接口,Collections是工具类。 23. **try-catch-finally,try里有return,finally还执行么** - 答案要点:finally会执行,如果finally中也有return,则覆盖try中的return。 24. **Exception与Error包结构,OOM你遇到过哪些情况,SOF你遇到过哪些情况** - 答案要点:Exception和Error都继承Throwable;OOM:堆溢出、方法区溢出等;SOF:栈溢出。 25. **Java(OOP)面向对象的三个特征与含义** - 答案要点:封装、继承、多态。 26. **Override和Overload的含义与区别** - 答案要点:Override重写(子类覆盖父类方法),Overload重载(同一类中方法名相同参数不同)。 27. **Interface与abstract类的区别** - 答案要点:接口多继承,不能有方法体(Java8可以有default方法);抽象类单继承,可以有抽象方法和具体方法。 28. **Static class 与 non static class的区别** - 答案要点:静态内部类不持有外部类引用,非静态内部类持有。 29. **java多态的实现原理** - 答案要点:基于继承和接口,通过方法表(虚方法表)实现。 30. **foreach与正常for循环效率对比** - 答案要点:对于数组,for循环效率略高;对于集合,foreach使用迭代器,效率与for循环接近。 31. **Java IO与NIO** - 答案要点:IO是同步阻塞,NIO是同步非阻塞(多路复用)。 32. **java反射的作用于原理** - 答案要点:在运行时获取类的信息并操作类,原理:通过Class对象。 33. **泛型常用特点解析** - 答案要点:类型安全,编译时检查,避免强制类型转换。 34. **XML的几种方式的原理与特点:DOM、SAX** - 答案要点:DOM将整个文档加载到内存形成树结构;SAX事件驱动,逐行解析。 35. **Java1.7与1.8,1.9,10 新特性** - 答案要点:Java7:try-with-resources;Java8:Lambda、Stream API;Java9:模块化;Java10:局部变量类型推断。 ### 二、设计模式 36. **设计模式:单例、工厂、适配器、责任链、观察者等** - 答案要点:单例模式(一个类只有一个实例)、工厂模式(创建对象)、适配器模式(接口转换)等。 37. **单例模式的应用场景** - 答案要点:如任务管理器、回收站、数据库连接池等(引用[3])。 ### 三、并发 38. **线程有哪几种状态?** - 答案要点:新建(New)、可运行(Runnable)、阻塞(Blocked)、等待(Waiting)、计时等待(Timed Waiting)、终止(Terminated)。 39. **并发概念** - 答案要点:包括线程安全、锁、同步等。 40. **JVM内部,32位JVM和64位JVM的区别** - 答案要点:32位JVM最大内存约4G,64位支持更大内存;64位对象引用占用空间更大。 ### 四、其他 41. **单元测试及整洁代码** - 答案要点:单元测试框架如JUnit;整洁代码原则如可读性、可维护性等。 42. **JNI的使用** - 答案要点:Java本地接口,用于调用本地方法(如C/C++)。 43. **AOP是什么** - 答案要点:面向切面编程,将横切关注点(如日志、事务)与业务逻辑分离。 44. **OOP是什么** - 答案要点:面向对象编程。 45. **AOP与OOP的区别** - 答案要点:OOP关注对象和类,AOP关注横切关注点。 以上问题覆盖了引用中提到的内容,并按照类别进行了整理。实际准备时,每个问题都需要详细回答,并可以结合代码示例。 §§相关问题§§ 1. Java中如何实现线程安全? 2. 请详细解释Java内存模型(JMM)? 3. 垃圾回收算法有哪些?各自的特点是什么? 4. 如何排查Java中的内存泄漏问题? 5. 请解释Java类加载机制?</think>以下是Java面试常见问题及答案总结,结合高频考点和实际面试需求整理,涵盖基础、集合、并发、JVM、设计模式等核心领域: --- ### **一、Java基础** 1. **八种基本数据类型及封装类** - 类型:`byte`(1字节)、`short`(2)、`int`(4)、`long`(8)、`float`(4)、`double`(8)、`char`(2)、`boolean`(未明确定义) - 封装类:`Byte`, `Short`, `Integer`, `Long`, `Float`, `Double`, `Character`, `Boolean` - **自动装箱/拆箱原理**:编译器自动转换基本类型与封装类(例:`Integer i = 10` → `Integer.valueOf(10)`) 2. **`equals()`与`==`的区别** - `==`:比较对象内存地址(引用类型)或值(基本类型) - `equals()`:默认比较地址,可重写(如`String`重写为比较内容) - **重写规范**:若重写`equals()`,必须重写`hashCode()`(确保相同对象哈希值相同) 3. **`String`、`StringBuilder`、`StringBuffer`区别** | 类 | 可变性 | 线程安全 | 性能 | |-------------|--------|----------|------------| | `String` | 不可变 | ✅ | 低(频繁拼接) | | `StringBuilder`| 可变 | ❌ | 高 | | `StringBuffer` | 可变 | ✅(synchronized)| 中 | 4. **`try-catch-finally`执行顺序** ```java try { System.out.println("try"); // 可能抛出异常 return 1; } catch (Exception e) { return 2; } finally { System.out.println("finally"); // 始终执行,若finally有return则覆盖try/catch的返回值 } ``` --- ### **二、面向对象** 1. **多态的实现原理** - **编译时**:方法重载(Overload,静态绑定) - **运行时**:方法重写(Override,动态绑定),基于JVM方法表(虚方法表) - 示例:父类引用指向子类对象 `Animal a = new Dog(); a.sound();` 2. **接口 vs 抽象类** | 特性 | 接口 (`interface`) | 抽象类 (`abstract class`) | |--------------|--------------------------|---------------------------| | 方法实现 | Java 8+支持`default`方法 | 可包含具体方法 | | 变量 | 默认`public static final`| 无限制 | | 继承 | 多继承(实现多个接口) | 单继承 | | 构造器 | ❌ | ✅ | 3. **四种引用类型** - **强引用**:`Object obj = new Object()`(GC不回收) - **软引用**:`SoftReference`(内存不足时回收,适合缓存) - **弱引用**:`WeakReference`(GC即回收,如`WeakHashMap`) - **虚引用**:`PhantomReference`(对象回收跟踪,需与`ReferenceQueue`配合) --- ### **三、集合框架** 1. **`HashMap`原理(JDK8)** - **结构**:数组 + 链表/红黑树(链表长度≥8时转红黑树) - **哈希冲突**:拉链法解决 - **扩容**:负载因子0.75,扩容时重新哈希(2倍扩容) - **线程不安全场景**:多线程扩容导致循环链表(JDK7)或数据覆盖(JDK8)[^2] 2. **`ConcurrentHashMap`线程安全实现** - **JDK7**:分段锁(Segment) - **JDK8**:CAS + synchronized(锁单个桶) - **对比`HashTable`**:锁粒度更细,性能更高(完全可替代`HashTable`) 3. **`ArrayList` vs `LinkedList`** | 操作 | `ArrayList` | `LinkedList` | |--------------|-------------------|-------------------| | 随机访问 | $O(1)$(数组) | $O(n)$(链表遍历)| | 头部插入 | $O(n)$(数据搬移)| $O(1)$ | | 内存占用 | 低(连续内存) | 高(节点指针) | --- ### **四、并发编程** 1. **线程状态转换** ```mermaid graph LR A[新建 NEW] --> B[可运行 RUNNABLE] B --> C[阻塞 BLOCKED](等锁) B --> D[等待 WAITING](wait/join) B --> E[超时等待 TIMED_WAITING](sleep(n)) C & D & E --> B B --> F[终止 TERMINATED] ``` 2. **`synchronized`优化(锁升级)** - 无锁 → 偏向锁(单线程) → 轻量级锁(CAS) → 重量级锁(OS互斥量) - **锁粗化/锁消除**:JIT编译器优化减少锁开销 3. **`volatile`关键字** - 保证可见性(直接读写主内存) - 禁止指令重排序(内存屏障) - **不保证原子性**(需配合`AtomicInteger`等) --- ### **五、JVM** 1. **内存区域** - **线程私有**:程序计数器、虚拟机栈、本地方法栈 - **线程共享**:堆(对象实例)、方法区(类信息) - **直接内存**:NIO使用的堆外内存(避免JVM堆与Native堆数据拷贝) 2. **垃圾回收算法** - **标记-清除**:碎片问题 - **复制算法**:新生代(Eden-S0-S1区) - **标记-整理**:老年代 - **分代收集**:结合年轻代(Minor GC)和老年代(Full GC)策略 3. **类加载过程** 1. 加载 → 2. 验证 → 3. 准备 → 4. 解析 → 5. 初始化 - **双亲委派模型**:避免类重复加载(`Application → Extension → Bootstrap`类加载器) --- ### **六、设计模式** 1. **单例模式实现** ```java public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { // 双重检查锁定 synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } ``` - **应用场景**:数据库连接池、日志管理器[^3] 2. **工厂模式** - **简单工厂**:一个工厂类创建多种产品 - **工厂方法**:每个产品对应一个工厂 - **抽象工厂**:创建产品族(如不同风格的UI组件) --- ### **七、新特性** - **Java 8**:Lambda表达式、Stream API、`Optional` - **Java 11**:HTTP Client API、局部变量类型推断(`var`) - **Java 17**:密封类(`sealed class`)、模式匹配(`instanceof`自动转型) ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值