1、陣列
型別固定、長度固定
連續的記憶體空間
順序存盤、隨機讀取
查詢快、新增洗掉慢,最好初始化的時候就指定陣列大小,這樣就可以避免一定的陣列擴容出現的記憶體消耗,
import java.util.Arrays;
import java.util.Iterator;
/**
* @author Administrator
* @date 2022-09-11 16:56
* 實作一個陣列
*/
public class MyArray<E> implements Iterable<E> {
private Object[] elementData; // Object存放資料
public MyArray(int capacity) // 構造方法 初始化容量大小
{
// 指定長度 初始化陣列 new 出一塊空間
elementData = https://www.cnblogs.com/rainbow-1/archive/2022/09/13/new Object[capacity];
}
/**
* 直接添加新元素
* @param element
* @return
*/
public boolean add (E element)
{
int size = elementData.length; // 獲取當前陣列大小
int newCapacity = size+1; // 擴容+1
// 此處發生性能消耗,新增資料時,需要擴容,整體資料需要復制遷移,實際上arraylist是1.5擴容!
elementData = Arrays.copyOf(elementData,newCapacity); // 把舊的空間復制一份到新的空間并+1
elementData[size]=element;
return true;
}
/**
* set 方法 根據索引位置新增元素
* @param index
* @param element
* @return
*/
public E set (int index ,E element)
{
E oldValue = (E) elementData[index]; // 獲取舊位置的元素值
elementData[index] = element; // 新值覆寫舊值
return oldValue; // 回傳舊值
}
public E get (int index)
{
return (E) elementData[index]; // 回傳對應索引位置的值
}
@Override
public Iterator iterator(){
return new MyIterator();
}
class MyIterator implements Iterator{
int index = 0;
@Override
public boolean hasNext() {
return index != elementData.length;
}
@Override
public E next() {
return (E) elementData[index++]; // 回傳下一個元素值并+1
}
@Override
public void remove() {
//
}
}
public static void main(String[] args) {
MyArray myArray= new MyArray(10); // 初始化一個容量為10的陣列
myArray.set(0,"q");
myArray.set(2,"w");
myArray.add("新增");
Iterator<String> iterator = myArray.iterator(); // 使用迭代器
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}

1.1、關于arraylist初始容量和擴容
ArrayList 新增元素的方法有兩種,一種是直接將元素加到陣列的末尾,另外一種是添加元素到任意位置,
arraylist默認構造器,在不指定大小的時候默認容量為 10,
在超出容量之后,每次擴容為當前容量大小的1.5倍+1,
1.2、關于迭代器
集合的頂層介面Collection繼承Iterable介面,在Iterable介面中有一個Iterator方法,它回傳一個Itertator物件,
public interface Iterable<T> {
/**
* Returns an iterator over elements of type {@code T}.
*
* @return an Iterator.
*/
Iterator<T> iterator();
}
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
}
迭代器遍歷中呼叫集合revome()方法觸發例外 java.util.ConcurrentModificationException 集合中并發修改的例外.
因為迭代器只負責遍歷,它使用的仍然是集合本身的資料,在List集合實作的時候陣列的長度size會因為remove發生變化的,同時元素的索引值也會因為remove( )方法的呼叫而發生變化,那么在遍歷的時候的remove就需要對這個點進行復刻,而且如果在迭代器里使用了List原生的remove方法,那么就會引起數值不同步的問題,
在ArrayList集合的iterator()方法中,是通過回傳Itr物件來獲得迭代器的,Itr是ArrayList的一個內部類,它實作了Iterator介面,代碼如下:
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;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = https://www.cnblogs.com/rainbow-1/archive/2022/09/13/ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
注意以下的三個屬性:
| cursor | 索引下標,表示下一個可以訪問的元素的索引,默認值為 0 |
|---|---|
| lastRet | 索引下標,表示上一個元素的索引,默認值為 -1 |
| expectedModCount | 對集合修改的次數,初始值為 0 |
我們知道:List的add和remove呼叫會增加modCount的值,也就是這兩個操作會被計入對集合的修改次數,
在迭代器的原始碼中,有一個方法是用來判斷 modCount 和 expectModCount 的值是否相等的,其中modCount的值來自List,expectModCount 是迭代器內定義的變數,那為什么要這么設計呢?
因為arraylist是執行緒不安全的,
結合iterrator的next方法,我們可以看到,如果沒有這個校驗,某個執行緒洗掉了list的一個元素,此時next方法不知道size變更了,依然去取陣列里的資料,會導致資料為null或ArrayIndexOutOfBoundsException例外等問題,
ConcurrentModificationException發生在Iterator( )和next( )方法實作中,每次呼叫都會檢查容器的結構是否發生變化,目的是為了避免共享資源而引發的潛在問題,
觀察HashMap和ArrayList底層Iterator#next(), 可以看到fast-fail只會增加或者洗掉(非Iterator#remove())拋出例外;改變容器中元素的內容不存在這個問題(主要是modCount沒發生變化),
在單執行緒中使用迭代器,對非執行緒安全的容器,但是只能用Iterator和remove;否則會拋出例外,
在多執行緒中使用迭代器,可以使用執行緒安全的容器來避免例外,
使用普通的for回圈遍歷,效率雖然比較低下,但是不存在ConcurrentModificationException例外問題,用的也比較少,
所以說如果在使用迭代器的時候,用到了List自帶的remove方法,那么modCount改變了,但是迭代器內定義的變數expectedCount卻沒有改變,這樣就會被拋出例外,
綜上:我們在使用迭代器的時候,不要混用List本身的remove方法,
Iterator介面有四個方法,hasNext、next、remove和forEachRemaining
其中forEachRemaining是java1.8新增的
這個方法是針對集合中剩余元素的操作
剩余的含義是沒有被iterator.next()遍歷過的元素
1.3、為什么迭代器在呼叫remove之前要先呼叫next
當使用Iterator迭代訪問Collection集合元素時,Collection集合里的元素不能被改變,只有通過Iterator的remove()方法洗掉上一次next()方法回傳的集合元素才可以;否則會引發java.util.ConcurrentModificationException例外,
查看next方法的原始碼可以看到 return (E) elementData[lastRet = i];這樣一行代碼,這行代碼表示next方法在讓陣列下標cursor向后移動一位的同時,還會把lastRet的值變成當前回傳的元素下標,這樣remove方法就可以根據這個下標完成對元素的洗掉,
好看請贊,養成習慣:) 本文來自博客園,作者:靠譜楊, 轉載請注明原文鏈接:https://www.cnblogs.com/rainbow-1/p/16690478.html
關于筆者: 我的主頁
文章同步51CTO,可以幫忙踩一踩 ~ 我的51CTO博客
更多日常分享盡在我的VX公眾號:小楊的挨踢IT生活
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/507210.html
標籤:其他
上一篇:Java安全之Java反射(一)
下一篇:Windows入侵排查
