Collection和Map的体系图
1.ArrayList
概念:
ArrayList内部是由数组实现的,换句话说也就是封装了对内部数组的操作,比如向数组中的添加,删除,插入新的元素或者数据的扩展和重定向.
浅谈数组:
数组是在内存中划分的一块连续的地址空间来进行元素的存储,由于他直接操作内存,所以数组是一种效率最高的储存和访问对象引用序列的方式.但是也有致命的缺点:就是初始化时必须指定大小,大小一旦确定不能再更改.在实际情况中我们遇到的更多的是一开始并不知道储存多少元素,而是希望容器能够自动的扩展它自身的容量以便能够储存更多的元素.
ArrayList就能够更好的满足这种需求,它能够自己扩展大小以适应储存元素的不断增加它的底层是基于数组实现的,因此它具有数组的特性,如查询快,插入删除慢.
- ArrayList的特性:
1.快速查询
2.允许放入多个null值
3.底层是一个Object[]数组
4.增加元素可能很慢(可能会存在扩容),删除数据也会很慢(可能会移动很多元 素),修改对应索引的元素比较快.
我们使用System.arraycopy()方法来看一下数组的复制:
* @param src the source array.
* @param srcPos starting position in the source array.
* @param dest the destination array.
* @param destPos starting position in the destination data.
* @param length the number of array elements to be copied.
.......
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
举例测试一下:
/**
* 测试ArrayList
* @author renjiaxing
* */
public class TestArrayList {
public static void main(String[] args) {
int[] a={0,1,2,3,4,5,6};
System.arraycopy(a, 3, a, 2, 4);
System.out.println(Arrays.toString(a));
}
}
执行结果如下:
[0, 1, 3, 4, 5, 6, 6]
由于我们使用的是native的方法,是有C来写的做数组的复制,效率会高效一些.
ArrayList的源码分析:
- 类信息:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
类信息中可以看到它继承了:AbstractList以最大的限度减少了实现”随机访问”数组储存支持List所需的工作.
实现很多的接口List 意味着ArrayList元素是一个有序的,可重复,可以有null元素的集合.
RandomAccess: 支持随机快速访问,实际上我们查看RandomAccess这个类的源码发现它并没有定义,因为ArrayList底层是一个数组,那么随机快速访问时理所当然的
Cloneable:表示可以被复制
注意:ArrayList里面的clone()复制其实是浅复制.
Serializable:表示可以被序列化
/**
* Returns a shallow copy of this <tt>ArrayList</tt> instance. (The
* elements themselves are not copied.)
*
* @return a clone of this <tt>ArrayList</tt> instance
*/
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
- 成员变量:
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
有成员变量可以看出 ArrayList的初始容量是10,ArrayList的元素是储存在数组的缓存区,ArrayList的容量是这个数组的缓存区的长度.当添加第一个元素的时候:
任何具有elementData==EMPTY_ELEMENTDATA的空ArrayList将张开为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
-
三个构造函数:
1.无参构造
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2.创建一个指定大小的容量的数组的构造方法
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
3.创建一个包含了指定集合内容的数组:
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();转化为数组赋值给Object[]数组
if ((size = elementData.length) != 0) {
在之前的版本这里是有size=elementData .length初始化数组的大小
// 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;
}
}
这里发现:源码中c.toArray might (incorrectly) not return Object[] 这句话的意思是 c.toArray可能不能返回Object[]数组,这里也好理解,java中对象允许向上造型,也就是说子类数组转为父类的数组是允许的.
应用的场景: 就是当我们创建指定的集合的时候,会将这个集合转化为 object[].
- 添加元素:
1.向末尾添加一个元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!确保数组有足够的空间
elementData[size++] = e;将元素加到数组末尾.
return true;
}
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}如果为空数组所需容量为默认容量和所需容量取最大值.
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)最小容量大于数组容量是则需要扩容
grow(minCapacity);
}
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
我们都知道整形的最大值为2^31-1这里为什么要-8呢?根据注释我们也会知道
在JVM中尝试分配较大的空间可能会导致oom
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);扩展到原来数组的1.5倍
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);
复制旧数据到新的数组
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
可以看出:只要数组的容量足够大,Add()方法的效率还是很高的,只有当ArrayList对容量又要求时,才需要进行扩容,扩容的过程中会进行大量的数组复制操作,而数组的复制过程最终调用的是还是System.arraycopy()方法,因此add()方法还是效率很高的!
2.向指定位置添加一个元素
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
由于ArrayList是基于数组实现的,而数组是一个连续的内存空间,如果在数组的 任意位置插入元素的时候,必然会导致在该位置的后的所有的元素需要重新排列.因此效率还是较低的!
- 删除元素:
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);将数组index+1的位置向前移动一位
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
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;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
移除index处的元素其实就是将index后面的数据全部向前移动了一位然后再把最后一个位置null空出位置.可以看出ArrayList每个有效元素在进行删除的时候,都要进行数组的重组,并且删除位置越靠前,重组的开销越大.
- 最后是一个clear
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
2.LinkedList
LinkedList是List接口的另一种实现,它的底层是基于双向链表实现的,因此它具有插入删除快而查找修改慢的特点,此外,通过对双向链表的操作还可以实现队列和栈的功能。
LinkedList的底层结构如下图所示。
F表示头结点引用,L表示尾结点引用,链表的每个结点都有三个元素,分别是前继结点引用§,结点元素的值(E),后继结点的引用(N)。结点由内部类Node表示,我们看看它的内部结构源码。
结点内部类
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;
}
}
Node这个内部类其实很简单,只有三个成员变量和一个构造器,item表示结点的值,next为下一个结点的引用,prev为上一个结点的引用,通过构造器传入这三个值。接下来再看看LinkedList的成员变量和构造器。
集合元素个数
transient int size = 0;
头节点引用
transient Node<E> first;
尾节点引用
transient Node<E> last;
无参构造器
public LinkedList() {
}
传入外部集合的构造器
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
LinkedList持有头结点的引用和尾结点的引用,它有两个构造器,一个是无参构造器,一个是传入外部集合的构造器。与ArrayList不同的是LinkedList没有指定初始大小的构造器。看看它的增删改查方法。
添加
public boolean add(E e) {
在链表尾部添加
linkLast(e);
return true;
}
插入
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size) {
在链表尾部添加
linkLast(element);
} else {
在链表中部插入
linkBefore(element, node(index));
}
}
删(给定下标)
public E remove(int index) {
检查下标是否合法
checkElementIndex(index);
return unlink(node(index));
}
删(给定元素)
public boolean remove(Object o) {
if (o == null) {
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;
}
改
public E set(int index, E element) {
检查下标是否合法
checkElementIndex(index);
获取指定下标的结点引用
Node<E> x = node(index);
获取指定下标结点的值
E oldVal = x.item;
将结点元素设置为新的值
x.item = element;
返回之前的值
return oldVal;
}
查
public E get(int index) {
检查下标是否合法
checkElementIndex(index);
返回指定下标的结点的值
return node(index).item;
}
LinkedList的添加元素的方法主要是调用linkLast和linkBefore两个方法,linkLast方法是在链表后面链接一个元素,linkBefore方法是在链表中间插入一个元素。LinkedList的删除方法通过调用unlink方法将某个元素从链表中移除。下面我们看看链表的插入和删除操作的核心代码。
链接到指定结点之前
void linkBefore(E e, Node<E> succ) {
获取给定结点的上一个结点引用
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++;
}
卸载指定结点
E unlink(Node<E> x) {
获取给定结点的元素
final E element = x.item;
获取给定结点的下一个结点的引用
final Node<E> next = x.next;
获取给定结点的上一个结点的引用
final Node<E> prev = x.prev;
如果给定结点的上一个结点为空, 说明给定结点为头结点
if (prev == null) {
将头结点引用指向给定结点的下一个结点
first = next;
} else {
将上一个结点的后继结点引用指向给定结点的后继结点
prev.next = next;
将给定结点的上一个结点置空
x.prev = null;
}
如果给定结点的下一个结点为空, 说明给定结点为尾结点
if (next == null) {
将尾结点引用指向给定结点的上一个结点
last = prev;
} else {
将下一个结点的前继结点引用指向给定结点的前继结点
next.prev = prev;
x.next = null;
}
将给定结点的元素置空
x.item = null;
集合元素个数减一
size--;
修改次数加一
modCount++;
return element;
}
inkBefore和unlink是具有代表性的链接结点和卸载结点的操作,其他的链接和卸载两端结点的方法与此类似,
链表的插入和删除操作的时间复杂度都是O(1),而对链表的查找和修改操作都需要遍历链表进行元素的定位,这两个操作都是调用的node(int 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;
}
通过下标定位时先判断是在链表的上半部分还是下半部分,如果是在上半部分就从头开始找起,如果是下半部分就从尾开始找起,因此通过下标的查找和修改操作的时间复杂度是O(n/2)。通过对双向链表的操作还可以实现单项队列,双向队列和栈的功能。
单向队列操作:
获取头结点
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
获取头结点
public E element() {
return getFirst();
}
弹出头结点
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
移除头结点
public E remove() {
return removeFirst();
}
在队列尾部添加结点
public boolean offer(E e) {
return add(e);
双向队列操作:
在头部添加
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
在尾部添加
public boolean offerLast(E e) {
addLast(e);
return true;
}
获取头结点
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
获取尾结点
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
栈操作:
入栈
public void push(E e) {
addFirst(e);
}
出栈
public E pop() {
return removeFirst();
}
不管是单向队列还是双向队列还是栈,其实都是对链表的头结点和尾结点进行操作,它们的实现都是基于addFirst(),addLast(),removeFirst(),removeLast()这四个方法,它们的操作和linkBefore()和unlink()类似,只不过一个是对链表两端操作,一个是对链表中间操作。可以说这四个方法都是linkBefore()和unlink()方法的特殊情况,因此不难理解它们的内部实现,在此不多做介绍。到这里,我们对LinkedList的分析也即将结束,对全文中的重点做个总结:
LinkedList是基于双向链表实现的,不论是增删改查方法还是队列和栈的实现,都可通过操作结点实现
LinkedList无需提前指定容量,因为基于链表操作,集合的容量随着元素的加入自动增加
LinkedList删除元素后集合占用的内存自动缩小,无需像ArrayList一样调用trimToSize()方法
LinkedList的所有方法没有进行同步,因此它也不是线程安全的,应该避免在多线程环境下使用
- ArrayList与LinkedList的性能上进行比较是什么样子的呢?
我们测试代码如下
/**
* 测试ArrayList与LinkedList的性能分析
* @author renjiaxing
*/
public class TestLinkedListOrArrayList {
public static void main(String[] args) {
Random random = new Random();
ArrayList<String> arrayList = new ArrayList<String>();
LinkedList<String> linkedList = new LinkedList<String>();
long time1 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
arrayList.add(new String("abc"));
}
long time2=System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
linkedList.add(new String("abc"));
}
long time3=System.currentTimeMillis();
System.out.println("ArrayList----"+(time2-time1));
System.out.println("LinkedList----"+(time3-time2));
long time11=System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
int rand = random.nextInt(10000);
String temp = arrayList .get(rand);
}
long time12=System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
int rand = random.nextInt(10000);
String temp = linkedList .get(rand);
}
long time13=System.currentTimeMillis();
System.out.println("ArrayList----"+(time12-time11));
System.out.println("LinkedList----"+(time13-time12));
long time111=System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
int rand1=random.nextInt(10000);
String remove = arrayList.remove(rand1);
}
long time222=System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
int rand1=random.nextInt(10000);
String remove = linkedList.remove(rand1);
}
long time333=System.currentTimeMillis();
System.out.println("ArrayList---"+(time222-time111));
System.out.println("LinkedList---"+(time333-time222));
}
}
测试结果分析:
在插入和读取的性能上 ArrayList的性能要比LinkedList性能要快,
但是在删除的性能中 linkedList的性能要比ArrayList的性能要高很多
从源码中分析:ArrayList底层是采用的数组来储存数据而LinkedList是通过链表来储存数据
在插入数据时 ArrayList是可以直接通过下标来访问将元素放入到我们数组中
LinkedList是要先new 一个 node生成一个节点,同时修改last尾部是元素,
因此LinkedList要比ArrayList慢
在随机访问时 ArrayList也是直接通过下标直接访问元素
而LinkedList需要从头开始遍历
因此ArrayList要比LinkedList快很多
在删除元素ArrayList在删除一个元素后,需要进行该元素之后的所有数据都要进行往前移动
而LinkedList只需要简单的修改指针即可,因此LinkedList要比ArrayList快的多
3.Vector
知识补充:Arrays.copyOf函数:
public static int[] copyOf(int[] original, int newLength) {
int[] copy = new int[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
可见copyOf()在内部新建一个数组,调用arrayCopy()将original内容复制到copy中去,并且长度为newLength。返回copy;
继续看一下System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)函数:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
src - 源数组。
srcPos - 源数组中的起始位置。
dest - 目标数组。
destPos - 目标数据中的起始位置。
length - 要复制的数组元素的数量。
- Vector简介
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
Vector类实现了一个可增长的对象数组,内部是以动态数组的形式来存储数据的。
Vector具有数组所具有的特性、通过索引支持随机访问、所以通过随机访问Vector中的元素效率非常高、但是执行插入、删除时效率比较低下。
继承了AbstractList,此类提供 List 接口的骨干实现,以最大限度地减少实现”随机访问”数据存储(如数组)支持的该接口所需的工作.对于连续的访问数据(如链表),应优先使用 AbstractSequentialList,而不是此类.
实现了List接口,意味着Vector元素是有序的,可以重复的,可以有null元素的集合.
实现了RandomAccess接口标识着其支持随机快速访问,实际上,我们查看RandomAccess源码可以看到,其实里面什么都没有定义.因为ArrayList底层是数组,那么随机快速访问是理所当然的,访问速度O(1).
实现了Cloneable接口,标识着可以它可以被复制.注意,ArrayList里面的clone()复制其实是浅复制
实现了Serializable 标识着集合可被序列化。
- Vector源码
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
保存Vector数据的数组
protected Object[] elementData;
实际数据的数量
protected int elementCount;
容量增长的系数
protected int capacityIncrement;
Vector的序列版本号
private static final long serialVersionUID = -2767605614048989439L;
指定Vector初始大小和增长系数的构造函数
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
指定初始容量的构造函数
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
Vector构造函数,默认容量为10
public Vector() {
this(10);
}
初始化一个指定集合数据的构造函数
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);
}
将Vector全部元素拷贝到anArray数组中
public synchronized void copyInto(Object[] anArray) {
System.arraycopy(elementData, 0, anArray, 0, elementCount);
}
当前的数组中元素个数大于记录的元素个数时,重新赋值给当前数组所记录的元素
public synchronized void trimToSize() {
modCount++;
int oldCapacity = elementData.length;
if (elementCount < oldCapacity) {
elementData = Arrays.copyOf(elementData, elementCount);
}
}
确定Vector的容量
public synchronized void ensureCapacity(int minCapacity) {
if (minCapacity > 0) {
// 将Vector的改变统计数+1
modCount++;
ensureCapacityHelper(minCapacity);
}
}
确定容量的帮助函数,如果所需容量大于当前的容量时则执行扩容
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
数组所允许的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
执行扩容函数
private void grow(int minCapacity) {
// overflow-conscious code
记录当前容量
int oldCapacity = elementData.length;
如果扩容系数大于0则新容量等于当前容量+扩容系数,如果扩容系数小于等于0则新容量等于当前容量的2倍
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);
}
巨大扩容函数,如果所需容量大于最大数组容量,则返回int形最大值(2^31 -1),否则返回最大数组容量
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
设置容量值为newSize,如果newSize大于当前容量,则扩容,否则newSize以后的所有元素置null
public synchronized void setSize(int newSize) {
modCount++;
if (newSize > elementCount) {
ensureCapacityHelper(newSize);
} else {
for (int i = newSize ; i < elementCount ; i++) {
elementData[i] = null;
}
}
elementCount = newSize;
}
返回当前Vector的容量
public synchronized int capacity() {
return elementData.length;
}
返回Vector元素的个数
public synchronized int size() {
return elementCount;
}
Vector元素个数是否为0
public synchronized boolean isEmpty() {
return elementCount == 0;
}
返回Vector元素的Enumeration,Enumeration 接口是Iterator迭代器的“古老版本”
Enumeration接口中的方法名称难以记忆,而且没有Iterator的remove()方法。如果现在编写Java程序,应该尽量采用
Iterator迭代器,而不是用Enumeration迭代器。
之所以保留Enumeration接口的原因,主要为了照顾以前那些“古老”的程序,那些程序里大量使用Enumeration接口,如果新版
本的Java里直接删除Enumeration接口,将会导致那些程序全部出错。
public Enumeration<E> elements() {
return new Enumeration<E>() {
int count = 0;
public boolean hasMoreElements() {
return count < elementCount;
}
public E nextElement() {
synchronized (Vector.this) {
if (count < elementCount) {
return elementData(count++);
}
}
throw new NoSuchElementException("Vector Enumeration");
}
};
}
返回Vector中是否包含对象o
public boolean contains(Object o) {
return indexOf(o, 0) >= 0;
}
查找并返回元素(o)在Vector中的索引值
public int indexOf(Object o) {
return indexOf(o, 0);
}
从index位置开始向后查找元素(o)。
若找到,则返回元素的索引值;否则,返回-1
public synchronized int indexOf(Object o, int index) {
if (o == null) {
for (int i = index ; i < elementCount ; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = index ; i < elementCount ; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
从后向前查找元素(o)。并返回元素的索引
public synchronized int lastIndexOf(Object o) {
return lastIndexOf(o, elementCount-1);
}
从index位置开始向前查找元素(o)。
若找到,则返回元素的索引值;否则,返回-1
public synchronized int lastIndexOf(Object o, int index) {
if (index >= elementCount)
throw new IndexOutOfBoundsException(index + " >= "+ elementCount);
if (o == null) {
for (int i = index; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = index; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
返回Vector中index位置的元素。
若index越界,则抛出异常
public synchronized E elementAt(int index) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
}
return elementData(index);
}
返回Vector中第0位置的元素。
public synchronized E firstElement() {
if (elementCount == 0) {
throw new NoSuchElementException();
}
return elementData(0);
}
返回Vector中最后一个元素。
public synchronized E lastElement() {
if (elementCount == 0) {
throw new NoSuchElementException();
}
return elementData(elementCount - 1);
}
设置index位置的元素值为obj
public synchronized void setElementAt(E obj, int index) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
elementData[index] = obj;
}
删除index位置处的元素
public synchronized void removeElementAt(int index) {
modCount++;
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
int j = elementCount - index - 1;
if (j > 0) {
System.arraycopy(elementData, index + 1, elementData, index, j);
}
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */
}
在index位置插入元素obj
public synchronized void insertElementAt(E obj, int index) {
modCount++;
if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index
+ " > " + elementCount);
}
ensureCapacityHelper(elementCount + 1);
System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
elementData[index] = obj;
elementCount++;
}
在vector后面添加对象obj
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
在Vector中查找并删除元素obj。
成功的话,返回true;否则,返回false。
public synchronized boolean removeElement(Object obj) {
modCount++;
int i = indexOf(obj);
if (i >= 0) {
removeElementAt(i);
return true;
}
return false;
}
删除Vector中所有元素
public synchronized void removeAllElements() {
modCount++;
// Let gc do its work
for (int i = 0; i < elementCount; i++)
elementData[i] = null;
elementCount = 0;
}
返回Vector的克隆。 该副本将包含对内部数据数组的克隆的引用,而不是对此对象的原始内部数据数组的引用。
public synchronized Object clone() {
try {
@SuppressWarnings("unchecked")
Vector<E> v = (Vector<E>) super.clone();
v.elementData = Arrays.copyOf(elementData, elementCount);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
返回包含Vector所有元素的数组
public synchronized Object[] toArray() {
return Arrays.copyOf(elementData, elementCount);
}
返回Vector的模板数组。所谓模板数组,即可以将T设为任意的数据类型
@SuppressWarnings("unchecked")
public synchronized <T> T[] toArray(T[] a) {
若数组a的大小 < Vector的元素个数;
则新建一个T[]数组,数组大小是“Vector的元素个数”,并将“Vector”全部拷贝到新数组中
if (a.length < elementCount)
return (T[]) Arrays.copyOf(elementData, elementCount, a.getClass());
若数组a的大小 >= Vector的元素个数;
则将Vector的全部元素都拷贝到数组a中。
System.arraycopy(elementData, 0, a, 0, elementCount);
if (a.length > elementCount)
a[elementCount] = null;
return a;
}
// Positional Access Operations
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
获取index处的元素
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
设置index处的元素为element,并返回被替换掉的元素
public synchronized E set(int index, E element) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
Vector末尾添加元素
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
移除Vector中第一个出现对象o的元素
public boolean remove(Object o) {
return removeElement(o);
}
在index位置添加对象element
public void add(int index, E element) {
insertElementAt(element, index);
}
移除index位置的元素
public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--elementCount] = null; // Let gc do its work
return oldValue;
}
清空Vector
public void clear() {
removeAllElements();
}
// Bulk Operations
返回Vector是否包含集合c
public synchronized boolean containsAll(Collection<?> c) {
return super.containsAll(c);
}
在Vector末尾添加集合c
public synchronized boolean addAll(Collection<? extends E> c) {
modCount++;
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityHelper(elementCount + numNew);
System.arraycopy(a, 0, elementData, elementCount, numNew);
elementCount += numNew;
return numNew != 0;
}
删除集合c的全部元素
public synchronized boolean removeAll(Collection<?> c) {
return super.removeAll(c);
}
删除“非集合c中的元素”
public synchronized boolean retainAll(Collection<?> c) {
return super.retainAll(c);
}
在index位置添加集合c中的元素
public synchronized boolean addAll(int index, Collection<? extends E> c) {
modCount++;
if (index < 0 || index > elementCount)
throw new ArrayIndexOutOfBoundsException(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityHelper(elementCount + numNew);
int numMoved = elementCount - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
elementCount += numNew;
return numNew != 0;
}
返回两个对象是否相等
public synchronized boolean equals(Object o) {
return super.equals(o);
}
计算哈希值
public synchronized int hashCode() {
return super.hashCode();
}
调用父类的toString()
public synchronized String toString() {
return super.toString();
}
获取Vector中fromIndex(包括)到toIndex(不包括)的子集
public synchronized List<E> subList(int fromIndex, int toIndex) {
return Collections.synchronizedList(super.subList(fromIndex, toIndex),
this);
}
删除Vector中fromIndex到toIndex的元素
protected synchronized void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = elementCount - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// Let gc do its work
int newElementCount = elementCount - (toIndex-fromIndex);
while (elementCount != newElementCount)
elementData[--elementCount] = null;
}
// java.io.Serializable的写入函数
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
final java.io.ObjectOutputStream.PutField fields = s.putFields();
final Object[] data;
synchronized (this) {
fields.put("capacityIncrement", capacityIncrement);
fields.put("elementCount", elementCount);
data = elementData.clone();
}
fields.put("elementData", data);
s.writeFields();
}
public synchronized ListIterator<E> listIterator(int index) {
if (index < 0 || index > elementCount)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
public synchronized ListIterator<E> listIterator() {
return new ListItr(0);
}
public synchronized Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
// Racy but within spec, since modifications are checked
// within or after synchronization in next/previous
return cursor != elementCount;
}
public E next() {
synchronized (Vector.this) {
checkForComodification();
int i = cursor;
if (i >= elementCount)
throw new NoSuchElementException();
cursor = i + 1;
return elementData(lastRet = i);
}
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
synchronized (Vector.this) {
checkForComodification();
Vector.this.remove(lastRet);
expectedModCount = modCount;
}
cursor = lastRet;
lastRet = -1;
}
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
synchronized (Vector.this) {
final int size = elementCount;
int i = cursor;
if (i >= size) {
return;
}
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) Vector.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
action.accept(elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
final class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}
public boolean hasPrevious() {
return cursor != 0;
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
public E previous() {
synchronized (Vector.this) {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
cursor = i;
return elementData(lastRet = i);
}
}
public void set(E e) {
if (lastRet == -1)
throw new IllegalStateException();
synchronized (Vector.this) {
checkForComodification();
Vector.this.set(lastRet, e);
}
}
public void add(E e) {
int i = cursor;
synchronized (Vector.this) {
checkForComodification();
Vector.this.add(i, e);
expectedModCount = modCount;
}
cursor = i + 1;
lastRet = -1;
}
}
@Override
public synchronized void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int elementCount = this.elementCount;
for (int i=0; modCount == expectedModCount && i < elementCount; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public synchronized boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
// figure out which elements are to be removed
// any exception thrown from the filter predicate at this stage
// will leave the collection unmodified
int removeCount = 0;
final int size = elementCount;
final BitSet removeSet = new BitSet(size);
final int expectedModCount = modCount;
for (int i=0; modCount == expectedModCount && i < size; i++) {
@SuppressWarnings("unchecked")
final E element = (E) elementData[i];
if (filter.test(element)) {
removeSet.set(i);
removeCount++;
}
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
// shift surviving elements left over the spaces left by removed elements
final boolean anyToRemove = removeCount > 0;
if (anyToRemove) {
final int newSize = size - removeCount;
for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
i = removeSet.nextClearBit(i);
elementData[j] = elementData[i];
}
for (int k=newSize; k < size; k++) {
elementData[k] = null; // Let gc do its work
}
elementCount = newSize;
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
return anyToRemove;
}
@Override
@SuppressWarnings("unchecked")
public synchronized void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final int expectedModCount = modCount;
final int size = elementCount;
for (int i=0; modCount == expectedModCount && i < size; i++) {
elementData[i] = operator.apply((E) elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
@SuppressWarnings("unchecked")
@Override
public synchronized void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, elementCount, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
@Override
public Spliterator<E> spliterator() {
return new VectorSpliterator<>(this, null, 0, -1, 0);
}
/** Similar to ArrayList Spliterator */
static final class VectorSpliterator<E> implements Spliterator<E> {
private final Vector<E> list;
private Object[] array;
private int index; // current index, modified on advance/split
private int fence; // -1 until used; then one past last index
private int expectedModCount; // initialized when fence set
/** Create new spliterator covering the given range */
VectorSpliterator(Vector<E> list, Object[] array, int origin, int fence,
int expectedModCount) {
this.list = list;
this.array = array;
this.index = origin;
this.fence = fence;
this.expectedModCount = expectedModCount;
}
private int getFence() { // initialize on first use
int hi;
if ((hi = fence) < 0) {
synchronized(list) {
array = list.elementData;
expectedModCount = list.modCount;
hi = fence = list.elementCount;
}
}
return hi;
}
public Spliterator<E> trySplit() {
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid) ? null :
new VectorSpliterator<E>(list, array, lo, index = mid,
expectedModCount);
}
@SuppressWarnings("unchecked")
public boolean tryAdvance(Consumer<? super E> action) {
int i;
if (action == null)
throw new NullPointerException();
if (getFence() > (i = index)) {
index = i + 1;
action.accept((E)array[i]);
if (list.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true;
}
return false;
}
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> action) {
int i, hi; // hoist accesses and checks from loop
Vector<E> lst; Object[] a;
if (action == null)
throw new NullPointerException();
if ((lst = list) != null) {
if ((hi = fence) < 0) {
synchronized(lst) {
expectedModCount = lst.modCount;
a = array = lst.elementData;
hi = fence = lst.elementCount;
}
}
else
a = array;
if (a != null && (i = index) >= 0 && (index = hi) <= a.length) {
while (i < hi)
action.accept((E) a[i++]);
if (lst.modCount == expectedModCount)
return;
}
}
throw new ConcurrentModificationException();
}
public long estimateSize() {
return (long) (getFence() - index);
}
public int characteristics() {
return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
}
}
}
- 总结
Vector实际上是通过一个数组去保存数据的。当我们构造Vecotr时;若使用默认构造函数,则Vector的默认容量大小是10。
当Vector容量不足以容纳全部元素时,Vector的容量会增加。若容量增加系数 >0,则将容量的值增加“容量增加系数”;否则,将容量大小增加一倍。
Vector的克隆函数,即是将全部元素克隆到一个数组中。 - Vector遍历方式
- 随机访问遍历,通过索引值去遍历
由于Vector实现了RandomAccess接口,它支持通过索引值去随机访问元素。
Integer value = null;
int size = vec.size();
for (int i=0; i<size; i++) {
value = (Integer)vec.get(i);
}
2. 通过迭代器遍历。即通过Iterator去遍历
Integer value = null;
Iterator<Integer> iterator = vec.iterator();
while (iterator.hasNext()) {
value = iterator.next();
}
- 通过增强for循环去遍历
Integer value = null;
for (Integer integ:vec) {
value = integ;
}
- 通过Enumeration遍历
Integer value = null;
Enumeration enu = vec.elements();
while (enu.hasMoreElements()) {
value = (Integer)enu.nextElement();
}
测试这些遍历方式效率的代码如下:
public class Test {
public static void main(String[] args) {
Vector<Integer> vector = new Vector<>();
for (int i = 0; i < 100000; i++)
vector.add(i);
iteratorThroughRandomAccess(vector);
iteratorThroughIterator(vector);
iteratorThroughFor2(vector);
iteratorThroughEnumeration(vector);
}
public static void iteratorThroughRandomAccess(List list) {
long startTime, endTime;
startTime = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
}
endTime = System.currentTimeMillis();
long time = endTime - startTime;
System.out.println("iteratorThroughRandomAccess:" + time + " ms");
}
public static void iteratorThroughIterator(List list) {
long startTime, endTime;
startTime = System.currentTimeMillis();
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
iterator.next();
}
endTime = System.currentTimeMillis();
long time = endTime - startTime;
System.out.println("iteratorThroughIterator:" + time + " ms");
}
public static void iteratorThroughFor2(List list) {
long startTime, endTime;
startTime = System.currentTimeMillis();
for (Object o : list) {
}
endTime = System.currentTimeMillis();
long time = endTime - startTime;
System.out.println("iteratorThroughFor2:" + time + " ms");
}
public static void iteratorThroughEnumeration(Vector vec) {
long startTime, endTime;
startTime = System.currentTimeMillis();
for (Enumeration enu = vec.elements(); enu.hasMoreElements(); ) {
enu.nextElement();
}
endTime = System.currentTimeMillis();
long time = endTime - startTime;
System.out.println("iteratorThroughEnumeration:" + time + " ms");
}
}
输出如下:
iteratorThroughRandomAccess:3 ms
iteratorThroughIterator:6 ms
iteratorThroughFor2:5 ms
iteratorThroughEnumeration:5 ms
所以:遍历Vector,使用索引的随机访问方式最快,使用迭代器最慢。
4.HashSet
- HashSet简介
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
HashSet 继承于AbstractSet 该类提供了Set 接口的骨架实现,以最大限度地减少实现此接口所需的工作量。
实现Set接口,标志着内部元素是无序的,元素是不可以重复的。
实现Cloneable接口,标识着可以它可以被复制。
实现Serializable接口,标识着可被序列化。
HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null元素。
- HashSet源码
对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成, HashSet的源代码如下:
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
底层使用HashMap来保存HashSet中所有元素。
private transient HashMap<E,Object> map;
定义一个虚拟的Object对象作为HashMap的value,将此对象定义为static final。
private static final Object PRESENT = new Object();
构造一个空的hashSet,实际底层会初始化一个空的HashMap,并使用默认初始容量为16和加载因子0.75。
public HashSet() {
map = new HashMap<>();
}
构造一个包含指定集合中元素的新集合。HashMap是使用默认加载因子(0.75)和足以包含指定
集合中元素的初始容量创建的。
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
以指定的initialCapacity和loadFactor构造一个空的HashSet。
实际底层以相应的参数构造一个空的HashMap。
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
以指定的initialCapacity构造一个空的HashSet。
实际底层以相应的参数及加载因子loadFactor为0.75构造一个空的HashMap。
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合。
此构造函数为包访问权限,不对外公开,实际只是是对LinkedHashSet的支持。
实际底层会以指定的参数构造一个空LinkedHashMap实例来实现。
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
返回对此set中元素进行迭代的迭代器。返回元素的顺序并不是特定的。
底层实际调用底层HashMap的keySet来返回所有的key。 可见HashSet中的元素,只是存放在了底层HashMap的key上
value使用一个static final的Object对象标识。
@return 对此set中元素进行迭代的Iterator。
public Iterator<E> iterator() {
return map.keySet().iterator();
}
返回此set中的元素的数量(set的容量)。
底层实际调用HashMap的size()方法返回Entry的数量,就得到该Set中元素的个数。
public int size() {
return map.size();
}
如果此set不包含任何元素,则返回true。
底层实际调用HashMap的isEmpty()判断该HashSet是否为空。
public boolean isEmpty() {
return map.isEmpty();
}
如果此set包含指定元素,则返回true。
更确切地讲,当且仅当此set包含一个满足(o==null ? e==null : o.equals(e))
的e元素时,返回true。
底层实际调用HashMap的containsKey判断是否包含指定key。
public boolean contains(Object o) {
return map.containsKey(o);
}
/**
* 如果此set中尚未包含指定元素,则添加指定元素。
* 更确切地讲,如果此 set 没有包含满足(e==null ? e2==null : e.equals(e2))
* 的元素e2,则向此set 添加指定的元素e。
* 如果此set已包含该元素,则该调用不更改set并返回false。
* 底层实际将将该元素作为key放入HashMap。
* 由于HashMap的put()方法添加key-value对时,当新放入HashMap的Entry中key
* 与集合中原有Entry的key相同(hashCode()返回值相等,通过equals比较也返回true),
* 新添加的Entry的value会将覆盖原来Entry的value,但key不会有任何改变,
* 因此如果向HashSet中添加一个已经存在的元素时,新添加的集合元素将不会被放入HashMap中,
* 原来的元素也不会有任何改变,这也就满足了Set中元素不重复的特性。
* @param e 将添加到此set中的元素。
* @return 如果此set尚未包含指定元素,则返回true。
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
/**
* 如果指定元素存在于此set中,则将其移除。
* 更确切地讲,如果此set包含一个满足(o==null ? e==null : o.equals(e))的元素e,
* 则将其移除。如果此set已包含该元素,则返回true
* (或者:如果此set因调用而发生更改,则返回true)。(一旦调用返回,则此set不再包含该元素)。
*
* 底层实际调用HashMap的remove方法删除指定Entry。
* @param o 如果存在于此set中则需要将其移除的对象。
* @return 如果set包含指定元素,则返回true。
*/
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
从此set中移除所有元素。此调用返回后,该set将为空。
底层实际调用HashMap的clear方法清空Entry中所有元素。
public void clear() {
map.clear();
}
返回此HashSet实例的浅表副本:并没有复制这些元素本身。
底层实际调用HashMap的clone()方法,获取HashMap的浅表副本,并设置到HashSet中。
@SuppressWarnings("unchecked")
public Object clone() {
try {
HashSet<E> newSet = (HashSet<E>) super.clone();
newSet.map = (HashMap<E, Object>) map.clone();
return newSet;
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
}
- 总结
HashSet就是限制了功能的HashMap,所以了解HashMap的实现原理,这个HashSet自然就通
对于HashSet中保存的对象,主要要正确重写equals方法和hashCode方法,以保证放入Set对象的唯一性
虽说Set是对于重复的元素不放入,倒不如直接说是底层的Map直接把原值替代了
HashSet没有提供get()方法,原意是同HashMap一样,Set内部是无序的,只能通过迭代的方式获得
5.HashMap
数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端。
数组
数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,数组的特点是:寻址容易,插入和删除困难;
链表
链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。
哈希表
那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表。哈希表((Hash table)既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。
哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法—— 拉链法,我们可以理解为“链表的数组”
从上图我们可以发现哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。
首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。
- 源码开整:
hashMap存取:
int hash = key.hashCode();这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值
int index = hash % Entry[].length;
Entry[index] = value;
int hash = key.hashCode();取值
int index = hash % Entry[].length;
return Entry[index];
1、put
疑问:如果两个key通过hash%Entry[].length得到的index相同,会不会有覆盖的危险?
这里HashMap里面用到链式数据结构的一个概念。上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。也就是说数组中存储的是最后插入的元素。到这里为止,HashMap的大致实现,我们应该已经清楚了。
public V put(K key, V value) {
if (key == null)
return putForNullKey(value); null总是放在数组的第一个链表中
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
遍历链表
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
如果key在链表中已存在,则替换为新value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e); 参数e, 是Entry.next
如果size超过threshold,则扩充table大小。再散列
if (size++ >= threshold)
resize(2 * table.length);
}
2、Get
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
先定位到数组元素,再遍历该元素处的链表
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
确定数组index:hashcode % table.length取模
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
return h & (length-1);
}
初始化容量:
public HashMap(int initialCapacity, float loadFactor) {
.....
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
注意容量的大小并不是initialCapacity而是>=initialCapacity的2的整数次幂