深入理解Java集合类

Java集合类

Java中常用的容器

Java中的集合类主要分为两大类:Collection接口和Map接口.前者是存储对象的集合类,后者存储的是键值对(key-value)

集合类
Collection接口下又分为List,Set,Queue接口.每个接口有其具体实现类,以下是主要的集合类:

  • List(成员之间是有序的):
    • ArrayList基于动态数组,支持随机访问,查询速度快,插入/删除慢
    • Vector:和ArrayList类似,但它是线程安全的.
    • LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素.不仅如此,LinkedList还可以用作栈,队列,和双向队列.线程不安全
  • Set(成员不允许重复)
    • TreeSet:基于红黑树实现,支持有序性地操作,存入的元素都是有序的
    • HashSet:基于Hash表实现,查找的时间复杂度为O(1),但是存入的元素是无序的,也就是说使用Iterator遍历HashSet得到的结果是不确定的
    • LinkedListHashSet:具有HashSet的查询效率,并且内部使用的是双向链表维护元素的插入顺序(有序)
  • Queue
    • PriorityQueue:基于优先级堆,元素按照自然顺序或指定比较器排序
    • LinkedList:可以作为队列使用,支持FIFO(先进先出)操作
  • Map(以键值对存储)
    • HashMap:基于Hash表实现,HashMap的值也是没有顺序的,根据key获取到value,和HashSet一样也具有O(1)的查找时间复杂度.HashMap允许键值对值为NULL,但是最多只允许一条记录key的值为null(多条会覆盖),允许多条记录的value为null.HashMap是线程不安全的
    • TreeMap:基于红黑树实现,把记录的key值按照升序排序,TreeMap不允许key值为NULL,TreeMap也是线程不安全的
    • HashTable:和HashMap类似,但是它的key和value不允许是null,HashTable是线程安全的,底层实现是使用synchronized锁住整个HashTable,效率非常低.–现在使用ConcurrentHashMap来支持线程安全,ConcurrentHashMap效率更高,jdk1.7使用Segment分段锁机制,基于ReentrantLock实现,jdk1.8使用CAS+synchronized实现,空节点插入使用CAS,有Node节点则使用synchronized加锁
    • LinkedHashMap:使用双向链表来维护插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,遍历的时候比HashMap慢,key和value允许为空,线程不安全

List,Set,Map三者的区别?

Java容器分为CollentionMap两大类,Collection集合的子接口有Set,List,Queue三种,常用的是SetList接口,Map接口不是Collection的子接口

  • List:一个有序(元素存入和取出的顺序是一致的)容器,元素可以重复,可以插入多个null元素,元素都有索引,List可以通过下标访问.常用的实现类有:ArrayList,LinkedList,Vector
  • Set:一个无序(元素存入和取出顺序可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素的唯一性,Set不可以通过下标访问,常用的实现类有:HashSet,TreeSet,LinkedHashSet
  • Map是一个键值对集合,存储键,值之间的映射.key无序且唯一,value不要求顺序,允许重复.Map常用实现类有:HashMap,TreeMap,LinkedListMap,HashTable,ConcurrentHashMap

ArrayList和LinkedList的区别

  • 线程安全:ArrayListLinkedList都是不同步的,都是线程不安全的
  • 底层数据结构:ArrayList底层是使用数组来实现的,LinkedList底层是使用双向链表来实现的
  • 在插入和删除元素时:
    • ArrayList是基于数组实现的,所以在插入和删除元素时,使用add(Element)方法,ArrayList会默认尾插,这种情况下时间复杂度为O(1),但是如果是指定下标进行插入的话(add(int index,E element)),时间复杂度就为O(N- i),因为在指定下标插入的时候,第i个和第i个位置之后的(n-i)个元素都要执行向后移/前移的操作
    • LinkedList采用链表存储,所以插入删除时间复杂度都不受元素位置的影响,都是近似于O(1),数组是近似于O(n)
  • 在随机访问时:ArrayList支持快速随机访问,借助下标进行访问;而LinkedList不行
    • 扩容机制:
      • LinkedList底层是双向链表,没有初始化大小,也没有扩容机制
      • ArrayList初始化如果不指定大小,初始大小为10;在使用add方法的时候,首先会调用ensureCapacityInternal方法,传入size+1检查是否需要扩容,newCapacity = 扩充数组为原来的1.5倍;ArrayList中copy数组的核心就是System.arraycopy方法
  • 内存空间占用:ArrayList的空间浪费主要体现在List结尾需要预留一部分空间,而LinkedList的空间浪费则体现在它的每一个元素都要消耗比ArrayList更多的空间(存放数据和下一个节点的引用)
  • 如果数量有百万级以上时,还是要使用ArrayList,因为LinkedList的时间主要消耗在遍历上,而ArrayList的时间主要消耗在移动和复制上,LinkedList的遍历速度是要慢于ArrayList的移动复制速度的,如果需要处理的数据是百万级以上时,还是ArrayList

ArrayList和Array的区别?什么时候更适合用Array?

  • 区别:
    • Array可以容纳基本数据类型和对象,而ArrayList只能容纳对象;
    • Array是指定大小的,而ArrayList的大小是固定的;
  • 什么时候适合用Array?
    • 如果数组大小已经指定,并且主要的操作是存储和遍历
    • 操作基本数据类型时
    • 如果要使用多维数组的时候

HashMap

HashMap的实现原理?

  • jdk1.7中:拉链法–将链表与数组结合
  • jdk1.8中:数组+链表/红黑树;当链表上的元素个数超过8个(产生hash冲突的元素)且数组长度>=64的时候链表会自动转换成红黑树,节点会变成树节点.当链表长度小于6的时候,就会变回链表

HashMap的put方法的执行过程

当想往一个HashMap中添加一对key-value时,系统首先会计算key的hash值(利用HashCode方法),然后根据hash值确认在table中存储的位置。如果该位置没有元素,则直接创建一个新节点并插入;如果该位置有元素,则遍历该元素位置的链表/红黑树并依次比较key的hash值(通过equals方法),如果两个hash值相等并且value相等,则用新的Entry的value覆盖原来节点的value(更新值并返回旧值),如果value不相等,就把新节点插入到该链表(1.7是头插,1.8是尾插)

HashMap的get方法的执行过程

  • 通过get(key)中的key的值找到table数组中索引处的Entry,然后返回该key对应的value
  • 这里能够根据key快速的取到value,是因为HashMap在存储的过程中没有将key和value分开存储,而是当作一个整体key-value来处理的,这个整体就是Entry对象.在存储的过程中,系统会根据key的HashCode来决定Entry在table中的存储位置,取得过程也同样根据key的HashCode取出相对应的Entry对象(value就包含在其中)

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

get方法的返回值不能判断一个key是否包含在map中,因为get返回null有可能是因为map中并不包含该key,也可能是因为key对应的value值为null.(因为HashMap中允许key和value为null)

ConcurrentHashMap,HashMap,HashTable的区别

HashMapHashTableConcurrentHashMap
null键××
null值××
线程安全×
效率非常高
数据结构数组+链表+红黑树数组+链表数组+链表+红黑树
同步方式synchronized同步方法1.7版本:基于segment分段锁机制,基于ReentrantLock实现
1.8版本:基于CAS+synchronized实现,空节点插入使用CAS,有Node节点则使用synchronized加锁
迭代器类型fail-fast迭代器,在遍历时不能更新元素,否则将抛出异常fail-safe迭代器,基于容器的克隆,因此遍历操作时,元素的更新不影响遍历fail-safe迭代器,基于容器的克隆,因此遍历操作时,元素的更新不影响遍历
  • HashMap允许键和值为null,HashTableConcurrentHashMap不允许
  • HashMap不保证线程安全,HashTableConcurrentHashMap是线程安全的
  • 对于效率问题:
    • 因为HashMap不保证线程安全,所以效率最高,适合用于单线程场景
    • HashTable是使用synchronized修饰方法实现线程安全的,锁住整个HashTable效率非常低;
    • ConcurrentHashMap则是使用CAS+synchronized实现线程安全( put方法存放元素时: 通过key对象的HashCode计算出数组的索引, 如果没有Node,则使用CAS尝试插入元素, 失败则无条件自旋直到插入成功; 如果存在Node,则使用synchronized锁住该Node元素(链表/红黑树的头结点), 再执行插入操作)所以效率比HashTable高, ConcurrentHashMap已经可以完全取代HashTable
  • 对于扩容机制
    • HashTable初始size为11, 扩容: newSize=oldSize*2+1
    • HashMapConcurrentHashMap初始size都是16, 扩容为newSize=oldSize*2, size一定为2的n次幂

ConcurrentHashMap在1.7和1.8的变化?

  • 在锁方面:由Segment分段锁升级为CAS+synchronized实现
  • 数据结构方面:将Segment块换成了Node,每个Node独立,原来的默认并发度为16,变成了每个Node都独立,提高了并发度
  • Hash冲突:1.7发生Hash冲突时用链表存储,1.8先用链表存储,如果添加元素的链表节点个数超过8个或者table数组长度大于64,就转换为红黑树存储来优化查询
  • 在链表存储时:1.7是使用头插法,而1.8使用尾插法

HashMap和TreeMap的区别

  • HashMap基于数组+链表/红黑树实现, TreeMap基于红黑树实现;
  • HashMap允许NULL作为键和值, TreeMap不允许Null;
  • HashMap存入元素时不保证顺序, TreeMap在存入元素时,实现了Comparable接口或者Comparator接口, 按照排序后的顺序存入元素
  • HashMapTreeMap都是线程不安全的
  • HashMapTreeMap的key都是不能重复, value可以重复的

HashSet

HashSet的实现原理

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

HashSet如何保证元素不重复?

public boolean add(E e){
	return map.put(e,PRESENT)==null;
}

元素值作为HashMap中的key,HashMap的value则是PRESENT变量,这个变量只作为放入HashMap时的一个占位符而存在,并没有什么实际用处。这个问题的答案其实显而易见:HashMap中的key不能重复,因为HashCode计算出相同的Hash值后,会在对应存储位置的链表上操作,并不能出现两个相同的key,而HashSet的元素又是作为HashMap的key存入的,当然也不能重复

LinkedHashMap的实现原理?

LinkedHashMap也是基于HashMap实现的,它是HashMap的亲儿子,它有自己的静态内部类Entry继承自HashMap.Entry,不同的是它新增了双向链表所需要的before、after指针,用来实现按照插入顺序或者访问顺序排序

Iterator

Iterator怎么使用?有什么特点?

Iterator(迭代器)是一种设计模式,它是一个对象,可以遍历并选择序列中的对象。迭代器通常被称为”轻量级“对象,因为他创建代价小。java中的Iterator只能单向移动

  • 使用方法Iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法的时候,它返回序列的第一个元素。(iterator()方法是java.lang.Iterable接口,被Collection继承)
  • 使用next()方法获得序列中的下一个元素。
  • 使用hasNext()方法检查序列中是否还有元素。
  • 使用remove()方法将迭代器新返回的元素删除

Iterator使用方法如下实例:

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class Test {
	public static void main(String[] args){
		List<String> ll = new LinkedList<String>();
        ll.add("first");
		ll.add("second");
		ll.add("third");
		ll.add("fourth");
		for(Iterator<String> iterator = ll.iterator();iterator.hasNext();){
			String string = iterator.next();
			System.out.println(string);
		}
	}
}

Iterator和ListIterator有什么区别?

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

Iterator和Enumeration接口的区别?

enumeration(枚举)

Enumeration接口相比,Iterator更安全,因为当一个集合正在被遍历的时候,它会阻止其他线程去修改集合,否则会抛出ConcurrentModificationException异常。这其实就是fail-fast机制。具体的区别如下:

假设有两个线程,第一个线程在通过Iterator进行遍历集合A中的元素的时候,在某个时刻另外一个线程修改了集合A的结构(是结构上的修改,不是简单的修改集合元素的内容),这个时候程序会抛出ConcurrentModificationException异常,从而产生fail-fast机制

  • Iterator的方法名比Enumeration更科学
  • Iterator有fail-fast机制,比Enumeration更安全
  • Iterator能够删除元素,Enumeration不能删除元素

fail-fast与fail-safe有什么区别?

  • Iterator的fail-fast属性与当前的集合共同起作用,因此他不会受到集合中任何改动的影响
  • Java.util包中所有的集合类都被设计为fail-fast的,而Java.util.concurrent中的集合类都为fail-safe的
  • 当前检测到正在遍历的集合结构被改变的时候,fail-fast迭代器会抛出ConcurrentModificationException,而fail-safe迭代器从不抛出ConcurrentModificationException

fail-fast:快速失败

是一种错误检测机制,当在遍历集合时检测到集合被修改(如添加、删除元素),会立即抛出 ConcurrentModificationException 异常

  • 实现原理:
    • 集合内部维护一个 修改计数器(modCount),用于记录集合被修改的次数。
    • 在创建迭代器时,迭代器会记录当前的 modCount 值。
    • 每次调用迭代器的 next()remove() 方法时,迭代器会检查 modCount 是否与初始值一致。
      • 如果不一致,说明集合被修改,抛出 ConcurrentModificationException

fail-safe:安全失败:

是一种容错机制,当在遍历集合时检测到集合被修改,不会抛出异常,而是继续遍历修改前的集合副本

  • 实现原理:
    • 集合在遍历时创建一个副本,迭代器操作的是副本而不是原始集合。
    • 即使原始集合被修改,也不会影响迭代器的遍历

Collection和Collentions的区别

CollectionCollections 是 Java 中两个完全不同的概念

  • Collection:

    是Java集合框架中的一个接口,位于java.util包中.它是所有集合类的根接口

  • Collections:

    是Java集合框架中的一个工具类,位于java.util包中.它提供了一系列静态方法,用于操作或返回集合(如排序,查找,同步等)

entions的区别

CollectionCollections 是 Java 中两个完全不同的概念

  • Collection:

    是Java集合框架中的一个接口,位于java.util包中.它是所有集合类的根接口

  • Collections:

    是Java集合框架中的一个工具类,位于java.util包中.它提供了一系列静态方法,用于操作或返回集合(如排序,查找,同步等)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值