java基础--集合

250 阅读14分钟

1 java中常用的容器有哪些

  • 常见的容器主要包括Collection和Map两种,Collection存储着对象的集合,而Map存储着键值对的映射表
  1. collection
    • Set
      • TreeSet:基于红黑树实现,支持有序性操作。例如:根据一个范围查找元素的操作。但是查找效率不如HashSet,HashSet查找的时间复杂度为O(1),TreeSet则为O(logN)
      • HashSet:基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用Iterator遍历HashSet得到的结果时不确定的
      • LinkedHashSet:具有HashSet的查找效率,且内部使用双向链表维护元素的插入顺序
    • List
      • ArrayList:基于动态数组实现,支持随机访问
      • Vector:和ArrayList类似,但是Vector线程安全
      • LinkedList:基于双向链表实现,只能顺序访问,但是可以快速的在链表中间插入和删除元素。不仅如此,LinkedList还可以用作栈,队列和双向队列
    • Queue
      • LinkedList:可以用它来实现双向队列
      • PriorityQueue:基于堆结构实现,可以用它来实现优先队列
  2. Map
    • TreeMap:基于红黑树实现
    • HashMap:基于哈希表实现
    • HashTable:和HashMap类似,但是它是线程安全的。它是遗留类,不应该取使用它。现在可以使用COncurrentHashMap来支持线程安全,并且ConcurrentHashMap效率更高
    • LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用顺序(LRU)

2 ArrayList和LinkedList区别

  1. ArrayList:底层基于数组实现,查找快,增删较慢
  2. LinkedList:底层基于链表实现。确切的在java6之前使用的是双向循环链表,java7取消了循环,为双向链表,查找满,增删快。LinkedList链表由一系列表项连接而成,一个表项包含三个部分:元素内容,前去表和后续表。链表内部有一个header选项,即使链表的开始也是链表的结尾。header的后继表项是链表中的第一个元素,header的前驱表项是链表中的最后一个元素
  3. 如果增删都在末尾来操作,此时ArrayList就不需要移动和复制数据了。如果数据量由百万级别时,速度时会比LinkedList要快的
  4. 如果删除操作在中间。由于LinkedList的消耗主要在遍历上,ArrayList的消耗主要在元素的移动和复制上。LinkedList的遍历速度是慢于ArrayList的赋值移动速度的,如果数据量由百万级别时,还是ArrayList要快

3 ArrayList实现RandomAccess接口有何作用?为何LinkedList却没有实现这个接口

  1. RandomAccess接口只是一个标志接口,只要List集合实现这个接口,就能支持快速随机访问。通过查看Collections类中的binarySearch()方法可以看出,判断List是否实现RandomAccess接口来实行indexedBinarySearch或iteratorBinarySearch方法。再通过查看这两个方法的源码发现:实现RandomAccess接口的List集合采用一般的for循环,而为实现这个接口的则采用迭代器,即ArrayList一般采用for循环,LinkedList一般采用迭代器
  2. ArrayList用for循环遍历比iterator迭代器快,LinkedList用iterator迭代器遍历比for循环快。

4 ArrayList扩容机制

  1. 当使用add方法时,首先调用ensureCapacityInternal方法,传入size + 1 进去,检查是否需要扩充elementData数组的大小
  2. newCapacity = 扩充数组大小为原来的1.5倍,不能自定义,如果还不够,就使用它指定要扩充的大小minCapacity,然后判断minCapacity是否大于MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8),如果大于,就取Integer.MAX_VALUE
  3. 扩容主要方法:grow
  4. ArrayList中copy数组的核心就是System.arrayCopy方法,将original数组的所有数据赋值到copy数组中,这是一个本地方法

5 Array和ArrayList有何区别,什么时候更适合Array

  1. Array可以容纳基本类型和对象,而ArrayList只能容纳对象
  2. Array是指定大小的,而ArrayList大小是固定的
  3. 如果列表的大小已经指定,大部分情况下是存储和遍历他们,更适合使用Array
  4. 对于遍历基本数据类型,尽管Collections使用自动装箱来减轻编码任务,再指定大小的基本类型的类表上工作也会变得很慢,更适合用Array
  5. 如果要使用多维数组,更适合Array

6 HashMap的实现原理,底层数据结构

  1. java7:Entry数组 + 链表
  2. java8:Node数组 + 链表/红黑树,当链表上元素个数超过8个并且数组长度大于等于64时,会自动转换成红黑树,节点变成树节点,以便调高搜索效率到O(logN)。Entry和Node都包含key,value,hash,next属性

7 HashMap的put方法的执行过程

  1. 首先根据key计算hash值,然后根据hash值确认在entry数组中的存储位置
  2. 若该位置没有元素,则直接插入。否则迭代该处元素链表应依次比较其key和hash值。如果两个hash值相等且key值相等,则用新的entry的value替换掉原来的value。如果两个hash值相等但key不相等,则将该节点插入该链表头部

8 HashMap的get方法执行过程

  1. 通过key的hash值找到在entry数组中的位置

9 HashMap的resize方法的执行过程

  • 由两种情况会调用resize方法
    1. 第一次调用HashMap的put方法时,会调用resize方法对table数组进行初始化,如果不传入指定值,默认大小为16
    2. 扩容时会调用resize
  • 每次扩容之后容量都是翻倍。扩容后要将原数组中的所有元素找到在新数组中合适的位置

10 HashMap的size为什么必须是2的整数次方

  1. 这样做总是能够保证HashMap的底层数组长度为2的n次方。当length为2的n次方时,h & (length - 1) 就相当于对length取模,而且速度比直接取模快的多,这是HashMap在速度上的一个优化。而且每次扩容时都是翻倍
  2. 如果length为2的整数次幂,则length - 1转化为2进制必定是各位均为1的形式,在于h的二进制进行操作时效率会非常的快,而且空间不浪费。

11 HashMap的死循环问题

  1. 主要是多线程同时put时,如果同时出发了rehash操作,会导致HashMap中的链表中出现循环节点,进而使得后面get的时候,会死循环

12 HashMap的get方法能否判断某个元素是否在map中

  1. HashMap的get函数的返回值不能判断一个key是否包含在map中,因为get返回null有可能是不包含该key,也有可能是该key对应的value为null。因为HashMap中允许key为null,也允许value为null

13 HashMap与HashTable的区别是什么

  1. HashTable基于Dictionary类,而HashMap是基于AbstractMap。Dictionary是任何可以将键映射到相应值的的抽象父类,而AbstractMap是基于Map接口实现的,它以最大限度减少实现此接口所需的工作
  2. HashMap的key和value都允许为null,而HashTable的key和value都不允许为null。HashMap遇到key为null的时候,调用putForNullKey方法进行处理,而对value没有处理;HashTable遇到null直接返回空指针异常
  3. HashTable是线程安全的,而HashMap不是线程安全的
  4. HashTable和HashMap的实现原理几乎一样,差别是HashTable不允许key和value为null。HashTable是线程安全的。但是HashTable线程安全的策略实现代价却太大了,简单粗暴,get/put所有相关操作都是synchronized,这就相当于给整个哈希表加了一把大锁,多线程访问的时候,只要有一个线程访问或操作该对象,那其他线程就只能阻塞,就相当于所有的操作串行化,在竞争激烈的并发场景中表现非常差

14 HashMap与ConcurrentHashMap的区别是什么

  1. HashMap不是线程安全的,而ConcurrentHashMap是线程安全的
  2. ConcurrentHashMap采用分段锁技术,将整个Hash桶分成了segment,每个segment上面都有锁的存在,在插入元素的时候就需要先找到应该插入到哪一个segment,然后再再这个片段上面进行插入,这且这里还需要获取segment锁,这样做明显减少了锁的粒度

15 HashTable与ConcurrentHashMap的区别

  1. ConcurrentHashMap是分段锁,HashTable是整体加锁

16 ConcurrentHashMap的实现原理是什么

  • java7:ConcurrentHashMap采用了数组+segment+分段锁的方式实现
  • java8:ConcurrentHashMap采用了数组+链表+红黑树的实现方式来设计,内部大量采用CAS操作
  1. ConcurrentHashMap采用了非常精妙的分段锁策略,ConcurrentHashMap的主干是一个Segment数组
  2. Segment继承了ReentrantLock,所以它就是一种可重入锁。在ConcurrentHashMap,一个Segment就是一个子哈希表,Segment里维护了一个HashEntry数组,在并发环境下,对于不同的Segment的数据进行操作是不用考虑锁竞争的。就按默认的ConcurrentLevel为16来讲,理论上就允许16个线程并发执行
  3. 所以对于同一个Segment的操作才需要考虑线程同步,不同的Segment则无需考虑。Segment类似于HashMap,一个Segment维护着一个HashEntry数组
  4. HashEntry是目前我们提到的最小的逻辑处理单元。一个ConcurrentHashMap维护者一个Segment数组,一个Segment维护者一个HashEntry数组。因此,ConcurrentHashMap定位一个元素的过程需要两次Hash操作。第一次定位到Segment第二次定位到元素所在链表的头部

17 HashSet的实现原理

  1. HashSet的实现是依赖于HashMap的,HashSet的值都是存储在HashMap中。HashSet的构造方法中会初始化一个HashMap对象,HashSet不允许值重复。因此HashSet的值是作为HashMap的key存储在HashMap中的,当存储值已经存在时返回false

18 HashSet怎么保证元素不重复

  1. 元素值作为的是map的key,map的value则是PRESENT变量,这个变量只作为放入map时的一个占位符而存在,所以没有什么实际用处。HashMap的key时不能重复的,而HashSet的元素有时作为map的key,因此保证了不重复

19 LinkedHahsMap的实现原理

  1. LinkedHashMapd也是基于HashMap实现的,不同的是他定一个了一个Entry header,这个header不是放在table里的,而是额外独立出来的。LinkedHashMap通过继承HashMap中的Entry,并添加两个属性Entry befor,after 和header结合起来组成一个双向链表来实现按插入顺序或访问顺序排序
  2. LinkedHashMap定义了排序模式accessOrder,该属性为boolean型变量,对于访问顺序,为true,对于插入顺序,为false。一般情况下,不必指定排序模式,其迭代顺序即为默认插入顺序

20 Iterator怎么使用,有什么特点

  1. 迭代器是一种设计模式,他是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为轻量级对象,因为创建它的代价小。java中的Iterator功能比较简单,而且只能单向移动
  2. 使用方法iterator要求容器返回一个Iterator对象。第一次调用Iterator的next方法时,返回第一个元素。iterator方法时java.lang.Iterable接口,被Collection继承
  3. 使用next获取序列中的下一个元素
  4. 使用hasNext检查序列中是否还有元素
  5. 使用remove将迭代器新返回的元素删除

21 Iterator和ListIterator有什么区别

  1. Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。Iterator对List只能向前遍历,ListIterator既可以向前也可以向后。ListIterator实现了Iterator接口,并包含其它功能,比如增加元素,替换元素,获取前一个和后一个元素索引等

22 Iterator和Enumeration接口的区别

  1. 于Enumeration相比,Iterator更加安全,因为当一个集合正在被遍历的时候,会阻止其他线程取修改集合。否则会抛出ConcurrentModificationException异常。这其实就是fail-fast机制,具体区别由以下三点
  2. Iterator的方法名比Enumeration更科学
  3. Iterator由fail-fast机制,比Enumeration更安全
  4. Iterator能够删除元素,Enuremation不能

23 fail-fast和fail-safe由什么区别

  1. Iterator的fail-fast属性与当前的集合共同起作用,因此他不会收到集合中任何改动的影响。java.util包中所有的集合类都被设计为fail-fast的,而java.util.concurrent中的集合类都是fail-safe的。当检测到当前正在便利的集合的结构被改变时,fail-fast迭代器抛出ConcurrentModificationExcetpion而fail-safe迭代器从来不抛出ConcurrentModifacationException

24 Collection和Collections由什么区别

  1. Collection:最基本的集合接口,一个Collection代表一组Object,即Collection的元素。他的直接继承接口由List,Set和Queue
  2. Collections:是不属于java的集合框架的,它是集合类的一个工具类。此类不能被实例化,服务于Java的Collection框架。它包含有集合操作的静态方法,实现对各种集合的搜索,排序,线程安全等操作

25 集合框架图

- 说明: 1. 所有的集合类都位于java.util包下。java集合类主要由两个接口派生而出:Collection和Map,Collection和Map是java集合框架的根接口,这两个接口又包含了一些子接口和实现类 2. 集合接口:6个接口,表示不同集合类型,是集合框架的基础 3. 抽象类:5个抽象类,对集合接口的部分实现。可扩展为自定义集合类 4. 实现类:8个实现类,对接口的具体实现 5. Collection接口时一组允许重复的对象 6. Set接口继承Collection,集合元素不重复 7. List接口继承Collection,允许重复,维护元素插入顺序 8. Map接口是键-值对象,与Collection接口没有关系

26 Set List Map可以看作集合的三大类

- List集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问
- Set集合是无需集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问
- Map集合中保存键值对形式的元素,访问时只能根据每项元素的key来访问其value

27 Collection接口

- Collection接口时处理对象集合的根接口,其中定义了许多对元素操作的方法。Collection接口的两个主要子接口是List和Set。
- Collection接口中的方法
- 其中有几个比较常用的方法,比如add,addAll,contains等
- 另外Collection中有一个iterator函数,他的作用是返回一个iterator接口,通常我们通过迭代器来遍历集合

28 List接口

- List集合代表一个有序集合,集合中每个元素都有预期对应的顺序索引。List集合允许使用重复元素,可以通过索引来访问指定位置的集合元素
- List接口继承于Collection接口,它可以定义一个允许重复的有序集合。因为List中的元素是有序的,所以我们可以通过索引来访问List中的元素,这类似于Java数组
- List接口为Collection的直接接口。List所代表的是有序的Collection,即他用某种特定的插入顺序来维护元素顺序。用户可以对列表中每个元素的插入位置进行精确空值,同时可以根据元素的整数索引访问元素,并搜索列表中的元素。实现List接口的集合主要有ArrayList,LinkedList,Vector,Stack
    1. ArrayList
        - ArrayList是一个动态数组,也是我们最常用的集合。它允许任何符合规则的元素插入甚至包括null。每一个arraylist都有一个初始容量10,该容量代表了数组的大小。随着容器中的元素的不断增加,容器大小也会增加。每次向容器中增加元素的同时都会进行容量检查,当快溢出时就会进行扩容操作。所以如果我们明确插入元素的多少,最好指定一个初始的容量值,避免过多的扩容而浪费时间,效率
    2. Vector
        - 和arraylist一样,但是vector时同步的
    3. LinkedList
    4. Stack