Collection
list集合底层
list:有序、可重复
//ArrayList list = new ArrayList();调用的无参构造器 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//无参构造器会先创建一个空数组赋值给elementData } private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//空数组 //第一次追进list.add(i); public static Integer valueOf(int i) {//将int i,即for循环依次传进来的int类型装箱 if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } //装箱后,第二次追进list.add(i); public boolean add(E e) { //添加之前调用ensureCapacityInternal方法,确定容量够不够用? ensureCapacityInternal(size + 1); // Increments modCount!!//此处的size是当前集合的长度 //执行赋值,即for循环中的变量i elementData[size++] = e; return true; } //该方法确定容量够不够用?,传进来的minCapacity是size+1,即当前集合需要的最小容量:当前集合长度加上新增加的元素一个个数 private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));//调用该方法,参数是calculateCapacity()方法返回的10 } //计算当前需要的最小容量,传进来底层的elementData数组,以及最小容量minCapacity==size+1 private static int calculateCapacity(Object[] elementData, int minCapacity){//minCapacity==1 //先确定elementData是否是空数组 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //如果数组是空数组,就返回默认容量10和最小容量之间的最大值,此案例第一次执行list.add()方法,所以minCapacity==1,故返回10 return Math.max(DEFAULT_CAPACITY, minCapacity);//返回10 } //如果数组elementData不是空数组 return minCapacity;//返回当前集合长度+1 } private void ensureExplicitCapacity(int minCapacity) {//观察调用该方法的地方,即38行,参数是10,即int minCapacity==10 modCount++;//统计集合修改的次数 // overflow-conscious code if (minCapacity - elementData.length > 0)//如果我的最小需要的容量,减去当前list集合底层数组的实际大小大于零,即容量不够用,需要扩容 grow(minCapacity);//扩容 } //真正的扩容机制 private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1);//oldCapacity >> 1相当于除以2,即new=1.5old,old是一开始的elementData.length if (newCapacity - minCapacity < 0)//如果是第一次使用,elementcity.length==0;则newCapacity==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);//数组复制,将前面的复制到后面的,扩容的容量是null } }
ArrayList list = new ArrayList(); for(int i = 1;i <= 10;i++){ list.add(i);//每次执行都需要判断是否需要扩容 } //第二次扩容 for(int i = 11;i <=15;i++){ list.add(i); } //idea默认情况下,dubug显示的数据是简化后的如果希望看到完整的数据,需要做设置:setting->Build->Debuger->Data views->java //enable alternative view for collections classes取消勾选 list.add(100); list.add(200); list.add(null);
//分析使用有参构造其,创建和使用ArrayList的源码 //ArrayList list = new ArrayList(8);//追进有参构造器 public ArrayList(int initialCapacity) {//此时初始化的值,即形参initialCapacity的值是8 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity];//创建一个指定大小8的elementData数组 } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } //第一次执行for循环,执行list.add(i)方法,同样是先将int类型装箱 public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } //退出上述方法,再次进入list.add(i)方法 public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!!//size同样是当前list集合中元素的个数,0 elementData[size++] = e; return true; } //同样执行add方法,继续追进ensureCapacityInternal()方法 private void ensureCapacityInternal(int minCapacity) {//minCapacity==size + 1 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));//同样先判断是否需要扩容 } private static int calculateCapacity(Object[] elementData, int minCapacity) {//minCapacity==size + 1 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//这里elementData数组不是空 return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity;//不执行if语句,直接返回,minCapacity==size + 1 } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0)//1-8<0不执行if语句 grow(minCapacity); } //判断完是否需要扩容后,逐步Step Out,add()方法会直接执行elementData[size++] = e;,将e直接放入数组elementData下标是0的位置 //......... //当for循环执行8次list.add(i)方法后,初始化的elementData[8]已经用完,再次执行list.add(i)方法后就需要扩容 //同样1.5倍扩容 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); } */ @SuppressWarnings({"all"}) public static void main(String[] args) { ArrayList list = new ArrayList(8);//加断点 for (int i = 1; i <= 10; i++) { list.add(i); } for (int i = 11; i <= 15; i++) { list.add(i); } list.add(100); list.add(200); list.add(null); } //ArrayList()线程不安全;效率高 //Vector()线程安全;效率低
//分析使用有参构造其,创建和使用Vector的源码 public static void main(String[] args) { Vector vector = new Vector();//无参构造器,此处加断点 for (int i = 1; i <= 10; i++) { vector.add(i); } vector.add(100);//此处加断点 new Vector(10,10); /* 分析使用有参构造其,创建和使用Vector的源码 如果调用无参,默认10,满后,就按2倍扩容;如果指定大小,则每次直接按2倍扩容 //追Vector vector = new Vector();//无参构造器 public Class<?> loadClass(String name) throws ClassNotFoundException {//不懂 return loadClass(name, false); } public Vector() { this(10);//使用无参构造器,会直接赋值10;如果继续追则会追进有参构造器 } public Vector(int initialCapacity) { this(initialCapacity, 0);//此时形参initialCapacity值为10,若继续追,会进入下面的Vector方法 } public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity < 0)//False throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity];//创建一个数组,容量为10,赋给elementData数组 this.capacityIncrement = capacityIncrement; } //创建数组后 //第一次执行for循环中的vector.add(i),第一次追进 public static Integer valueOf(int i) {//同样是先进行装箱 if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } public synchronized boolean add(E e) { modCount++;//统计对该集合的修改次数 ensureCapacityHelper(elementCount + 1);//追进;elementCount是当前集合已经存入的元素个数 elementData[elementCount++] = e; return true; } private void ensureCapacityHelper(int minCapacity) {//minCapacity==1 // overflow-conscious code if (minCapacity - elementData.length > 0)//无需扩容 grow(minCapacity); } //当for循环执行完,则当前初始的Vector已满,再次执行vector.add(100);就需要扩容 //扩容机制主要语句:int newCapacity = oldCapacity + ((capacityIncrement > 0) ? // capacityIncrement : oldCapacity); //Vector有参构造器有Vector(int initialCapacity, int capacityIncrement);capacityIncrement是指定的每次扩容的大小,我们没有指定,则capacityIncrement==0 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 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); } */ }
分析LinkedList的常用方法:
/** * LinkedList的全面说明: * 1、LinkedList实现了双向链表和双端队列的特点 * 2、可以添加任意元素(元素可以重复),包括null * 3、线程不安全,没有实现同步 */ /** * LinkedList的底层操作机制: * 1、底层维护了一个双向链表 * 2、维护了两个first和last分别指向首节点和尾节点 * 3、每个节点(Node对象),里面又维护了prev,next,item三个属性,其中通过prev指向前一个,通过next指向后一个节点 * 4、所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高 */
添加
LinkedList linkedList = new LinkedList();//断点 /* public LinkedList() {//追进LinkedList linkedList = new LinkedList();初始化 } */ linkedList.add(1); linkedList.add(2); linkedList.add(3); System.out.println("LinkedList=" + linkedList); /* 添加方法源码 追进linkedlist.add(); public static Integer valueOf(int i) {//同样是先进行装箱 if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } //Step Out //Step Into public boolean add(E e) { linkLast(e); return true; } //Step Into void linkLast(E e) {//链表操作,将新的结点加入到双向链表的最后 final Node<E> l = last;//last一开始就是指向null,这里声明一个新的指针l指向last final Node<E> newNode = new Node<>(l, e, null);//创建一个新节点,将e的值赋值给新的节点,并让新节点的前驱指向l,后驱指向null last = newNode;//再让last指向新结点,要保证last一直指向新添加的节点 if (l == null)//l什么情况下==null?当新节点是第一个节点时 first = newNode;//当新节点是第一个节点就让first指向新节点 else l.next = newNode;//如果不是第一个节点说明新节点newNode的前驱l不为空,则连接这两个节点,让l.next指向新结点 size++;//链表长度+1 modCount++;//修改次数+1 } */
删除
LinkedList linkedList = new LinkedList();//断点 /* public LinkedList() {//追进LinkedList linkedList = new LinkedList();初始化 } */ linkedList.add(1); linkedList.add(2); linkedList.add(3); System.out.println("LinkedList=" + linkedList); //演示删除一个结点的源码 linkedList.remove();//这里默认删除的是第一个节点//断点 int a = (int)linkedList.remove();//这里默认删除的是第一个节点 /* 追进删除节点的源码: linkedList.remove(); Step Into: public E remove() { return removeFirst();//调用该方法 } Step Into: public E removeFirst() { final Node<E> f = first;//首先声明一个指针指向first,即linkedList的底层双链表的第一个结点 if (f == null)//如果f为空即当前linkedList集合没有值,删除报异常 throw new NoSuchElementException(); return unlinkFirst(f);//真正进行删除的方法 } private E unlinkFirst(Node<E> f) { // assert f == first && f != null; final E element = f.item;//声明一个泛型,将linkedList底层双链表的第一个结点的item属性(集合的第一个值)赋给element final Node<E> next = f.next;//声明一个新结点next指向当前删除的节点的后一个结点 f.item = null;//将第一个结点的值赋null f.next = null; // help GC//第二个节点也赋空,并请求jvm回收机制 first = next;//让底层双链表的头结点first指向next if (next == null)//next何时为空?当前链表只有一个结点,即删除后链表为空,也就是linkedList集合只有一个值 last = null; else next.prev = null;//如果不是,就让第一个结点的前驱赋为null size--;//删除成功,长度-- modCount++; return element;//返回删除的值 } */ //删除指定索引的节点 linkedList.remove(2);//断点 /* 追进按指定索引删除节点的源码: linkedList.remove(2); public E remove(int index) { checkElementIndex(index); return unlink(node(index)); } private void checkElementIndex(int index) { if (!isElementIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } Step Into:isElementIndex(index) private boolean isElementIndex(int index) {//判断索引是否合法 return index >= 0 && index < size; } //Step Into:return unlink(node(index)); Node<E> node(int index) {//作用:根据参数index,找到要删除的节点位置 // assert isElementIndex(index); if (index < (size >> 1)) {//如果索引位于链表的前半部分 Node<E> x = first; for (int i = 0; i < index; i++)//就从头遍历,找到要删除的结点 x = x.next; return x; } else {//如果索引位于链表的后半部分,就从尾部遍历 Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } } Step Into:return unlink(node(index)); E unlink(Node<E> x) { // assert x != null; final E element = x.item;//保留要删除的节点值 final Node<E> next = x.next;//指向要删除节点的后驱 final Node<E> prev = x.prev;//指向要删除结点的前驱 //该if语句解决:x.prev.next = x.next;//因为是双向链表,这里仅仅解决了一个方向 if (prev == null) {//如果前驱为空,说明要删除的是第一个结点 first = next;//直接让first指向删除节点的下一个结点,即next } else { prev.next = next;//如果要删除的不是第一个结点,就让要删除节点的前驱结点的后驱指针指向要删除的结点的后驱 x.prev = null;//作用:彻底解除被移除结点x与其前驱结点的双向连接,确保链表结构的完整性和垃圾回收GC的有效性 //若没有置空,会导致x与链表残留关联,可能引发内存泄漏,无法被GC收回 } //该if语句解决:x.next.prev = x.prev; if (next == null) {//如果next为空,说明删除的结点是最后一个 last = prev;//让链表的尾指针重新指向当前要被删除的结点的前驱 } else { next.prev = prev;//如果不是删除的最后一个节点,就让next的前驱指针,指向要删除的结点的前驱 x.next = null; } x.item = null; size--; modCount++; return element; } */
修改
LinkedList linkedList = new LinkedList();//断点 /* public LinkedList() {//追进LinkedList linkedList = new LinkedList();初始化 } */ linkedList.add(1); linkedList.add(2); linkedList.add(3); //修改某个节点对象 linkedList.set(1,999); /* linkedList.set(1,999); 修改源码: public static Integer valueOf(int i) {//进行装箱 if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } public E set(int index, E element) { checkElementIndex(index);//判断index的合法性 Node<E> x = node(index);//通过查找找到当前要修改的节点的位置 E oldVal = x.item;//将要修改的节点的值保存 x.item = element;//将新值赋给要修改的结点的item属性 return oldVal; } private void checkElementIndex(int index) { if (!isElementIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } Node<E> node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } } */
查到
//得到某个节点对象 Object obj = linkedList.get(1); System.out.println("linkedList: "+linkedList); /* public E get(int index) { checkElementIndex(index); return node(index).item; } */
Set集合底层
开胃小菜:
== 操作符:比较两个对象的 引用地址,即检查它们是否是内存中的同一个对象。 意义:若两个对象的引用相同(例如同一个实例),则无需进一步比较值,直接判定为“相等”。 equals() 方法,比较两个对象的 内容是否逻辑相等。 若 equals() 被正确重写(如 String、Integer 等标准类),则比较值是否相等。 若未重写 equals()(默认继承自 Object 类),则等价于 ==,仅比较引用地址。
public class Set_ { public static void main(String[] args) { /** * 1、无序,添加和取出的顺序不一致,没有索引 * 2、不允许重复元素,所以最多包含一个null */ /** * 1、HashSet实现了Set接口 * 2、HashSet实际上是HashMap * 3、可以存放null值,但是只能有一个null * 4、HashSet不保证元素使有序的,取决于hash后,在确定的索引的结果 * 5、不能有重复的元素/对象 */ Set set = new HashSet(); set.add(new Dog("jack")); set.add(new Dog("jack"));//两个jack set.add(new String("wzc")); set.add(new String("wzc"));//只有一个wzc System.out.println(set.toString()); } } class Dog { private String name; public Dog(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + '}'; } }
import java.util.HashSet; import java.util.Set; /** * @author 武振川 * @version 1.0 */ /* 1、HashSet底层是HashMap 2、添加一个元素时,先得到hash值,会将该hash值转成索引 3、找到存储数据表table,看到这个索引位置是否已经存放元素 4、如果没有直接加入 5、如果有,调用equals方法比较,如果相同,就放弃添加,如果不相同,就添加到最后 6、在java8中,添加数据,当数据的hashcode值相同,但是key不相同,就产生链表,第一个结点是table数组中数 据,挨个添加到后面,当某个链表的长度达到8个,但是table的长度没有达到64,就会继续调用resize,进行毁容 table数组,当table数组的每一个索引后的链表都达到8个数组,且table长度达到64,就会进行树化 */ public class Set_ { @SuppressWarnings({"all"}) public static void main(String[] args) { HashSet hashSet = new HashSet(); hashSet.add("java"); } } /* //Step Into: HashSet hashSet = new HashSet(); public HashSet() { map = new HashMap<>();//hashset底层也是调用HashMap,使用HashMap进行存储元素,元素作为HashMap的键 //table是HashMap的一个字段,在实例化时不会立即初始化table数组 } public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; //DEFAULT_LOAD_FACTOR==0.75,将其赋给负载因子 } //Step Into: hashSet.add("java"); public boolean add(E e) { return map.put(e, PRESENT)==null;//调用HashMap的put方法,PRESENT底层是一个Object,是一个静态变量,是所有共享的, //PRESENT仅仅起到站位作用,因为put方法必须有两个形参 } private static final Object PRESENT = new Object(); //Step Into: map.put(e, PRESENT)==null; public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //h >>> 16:是无符号操作,将h的高位移动到低16位,高位补0 //(h = key.hashCode()) ^ (h >>> 16):让key的hashcode的前16位于低16位异或 //为什么不直接使用hashcode,而是让h的高16位右移再异或? //答:将hashcode的高16位于低16位进行异或,是可以让hashcode的高位变化也能影响最终返回的结果, //当哈希容量较小时,索引计算方式:(n-1) & hash 只会用到hashcode的低4位 //很容易造成哈希冲突 } //最重要,最难懂得方法 //将哈希表想象成一个由多个“桶”组成的数组,每个桶对应一个哈希值范围,用于存放具有相同或相似哈希值的元素。 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {//形参中,hash是key的hashcode值处理过后的结果,key,value是要存入的键值对,onlyIfAbsent: 若为 true,仅在键不存在时插入。 Node<K,V>[] tab; Node<K,V> p; int n, i;//tab:哈希表数组的引用。,p:指向当前桶(数组位置)的头节点。n:哈希表的当前容量。i:计算出键在数组中的索引 if ((tab = table) == null || (n = tab.length) == 0) // transient Node<K,V>[] table;该if语句用于初始化哈希表,因为我们调用的无参构造器,即没有给默认值,则table是一个空的。这里的tab是table的一个引用,即修改tab就是直接修改table n = (tab = resize()).length;//如果table为空,调用resize()扩容,第一次默认空间16,负载因子0.75,即默认空间16,使用到16*0.75就再次扩容 if ((p = tab[i = (n - 1) & hash]) == null)//(p = tab[i = (n - 1) & hash])查看tab数组中索引为p的位置是否为空,如果为空,就将值放入tab[i] tab[i] = newNode(hash, key, value, null);//null表示将该结点放入tab数组中下标为i的位置,因为数组中每一个位置又是一个链表,又是第一次添加,所以应该让tab[i].next==null else {//如果tab[i]已经存放元素了 Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) //通过 hashCode() 和 equals() 判断要插入的元素的值是否已经存在 e = p;//如果已经存在,先标记 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { //如果table对应索引位置已经是一个链表,就是用for循环比较 //1、依次和该链表的每一个元素比较后,都不相同,则加入到该链表的最后 //注意在把元素添加到链表后,立即判断链表是否已经达到8个结点,如果已经达到,就调用trrrifyBin(tab, hash)方法,对当前这个链表(仅仅将当前满足8个结点的链表转为红黑树)进行树化(转成红黑树)。在转成红黑树时,要进行判断,判断条件 if(tab == null || (n = tab.length) < MIN_TREEITY_CAPACITY){ resize();//扩容,其中MIN_TREEITY_CAPACITY == 64,即当table数组的长度到达64才会扩容 } //2、依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break 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) { // e不为空,说明要填加的元素已经重复,返回PRESENT。也就是说只要添加的元素是重复的,e就不为空,e不为空,就会返回PRESENT,而只有返回结果为null时才是添加成功 V oldValue = e.value; if (!onlyIfAbsent || oldValue == null)//onlyIfAbsent 参数为 false(默认),因此无论oldValue是否为空都会执行该if e.value = value;//将值从 PRESENT 替换为 PRESENT(无实际意义) afterNodeAccess(e); return oldValue;//如果e!=null } } ++modCount; //size:就是每添加一个结点Node就会执行size++ if (++size > threshold) resize();//扩容方法 afterNodeInsertion(evict); return null;//添加成功返回null } void afterNodeInsertion(boolean evict) { }//该方法没有内容,仅仅是为了HashMap的子类进行重写 HashMap的扩容机制: final Node<K,V>[] resize() { // 保存原来的哈希表、容量和扩容阈值 Node<K,V>[] oldTab = table; // 原哈希表数组(扩容前) int oldCap = (oldTab == null) ? 0 : oldTab.length; // 原数组长度(若未初始化则为0) int oldThr = threshold; // 原扩容阈值(比如默认是12) int newCap = 0, newThr = 0; // 新容量和新阈值初始化 // ----------- 计算新容量和阈值 ----------- // 情况1:哈希表已初始化过(正常扩容场景) if (oldCap > 0) { // 容量已达最大限制(1<<30),不再扩容 if (oldCap >= MAXIMUM_CAPACITY) {//如果旧数组的长度超过的最大值,就不在扩容,返回原数组 threshold = Integer.MAX_VALUE; // 阈值设为整型最大值(2^31-1) return oldTab; // 直接返回原数组,不再变化 } // 常规扩容:新容量 = 旧容量翻倍(比如16→32) else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && // 左移1位相当于*2 oldCap >= DEFAULT_INITIAL_CAPACITY) // 原容量≥默认初始容量(16) newThr = oldThr << 1; // 新阈值也翻倍(比如12→24) } // 情况2:构造方法指定了初始容量(比如new HashMap(20)) else if (oldThr > 0) newCap = oldThr; // 新容量 = 构造方法设置的初始容量(比如20) // 情况3:无参构造方法(首次初始化) else { newCap = DEFAULT_INITIAL_CAPACITY; // 新容量 = 默认初始容量(16) newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 新阈值=16*0.75=12 } // 处理未明确计算阈值的情况(比如自定义初始容量或旧容量<16) if (newThr == 0) { float ft = (float)newCap * loadFactor; // 计算临时阈值 = 新容量 * 负载因子 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); // 确保阈值不超过最大容量限制 } threshold = newThr; // 更新全局扩容阈值 // ----------- 创建新数组并迁移数据 ----------- // 创建新的哈希表数组(比如容量从16→32) @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; // 将新数组设为当前哈希表 // 若旧表不为空,开始数据迁移 if (oldTab != null) { // 遍历旧表的每个桶(索引从0到oldCap-1) for (int j = 0; j < oldCap; ++j) { Node<K,V> e; // 当前桶的头节点 if ((e = oldTab[j]) != null) { // 如果当前桶有数据 oldTab[j] = null; // 清空旧桶(帮助垃圾回收) // 情况1:桶中只有一个节点(无哈希冲突) if (e.next == null) // 直接计算新位置:哈希值 & (新容量-1) newTab[e.hash & (newCap - 1)] = e; // 情况2:桶中是红黑树结构 else if (e instanceof TreeNode) // 拆分红黑树(可能退化为链表) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); // 情况3:桶中是链表结构(保持顺序重组) else { // 定义低位链表(位置不变)和高位链表(位置+旧容量) 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) { // 加入低位链表(位置j) if (loTail == null) // 若链表为空 loHead = e; // 头节点指向当前节点 else loTail.next = e; // 将节点链接到链表尾部 loTail = e; // 更新尾节点指针 } else { // 加入高位链表(位置j + oldCap) if (hiTail == null) // 若链表为空 hiHead = e; // 头节点指向当前节点 else hiTail.next = e; // 将节点链接到链表尾部 hiTail = e; // 更新尾节点指针 } } while ((e = next) != null); // 循环直到链表末尾 // 将低位链表放入新表原位置(j) if (loTail != null) { loTail.next = null; // 尾节点的next置空 newTab[j] = loHead; // 新表j位置指向链表头 } // 将高位链表放入新表新位置(j + oldCap) if (hiTail != null) { hiTail.next = null; // 尾节点的next置空 newTab[j + oldCap] = hiHead; // 新表j+oldCap位置指向链表头 } } } } } return newTab; // 返回扩容后的新哈希表 */
import java.util.HashSet; import java.util.Set; /** * @author 武振川 * @version 1.0 */ public class Set_ { @SuppressWarnings({"all"}) public static void main(String[] args) { HashSet hashSet = new HashSet(); hashSet.add("java"); hashSet.add("php"); hashSet.add("java"); } } /* 插入相同元素时 return oldValue;//如果e!=null */
LinkedHashSet底层源码
1、LinkedHashSet是HashSet的子类
2、LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表
3、LinkedHashSet根据元素的hashcode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的
4、LinkedHashSet不允许添加重复元素
public class LinkedHashSet_ { /* 1、每一个节点有befor和after属性,这样可以形成双向链表 2、在添加一个元素时,先求hash值,再求索引,确定该元素在table数组的位置,然后添加 的元素加入到求得的索引的位置 1、LinkedHashSet加入顺序和取出元素的顺序一致 2、底层维护一个LinkedHashMap 3、底层结构数组table+双向链表 4、添加第一次时,直接将数组table扩容到16,存放的结点类型是LinkedHashMap$Entry 5、数组是 HashMap$Node[]存放的元素,是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); } } */ public static void main(String[] args) { Set set = new LinkedHashSet(); set.add(new String("AA")); set.add(456);//add方法和HashSet走的过程一样 set.add(456); set.add(123); } }
Map
1、Map用于存放具有映射关系的数据 2、Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中 3、Map中的key不允许重复原因和HashSet一样 4、Map中的value可以重复 5、Map的key可以为null,value也可以为null,但key只能存放一个null,value可以多个 6、常用String类型作为key 7、key和value是一对一的关系 8、Map中的k-v存放在一个Node中,因为Node实现了Entry接口。为什么存放Entry接口?因为Entry接口有相关get、set方法,便于遍历 9、有的教材说key存放在一个set集合中,value存放在collection中,进行一对一的关系。但是set集合和collection集合都是存放的引用,真是的数据还是存放在HashMap$Node中,只是set集合中存放了key在HashMap$Node中的引用,value同理。 10、transient Set<Map.Entry<K,V>> entrySet;//entrySet集合中定义的类型是Map.Entry,但实际上存放的还是HashMap$Node只是因为Entry中有相关的get、set方法,便于遍历 11、HashMap没有实现同步,线程不安全 12、HashMap底层是数组+链表+红黑树
HashMap底层源码
public class HashMap_ { @SuppressWarnings({"all"}) public static void main(String[] args) { HashMap map = new HashMap(); map.put("java",10); map.put("php",10); map.put("java",20); } }
public class HashMap_ { @SuppressWarnings({"all"}) public static void main(String[] args) { HashMap map = new HashMap(); map.put("java",10); map.put("php",10); map.put("java",20); } /* 1、执行构造器,初始化加载因子0.75 HashMap$Node[] table = null public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } 2、装箱后执行put方法,调用hash方法,根据key的hashcode计算key的hash值(h = key.hashcode()) ^ (h >>> 16) public V put(K key, V value) {//key="java" value=10 return putVal(hash(key), key, value, false, true); //HashMap中的hash()方法是对这个hashCode进行二次加工,生成最终的哈希值。这一步处理有助于提高散列分布,减少碰撞,尤其是在哈希表容量较小时。如果直接使用 hashCode,在哈希表容量较小时(如 n=16),索引计算 (n-1) & hash 只会用到哈希值的低 4 位(因为 n-1=15,二进制是 1111)。 //通过混合高位和低位,可以让高位信息间接影响索引计算,减少冲突。 } 3、执行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; if ((tab = table) == null || (n = tab.length) == 0)//如果底层的table数组为空,或者length==0就扩容到16 n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null)//取出hash值对应的table的索引位置的Node,如果为null,就直接添加 //首先,n是当前数组的长度,也就是容量。当n是2的幂时,(n - 1)的二进制表示会是全1的形式。比如n=16时,n-1=15,二进制是1111。这时候,任何数与15进行按位与操作,实际上就是取该数的低4位,结果的范围在0到15之间,正好对应数组的索引。这相当于取模运算,但位运算比取模运算更快。 //那为什么要确保n是2的幂呢?因为在HashMap中,数组的长度总是2的幂。当用户指定初始容量时,HashMap会将其调整为最近的2的幂。这样做的好处是可以更高效地进行位运算,而不是使用较慢的模运算。 //接下来,哈希值是通过hash(key)方法计算得到的,这个方法将原始哈希码的高位和低位进行异或,以减少哈希冲突。这一步是为了让高位的信息参与到索引计算中,避免低位相同导致的大量冲突。 tab[i] = newNode(hash, key, value, null);//创建一个Node,加入到该位置即可 else { Node<K,V> e; K k;//辅助变量 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p;//如果table的索引位置的key的hash和新的key的hash值相同, //并满足table的现有的结点的key和准备添加的key是同一个对象或equals返回真,就认为不可以添加新的键值对k-v else if (p instanceof TreeNode)//如果当前的table已有的Node是红黑树,就按照红黑树的方式处理 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);//如果table为null或者大小没有达到64,暂时不树化,而是进行扩容 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;//如果添加的key相同,上面会给e赋值,这里e不为null就会执行该if语句, afterNodeAccess(e);//就会将两个相同的key中,后添加的value替代先添加的 return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; } 4、执行扩容方法 final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold;//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;//让table数组指向新扩容的数组 if (oldTab != null) {//如果扩容前的table里面有数据,就迁移到新数组 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;//返回新数组 } */ }
HashTable底层源码
1、存放的元素是键值对k-v
2、k-v都不能为null,否则抛出NullPointerException
3、hashtable线程安全
public class HashTable_ { @SuppressWarnings({"all"}) public static void main(String[] args) { Hashtable hashtable = new Hashtable(); hashtable.put("john",100); // hashtable.put(null,100); // hashtable.put("john",null); hashtable.put("lucy",100); // hashtable.put("lic",100); hashtable.put("lic",200); hashtable.put("hello11",200); hashtable.put("hello12",200); hashtable.put("hello13",200); hashtable.put("hello14",200); hashtable.put("hello15",200); hashtable.put("Taylor",1989); System.out.println(hashtable); /* 1、底层有数组,数组存放类型是Hashtable$Entry() 初始化大小为11 2、阈值:threshold 8 = 11 * 0.75 3、扩容:按照Hashtable的扩容机制执行 4、执行第九次put,依旧先装箱 public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } 5、首先判断是否为null,如果value为null直接抛异常 if (value == null) { throw new NullPointerException(); } 6、addEntry(hash, key, value, index);执行该方法,添加k-v,封装到Entry 7、 private void addEntry(int hash, K key, V value, int index) { modCount++; Entry<?,?> tab[] = table; if (count >= threshold) {//当添加次数count达到阈值,就执行扩容方法 // Rehash the table if the threshold is exceeded rehash();//扩容方法 tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; } protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; // overflow-conscious code int newCapacity = (oldCapacity << 1) + 1;//oldCapacity << 1 ==> oldCapacity*2 if (newCapacity - MAX_ARRAY_SIZE > 0) { if (oldCapacity == MAX_ARRAY_SIZE) // Keep running with MAX_ARRAY_SIZE buckets return; newCapacity = MAX_ARRAY_SIZE; } Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];//创建扩容数组 modCount++; threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); table = newMap; for (int i = oldCapacity ; i-- > 0 ;) { for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) { Entry<K,V> e = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = (Entry<K,V>)newMap[index]; newMap[index] = e; } } } */ }
TreeSet和TreeMap
TreeSet底层就是TreeMap,当执行add方法添加多个顺序时,调用无参会根据按 字典顺序(Unicode 码点顺序) 排序。如果指定构造器,如compareTo会根据程序员定义的规则排序
Properties类
1、Properties类继承制Hashtable类,并且实现了Map接口,也是使用键值对保存数据
2、使用方式与Hashtable类似
3、Properties可用于从xxx.properties文件中,加载数据到Properties对象中,并进行读取和修改
Collections工具类
collections是一个操作Set,List和Map等集合的工具类,提供了一系列的静态方法对集合进行排序、查询、修改等操作
reverse(List):反转List集合中的元素顺序
shuffle(List):对List集合进行随机排序
sort(List):根据元素的自然顺序对指定的List集合元素按升序排序
sort(List, Comparator):根据指定的Comparator产生的顺序对List集合排序
swap(List,int i,int j):将指定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对象的所有旧值oldVal
总结
选择什么集合实现类?
1、先判断存储的类型(一组对象【单列】或一组键值对【双列】
2、一组对象【单列】:Collection接口
允许重复:List
增删多:LinkedList底层维护了一个双向链表
改查多:ArrayList底层维护Object类型的可变数组
不允许重复:Set
无序:HashSet:底层是HashMap,维护了一个hash表,即数组+链表+红黑树
排序:TreeSet
插入和取出顺序一致:LinkedHashSet,维护了一个数组+双向链表
3、一组键值对【双列】:Map
键无序:HashMap,底层是哈希表,数组+链表+红黑树
键排序:TreeMap
键插入和取出顺序一致:LinkedHashMap
读取文件:Properties
这里只有TreeSet和TreeMap由于对匿名类不熟悉,就草草略过。
我只是根据我自己的理解写的笔记