扩容问题
类名
增长速率
初始值
ArrayList
1.5x+1
默认10
Vector
2x(如果有增量y,x+y)
默认10
HashTable
2x+1
默认11
HashMap
2x
默认16
StringBuffer
2x+2
默认16
StringBuilder
2x+2
默认16
ArrayList(顺序表)
List接口的可调整大小的数组实现。
==底层由数组实现,内存连续,默认初始容量为10,默认底==
==层是根据右移运算来扩容是在原来的基础上增加一半。增==
==删效率较低、查询效率较高。线程不安全的集合==
增删慢,查询快
线程不安全
构造函数的初始容量大小是10
内存连续
扩容:(JDK 1.9)根据右移运算来扩容是在原来的基础上增加一半。
其没有自己的缩容方法,是依赖系统数组的缩容
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); //1.5倍
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
//将此 ArrayList 实例的容量调整为列表的当前大小。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
思考题:在查询和增删操作相同的场景下, 选LinkedList还是
ArrayList?
此场景时间差不多。决定效率高低的是占用的空间,考虑空间,LinkedList内存不连续==不需要考虑扩容==,所以占用空间更少,不会产生浪费,更佳。
Vector(JDK1.0)
Vector类实现了一个可生长的数组对象。
底层基于数组实现
==默认初始容量是10,默认的扩容是根据底层的三目运算为原来的两倍==
==如果指定了增量大小,每次扩容就是在原来的基础上加上增量的值。==
最早的集合类
线程安全
protected int capacityIncrement; //容量的增量
//指定增量的空向量
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
private Object[] grow() {
return grow(elementCount + 1);
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity <= 0) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
//对此向量的容量进行微调,使其等于向量的当前大小。
public synchronized void trimToSize() {
modCount++;
int oldCapacity = elementData.length;
if (elementCount < oldCapacity) {
elementData = Arrays.copyOf(elementData, elementCount);
}
}
//用于遍历,返回最古老的迭代器 ,只存在于Voctor
public Enumeration elements() {
return new Enumeration() {
int count = 0;
public boolean hasMoreElements() { //放在while循环,判断是否还有元素
return count < elementCount;
}
public E nextElement() { //用于获取元素
synchronized (Vector.this) {
if (count < elementCount) {
return elementData(count++);
}
}
throw new NoSuchElementException("Vector Enumeration");
}
};
}
HashSet
public class HashSet extends AbstractSetimplements Set, Cloneable, Serializable
此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素。
底层是由HashMap(数组+单链表)实现
构造方法摘要
HashSet() 构造一个新的空 set,其底层 HashMap 实例的默认初始容量是 ==16==,加载因子是 ==0.75==。
HashSet(Collection extends E> c) 构造一个包含指定 collection 中的元素的新 set。
HashSet(int initialCapacity) 构造一个新的空 set,其底层 HashMap 实例具有指定的初始容量和默认的加载因子(0.75)。
HashSet(int initialCapacity, float loadFactor) 构造一个新的空 set,其底层 HashMap 实例具有指定的初始容量和指定的加载因子。
public HashSet(Collection extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
扩容:(Jdk1.7)
==加载因子:如果已经使用的桶数/总桶数大于加载因子(0.75),默认就需要扩容为原来的两倍。==
==扩容完之后需要保证还是散列分布,重写计算原来已经存储的对象的二次运算,重新分布-------rehash会重新二次运算哈希值==
如果加载因子越大,导致大量的元素对象存储在某些链上,导致链的长度过长,就对元素的查询降低效率
如果加载因子越小,导致会进行频繁的扩容和rehash操作,浪费大量的内存空间。
JDK1.8之后,如果某个桶太长,超过==8==个,将链表扭转成一个二叉树(==红黑树==),提高查询效率
1574247788958.png
StringBuffer
jdk1.0
线程安全
父类是AbstractStringBuilder,
构造函数可以看到是直接调用父类的构造函数,初始容量设为==16==
public StringBuffer() {
super(16);
}
public StringBuffer(int capacity) {
super(capacity);
}
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
StringBuilder
jdk1.5
线程不安全
父类是AbstractStringBuilder
构造函数可以看到是直接调用父类的构造函数,初始容量设为==16==
public StringBuilder() {
super(16);
}
public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
其中StringBuffer和StringBuilder的父类都是AbstractStringBuilder,其中需要扩容的操作都是直接调用父类的方法,所以父类的扩容就是他们的扩容方案
AbstractStringBuilder中的扩容:==原容量左移一位+2,先尝试将容量夸大至2倍+2,如果还是不够,则直接扩容至需要的大小==(jdk1.8)
新建了一个数组,将原来旧数组的内容复制到新数组,扩容机制根据当前数组长度的2倍+2和新增加字符串长度+原有数组长度进行比较,如果前者小于后者,那么扩容后的长度就是后者,如果前者大于后者那么扩容后的数组长度就是前者,每次append或者insert会再次进行比较.
一般拼接字符串时都会以","加空格来分隔,字符数组扩容之后是为了存放很多的要拼接的内容,但是又不想因为","和空格这两个字符而扩容所以每次加了2个
/**
* The count is the number of characters used.
*/
int count;
//从append开始
public AbstractStringBuilder append(char[] str) {
int len = str.length;
ensureCapacityInternal(count + len);
appendChars(str, 0, len); //len=str.length
return this;
}
public AbstractStringBuilder append(String str) {
if (str == null) {
return appendNull();
}
int len = str.length();
ensureCapacityInternal(count + len);
putStringAt(count, str);
count += len;
return this;
}
//确保内部容量,用在append等方法中
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
int oldCapacity = value.length >> coder;
if (minimumCapacity - oldCapacity > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity) << coder);
}
}
//扩容操作,原容量左移一位+2
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = value.length >> coder;
int newCapacity = (oldCapacity << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
int SAFE_BOUND = MAX_ARRAY_SIZE >> coder;
return (newCapacity <= 0 || SAFE_BOUND - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}