1.Collection接口:单列数据,定义了存取一组对象的方法的集合
1.List:元素有序,可重复,Vector,ArrayList,LinkedList
2.Set:元素无序,不可重复,HashSet,LinkedHashSet,TreeSet
2.Map接口:双列数据,保存具有映射关系“key-value”的集合
Hashmap,LinkedHashMap,TreeMap,Hashtable,Properties
一、测试Collection中的方法
public class CollectionTest {
@Test
public void test1(){
ArrayList<Object> coll = new ArrayList<>();
coll.add("AA");
coll.add(123);
coll.add(new Date());
System.out.println(coll.size());//3
System.out.println(coll.isEmpty());//false
System.out.println(coll.contains(123));//true,调用equals方法,类需重写该方法
System.out.println(coll.hashCode());//1463284720
//集合到数组
Object[] arr = coll.toArray();
for (int i = 0; i <arr.length ; i++) {
System.out.println(arr[i]);
}
//数组到集合
List<Object> list = Arrays.asList(arr);
System.out.println(list.toString()); //[AA, 123, Fri Sep 04 11:11:26 CST 2020]
}
}
向Collection接口的实现类的对象中添加数据时,要求数据所在类重写equals方法,自定义类一定要重写equals方法。
二、使用Iterator遍历Collection集合
public class IteratorTest {
@Test
public void test1(){
ArrayList<Object> coll = new ArrayList<>();
coll.add(123);
coll.add(456);
coll.add(new String("Tom"));
coll.add(false);
System.out.println(coll.toString());
Iterator<Object> iterator = coll.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
如上代码所示,创建一个集合叫coll,里面有四个元素,然后利用iterator()方法创建了一个迭代器,可以遍历集合中的元素。遍历原理是:iterator对象的指针初始在集合的第一个元素的上方,然后调用hasNext()方法判断指针的下一个地址是否存在集合元素,存在的话,调用Next()方法将指针下移,并返回该地址的元素,因此完成集合元素的遍历。
注意:每次集合对象调用iterator()方法得到一个全新的迭代器对象。
三、使用foreach遍历Collection集合
public class foreachTest {
@Test
public void test2(){
ArrayList<Object> coll = new ArrayList<>();
coll.add(123);
coll.add(456);
coll.add(new String("Tom"));
coll.add(false);
for(Object obj : coll){
System.out.println(obj);
}
}
}
List接口——有序的、可重复的
四、List接口常用实现类的对比
ArrayList、LinkedList、Vector三者的异同?
同:三个类都是实现了List接口,存储有序、可重复的数据。
异:ArrayList线程不安全,但是效率高;Vector线程安全,但是效率低;二者底层都是使用Object[ ] elementData存储。LinkedList底层使用双向链表存储,对于频繁的插入和删除操作,使用此类效率较高。
五、ArrayList源码分析
A、JDK7情况下:
1.ArrayList空参构造器
public ArrayList() {
this(10);
}
new ArrayList()空参构造器在底层创建了长度为10的object[ ]数组 elementData。下面是add函数的源码:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
首先判断集合的空间是否够(初始为10),如果够,就执行elementData[size++] = e操作,如果不够,执行grow(minCapacity)进行扩容,下面看grow函数源码:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//扩容为原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
一般情况下,如果超出集合的空间大小,将扩容为原大小的1.5倍,但是如果扩大1.5倍后还是不够,就把待存储数据加上原数据的总大小赋值给新的空间大小;如果新空间大小超过规定的MAX_ARRAY_SIZE,就把更大的一个值Integer.MAX_VALUE赋值给新空间大小。新空间大小确定后,再把原来的数据传到新空间,从而完成扩容操作。
2.ArrayList带参构造器
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
带参构造器直接把表示空间大小的数据传给构造器,然后底层创建一个相应大小的object[ ]数组。
B、JDK8情况下:
1.ArrayList空参构造器
1.private static final int DEFAULT_CAPACITY = 10;
2.private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
3. public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
如上所示,默认容量大小DEFAULT_CAPACITY = 10,调用ArrayList空参构造器时,底层新建一个长度为0的object[]数组elementData,下面看add方法:
1. public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
2. private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
3.private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
4. private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
如上所示,当调用add方法时,首先跟jdk7一样判断空间是否够用,不同点是如果是首次添加元素,jdk8会先判断待添加的元素是否超过默认容量10,把较大的值赋给minCapacity。然后执行扩容操作,对于首次添加元素时,直接把newCapacity = minCapacity即可;如果不是首次添加元素,就把空间扩大为原来的1.5倍,这块与jdk7一致。总之jdk7与jdk8不同处在于首次添加元素初始化空间大小时,jdk8考虑了首次添加的元素可能超过默认容量大小10的情况。
jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
2.ArrayList带参构造器
1. private static final Object[] EMPTY_ELEMENTDATA = {};
2. public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
六、LinkedList源码分析
jdk8下:
每个元素的内部结构:
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
下面看add方法:
1. public boolean add(E e) {
linkLast(e);
return true;
}
2.void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
如上代码所示,调用add方法后会调用linkLast方法,然后返回是否添加成功的布尔类型数据。在linkLast里,last表示目前链表结构里的最后一个数据。当添加一个元素时,将之前的最后一个数据作为该元素的prev属性,同时添加的数据的next属性设置为null,并将添加的元素设置为last。如果待添加的元素是首个元素时,就将该元素声明为first,如果不是,就把原最后一个数据(现倒数第二个数据)的next属性设置为刚添加的元素。
七、Vector源码分析
1.public Vector() {
this(10);
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
2.public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
如上所示是Vector的空参构造器和带参构造器,空参构造器默认情况下底层创建一个容量大小为10的Object[]数组,带参构造器底层创建一个容量大小为参数大小的Object[]数组。
下面看如何添加元素:
1.public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
2. private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
3.private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
注意:一般情况下Vector容量不够时扩容为原来的两倍
八、List,Set遍历方法总结
1.迭代器Iterator()遍历
2.foreach循环
3.普通的循环——get( int index)
九、区分remove(index)和remove(Object obj)
remove(2)表示删除list集合里索引为2即第三个元素,如果想删除内容为2的元素,需要remove(new Integer(2))来实现。
Set接口——无序的、不可重复的
十、Set接口实现类的对比——HashSet,LinkedHashSet,TreeSet
1.HashSet:线程不安全,可以存储null值
2.LinkedHashSet:底层使用链表存储,作为HashSet子类,遍历内部元素,可以按照添加的顺序遍历。
3.TreeSet:底层使用红黑树存储,可以按照添加对象的指定属性进行排序。
十一、分析Set如何实现无序性,不可重复性
举例HashSet,存储的每个数据有它的哈希值。由hashcode()产生,哈希值决定了数据的存储位置,因此Set是无序的。基本数据类型(包装类)都重写了equals()和 hashcode()方法,如果自己写的类需要自己重写equals()和 hashcode()方法,这样如果在Set里添加这些类时,就不能够重复,如果该类未重写上述两个方法,添加时就不存在不可重复性。下面分析其原理:
1.我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
2.此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置,判断数组此位置上是否已经元素:
1.如果此位置上没其他元素,则元素a添加成功。 —>情况1
2.如果此位置上其他元素b(或以链表形式存在的多个元素,则比较元素a与元素b的hash值:
1.如果hash值不相同,则元素a添加成功。—>情况2
2.如果hash值相同,进而需要调用元素a所在类的equals()方法:
1. equals()返回true,元素a添加失败
2.equals()返回false,则元素a添加成功。—>情况3
注意点:
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下
HashSet底层:数组(初始容量为16)+链表的结构。(前提:jdk7)
十二、LinkedHashSet的无序性以及输出有序的分析
LinkedHashSet作为HashSet的子类,添加元素时,元素的存放位置跟HashSet一样的规则,但是遍历输出时却是按照添加的先后顺序输出的,这与LinkedHashSet存储的每个数据都是一个链表结构有关。
十三、TreeSet
1.向TreeSet中添加数据时,必须是相同的类的对象。
2.输出TreeSet中数据默认按照从小到大输出,如果是自己定义的类,跟类中的equals方法有关。
3.注意:TreeSet中不可重复性根据类的compareTo进行比较,而不是重写的equals方法,因此例如我们有个person类,一个是Tom,22岁,另一个是Tom,34岁,而compareTo比较的是二者的name,那么只能添加成功一个对象。因此解决方案要不是在compareTo方法中考虑到所有属性的比较,要不调用TreeSet带参构造器,参数是new comparator类型的,称为定制排序。
Map接口——双列数据
十四、Map接口的对比
1.HashMap:线程不安全,效率高,存储null的key和value值
2.LinkedHashMap:按照添加的顺序遍历,每个元素有一对指针,便于遍历。
3.TreeMap(红黑树):根据key的自然排序或者自己定制排序来实现排序遍历
4.Hashtable:线程安全,效率低,不能存储null的key和value值
注意:Map中的key使用Set存储,保证不可重复性。value使用Collection存储,实现可重复性,但是不具有有序性。每个key-value使用一个Entry对象存储,Entry具有无序的,不可重复的特点。key所在的类需要重写equals和hashcode方法,value所在的类需要重写equals方法。如果是TreeMap,则需要重写compareTo方法或者定制排序。
十五、HashMap的底层实现原理
HashMap底层实现与HashSet类似,首先底层创建一个长度为16的Entry[ ]数组,然后执行put(key1,value1)往里面存放数据。过程如下:
调用key1(无序的,不可重复的)所在类的hashcode()方法,计算key1的哈希值,此哈希值经过某种算法(散列函数)计算得到在entry[ ]数组中的位置。
1.如果此位置无数据,此时key1-value1添加成功。
2.如果此位置上有数据,一个或者多个,就比较它们的哈希值。
1.如果哈希值全不相同,则key1-value1添加成功。
2.如果哈希值和其中某个数据key2哈希值相同,则调用key1所在类的equals方法与key2比较.
1.如果equals返回false.则key1-value1添加成功。
1.如果equals返回true.则使用value1替换value2。
注意:HashMap与HashSet添加数据不同点是HashMap当执行到最后发现存储地址相同,哈希值相同,内容也相同时,HashSet就会添加失败,而HashMap则会执行替换操作。
十六、HashMap在JDK7中的源码分析
JDK7下源码:
A.构造器
1. public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
2. public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
init();
}
如上代码所示,先看HashMap构造器,空参构造器默认情况下初始容量为16的Entry[ ]数组,默认加载因子为0.75。
while (capacity < initialCapacity)
capacity <<= 1;
注意看下这两行代码,说明一个问题:我们创建HashMap时,容量只能是2的倍数。例如new HashMap(15),底层的capacity 会被设置为16。说明带参的构造器底层创建的容量只会是比参数大的最小的2的幂数。
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
threshold代表扩容的临界值,默认情况下扩容的临界值=容量*填充因子=16 * 0.75 => 12,因此当存储的数据满12时,就进行扩容。
B.下面看如何增加元素:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
int hash = hash(key);该代码得到key的哈希值,然后执行 int i = indexFor(hash, table.length);得到key的存放位置。下面是HashMap计算哈希值和通过哈希值计算存储位置的源码:
1. final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
2.static int indexFor(int h, int length) {
return h & (length-1);
}
求得数据的存放位置后,先看该位置是否有数据,无的话直接addEntry(hash,key,value,i)添加元素,有的话接着判断hash值是否相同。如果for循环里所有的哈希值都不等,执行addEntry(hash,key,value,i)添加元素。如果和某个元素的哈希值相同,则比较value ,不等的话添加成功;相等的话进入if语句,执行新value替换原value操作。
C .扩容问题
1. void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
2. void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
在这里插入代码片
如上代码所示,首先判断是否超过临界值并且待存放的位置不为空,则扩容为原来的两倍大小。并重新计算key的哈希值和存放位置。在createEntry函数里,首先把原先位置上的数据取出来,传给e,然后将新存放的数据放在该位置上,并把该数据的next设置为e。这就说明了jdk7中新存放的数据放在旧数据的上面,然后指向旧数据。
JDK8下源码:
A.构造器
1. public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
2. public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
如上代码所示,jdk8下的HashMap空参与带参构造器都没有新建一个长度为16的Entry[ ]数组,只是将加载因子和扩容的临界值分别赋值。在添加元素时,空参构造器新建一个长度为16的数组,带参构造器的底层创建大于等于参数的2的幂次数的值大小的容量。
B.如何添加元素
1. public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
2.final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
首次添加元素时,jdk8会在resize函数里新建长度为16的Node[ ] 数组。三种添加成功的情况与jdk7类似。
注意:
A、HashMap底层典型属性的属性的说明:
1.DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
2.DEFAULT_LOAD_FACTOR: 3.HashMap的默认加载因子:0.75
4.threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
5.TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
6.MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
B、HashMap在jdk8中相较于jdk7在底层实现方面的不同:
1. new HashMap():底层没创建一个长度为16的数组
2. jdk 8底层的数组是:Node[],而非Entry[]
3. 首次调用put()方法时,底层创建长度为16的数组
4. jdk7底层结构只:数组+链表。jdk8中底层结构:数组+链表+红黑树。
4.1.形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
4.2.当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。当前数组的长度<64时,进行扩容。
十七、LinkedHashMap的底层实现
LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap.
区别就在于:LinkedHashMap内部提供了Entry,替换HashMap中的Node.
JDK 8下:
HashMap中的内部类:Node
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
LinkedHashMap中的内部类:Entry
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
十八、TreeMap
public class TreeMapTest {
@Test
public void test1(){
TreeMap map = new TreeMap();
String u1 = new String("Tom ");
String u2 = new String("Jerry ");
String u3 = new String("Jack ");
String u4 = new String("Rose ");
map.put(u1,98);
map.put(u2,95);
map.put(u3,93);
map.put(u4,50);
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while(iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry)obj;
System.out.println(entry.getKey()+"****"+entry.getValue());
}
}
}
TreeMap里输出时按照排序后的输出,需要添加的key所在类重写了compareTo排序方法,可以选择自然排序或者定制排序方式。
十九、Collections工具类的使用
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所旧值
说明:ArrayList和HashMap都是线程不安全的,如果程序要求线程安全,我们可以将ArrayList、HashMap转换为线程的。
使用synchronizedList(List list) 和 synchronizedMap(Map map)