总述
Java中有数组可以存储数据,但是为了解决数组不能动态增长的问题,由此产生了各种的集合类来方便我们存储数据,以及类中的各种方法来对让我们对数据进行各种各样的操作。
总的来说,各种集合类分为Collection和Map两大接口。Collection的子接口又可分为List和Set,在List和Set下又有各种实现类;在Map下也有各种的实现类。具体的实现类以及类中的方法实现会在之后给出,下面我们先给出各种接口和类的继承关系图:
##?????(相关类的继承图
一、List
·ArrayList
1> 相关类继承关系图
我们可以看到ArrayList继承了AbstractList类,AbstractList类继承了AbstractCollection抽象类并实现了List接口
1.1> AbstractColletion抽象类
由于AbstractColletion类也被其他集合类继承,所以我们先对这个抽象类进行查看,如下:
可以看到这个类实现了Collection接口,而Collection接口又继承了iterable接口(是为了方便实现对集合进行迭代)
在AbstractColletion类中中,实现了对Collectiion接口方法的覆写,常用的主要有如下:属性
方法
1)public abstract Iterator<E> iterator();
返回是迭代器
2) public abstract int size();
用于返回集合的数据大小
3) public boolean isEmpty() {
return size() == 0;
}
用于判断集合是否为空
##?????相关方法
2> ArrayList具体实现以及增删改查
ArrayList是底层是由数组组成的,在ArrayList中有这个数组来存储数据
这个数组是存储ArrayList元素的数组缓冲区。ArrayList的容量是此数组缓冲区的长度。添加第一个元素时,任何即使是空的ArrayList也都将扩展到默认容量(这个默认容量是10,鉴于我的版本是JDK8,其他版本可能不同,还没试过...)这个size属性是用来存储当前对象所具有的元素个数(在之后会有涉及,先有个印象)
和elementData对象相关的还有这个空方法体的函数,这是为了解决当我们需要空对象时,还去无限地创建不同的对象实例而浪费空间,因为创建一个对象时我们会在虚拟机的堆中开辟空间存储它。但由于我们需要的只是空对象,所以我们可以通过这个静态方法创建同一个空对象,公共使用。
2.1>ArrayList添加元素
add(E e)
除去第一行先不看,其余是将相应的元素e存放进数组elementData[]中,并返回 true。但由于数组是定长的,有可能会造成越界的问题,所以有第一行的ensureCapacityInternal(size + 1); 方法来防止溢出,也就是扩容机制,如下:
由这个方法名我们也可以知道它是用来确保内部(数组)空间足够大的。该方法先进行判断是否是空数组,如果是,则在默认长度和当前长度中选出大的(这里之所以会造成不同,有可能是因为数组长度小于10),再将较大值传入ensureExplicitCapacity
如果minCapacity大于elementData的长度,使用grow方法进行扩容
划线处的newCapacity是新的容量大小=旧容量+旧容量右移一位=1.5倍的旧容量。 如果新容量比当前容量小,则newCapacity为当前容量; 如果新容量比最大容量还大,则进行###???
2.2>ArrayList删除元素
remove(int index)
先检查该数字是否在允许范围内,再将要返回的删除的值存入oldValue,numMoved是计算要删除位置的元素后还有几个元素,用于后面的操作
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
这是jdk的一个本地方法,用于将一个数组从指定位置复制到目标数组的指定位置,也就等同于删除相应的元素了 。其中numMoved就是要复制的个数,也就是被删除元素后面的元素个数。
remove(Object o)
这个方法大致都是寻找后面的所需要的numMoved来交给System的arraycopy方法来处理,几乎同上。
2.3>ArrayList查询元素
get(int index)
rangeCheck(index)方法是用于检查数值是否越界,而真正实现是在elementData(index)中,直接返回数组中所需要的值,如下:
2.4>ArrayList更改元素
set(int index, E element)
·LinkedList
1> 相关类继承关系图
LinkedList底层是由链表实现的,我们可以找到结点的具体结构用成员内部类实现如下
可以清楚的看到,这个结点有前驱和后驱,具体的值,所以它是组成双向链表来存储数据的,为了方便对数据进行操作就在主类的属性中记录了头"指针"和尾"指针"(这里只是起到了类似指针的作用,并不是真正的指针,因为Java中为了避繁化简,就尽量去避免一些C中的指针的概念)我在总览了LinkedlList后将该类的内容后,把它大致分为四个部分:
· 内部类
这个内部类在
ListItr内部类是实现ListIterator的迭代器具体实现,可以理解为是另外一个list的实现
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned = null;//当前这个结点
private Node<E> next;//下一个结点
private int nextIndex;//下一个结点的下标
private int expectedModCount = modCount;
//必须实现该有参构造
ListItr(int index) {
next = (index == size) ? null : node(index);//如果是最后一个元素,则下一个元素就为Null,否则就是下一个结点
nextIndex = index;//给出的下标数
}
//用于判断是否有下一个元素
public boolean hasNext() {
return nextIndex < size;
}
//使该迭代器指向下一个元素,并返回下一个元素
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
//判断是否是第一个元素,为previous方法做准备
public boolean hasPrevious() {
return nextIndex > 0;
}
////使该迭代器指向下一个元素,并返回下一个元素
public E previous() {
checkForComodification();//检查是否存在版本问题
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
//获取下一个元素的下标
public int nextIndex() {
return nextIndex;
}
//获取上一个元素的下标
public int previousIndex() {
return nextIndex - 1;
}
//--------------------------
//这部分是对迭代器的操作的方法(CRUD),由于和LinkedList中方法类似,在下面会介绍
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
//----------------------------------
//用于保证线程安全的
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
其中还对ListItr内部类做了一层包装,这个也就是我们平时使用的迭代器的直接使用的类,主要提取出来了, next(),remove(),hasNext(),来方便遍历list集合
private class DescendingIterator implements Iterator<E> {
private final ListItr itr = new ListItr(size());
public boolean hasNext() {
return itr.hasPrevious();
}
public E next() {
return itr.previous();
}
public void remove() {
itr.remove();
}
}
· 转型
这个部分主要是LinkedList转型为ArrayList所用,但是有两个方法,先来看简单的一个
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
它是直接新创建了一个数组然后for循环遍历Linked链表,依次赋值.
另外一个toArray方法是带有一个数组参数 总的来说,构造了一个对应类型的,长度和ArrayList的size一致的空数组,虽然方法本身还是以 Object数组的形式返回结果,不过由于构造数组使用的ComponentType跟需要转型的ComponentType一致,猜测是为了不会产生转型异常,总的其他操作过程还是和上个方法一致的。
public <T> T[] toArray(T[] a) {
if (a.length < size)//这个size是Linkedlist的元素个数
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
if (a.length > size)
a[size] = null;
return a;
}
· 异常处理
这部分是为了捕获异常而需要做的判断方法为了解耦而提取出来了。
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
· 增删改查
在后部分会详细介绍,需要提出的应该是(猜测)为了满足栈的数据结构的条件,源码中特地写了许多删改方法,例如,
public boolean offerFirst(E e)
public void push(E e)
public boolean removeFirstOccurrence(Object o)
···等等等等
2> LinkeList具体实现以及增删改查
2.1>LinkedList增加元素
通过linkedasts具体实现
在linkedasts方法中,首先让l和last"指向"同一个对象,然后判断是否l为null(即判断这个链表是否为空),如果是就让新结点赋值给first,否则是l的next为新结点。
2.2>LinkedList删除元素
· 方法一 由unLIinked具体实现
在unLIinked方法中,先进行判断是否是头结点和尾节点,进行特殊操作,否则如果是中间的结点,就直接实现双向链表的删除就可以了
· 方法二
public void remove() {
checkForComodification();//用于检查判断Mod的版本,保证数据安全
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
2.3>LinkedList查找元素
linkedlist之所以是用双向链表实现,而不是单向的,很大程度上也是为了加快查找效率
public E get(int index) {
checkElementIndex(index);//检查是否超越链表个数
return node(index).item;//交给Node(index)
}
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;
}
}
2.3>LinkedList更改元素
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
·Vector
1> 相关类继承关系图
它的类的继承关系和ArrayList是完全相同的,但它和arraylist的不同点是在于它是线程安全的,在方法的各处具体流程类似,但是会在方法前加上synchronized关键字,代价是Vector的性能变差了Map
·HashMap
1> 相关类继承关系图
基本的属性和内部类
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//默认最大容量大小
static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量的最大值
static final float DEFAULT_LOAD_FACTOR = 0.75f;//加载因子,被使用的地址数/总共的地址数
transient int size;//当前的被使用容量大小
int threshold;//阈值,如果size大小超过阈值需要resize
final float loadFactor;//当前的加载因子
transient int modCount;//修改的版本
transient int hashSeed = 0;//???
这个是用于存储数据的数据结构的,可以底层也是由数组组成的,至于Entry的内部类的结构,可以看到是由单向链表实现的,所以它的数据结构是如图:
static final Entry<?,?>[] EMPTY_TABLE = {};
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
....
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
}
public final String toString() {
return getKey() + "=" + getValue();
}
...
}
基本构造方法有四种:
总的参数有负载因子(默认为0.75f)和初始化大小(默认为16),所以默认被使用到的大小为12.
public HashMap(int initialCapacity, float loadFactor) {
//initialCapacity和loadFactor的大小必须满足的范围
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//这个NaN是float值的被0除的时候的数
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;//赋值给相应的属性
threshold = initialCapacity;//这是阈值
init();
}
这个方法是采用已有的Map来构造新的Hashmap.
public HashMap(Map<? extends K, ? extends V> m) {
//Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,DEFAULT_INITIAL_CAPACITY)是选出来传进来的map和默认的初始化的大小的较大值,作为容量大小
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
inflateTable(threshold);//创建新的table表
putAllForCreate(m);//将给出的map依次赋值到
}
其中采用inflateTable方法来初始化容量大小,并构造相应的Entry类型的数组,而initHashSeedAsNeeded方法是用来推迟hash值的计算,直到我们需要用到的时候再去计算。
其余两个构造方法是交给第一个构造方法处理public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
2> HashMap具体实现以及增删改查
2.1>HashMap增加元素
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);//如果table是空的那么就初始化table
}
if (key == null)
return putForNullKey(value);//
int hash = hash(key);//计算要新加入的节点的hash值
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);//如果再哈希了,就转入addEntry方法
return null;
}
addEntry(hash, key, value, i);再哈希过的方法:
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);//由于size比阈值大了,就需要resize扩大哈希表
hash = (null != key) ? hash(key) : 0;//如果key值为空值就存放在表的0号位置
bucketIndex = indexFor(hash, table.length);//再计算一遍哈希值
}
createEntry(hash, key, value, bucketIndex);//是将再哈希过的值存入相应的地址位置
}
着重看一下resize方法,他是默认resizet哈希表长变为原长的两倍
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {//如果表长已经是默认最长的数了,那就不再扩容
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable, initHashSeedAsNeeded(newCapacity));//迁移数据到新表中
//重新置值
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
2.1>HashMap删除元素
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}//这是用于存放
int hash = (key == null) ? 0 : hash(key);
int i = indexFor(hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}