JavaSE集合框架(一)

160 阅读30分钟

接口和具体类

Collections 类提供了对集合进行排序,遍历等多种算法实现! 

  • Collection 接口存储一组不唯一,无序的对象

  • List 接口存储一组不唯一,有序的对象。

  • Set 接口存储一组唯一,无序的对象

  • Map 接口存储一组键值对象,提供key到value的映射

  • ArrayList实现了长度可变的数组,在内存中分配连续的空间。遍历元素和随机访问元素的效率比较高 

  • LinkedList采用链表存储方式。插入、删除元素时效率比较高

  • HashSet:采用哈希算法实现的Set(HashSet的底层是用HashMap实现的,因此查询效率较高,由于采用hashCode算法直接确定元素的内存地址,增删效率也挺高的。 )

ArrayList 实践

问题:我们现在有4只小狗,我们如何存储它的信息,获取总数,并能够逐条打印狗狗信息!

分析:通过List 接口的实现类ArrayList 实现该需求

  • 元素个数不确定

  • 要求获得元素的实际个数

  • 按照存储顺序获取并打印元素信息

    class Dog { private String name; //构造。。。set、get、。。。toString() }public class TestArrayList { public static void main(String[] args) { //创建ArrayList对象 , 并存储狗狗 List dogs = new ArrayList(); dogs.add(new Dog("小狗一号")); dogs.add(new Dog("小狗二号")); dogs.add(new Dog("小狗三号")); dogs.add(2,new Dog("小狗四号"));// 添加到指定位置 // .size() : ArrayList大小 System.out.println("共计有" + dogs.size() + "条狗狗。"); System.out.println("分别是:"); // .get(i) : 逐个获取个元素 for (int i = 0; i < dogs.size(); i++) { Dog dog = (Dog) dogs.get(i); System.out.println(dog.getName()); } } }

ArrayList概述 

  1. ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类。
  2. 该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity【容量】属性,表示它们所封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加。如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以减少增加重分配的次数提高性能。
  3. ArrayList的用法和Vector向类似,但是Vector是一个较老的集合,具有很多缺点,不建议使用。 

另外,ArrayList和Vector的区别是:ArrayList是线程不安全的,当多条线程访问同一个ArrayList集合时,程序需要手动保证该集合的同步性,而Vector则是线程安全的。

类中的属性 

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { // 版本号 private static final long serialVersionUID = 8683452581122892189L; // 缺省容量 private static final int DEFAULT_CAPACITY = 10; // 空对象数组 private static final Object[] EMPTY_ELEMENTDATA = {}; // 缺省空对象数组 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 元素数组 transient Object[] elementData; // 实际元素大小,默认为0 private int size; // 最大数组容量 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; } 

构造方法

无参构造方法 

/* Constructs an empty list with an initial capacity of ten. 这里就说明了默认会给10的大小,所以说一开始arrayList的容量是10. *///ArrayList中储存数据的其实就是一个数组,这个数组就是elementData. public ArrayList() { super(); //调用父类中的无参构造方法,父类中的是个空的构造方法 this.elementData = EMPTY_ELEMENTDATA; //EMPTY_ELEMENTDATA:是个空的Object[], 将elementData初始化,elementData 也是个Object[]类型。空的Object[]会给默认大小10,等会会解释什么时候赋值的。 } 

有参构造方法 1

/*Constructs an empty list with the specified initial capacity. 构造具有指定初始容量的空列表。 @param initialCapacity the initial capacity of the list 初始容量列表的初始容量 @throws IllegalArgumentException if the specified initial capacity is negative如果指定的初始容量为负,则为IllegalArgumentException */public ArrayList(int initialCapacity) { if (initialCapacity > 0) { ////将自定义的容量大小当成初始化 initialCapacity 的大小 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; //等同于无参构造方法 } else { ////判断如果自定义大小的容量小于0,则报下面这个非法数据异常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } 

有参构造方法 2 

/* Constructs a list containing the elements of the specified collection, in the order they are returned by the collection's iterator. 按照集合迭代器返回元素的顺序构造包含指定集合的元素的列表。 @param c the collection whose elements are to be placed into this list @throws NullPointerException if the specified collection is null */public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); //转换为数组 //每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类 型,那么久需要使用ArrayList中的方法去改造一下。 if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } } 

boolean add(E) 

/** * Appends the specified element to the end of this list. * 添加一个特定的元素到list的末尾。 * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { //确定内部容量是否够了,size是数组中数据的个数,因为要添加一个元素,所以size+1,先判 断size+1的这个个数数组能否放得下,就在这个方法中去判断是否数组.length是否够用了。 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; //在数据中正确的位置上放上元素e,并且size++ return true; } 

【分析:ensureCapacityInternal(xxx); 确定内部容量的方法】

private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }private static int calculateCapacity(Object[] elementData, int minCapacity) { //看,判断初始化的elementData是不是空的数组,也就是没有长度 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //因为如果是空的话,minCapacity=size+1;其实就是等于1,空的数组没有长度就存 放不了,所以就将minCapacity变成10,也就是默认大小,但是在这里,还没有真正的初始化这个 elementData的大小。 return Math.max(DEFAULT_CAPACITY, minCapacity); }//确认实际的容量,上面只是将minCapacity=10,这个方法就是真正的判断elementData是否 够用 return minCapacity; }private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code//minCapacity如果大于了实际elementData的长度,那么就说明elementData数组的长度不 够用,不够用那么就要增加elementData的length。这里有的同学就会模糊minCapacity到底是什么 呢,这里给你们分析一下 /*第一种情况:由于elementData初始化时是空的数组,那么第一次add的时候, minCapacity=size+1;也就minCapacity=1,在上一个方法(确定内部容量 ensureCapacityInternal)就会判断出是空的数组,就会给将minCapacity=10,到这一步为止, 还没有改变elementData的大小。 第二种情况:elementData不是空的数组了,那么在add的时候,minCapacity=size+1;也就是 minCapacity代表着elementData中增加之后的实际数据个数,拿着它判断elementData的length 是否够用,如果length 不够用,那么肯定要扩大容量,不然增加的这个元素就会溢出。*/ if (minCapacity - elementData.length > 0) grow(minCapacity); }//arrayList核心的方法,能扩展数组大小的真正秘密。 private void grow(int minCapacity) { // overflow-conscious code //将扩充前的elementData大小给oldCapacity int oldCapacity = elementData.length; //newCapacity就是1.5倍的oldCapacity int newCapacity = oldCapacity + (oldCapacity >> 1); //这句话就是适应于elementData就空数组的时候,length=0,那么oldCapacity=0, newCapacity=0,所以这个判断成立,在这里就是真正的初始化elementData的大小了,就是为10. 前面的工作都是准备工作。 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //如果newCapacity超过了最大的容量限制,就调用hugeCapacity,也就是将能给的最大值给 newCapacity if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: //新的容量大小已经确定好了,就copy数组,改变容量大小咯。 elementData = Arrays.copyOf(elementData, newCapacity); }//这个就是上面用到的方法,很简单,就是用来赋最大值。 private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //如果minCapacity都大于MAX_ARRAY_SIZE,那么就Integer.MAX_VALUE返回,反之将 MAX_ARRAY_SIZE返回。因为maxCapacity是三倍的minCapacity,可能扩充的太大了,就用 minCapacity来判断了。 //Integer.MAX_VALUE:2147483647 MAX_ARRAY_SIZE:2147483639 也就是说最大也就能 给到第一个数值。还是超过了这个限制,就要溢出了。相当于arraylist给了两层防护。 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } 

 void add(int,E)

public void add(int index, E element) { //检查index也就是插入的位置是否合理。 rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! //这个方法就是用来在插入元素之后,要将index之后的元素都往后移一位, System.arraycopy(elementData, index, elementData, index + 1, size - index); //在目标位置上存放元素 elementData[index] = element; size++; } 

【分析:rangeCheckForAdd(index)】 

private void rangeCheckForAdd(int index) { //插入的位置肯定不能大于size 和小于0 if (index > size || index < 0) //如果是,就报这个越界异常 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } 

【System.arraycopy(...):就是将elementData在插入位置后的所有元素,往后面移一位.】 

public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) src:源对象 srcPos:源对象对象的起始位置 dest:目标对象 destPost:目标对象的起始位置 length:从起始位置往后复制的长度。 //这段的大概意思就是解释这个方法的用法,复制src到dest,复制的位置是从src的srcPost开始, 到srcPost+length-1的位置结束,复制到destPost上,从destPost开始到destPost+length-1 的位置上, Copies an array from the specified source array, beginning at the specified position, to the specified position of the destination array. A subsequence of array components are copied from the source array referenced by src to the destination array referenced by dest. The number of components copied is equal to the length argument. The components at positions srcPos through srcPos+length-1 in the source array are copied into positions destPos through destPos+length-1, respectively, of the destination array. //告诉你复制的一种情况,如果A和B是一样的,那么先将A复制到临时数组C,然后通过C复制到B,用了 一个第三方参数 If the src and dest arguments refer to the same array object, then the copying is performed as if the components at positions srcPos through srcPos+length-1 were first copied to temporary array were copied into positions destPos through destPos+length-1 of the destination array. //这一大段,就是来说明会出现的一些问题,NullPointerException和 IndexOutOfBoundsException 还有ArrayStoreException 这三个异常出现的原因。 If dest is null, then a NullPointerException is thrown. If src is null, then a NullPointerException is thrown and the destination array is not modified. Otherwise, if any of the following is true, an ArrayStoreException is thrown and the destination is not modified: The src argument refers to an object that is not an array. The dest argument refers to an object that is not an array. The src argument and dest argument refer to arrays whose component types are different primitive types. The src argument refers to an array with a primitive component type and the dest argument refers to an array with a reference component type. The src argument refers to an array with a reference component type and the dest argument refers to an array with a primitive component type. Otherwise, if any of the following is true, an IndexOutOfBoundsException is thrown and the destination is not modified: The srcPos argument is negative. The destPos argument is negative. The length argument is negative. srcPos+length is greater than src.length, the length of the source array. destPos+length is greater than dest.length, the length of the destination array. //这里描述了一种特殊的情况,就是当A的长度大于B的长度的时候,会复制一部分,而不是完全失败。 Otherwise, if any actual component of the source array from position srcPos through srcPos+length-1 cannot be converted to the component type of the destination array by assignment conversion, an ArrayStoreException is thrown. In this case, let k be the smallest nonnegative integer less than length such that src[srcPos+k] cannot be converted to the component type of the destination array; when the exception is thrown, source array components from positions srcPos through srcPos+k-1 will already have been copied to destination array positions destPos through destPos+k-1 and no other positions of the destination array will have been modified. (Because of the restrictions already itemized, this paragraph effectively applies only to the situation where both arrays have component types that are reference types.) //这个参数列表的解释,一开始就说了, Parameters: src - the source array. srcPos - starting position in the source array. dest - the destination array. destPos - starting position in the destination data. length - the number of array elements to be copied. 

 remove(int):删除指定位置上的元素

public E remove(int index) { rangeCheck(index);//检查index的合理性 modCount++;//这个作用很多,比如用来检测快速失败的一种标志。 E oldValue = elementData(index);//通过索引直接找到该元素 int numMoved = size - index - 1;//计算要移动的位数。 if (numMoved > 0) //这个方法也已经解释过了,就是用来移动元素的。 System.arraycopy(elementData, index+1, elementData, index, numMoved); //将--size上的位置赋值为null,让gc(垃圾回收机制)更快的回收它。 elementData[--size] = null; // clear to let GC do its work //返回删除的元素。 return oldValue; }

remove(Object)

//感觉这个不怎么要分析吧,都看得懂,就是通过元素来删除该元素,就依次遍历,如果有这个元素, 就将该元素的索引传给fastRemobe(index),使用这个方法来删除该元素, //fastRemove(index)方法的内部跟remove(index)的实现几乎一样,这里最主要是知道 arrayList可以存储null值 public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } }return false; } 

clear():将elementData中每个元素都赋值为null,等待垃圾回收给回收掉

public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }

removeAll(collection c)

public boolean removeAll(Collection<?> c) { return batchRemove(c, false);//批量删除 } 

 batchRemove(xx,xx):用于两个方法,一个removeAll():它只清楚指定集合中的元素,retainAll()用来测试两个集合是否有交集。 

//这个方法,用于两处地方,如果complement为false,则用于removeAll如果为true,则给 retainAll()用,retainAll()是用来检测两个集合是否有交集的。 private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; //将原集合,记名为A int r = 0, w = 0; //r用来控制循环,w是记录有多少个交集boolean modified = false; try {for (; r < size; r++) //参数中的集合C一次检测集合A中的元素是否有, if (c.contains(elementData[r]) == complement) //有的话,就给集合A elementData[w++] = elementData[r]; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. //如果contains方法使用过程报异常 if (r != size) { //将剩下的元素都赋值给集合A, System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; }if (w != size) { //这里有两个用途,在removeAll()时,w一直为0,就直接跟clear一样,全是为 null。 //retainAll():没有一个交集返回true,有交集但不全交也返回true,而两个集合 相等的时候,返回false,所以不能根据返回值来确认两个集合是否有交集,而是通过原集合的大小是否 发生改变来判断,如果原集合中还有元素,则代表有交集,而元集合没有元素了,说明两个集合没有交 集。 // clear to let GC do its work for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } }return modified; } 

remove函数,用户移除指定下标的元素,此时会把指定下标到数组末尾的元素向前移动一个单

位,并且会把数组最后一个元素设置为null,这样是为了方便之后将整个数组不被使用时,会被GC,可以作为小的技巧使用。

set()方法

说明:设定指定下标索引的元素值

public E set(int index, E element) { // 检验索引是否合法 rangeCheck(index); // 旧值 E oldValue = elementData(index); // 赋新值 elementData[index] = element; // 返回旧值 return oldValue; } 

indexOf()方法

说明:从头开始查找与指定元素相等的元素,注意,是可以查找null元素的,意味着ArrayList中可以存放null元素的。与此函数对应的lastIndexOf,表示从尾部开始查找。 

// 从首开始查找数组里面是否存在指定元素 public int indexOf(Object o) { if (o == null) { // 查找的元素为空 for (int i = 0; i < size; i++) // 遍历数组,找到第一个为空的元素,返回下标 if (elementData[i]==null) return i; } else { // 查找的元素不为空 for (int i = 0; i < size; i++) // 遍历数组,找到第一个和指定元素相等的元 素,返回下标 if (o.equals(elementData[i])) return i; }// 没有找到,返回空 return -1; } 

get()方法

public E get(int index) { // 检验索引是否合法 rangeCheck(index); return elementData(index); } 

说明:get函数会检查索引值是否合法(只检查是否大于size,而没有检查是否小于0),值得注意的是,在get函数中存在element函数,element函数用于返回具体的元素,具体函数如下: 

E elementData(int index) { return (E) elementData[index]; } 

说明:返回的值都经过了向下转型(Object -> E),这些是对我们应用程序屏蔽的小细节。 

  1. arrayList可以存放null。
  2. arrayList本质上就是一个elementData数组。
  3. arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法。
  4. arrayList中removeAll(collection c)和clear()的区别就是removeAll可以删除批量指定的元素,而clear是全是删除集合中的元素。
  5. arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果
  6. arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。

LinkedList概述 

LinkedList是一种可以在任何位置进行高效地插入和移除操作的有序序列,它是基于双向链表实现的。

LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。

LinkedList 实现 List 接口,能对它进行队列操作。

LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。

LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。

LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。

LinkedList 是非同步的。 

LinkedList的特性 

  1. 异步,也就是非线程安全

  2. 双向链表。由于实现了list和Deque接口,能够当作队列来使用。链表:查询效率不高,但是插入和删除这种操作性能好。

  3. 是顺序存取结构(注意和随机存取结构两个概念搞清楚)

  4. List接口:列表,add、set、等一些对列表进行操作的方法

  5. Deque接口:有队列的各种特性,

  6. Cloneable接口:能够复制,使用那个copy方法。

  7. Serializable接口:能够序列化。

  8. 应该注意到没有RandomAccess:那么就推荐使用iterator,在其中就有一个foreach,增强的for循环,其中原理也就是iterator,我们在使用的时候,使用foreach或者iterator都可以。

类的属性 

public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable { // 实际元素个数 transient int size = 0; // 头结点 transient Node<E> first; // 尾结点 transient Node<E> last; }

 LinkedList的属性非常简单,一个头结点、一个尾结点、一个表示链表中实际元素个数的变量。注意,头结点、尾结点都有transient关键字修饰,这也意味着在序列化时该域是不会序列化的。

构造方法

【空参构造函数】

public LinkedList() { } 

【有参构造函数】

//将集合c中的各个元素构建成LinkedList链表。 public LinkedList(Collection<? extends E> c) { // 调用无参构造函数 this(); // 添加集合中所有的元素 addAll(c); } 

说明:会调用无参构造函数,并且会把集合中所有的元素添加到LinkedList中。 

【add()方法】

public boolean add(E e) { // 添加到末尾 linkLast(e); return true; } 

说明:add函数用于向LinkedList中添加一个元素,并且添加到链表尾部。具体添加到尾部的逻辑是由linkLast函数完成的。

【LinkLast(XXXXX)】

/** * Links e as last element. */void linkLast(E e) { final Node<E> l = last; //临时节点l(L的小写)保存last,也就是l指向了最后一个 节点 final Node<E> newNode = new Node<>(l, e, null);//将e封装为节点,并且e.prev 指向了最后一个节点 last = newNode;//newNode成为了最后一个节点,所以last指向了它 if (l == null) //判断是不是一开始链表中就什么都没有,如果没有,则newNode就成为 了第一个节点,firstlast都要指向它 first = newNode; else //正常的在最后一个节点后追加,那么原先的最后一个节点的next就要指向现在真正的 最后一个节点,原先的最后一个节点就变成了倒数第二个节点 l.next = newNode; size++;//添加一个节点,size自增 modCount++; } 

说明:对于添加一个元素至链表中会调用add方法 -> linkLast方法。 

【addAll方法】 

 addAll有两个重载函数,addAll(Collection<? extends E>)型和addAll(int, Collection<? extends E>)型,我们平时习惯调用的addAll(Collection<? extends E>)型会转化为addAll(int, Collection<? extendsE>)型。 

public boolean addAll(Collection<? extends E> c) { //继续往下看 return addAll(size, c); } 

addAll(size,c):这个方法,能包含三种情况下的添加,我们这里分析的只是构造方法,空链表的情况,看的时候只需要按照不同的情况分析下去就行了。

//真正核心的地方就是这里了,记得我们传过来的是size,c public boolean addAll(int index, Collection<? extends E> c) { //检查index这个是否为合理。这个很简单,自己点进去看下就明白了。 checkPositionIndex(index); //将集合c转换为Object数组 a Object[] a = c.toArray(); //数组a的长度numNew,也就是由多少个元素 int numNew = a.length; if (numNew == 0) //集合c是个空的,直接返回false,什么也不做。 return false; //集合c是非空的,定义两个节点(内部类),每个节点都有三个属性,item、next、prev。注 意:不要管这两个什么含义,就是用来做临时存储节点的。这个Node看下面一步的源码分析,Node就是 linkedList的最核心的实现,可以直接先跳下一个去看Node的分析 Node<E> pred, succ; //构造方法中传过来的就是index==size if (index == size) { //linkedList中三个属性:size、first、last。 size:链表中的元素个数。 first:头节点 last:尾节点,就两种情况能进来这里 //情况一、:构造方法创建的一个空的链表,那么size=0last、和first都为null。 linkedList中是空的。什么节点都没有。succ=null、pred=last=null //情况二、:链表中有节点,size就不是为0,first和last都分别指向第一个节点,和最 后一个节点,在最后一个节点之后追加元素,就得记录一下最后一个节点是什么,所以把last保存到 pred临时节点中。 succ = null; pred = last; } else { //情况三、index!=size,说明不是前面两种情况,而是在链表中间插入元素,那么就得知 道index上的节点是谁,保存到succ临时节点中,然后将succ的前一个节点保存到pred中,这样保存 了这两个节点,就能够准确的插入节点了 //举个简单的例子,有2个位置,12、如果想插数据到第二个位置,双向链表中,就需要知 道第一个位置是谁,原位置也就是第二个位置上是谁,然后才能将自己插到第二个位置上。如果这里还不 明白,先看一下文章开头对于各种链表的删除,add操作是怎么实现的。 succ = node(index); pred = succ.prev; }//前面的准备工作做完了,将遍历数组a中的元素,封装为一个个节点。 for (Object o : a) { @SuppressWarnings("unchecked") E e = (E) o; //pred就是之前所构建好的,可能为null、也可能不为null,为null的话就是属于情况 一、不为null则可能是情况二、或者情况三 Node<E> newNode = new Node<>(pred, e, null); //如果pred==null,说明是情况一,构造方法,是刚创建的一个空链表,此时的newNode 就当作第一个节点,所以把newNode给first头节点 if (pred == null) first = newNode; else//如果pred!=null,说明可能是情况2或者情况3,如果是情况2,pred就是last, 那么在最后一个节点之后追加到newNode,如果是情况3,在中间插入,pred为原index节点之前的一 个节点,将它的next指向插入的节点,也是对的 pred.next = newNode; //然后将pred换成newNode,注意,这个不在else之中,请看清楚了。 pred = newNode; }if (succ == null) { /*如果succ==null,说明是情况一或者情况二, 情况一、构造方法,也就是刚创建的一个空链表,pred已经是newNode了, last=newNode,所以linkedList的first、last都指向第一个节点。 情况二、在最后节后之后追加节点,那么原先的last就应该指向现在的最后一个节点 了,就是newNode。*/ last = pred; } else { //如果succ!=null,说明可能是情况三、在中间插入节点,举例说明这几个参数的意义, 有12两个节点,现在想在第二个位置插入节点newNode,根据前面的代码,pred=newNode, succ=2,并且1.next=newNode,已经构建好了,pred.next=succ,相当于在newNode.next = 2; succ.prev = pred,相当于 2.prev = newNode, 这样一来,这种指向关系就完成了。 first和last不用变,因为头节点和尾节点没变 pred.next = succ; //。。 succ.prev = pred; }//增加了几个元素,就把 size = size +numNew 就可以了 size += numNew; modCount++; return true; } 


说明:参数中的index表示在索引下标为index的结点(实际上是第index + 1个结点)的前面插

入。

在addAll函数中,addAll函数中还会调用到node函数,get函数也会调用到node函数,此函数是根据索引下标找到该结点并返回,具体代码如下: 

Node<E> node(int 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; // 返回该结点 } }

说明:在根据索引查找结点时,会有一个小优化,结点在前半段则从头开始遍历,在后半段则从尾开始遍历,这样就保证了只需要遍历最多一半结点就可以找到指定索引的结点。 

remove(Object o)

/** * Removes the first occurrence of the specified element from this list, * if it is present. If this list does not contain the element, it is * unchanged. More formally, removes the element with the lowest index * {@code i} such that * <tt>(o==null ? get(i)==null : o.equals(get(i))) </tt>* (if such an element exists). Returns {@code true} if this list * contained the specified element (or equivalently, if this list * changed as a result of the call). ** @param o element to be removed from this list, if present * @return {@code true} if this list contained the specified element */ //首先通过看上面的注释,我们可以知道,如果我们要移除的值在链表中存在多个一样的值,那么我们 会移除index最小的那个,也就是最先找到的那个值,如果不存在这个值,那么什么也不做。 public boolean remove(Object o) { //这里可以看到,linkedList也能存储null if (o == null) { //循环遍历链表,直到找到null值,然后使用unlink移除该值。下面的这个else中也一样 for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) { unlink(x); return true; } } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) { unlink(x); return true; } } }return false; } 

【unlink(xxxx)】 

/** * Unlinks non-null node x. */ //不能传一个null值过,注意,看之前要注意之前的next、prev这些都是谁。 E unlink(Node<E> x) { // assert x != null; //拿到节点x的三个属性 final E element = x.item;final Node<E> next = x.next; final Node<E> prev = x.prev; //这里开始往下就进行移除该元素之后的操作,也就是把指向哪个节点搞定。 if (prev == null) { //说明移除的节点是头节点,则first头节点应该指向下一个节点 first = next; } else { //不是头节点,prev.next=next:有123,将1.next指向3 prev.next = next; //然后解除x节点的前指向。 x.prev = null; }if (next == null) { //说明移除的节点是尾节点 last = prev; } else { //不是尾节点,有123,将3.prev指向1. 然后将2.next=解除指向。 next.prev = prev; x.next = null; }//x的前后指向都为null了,也把item为null,让gc回收它 x.item = null; size--; //移除一个节点,size自减 modCount++; return element; //由于一开始已经保存了x的值到element,所以返回。 } 

【get(index)查询元素的方法】

/** * Returns the element at the specified position in this list. ** @param index index of the element to return * @return the element at the specified position in this list * @throws IndexOutOfBoundsException {@inheritDoc} */ //这里没有什么,重点还是在node(index)中 public E get(int index) { checkElementIndex(index); return node(index).item; } 

【node(index)】

/** * Returns the (non-null) Node at the specified element index. */ //这里查询使用的是先从中间分一半查找 Node<E> node(int index) { // assert isElementIndex(index); //"<<":*2的几次方 “>>”:/2的几次方,例如:size<<1:size*21次方, //这个if中就是查询前半部分 if (index < (size >> 1)) {//index<size/2 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; } } 

indexOf(Object o)

//这个很简单,就是通过实体元素来查找到该元素在链表中的位置。跟remove中的代码类似,只是返回 类型不一样。 public int indexOf(Object o) { int index = 0; if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } }return -1; } 

  1. linkedList本质上是一个双向链表,通过一个Node内部类实现的这种链表结构。
  2. 能存储null值
  3. 跟arrayList相比较,就真正的知道了,LinkedList在删除和增加等操作上性能好,而ArrayList在查询的性能上好
  4. 从源码中看,它不存在容量不足的情况
  5. linkedList不光能够向前迭代,还能像后迭代,并且在迭代的过程中,可以修改值、添加值、还能移除值。
  6. linkedList不光能当链表,还能当队列使用,这个就是因为实现了Deque接口

Vector 

  1. Vector是一个可变化长度的数组
  2. Vector增加长度通过的是capacity和capacityIncrement这两个变量,目前还不知道如何实现自动扩增的,等会源码分析
  3. Vector也可以获得iterator和listIterator这两个迭代器,并且他们发生的是fail-fast,而不是fail-safe,注意这里,不要觉得这个vector是线程安全就搞错了,具体分析在下面会说
  4. Vector是一个线程安全的类,如果使用需要线程安全就使用Vector,如果不需要,就使用arrayList
  5. Vector和ArrayList很类似,就少许的不一样,从它继承的类和实现的接口来看,跟arrayList一模一样。 

构造方法作用:

  1. 初始化存储元素的容器,也就是数组,elementData,
  2. 初始化capacityIncrement的大小,默认是0,这个的作用就是扩展数组的时候,增长的大小,为0则每次扩展2倍 

【Vector():空构造】

/** * Constructs an empty vector so that its internal data array * has size {@code 10} and its standard capacity increment is * zero. */ //看注释,这个是一个空的Vector构造方法,所以让他使用内置的数组,这里还不知道什么是内置的数 组,看它调用了自身另外一个带一个参数的构造器 public Vector() { this(10); } 

【Vector(int)】 

/** * Constructs an empty vector with the specified initial capacity and * with its capacity increment equal to zero. ** @param initialCapacity the initial capacity of the vector * @throws IllegalArgumentException if the specified initial capacity * is negative */ //注释说,给空的cector构造器用和带有一个特定初始化容量用的,并且又调用了另外一个带两个参数 的构造器,并且给容量增长值(capacityIncrement=0)为0,查看vector中的变量可以发现 capacityIncrement是一个成员变量 public Vector(int initialCapacity) { this(initialCapacity, 0); } 

【ector(int,int)】 

/** * Constructs an empty vector with the specified initial capacity and * capacity increment. ** @param initialCapacity the initial capacity of the vector * @param capacityIncrement the amount by which the capacity is * increased when the vector overflows * @throws IllegalArgumentException if the specified initial capacity * is negative */ //构建一个有特定的初始化容量和容量增长值的空的Vector, public Vector(int initialCapacity, int capacityIncrement) { super();//调用父类的构造,是个空构造 if (initialCapacity < 0)//小于0,会报非法参数异常:不合法的容量 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity];//elementData是一个成员变量 数组,初始化它,并给它初始化长度。默认就是10,除非自己给值。 this.capacityIncrement = capacityIncrement;//capacityIncrement的意思是如果 要扩增数组,每次增长该值,如果该值为0,那数组就变为两倍的原长度,这个之后会分析到 } 

【Vector(Collection<? extends E> c)】 

/** * Constructs a vector containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. ** @param c the collection whose elements are to be placed into this * vector * @throws NullPointerException if the specified collection is null * @since 1.2 */ //将集合c变为Vector,返回Vector的迭代器。 public Vector(Collection<? extends E> c) { elementData = c.toArray(); elementCount = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, elementCount, Object[].class); } 

【add()方法】 

/** * Appends the specified element to the end of this Vector. ** @param e element to be appended to this Vector * @return {@code true} (as specified by {@link Collection#add}) * @since 1.2 */ //就是在vector中的末尾追加元素。但是看方法,synchronized,明白了为什么vector是线程安全 的,因为在方法前面加了synchronized关键字,给该方法加锁了,哪个线程先调用它,其它线程就得 等着,如果不清楚的就去看看多线程的知识,到后面我也会一一总结的。 public synchronized boolean add(E e) { modCount++; //通过arrayList的源码分析经验,这个方法应该是在增加元素前,检查容量是否够用 ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; } 

【ensureCapacityHelper(int)】 

/** * This implements the unsynchronized semantics of ensureCapacity. * Synchronized methods in this class can internally call this * method for ensuring capacity without incurring the cost of an * extra synchronization. ** @see #ensureCapacity(int) */ //这里注释解释,这个方法是异步(也就是能被多个线程同时访问)的,原因是为了让同步方法都能调用 到这个检测容量的方法,比如add的同时,另一个线程调用了add的重载方法,那么两个都需要同时查询 容量够不够,所以这个就不需要用synchronized修饰了。因为不会发生线程不安全的问题 private void ensureCapacityHelper(int minCapacity) { // overflow-conscious code if (minCapacity - elementData.length > 0) //容量不够,就扩增,核心方法 grow(minCapacity); }

【grow(int)】

//看一下这个方法,其实跟arrayList一样,唯一的不同就是在扩增数组的方式不一样,如果 capacityIncrement不为0,那么增长的长度就是capacityIncrement,如果为0,那么扩增为2倍 的原容量 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); } 

Stack

class Stack<E> extends Vector<E> {} 

【Vector总结(通过源码分析)】

  1. Vector线程安全是因为它的方法都加了synchronized关键字
  2. Vector的本质是一个数组,特点能是能够自动扩增,扩增的方式跟capacityIncrement的值有关
  3. 它也会fail-fast,还有一个fail-safe两个的区别在下面的list总结中会讲到。

【Stack的总结】

  1. 对栈的一些操作,先进后出
  2. 底层也是用数组实现的,因为继承了Vector
  3. 也是线程安全的

【arrayList和LinkedList区别】

arrayList底层是用数组实现的顺序表,是随机存取类型,可自动扩增,并且在初始化时,数组的长度是0,只有在增加元素时,长度才会增加。默认是10,不能无限扩增,有上限,在查询操作的时候性能更好

LinkedList底层是用链表来实现的,是一个双向链表,注意这里不是双向循环链表,顺序存取类型。在源码中,似乎没有元素个数的限制。应该能无限增加下去,直到内存满了在进行删除,增加操作时性能更好。

两个都是线程不安全的,在iterator时,会发生fail-fast:快速失效。

【arrayList和Vector的区别】

arrayList线程不安全,在用iterator,会发生fail-fast

Vector线程安全,因为在方法前加了Synchronized关键字。也会发生fail-fast 

【fail-fast和fail-safe区别和什么情况下会发生】

简单的来说:在java.util下的集合都是发生fail-fast,而在java.util.concurrent下的发生的都是fail-safe。

1)fail-fast

快速失败,例如在arrayList中使用迭代器遍历时,有另外的线程对arrayList的存储数组进行了改变,比如add、delete、等使之发生了结构上的改变,所以Iterator就会快速报一个

java.util.ConcurrentModifificationException 异常(并发修改异常),这就是快速失败。 

2)fail-safe

 安全失败,在java.util.concurrent下的类,都是线程安全的类,他们在迭代的过程中,如果有线程进行结构的改变,不会报异常,而是正常遍历,这就是安全失败。 

3)为什么在java.util.concurrent包下对集合有结构的改变,却不会报异常?

在concurrent下的集合类增加元素的时候使用Arrays.copyOf()来拷贝副本,在副本上增加元素,如果有其他线程在此改变了集合的结构,那也是在副本上的改变,而不是影响到原集合,迭代器还是照常遍历,遍历完之后,改变原引用指向副本,所以总的一句话就是如果在此包下的类进行增加删除,就会出现一个副本。所以能防止fail-fast,这种机制并不会出错,所以我们叫这种现象为fail-safe。 

4)vector也是线程安全的,为什么是fail-fast呢?

这里搞清楚一个问题,并不是说线程安全的集合就不会报fail-fast,而是报fail-safe,你得搞清楚前面所说答案的原理,出现fail-safe是因为他们在实现增删的底层机制不一样,就像上面说的,会有一个副本,而像arrayList、linekdList、verctor等,他们底层就是对着真正的引用进行操作,所以才会发生异常。 

5)既然是线程安全的,为什么在迭代的时候,还会有别的线程来改变其集合的结构呢(也就是对其删除和增加等操作)?

首先,我们迭代的时候,根本就没用到集合中的删除、增加,查询的操作,就拿vector来说,我们都没有用那些加锁的方法,也就是方法锁放在那没人拿,在迭代的过程中,有人拿了那把锁,我们也没有办法,因为那把锁就放在那边。 

【为什么现在都不提倡使用vector了】

1)vector实现线程安全的方法是在每个操作方法上加锁,这些锁并不是必须要的,在实际开发中,

一般都是通过锁一系列的操作来实现线程安全,也就是说将需要同步的资源放一起加锁来保证线程安全。

2)如果多个Thread并发执行一个已经加锁的方法,但是在该方法中,又有vector的存在,vector本身实现中已经加锁了,那么相当于锁上又加锁,会造成额外的开销。

3)就如上面第三个问题所说的,vector还有fail-fast的问题,也就是说它也无法保证遍历安全,在遍历时又得额外加锁,又是额外的开销,还不如直接用arrayList,然后再加锁呢。 

HashMap概述 

 HashMap是基于哈希表的Map接口实现的,它存储的是内容是键值对<key,value>映射。此类不保证映射的顺序,假定哈希函数将元素适当的分布在各桶之间,可为基本操作(get和put)提供稳定的性能。 

HashMap的属性 

HashMap的实例有两个参数影响其性能。

初始容量:哈希表中桶的数量

加载因子:哈希表在其容量自动增加之前可以达到多满的一种尺度

当哈希表中条目数超出了当前容量*加载因子(其实就是HashMap的实际容量)时,则对该哈希表进行rehash操作,将哈希表扩充至两倍的桶数。

Java中默认初始容量为16,加载因子为0.75。 

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 static final float DEFAULT_LOAD_FACTOR = 0.75f; 

【loadFactor加载因子】

定义:loadFactor译为装载因子。装载因子用来衡量HashMap满的程度。loadFactor的默认值为0.75f。计算HashMap的实时装载因子的方法为:size/capacity,而不是占用桶的数量去除以capacity。

loadFactor加载因子是控制数组存放数据的疏密程度,loadFactor越趋近于1,那么数组中存放的数据(entry)也就越多,也就越密,也就是会让链表的长度增加,loadFactor越小,也就是趋近于0,那么数组中存放的数据也就越稀,也就是可能数组中每个位置上就放一个元素。那有人说,就把loadFactor变为1最好吗,存的数据很多,但是这样会有一个问题,就是我们在通过key拿到我们的value时,是先通过key的hashcode值,找到对应数组中的位置,如果该位置中有很多元素,则需要通过equals来依次比较链表中的元素,拿到我们的value值,这样花费的性能就很高,如果能让数组上的每个位置尽量只有一个元素最好,我们就能直接得到value值了,所以有人又会说,那把loadFactor变得很小不就好了,但是如果变得太小,在数组中的位置就会太稀,也就是分散的太开,浪费很多空间,这样也不好,所以在hashMap中loadFactor的初始值就是0.75,一般情况下不需要更改它。 

【capacity】

capacity译为容量代表的数组的容量,也就是数组的长度,同时也是HashMap中桶的个数。默认值是16。 

【size的含义】

size就是在该HashMap的实例中实际存储的元素的个数

【threshold的作用】

threshold = capacity * loadFactor,当Size>=threshold的时候,那么就要考虑对数组的扩增了,也就是说,这个的意思就是衡量数组是否需要扩增的一个标准。

【HashMap()】 

//看上面的注释就已经知道,DEFAULT_INITIAL_CAPACITY=16,DEFAULT_LOAD_FACTOR=0.75 //初始化容量:也就是初始化数组的大小 //加载因子:数组上的存放数据疏密程度。 public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); }

【HashMap(int)】 

public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } 

【HashMap(int,flfloat)】 

public HashMap(int initialCapacity, float loadFactor) { // 初始容量不能小于0,否则报错 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); // 初始容量不能大于最大值,否则为最大值 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; // 填充因子不能小于或等于0,不能为非数字 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // 初始化填充因子 this.loadFactor = loadFactor; // 初始化threshold大小 this.threshold = tableSizeFor(initialCapacity); } 

【HashMap(Map<? extends K, ? extends V> m)】

public HashMap(Map<? extends K, ? extends V> m) { // 初始化填充因子 this.loadFactor = DEFAULT_LOAD_FACTOR; // 将m中的所有元素添加至HashMap中 putMapEntries(m, false); } 

【putMapEntries(Map<? extends K, ? extends V> m, boolean evict)函数将m的所有元素存入本HashMap实例中】

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { int s = m.size(); if (s > 0) { // 判断table是否已经初始化 if (table == null) { // pre-size // 未初始化,s为m的实际元素个数 float ft = ((float)s / loadFactor) + 1.0F; int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); // 计算得到的t大于阈值,则初始化阈值 if (t > threshold) threshold = tableSizeFor(t); }// 已初始化,并且m元素个数大于阈值,进行扩容处理 else if (s > threshold) resize(); // 将m中的所有元素添加至HashMap中 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); } } } 

【put(K key,V value)】 

public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }

【putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict)】

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; // table未初始化或者长度为0,进行扩容 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点 是放在数组中) if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); // 桶中已经存在元素 else {Node<K,V> e; K k; // 比较桶中第一个元素(数组中的结点)的hash值相等,key相等 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) // 将第一个元素赋值给e,用e来记录 e = p; // hash值不相等,即key不相等;为红黑树结点 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; }// 判断链表中结点的key值与插入的元素的key值是否相等 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) // 相等,跳出循环 break; // 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表 p = e; } }// 表示在桶中找到key值、hash值与插入元素相等的结点 if (e != null) { // 记录e的value V oldValue = e.value; // onlyIfAbsent为false或者旧值为null if (!onlyIfAbsent || oldValue == null) //用新值替换旧值 e.value = value; // 访问后回调 afterNodeAccess(e); // 返回旧值 return oldValue; } }// 结构性修改 ++modCount; // 实际大小大于阈值则扩容 if (++size > threshold) resize(); // 插入后回调 afterNodeInsertion(evict); return null; }

HashMap并没有直接提供putVal接口给用户调用,而是提供的put函数,而put函数就是通过putVal来插入元素的。 

【get(Object key)】 

public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } 

【getNode(int hash,Pbject key)】

final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; // table已经初始化,长度大于0,根据hash寻找table中的项也不为空 if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { // 桶中第一项(数组元素)相等 if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; // 桶中不止一个结点 if ((e = first.next) != null) { // 为红黑树结点 if (first instanceof TreeNode) // 在红黑树中查找 return ((TreeNode<K,V>)first).getTreeNode(hash, key); // 否则,在链表中查找 do {if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } }return null;} 

 HashMap并没有直接提供getNode接口给用户调用,而是提供的get函数,而get函数就是通过getNode来取得元素的。 

【resize方法】 

final Node<K,V>[] resize() { // 当前table保存 Node<K,V>[] oldTab = table; // 保存table大小 int oldCap = (oldTab == null) ? 0 : oldTab.length; // 保存当前阈值 int oldThr = threshold; int newCap, newThr = 0; // 之前table大小大于0 if (oldCap > 0) { // 之前table大于最大容量 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 }// 之前阈值大于0 else if (oldThr > 0) newCap = oldThr; // oldCap = 0并且oldThr = 0,使用缺省值(如使用HashMap()构造函数,之后再插入一个 元素会调用resize函数,会进入这一步) else {newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); }// 新阈值为0 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"}) // 初始化table Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; // 之前的table已经初始化过 if (oldTab != null) { // 复制元素,重新进行hash 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; // 将同一桶中的元素根据(e.hash & oldCap)是否为0进行分割,分成两 个不同的链表,完成rehashdo {next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; elseloTail.next = e; loTail = e; }else {if (hiTail == null) hiHead = e; elsehiTail.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; } 

进行扩容,会伴随着一次重新hash分配,并且会遍历hash表中所有的元素,是非常耗时的。在编写程序中,要尽量避免resize。 

迭代器

所有实现了Collection接口的容器类都有一个iterator方法用以返回一个实现Iterator接口的对象

Iterator对象称作为迭代器,用以方便的对容器内元素的遍历操作,Iterator接口定义了如下方法:

  • boolean hashNext();//判断是否有元素没有被遍历
  • Object next();//返回游标当前位置的元素并将游标移动到下一个位置
  • void remove();//删除游标左边的元素,在执行完next之后该操作只能执行一次

通过迭代器Iterator实现遍历

获取Iterator :Collection 接口的iterator()方法

Iterator的方法:

boolean hasNext(): 判断是否存在另一个可访问的元素

Object next(): 返回要访问的下一个元素

Set keys=dogMap.keySet(); //取出所有key的集合 Iterator it=keys.iterator(); //获取Iterator对象 while(it.hasNext()){ String key=(String)it.next(); //取出key Dog dog=(Dog)dogMap.get(key); //根据key取出对应的值 System.out.println(key+"\t"+dog.getStrain()); } 

增强for循环 

for(元素类型t 元素变量x : 数组或集合对象){ 引用了x的java语句 } 

泛型 

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许

程序员在编译时检测到非法的类型。

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

如何解决以下强制类型转换时容易出现的异常问题?

List的get(int index)方法获取元素

Map的get(Object key)方法获取元素

Iterator的next()方法获取元素

通配符: < ? >