Java中已经有了数组为什么还要引入集合这个概念?回答这个问题前我们不妨先来看看数组的特点:
①数组一旦初始化,长度、类型就不能再改变了,只能按照初始化的数据类型和长度进行存储数据了。
②数组中提供的方法比较有限,对于数据的增删改查操作不方便,并且效率也不太高。
③可以存储重复的值,并且有序特点单一。
数组在存储和操作中的存在诸多不便,而集合恰巧能够解决上述问题,闲话少叙,直接进入重点。
在接口中继承和实现关系如下图所示:
Collection接口的的子接口为List和Set;其中List的实现类为ArrayList、Linked List、Vector;Set的实现类为HashSet和TreeSet等。
Map的主要实现类:HashMap、Hashtable等
//源码如下
public interface Collection<E> extends Iterable<E> {
Collection接口:单列集合,用来存储一个一个的对象。实现的子接口有两个:①List接口,用于存储有序的、可重复的数据(可以想象成动态的数组)②Set接口,存储无序的、不可重复的数据(类似于高中的集合)
ArrayList底层实现源代码:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
可以看出ArrayList底层是使用Object[] elementData存储的。从字面上可以看出有Array指定和数组有点瓜葛,不错,许多性质和数组也类似,比如,存在索引,可以根据索引取元素等。
ArrayList的初始化方式有两种,直接整源码:
//使用空参构造器创建集合:
ArrayList list = new Arraylist();
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//其中DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的数组。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//使用带参构造器创建集合:(initialCapacity:指定集合的长度)
ArrayList list = new Arraylist(3);
public ArrayList(int initialCapacity) {
//如果初始化长度大于0则根据传入的初始化长度创建一个数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
//如果长度等于0就和空参构造器的情况一样
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
//否则就抛出一个异常
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
分为两种第一种,为空参构造器初始化。第二种,调用带参构造器初始化。
一、空参构造器初始
//情况一:(不想看源码,可以直接跳过代码段直接看结论)
//①:根据上述源码可以看出使用空参构造器创建集合时,初始化长度为0;
//从add方法中可以看出第一次调用时会调用ensureCapacityInternal方法进行初始化长度
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//ensureCapacityInternal方法
private void ensureCapacityInternal(int minCapacity) {
//如果第一次调用的的话,就会将长度初始化为10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//DEFAULT_CAPACITY常量为第一次添加元素的初始化10
private static final int DEFAULT_CAPACITY = 10;
//如果第一次的10不够用,或者后续的扩容不够用就会调用grow方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
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);
}
//MAX_ARRAY_SIZE能够扩容的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//hugeCapacity方法
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
至此,使用空参构造器初始化集合的扩容结束;作以总结:
当使用空参构造器进行初始化时,起始长度为0,当开始添加进第一个元素后,底层就会分配长度为10的空间,当存储第十一个时就会按照原来的长度的1.5倍进行扩容,也就是15,以后每次扩容都是按照之前按的1.5倍进行扩容,数组的最大容量在Integer.MAX_VALUE-8至Integer.MAX_VALUE之间,如果超出,则抛出OutOfMemoryError错误。上述结论是基于JDK1.8版本,而在JDK1.7中略有不同ArrayList list = new ArrayList();底层初始化时就创建了长度为10的Object[]数组elementData,而扩容方式与JDK1.8无区别。jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
二、调用带参构造器初始化
//情况二
//使用含参构造器:(上面有解析)
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);
}
}
//扩容的方式和空参一样,扩容为原来的1.5倍
带参构造器和空参构造器的区别就是在初始化时,在扩容的时候是一样(没有一个猿想多写一行代码,有的话就不是合格的猿)含参总结:当使用有参构造器时,指定的长度就是起始长度,后面扩容也是按照之前的1.5倍进行扩容,也就是说100第一次扩容那个为150.
性质决定用途:ArrayListde 适合应用于数据连续性遍历,读多写少的场景
ArrayList常用方法:
方法 | 含义 |
---|---|
list.add(" "); | 添加元素:默认添加至集合尾部 |
list.add(2," "); | 添加元素:添加至指定位置 |
List lists = Arrays.asList(" “,” “,” “,” ");list.addAll(listx);默认添加到集合尾部 list.addAll(0,lists);//添加至指定位置 |
添加元素:添加指定集合中的所有元素 |
lists.clear(); | 清空列表 |
boolean b = list.contains(); | 判断集合中是否存在指定元素 |
int i = list.indexOf(); | 查找指定集合内元素的下标 |
ArrayList cloneList = (ArrayList ) list.clone(); |
集合克隆;克隆出来的是独立存在的两个集合,但是两个集合内容相同,计算出来的hashCode值相等,因此equals判断也相等,如果向克隆后添加内容,不会影响原来的集合。 |
boolean isEquals = list.equals(cloneList); | 判断两个集合内元素是否相同 |
list.remove(0); | 按照下标删除元素 |
boolean b2 = list.removeAll(Arrays.asList(“李白”, “杜甫”)); | 按照指定集合的元素进行删除 |
list1.retainAll(list2); | 交集 |
list3.set(list3.size() - 1,“h鄠邑”); | 替换 |
list3.sort(new Comparator () {@Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }); |
排序;需要重写compare方法 |
List retList = musucList.subList(0, 2); |
截取子集合 |
Object[] array1 = musucList.toArray(); | 集合转化数组,如果数组初始化长度小于集合长度,则将其复制进一个等大的数组,如果初始化长度大,则后面多余的部分用null填充 |
LinkedList是基于双向链表实现,链表中的每个节点都是一个Node类型的对象,Node对象由item、prev、next三部分组成。对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储。
//初始化方式;只有空参初始化
LinkedList list = new LinkedList();
public LinkedList() {
}
由于采用链表结构,每次添加元素,都会创建新的Node节点并分配空间,所以不存在扩容,存储结构在关于ArrayList和LinkedList中做了详细的介绍。
public boolean add(E e) {
linkLast(e);
return true;
}
//其中,Node定义为:体现了LinkedList的双向链表的说法
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;
}
}
//向集合中添加元素(默认在最后节点进行添加)
void linkLast(E e) {
final Node<E> l = last;
//将需要存的元素中的l指向前一个节点,e存储当前内容,null代表后面没有节点因此为空
//Node<>(指向上一个元素,当前元素内容,指向下一个元素)
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
//这里将向集合头部添加元素的源码也作以解释,和上述向尾部添加做以对比
private void linkFirst(E e) {
final Node<E> f = first;
//将需要存的元素中的null代表后面没有节点因此为空,e存储当前内容,f指向下一个节点,
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
//向指定节点前插入元素
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
//指向更改就行,具体操作和上述操作无区别
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
特点:LinkedList底层是使用双向链表,因此在插入和元素时只需将指向更改就行,不存在ArrayList需要将插入或删除后面的元素进行后移或前移,但也存在弊端就是无法像ArrayList那样直接访问集合中的元素,也就是说LinkedList适合于频繁进行插入或删除操作,而不适合频繁访问,因为访问操作只能从头部或尾部开始。
方法 | 含义 |
---|---|
list.add() | 在尾部插入元素 |
list.addFirst(" "); | 在头部插入元素 |
list.addLast(" "); | 在尾部插入元素 |
list.getFirst(); | 获取头部元素 |
list.getLast(); | 获取尾部元素 |
list.get() | 根据下标获取元素 |
list.removeFirst(); | 删除头部元素 |
list.removeLast(); | 删除尾部元素 |
list.remove() | 删除头部元素 |
list3.sort(new Comparator () {@Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }); |
排序;需要重写compare方法 |
list.toArray(T[ ] a) | 将链表转换为数组 |
Vector集合属于远古’物件‘现在使用不多,因此不过多赘述。通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。线程安全,性能较差。
//创建时初始化长度就为10
public Vector() {
this(10);
}
特点:无序、值唯一;无序性:不等于随机性。存储的数据在底层数组中并非照数组索引的顺序添加,而是根据数据的哈希值决定的。值唯一:保证添加的元素照equals()判断时,不能返回true.即:相同的元素只能添加一个。
在创建HashSet时,其构造方法创建维护了一个HashMap(下文会做出解释)来进行存储,元素值保存在HashMap的key位置,而value位置是由系统进行同意分配的PRESET值。是线程不安全的,可以存储null值。
//创建HashSet集合的方式:
//使用空参构造器
HashSet<String> set = new HashSet<String>();
//使用带参构造器
HashSet<String> set = new HashSet<String>(4);
//构造器
public HashSet() {
map = new HashMap<>();
}
//添加方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();
HashSet中常用的方法:
方法 | 解释 |
---|---|
set.add() | 添加新元素 |
set.clerar() | 清空集合 |
set.contains() | 判断集合中是否包含指定元素 |
set.isEmpty() | 判断集合是否为空 |
set.remove() | 删除元素 |
set.size() | 获取集合长度 |
set.iterator | 获取迭代器 |
元素的添加过程:
1、向集合中添加元素时,首先调用元素的hashCode()方法,计算元素的哈希值。然后计算出在集合中的存放位置。
2、判断此位置上是否有其他元素:
(1):位置上没有其他元素,则元素添加成功 。
(2):位置上已经存在其他元素,或以其他的方式存在(链表),则比较待插入
元素和原来元素的hash值
①:如果hash值不相同,则元素添加成功。
③:如果hash值相同,则继续需要调用元素所在类的equals方法
A:equals方法返回true,则元素添加失败
B:equals方法返回false,则元素添加成功
另外需要说明的是数组上已经存在需要放入数组时的情况在JDK1.7和JDK1.8的情况还
是有所区别的:JDK1.7是将元素放入到数组上,然后指向原来的元素(头插法);
JDK1.7是将元素存入链表尾部,原来元素指向新元素(尾插法);简单记就是”七上八
下“,7上数组,8下数组。
LinkedHashSet作为HashSet的子类是在HashSet的基础上增加了双向链表维护的两个引用,记录此数据前一个数据和后一个数据。对于频繁的遍历操作,LinkedHashSet效率高于HashSet。创建LinkedHashSet时,其构造方法调用父类构造方法,在内部创建维护了一个LinkedHashMap,用于维持元素的有序性。缘于继承关系。因此,与HashSet的特点几乎一致,不过多赘述。
LinkedHashSet和HashSet存储对象所在类的要求(原因在上文”元素的添加过程“已做出解释):
①:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
②:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
③:对象中用作 equals() 方法比较的字段,都应该用来计算 hashCode 值。
可以照添加对象的指定属性,进行排序。但在排序时有两点需要注意:
①:向TreeSet中添加的数据,要求是相同类的对象。
②:两种排序方式:自然排序(实现Comparable接口 和 定制排序(Comparator)
A.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
B.定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().
//方式一:自然排序
@Test
public void test1(){
TreeSet set = new TreeSet();
//失败:不能添加不同类的对象
// set.add(123);
// set.add(456);
// set.add("AA");
// set.add(new User("Tom",12));
//举例一:
// set.add(34);
// set.add(-34);
// set.add(43);
// set.add(11);
// set.add(8);
//举例二:
set.add(new User("Tom",12));
set.add(new User("Jerry",32));
set.add(new User("Jim",2));
set.add(new User("Mike",65));
set.add(new User("Jack",33));
set.add(new User("Jack",56));
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
//方式二:定制排序
@Test
public void test2(){
TreeSet set = new TreeSet(new Comparator() {
//照年龄从小到大排列
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(),u2.getAge());
}else{
throw new RuntimeException("输入的数据类型不匹配");
}
}
});
set.add(new User("Tom",12));
set.add(new User("Jerry",32));
set.add(new User("Jim",2));
set.add(new User("Mike",65));
set.add(new User("Mary",33));
set.add(new User("Jack",33));
set.add(new User("Jack",56));
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
Map键值对集合,集合中按Key-Value键值对方式存储,类似于高中的函数:y = f(x);
作为Map的主要实现类;线程不安全,但效率高;无序、key唯一、value允许重复、key和value允许为null。
HashMap在jdk8中相较于jdk7在底层实现方面的不同:
// 哈希值计算方法及冲突处理方法
String s = "hahaha";
int h;
System.out.println(s.hashCode());
// 计算哈希值
int hash = (h = s.hashCode()) ^ (h >>> 16);
System.out.println(hash);
int n = 16;//table 数组长度
// 方式一
// 通过%运算,效率太低(已经被抛弃了)
int index1 = hash % n;
System.out.println("下标" + index1);
// 方式二
// 通过&运算,效率高
int index2 = (n - 1) & hash;
System.out.println("下标" + index2);
源码做以简单分析(乍一看挺长,稳住别慌奥)存储方式:
//将键值对传入后调用putVal方法:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//首先对Node源码进行分析:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;//计算的哈希值
final K key;//键
V value;//值
Node<K,V> next;//指向下一个节点
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
//putVal方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//对数组的初始化,按照默认长度16创建
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;
//判断哈希是是否相等,key是否相等,也就是判断key是否已经存在了,如果已经存在了,就将新的value值替换旧的value值。
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//判断是否为树Node节点(下文有解释)
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//非数组非红黑树节点,就只剩下能存放进链表了
else {
//遍历链表
for (int binCount = 0; ; ++binCount) {
//判断是否为为尾节点(JDK1.8的尾节点)
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//链表不为空则判断是否长度大于7(TREEIFY_THRESHOLD是定义长度为8的常量),超过就将其转化为树
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;
}
HashMap<String, String> map = new HashMap<>();
map.put("010","刘二");
map.put("029","张三");
map.put("020","李四");
map.put("0912","王五");
map.put("0998","赵六");
// 清空所有KV键值对
map.clear();
// 根据key删除键值对
map.remove("0998");
// 根据key+value删除键值对
// 获取KV键值对
map.remove("0998","赵六");
// 根据key替换value
map.replace("0998","田七");
// 判断key或value是否存在
System.out.println("val 赵六 ?" + map.containsKey("赵六"));
System.out.println("kay 0998 ?" + map.containsKey("0998"));
// 根据key获取value
String city1 = map.get("010");
System.out.println("010=>" + city1);
String city2 = map.get("0755");
System.out.println("0755=>" + city2);
// 指定默认值(如果存在对应的值就获取,如果不存在就返回后面的内容)
String city3 = map.getOrDefault("0755", "未知人名");
System.out.println("0755=>" + city3);
// 获取所有的KV键值对
// 1.获取所有的key
Set<String> keySet = map.keySet();
System.out.println("所有的key" + keySet);
System.out.println(keySet.getClass());
// 2.获取所有的value
Collection<String> values = map.values();
System.out.println("所有的val" + values);
System.out.println(values.getClass());
System.out.println(map);
// 获取每个KV键值对(Entry类型的对象)
Set<Map.Entry<String, String>> set = map.entrySet();
for (Map.Entry<String,String> entry: set) {
System.out.println(entry.getKey() + "\t" + entry.getValue());
}
HashMap<String, Integer> map = new HashMap<>();
map.put("Mate60",8000);
map.put("K60",4000);
map.put("iphone",10000);
//依次获取Map中的每个【key】和【value】后,传入【BiFunction接口的实现类对象】进行替换操作后,将【替换结果】重新存入Map;
map.replaceAll(new BiFunction<String, Integer, Integer>() {
@Override
public Integer apply(String s, Integer i) {
return i - 1;
}
});
//根据key,将获取到的value传入【BiFunction接口实现类】中进行计算,并将【计算结果】,重新存入map;如果key不存在,则value默认为null
map.compute("K60", new BiFunction<String, Integer, Integer>() {
@Override
public Integer apply(String s, Integer i) {
return i * 2;
}
});
//如果key不存在,则重新计算value,并存入map;如果key存在,则不计算;
map.computeIfAbsent("K60",new Function<String, Integer>() {
@Override
public Integer apply(String key) {
return 1;
}
});
//如果key存在,则重新计算value,并存入map;如果key不存在,则不计算;
map.computeIfPresent("k60",new BiFunction<String,Integer,Integer>(){
@Override
public Integer apply(String t,Integer u){
System.out.println(t+":"+u);
return u+10;
}
});
//如果key存在,则将【原value值】与【新value值】传入【BiFunction接口实现类】中进行计算,并将【计算结果】,存入map;如果key不存在,则不进行任何计算,直接存入map;
map.merge("K60", 4000, new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer i, Integer i2) {
System.out.println(i + ":" + i2);
return i + i2;
}
});
map.merge("k80", 4000, new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer i, Integer i2) {
System.out.println(i + ":" + i2);
return i + i2;
}
});
//forEach方法:
map.forEach(new BiConsumer<String, Integer>(){
@Override
public void accept(String key, Integer val) {
System.out.printf("%d:%d",key,val);
}
});
System.out.println(map);
/*
DEFAULT_LOAD_FACTOR加载因子:加载因子实际上通俗来说就是集合的总容量与
集合中元素的比值,通常情况,设置为0.75也就是说,当初始16的容量达到12时
就会扩容。
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
static final float DEFAULT_LOAD_FACTOR = 0.75f
//resize()方法;
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) { //static final int MAXIMUM_CAPACITY = 1 << 30
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)//static final int DEFAULT_INITIAL_CAPACITY = 1 << 4
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;
}
代码放出来就是吓唬吓唬大家,能看懂的就尽量看懂,毕竟懂底层没啥错,是在看不懂就直接上结论:
HashMap在jdk8中相较于jdk7在底层实现方面的不同:
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
因为要照key进行排序:自然排序 、定制排序:和上面一样。
LinkedHashMap的底层实现原理:LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap。区别就在于:LinkedHashMap内部提供了Entry,替换HashMap中的Node。按照键值对方式存储,在HashMap的基础上,多维护了一条双向链表,用于存储顺序。事实上面的内容如果理解了,就基本懂了。
//HashMap中的内部类:Node
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
//LinckedHashMap内部类: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);
}
}
报以学习心态写,欢迎大家评论区批评指正!!!