「java扩容原理」java扩容机制

博主:adminadmin 2022-12-14 13:15:07 75

本篇文章给大家谈谈java扩容原理,以及java扩容机制对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。

本文目录一览:

ArrayList与LinkedList的扩容

我们都知道数组不能扩容,ArrayList可以扩容,但ArrayList的底层是数组,他是怎么进行扩容的呢?

一、ArrayList扩容实现步骤

1.扩容: 把原来的数组复制到另一个内存空间更大的数组中;

 2.添加元素: 把新元素添加到扩容以后的数组中。

二、源码分析

关键属性:

解析ArrayList的三个构造方法:

分析常用方法:

LinkedList的扩容机制又是怎么样的呢?

1.LinkedList是一个继承于AbstractSequentialList的双向链表。

2.由于他的底层是用双向链表实现的,没有初始化大小,所以没有油扩容机制,就是一直在前面或者是后面新增就好。

二者区别:

二者的顶层接口都是Collection,

ArrayList是基于数组实现的,查询速度较快,LinkedList是双向链表,可以从头插入也可以从末尾插入,所以在增加和删除的时候比较快,是基于链式存储结构的。

LinkedList是离散空间所以不需要主动扩容,而ArrayList是连续空间,内存空间不足时,会主动扩容。

两者都不是线程安全的

参考资料:

【Java基础】ArrayList 扩容原理

ArrayList详解,看这篇就够了

java里面数组扩容怎么做的?

数组扩容可以通过新建一个数组长度设大点,然后通过 System.arraycopy(a1,0,a2,0,a.length)这种方式扩容,其他方式貌似没有。。。

java Arraylist扩容为什么是1.5倍+1

// ArrayList类的源码:对内部数组扩容 

public void ensureCapacity(int minCapacity) {

 modCount++;

 int oldCapacity = elementData.length;

 if (minCapacity  oldCapacity) {

     Object oldData[] = elementData;

     int newCapacity = (oldCapacity * 3)/2 + 1;// 计算新容量

     if (newCapacity  minCapacity) newCapacity = minCapacity;

     // minCapacity is usually close to size, so this is a win:

     elementData = Arrays.copyOf(elementData, newCapacity);

 }

}

ArrayList详解,自动扩容原理

ArrayListE:说明ArrayList支持泛型。

extends AbstractListE :继承了AbstractList。AbstractList提供List接口的骨干实现,以最大限度地减少“随机访问”数据存储(如ArrayList)实现Llist所需的工作。

implements ListE:实现了List。实现了所有可选列表操作。

implements RandomAccess:表明ArrayList支持快速(通常是固定时间)随机访问。此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。

implements Cloneable:表明其可以调用clone()方法来返回实例的field-for-field拷贝。

implements java.io.Serializable:表明该类具有序列化功能。有序列化功能

下面来看一些比较重要的参数:

ArrayList的实际大小(数组包含真正的元素个数)

执行完构造方法时还是一个空数组,等到add方法执行的时候则会初始化容量为10;

自己穿入想要的容量参数,对容量进行判断如果容量小于零,则会抛出异常,否则会创建一个容量为initialCapacity的空ArrayList

构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。

先来看其中的add()方法:

add方法首先会调用ensureCapacityInternal(size+1)方法,

ensureCapacityInternal(size+1)方法是用来进行容量检查,决定扩容的想要的最小容量,举个例子,第一次扩容的时候,size默认为0则minCapacity(即想要的最小容量)为1,执行完Math.max语句minCapacity就变为10,这个时候也就是进行空构造第一次add的情况,当ensureCapacityInternal(size+1)传入的参数小于默认参数的时候,就把默认参数当做想要的最小容量,如果大于默认参数就把你想要的参数当做想要的最小容量

这个方法是用来判断是否扩容,如果你想要的最小容量大于数组长度则会调用grow方法进行扩容

通过grow方法可以看到新容量为当前数组容量的1.5倍,然后进行判断,如果理论扩容后新的容量小于小于你想要的最小容量(但我觉得这种情况不太可能会出现,因为为了节省空间,假如当前容量为10,只会传入11来和扩容后的进行比较因此我自己认为不会出现if为真的情况)真正的实现扩容其实是Arrays.copy方法,就是复制数组实现扩容,

但是如果出现了if为真的情况,从这个方法中可以看到数组的理论最大值为Integer的最大值减去8,但是如果你想要的最小容量已经大于数组的理论最大值,则会进行大容量分配为Integer的最大值至此扩容结束,

下面粗略的谈一下快速失败机制(因为对线程还没有一个好的认识)

fail-fast是怎么产生的。步骤如下:

新建了一个ArrayList,名称为arrayList。

向arrayList中添加内容

新建一个“线程a”,并在“线程a”中通过Iterator反复的读取arrayList的值。

新建一个“线程b”,在“线程b”中删除arrayList中的一个“节点A”。

这时,就会产生有趣的事件了。

在某一时刻,“线程a”创建了arrayList的Iterator。此时“节点A”仍然存在于arrayList中,创建arrayList时,expectedModCount = modCount(假设它们此时的值为N)。

在“线程a”在遍历arrayList过程中的某一时刻,“线程b”执行了,并且“线程b”删除了arrayList中的“节点A”。“线程b”执行remove()进行删除操作时,在remove()中执行了“modCount++”,此时modCount变成了N+1!

“线程a”接着遍历,当它执行到next()函数时,调用checkForComodification()比较“expectedModCount”和“modCount”的大小;而“expectedModCount=N”,“modCount=N+1”,这样,便抛出ConcurrentModificationException异常,产生fail-fast事件。

至此,我们就完全了解了fail-fast是如何产生的!

即,当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变(即其它线程通过add、remove、clear等方法,改变了modCount的值);这时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

方法1

在单线程的遍历过程中,如果要进行remove操作,可以调用迭代器的remove方法而不是集合类的remove方法。看看ArrayList中迭代器的remove方法的源码:

可以看到,该remove方法并不会修改modCount的值,并且不会对后面的遍历造成影响,因为该方法remove不能指定元素,只能remove当前遍历过的那个元素,所以调用该方法并不会发生fail-fast现象。该方法有局限性。

例子:

方法2

使用java并发包(java.util.concurrent)中的类来代替ArrayList 和hashMap。

比如使用 CopyOnWriterArrayList代替ArrayList,CopyOnWriterArrayList在是使用上跟ArrayList几乎一样,CopyOnWriter是写时复制的容器(COW),在读写时是线程安全的。该容器在对add和remove等操作时,并不是在原数组上进行修改,而是将原数组拷贝一份,在新数组上进行修改,待完成后,才将指向旧数组的引用指向新数组,所以对于CopyOnWriterArrayList在迭代过程并不会发生fail-fast现象。但 CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。

对于HashMap,可以使用ConcurrentHashMap,ConcurrentHashMap采用了锁机制,是线程安全的。在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。即迭代不会发生fail-fast,但不保证获取的是最新的数据。

Java 中 ArrayList 自动扩容的内存上的具体过程是怎样的

public ArrayList() {

this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

}

ArrayList的默认构造方法构建一个长度为0的对象数组

public boolean add(E e) {

ensureCapacityInternal(size + 1); // Increments modCount!!

elementData[size++] = e;

return true;

}

调用add方法时,首先调用ensureCapacityInternal()方法

private void ensureCapacityInternal(int minCapacity) {

if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

}

ensureExplicitCapacity(minCapacity);

}

private void ensureExplicitCapacity(int minCapacity) {

modCount++;

// overflow-conscious code

if (minCapacity - elementData.length 0)

grow(minCapacity);

}

private void grow(int minCapacity) {

// overflow-conscious code

int oldCapacity = elementData.length;

int newCapacity = oldCapacity + (oldCapacity 1);

if (newCapacity - minCapacity 0)

newCapacity = minCapacity;

if (newCapacity - MAX_ARRAY_SIZE 0)

newCapacity = hugeCapacity(minCapacity);

// minCapacity is usually close to size, so this is a win:

elementData = Arrays.copyOf(elementData, newCapacity);

}

最后的方法可以看到是通过给指定数组增加长度然后将原有元素拷贝回去的方式来扩容

java vector扩容需要重建数组吗

不需要。

vector的扩容原理是新增元素Vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,再插入新增的元素。

同时在扩容是有一些需要注意的内容,对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了 ,不同的编译器实现的扩容方式不一样。

java扩容原理的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于java扩容机制、java扩容原理的信息别忘了在本站进行查找喔。

The End

发布于:2022-12-14,除非注明,否则均为首码项目网原创文章,转载请注明出处。