Java中的容器就是集合。在集合中分为两大接口:分别是Collection接口和Map接口。
Collection接口不是顶级接口,它是Iterable接口的子接口。Iterable接口表示可迭代的。也就是说Collection接口所有子接口和实现类都是可以通过Iterator迭代器进行迭代。在Collection接口下有三个子接口,分别是Queue、List、Set。
Queue接口表示队列,Queue的子接口Deque表示双向队列。Deque的具体实现类ArrayDeque,表示基于数组的队列实现。当需要使用队列数据结构时可以考虑使用ArrayDeque。
List接口用来存储有序可重复数据。具体实现类有LinkedList、ArrayList、Vector。LinkedList底层是基于双向非循环链表实现的,适合用在增加、删除远远大于查询的场景。 ArrayList底层是基于数组实现的,遍历效率较高。Vector和ArrayList类似,使用synchronized保证线程安全,但是在不考虑线程安全的情况下,很少使用Vector。Vector还有一个子类Stack,用来表示栈。所以当考虑使用栈数据结构时,可考虑Stack。
Set接口所有的实现类和子接口都和Map接口对应。包含HashSet、TreeSet、子接口SortedSet。
HashSet是基于HashMap实现的,把HashSet的值作为HashMap的key,存储一个Object对象作为HashMap的Value。HashSet还有一个子类LinkedHashSet ,一个基于LinkedHashMap的类。子接口。SortedSet表示可排序集合, SortedSet还有一个子接口NavigableSet表示可做范围查询的集合。 TreeSet即实现了Set接口,又实现了NavigableSet,底层是TreeMap。
Map接口有两个实现类HashMap、 HashTable和一个子接口SortedMap。 HashMap底层是基于数组+链表+红黑树实现的,在需要快速获取容器中一个值的场景中使用频率比较高,是平时开发时非常常用的集合。 HashMap还有个子类LinkedHashMap,在一些JSON数据转换时默认的转换类型就是它。
HashTable和HashMap类似,但是有synchronized,每次操作会锁住整个HashTable,效率较低,目前使用较少。HashTable的子类Properties常用在属性文件读取上。 SortedMap表示可对Key做排序,子接口NavigableMap表示可做范围查询,具体实现类是TreeMap,底层是红黑树。
List和Set的区别?
List和Set都是Collection接口的子接口。分别表示有序可重复和无序不可重复。具 体点说:
List 接口:只要实现List接口都表示类中数据是有顺序的,存储时的顺序和添加顺序有关系。因为是有顺序的,所以里面的数据也是可以有重复数据的。
Set 接口:只要实现Set接口,里面的数据都是不允许重复的。且存储顺序和添加顺序是不一致的,所以认为是无序的。但是Set接口很多实现类都是基于Map实现的,所有存储的数据会按照特定的结构和顺序进行存储。我们每次从Set里面获取到的数据都是“固定顺序的”。
ArrayList和LinkedList的区别?
ArrayList和LinkedList都是List接口的实现类。这两个类都是存储有顺序、可重复数据的容器。他们的区 别在于底层数据结构, ArrayList底层是数组, LinkedList底层是链表。
ArrayList:底层是数组,在添加数据时如果数组存放不下进行扩容。由于底层是数组,所以遍历效率较高。
LinkedList:底层是双向非循环链表。添加数据时会在链表末尾添加一个节点。由于底层是链表,新增和删除效率较高。并且LinkedList因为是链表结构所以在实现时相比ArrayList还多了addFirst()、 addLast()、getFirst()、getLast()、 removeFist()、 removeLast()等头尾删除的方法。
同时因为LinkedList从1.6版本开始实现了Deque接口, LinkedList也表示基于链表实现的双向队列。还 提供了push()、 peek()和poll()等队列操作方法。
ArrayList和Vector的区别?
ArrayList:新增方法没有使用synchronized关键字修饰, get()、 remove()方法也使用了synchronized 修饰,扩容方法每次扩容1/2
Vector:新增方法使用synchronized关键字修饰,每次扩容1倍
ArrayList:底层每次扩容1/2,新数组长度是原数组长度的3/2。且由于不是synchronized的方法,所以 在多线程下效率更高,但不能保证线程安全性。
Vector:底层每次扩容1倍,新数组长度是原数组的2倍。方法使用synchronized关键字修饰,是线程安全的。多线程下其他线程必须等待当前线程执行完成才能执行。
HashMap底层原理
从Java8开始HashMap的底层是数组+链表+红黑树。而Java 7之前HashMap底层是数组+链表,没有把链表转换为红黑树这个规则。
当实例化HashMap时,在Java 7中会默认实例化一个长度为16的数组,并设置扩容因子为0.75。而从 Java 8开始只是设置了扩容因子为0.75,变成了在第一次新增时创建默认长度为16的数组。
当数组需要扩容时,Java7中只会判断元素个数是否到达总容量的75%,达到后会对数组中元素扩容,并根据元素Hash值重新放置到新数组中。而从Java 8 开始数组扩容有两种情况,第一种和Java7相同,判断元素个数是否到达75%,开始扩容值。第二种是数组长度小于64,但链表长度已经到达8时进行扩容。
当新增元素时,如果元素已经存在会覆盖掉之前的内容。如果元素不存在,在Java 7 中会创建一个Entry 对象,根据Key的Hash值,确定对象放置位置。当发生Hash碰撞后会形成链表,且使用头插法把对象插入到链表中。而从Java 8之后,元素类型变成Entry的子类Node类型对象。且不再使用头插法,而变成了尾插法。如果链表长度大于等于8,且数组长度大于等于64,会把链表转换为红黑树,来提升查询的性能。转化时元素类型变成了Node的子类TreeNode类型。这也是从Java 8开始HashMap底层最大的变化了。
当删除元素时,会对元素Key做Hash计算后定位到数组的某个位置,然后遍历这个位置对应的链表或红黑树,找到元素后进行删除。如果这个位置是红黑树,删除元素后,红黑树中元素个数小于等于6会把红黑树转换为链表。 HashMap只有扩容,并没有缩容。
当查询元素时,会对Key做Hash运算,定位到数组中某个位置。然后遍历链表或红黑树,直到找到这个 元素,找到后返回元素的地址。
HashMap和HashTable的区别?
HashMap和HashTable都是Map接口的实现类,底层都是散列表。
HashMap:方法没有使用synchronized修饰。Key和Value都允许为null。但是由于Map的key是不允许 重复的,第二次key=null时会覆盖上次的value。
HashTable:方法使用synchronized修饰。Key和Value都不允许为null。目前HashTable已经很少被使用了。如果希望在多线程下线程安全,推荐使用ConcurrentHashMap。