C++基礎(chǔ) - 實(shí)現(xiàn) Native 層的 ArrayList

ArrayList 我們在 java 中再熟悉不過了,記得自己在學(xué)習(xí) Collection 體系的時(shí)候,用得最多的也就是 ArrayList 。幾乎很少用到 Stack 和 LinkedList ,反正只要能用就行,所以剛開始并未過多去了解。但是當(dāng)我們真正了解了其內(nèi)部實(shí)現(xiàn)算法后,在寫代碼的時(shí)候我們就會根據(jù)業(yè)務(wù)邏輯,有意識做一些思考了。還有就是在面試的時(shí)候,我們也經(jīng)常會碰到類似的問題。

一.ArrayList 源碼分析
    // 默認(rèn)情況下,數(shù)組的初始化大小
    private static final int DEFAULT_CAPACITY = 10;

    // 空數(shù)組
    private static final Object[] EMPTY_ELEMENTDATA = {};

    // 空數(shù)組
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    // 數(shù)據(jù)
    transient Object[] elementData;

    // 數(shù)據(jù)大小
    private int size;

    // 給數(shù)組指定初始化大小
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            // 創(chuàng)建數(shù)組
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    public boolean add(E e) {
        // 判斷是否需要擴(kuò)容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            // 如果數(shù)組為空
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        
        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 如果超出當(dāng)前數(shù)組長度,需要擴(kuò)容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private void grow(int minCapacity) {
        // overflow-conscious code
        // 原來數(shù)組的大小
        int oldCapacity = elementData.length;
        // 默認(rèn)情況下擴(kuò)充為原來的一半
        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:
        // 創(chuàng)建一個(gè)新數(shù)組并把原來數(shù)組里的內(nèi)容拷貝到新數(shù)組中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }
    
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

    public E remove(int index) {
        // 是否越界
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        modCount++;
        // 獲取原來的位置
        E oldValue = (E) elementData[index];
        // 如果不是最后一個(gè),后面的需要往前面邏
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 解除掉對象的 GC root 引用
        elementData[--size] = null; 

        return oldValue;
    }
    
    // 通過 native 層去拷貝代碼
    // src :原來的數(shù)組
    // srcPos:原來數(shù)組的開始位置
    // dest:新的數(shù)組
    // destPos:新數(shù)組的開始位置
    // length:拷貝多少個(gè)
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

通過上面的代碼來分析,ArrayList 其內(nèi)部的實(shí)現(xiàn)方式其實(shí)就是數(shù)組,如果沒指定數(shù)組的大小,那么在第一次添加數(shù)據(jù)的時(shí)候,數(shù)組的初始大小是 10 ,每次當(dāng)不夠用的時(shí)候默認(rèn)會擴(kuò)充原來數(shù)組的 1/2 ,每次擴(kuò)充數(shù)組大小都會涉及到創(chuàng)建新數(shù)組和數(shù)據(jù)的拷貝復(fù)制。而數(shù)組的拷貝和邏動都是由我們的 native 層代碼實(shí)現(xiàn),可是為什么不直接用 java 代碼寫呢?接下來我們?nèi)タ聪?native 層的實(shí)現(xiàn)。

二.實(shí)現(xiàn) Native 層的 ArrayList
#ifndef MYAPPLICATION_ARRAYLIST_H
#define MYAPPLICATION_ARRAYLIST_H

#include <malloc.h>
//------------------類的定義-------------------//

template<class E>
class ArrayList {
public:
    // 數(shù)組頭指針
    E *array = NULL;
    // 數(shù)組長度
    int len = 0;
    // 數(shù)據(jù)大小
    int index = 0;
public:
    ArrayList();

    ArrayList(int len);

    ~ArrayList();

    ArrayList(const ArrayList &list);

public:
    bool add(E e);

    int size();

    E get(int index);

    E remove(int index);

private:
    void ensureCapacityInternal(int i);

    void grow(int capacity);
};

//------------------類的實(shí)現(xiàn)-------------------//
template<class E>
ArrayList<E>::ArrayList() {

}

template<class E>
ArrayList<E>::ArrayList(int len) {
    if (len == 0) {
        return;
    }
    this->len = len;
    this->array = (E *) malloc(sizeof(E) * len);
}

template<class E>
ArrayList<E>::~ArrayList() {
    if (this->array) {
        free(this->array);
        this->array = NULL;
    }
}

template<class E>
ArrayList<E>::ArrayList(const ArrayList &list) {
    this->index = list.index;
    this->len = list.len;
    // 深拷貝
    this->array = (E *) malloc(sizeof(E) * len);
    memcpy(this->array,list.array,sizeof(E) * len);
}

template<class E>
E ArrayList<E>::get(int index) {
    return this->array[index];
}

template<class E>
int ArrayList<E>::size() {
    return this->index;
}

template<class E>
E ArrayList<E>::remove(int index) {
    E old_value = this->array[index];
    // 計(jì)算出需要邏動的個(gè)數(shù)
    int numMoved = this->index - index - 1;

    // 從前面不斷的邏動
    for (int i = 0; i < numMoved; ++i) {
        array[index + i] = array[index + i + 1];
    }

    this->index -= 1;
    return old_value;
}

template<class E>
bool ArrayList<E>::add(E e) {
    ensureCapacityInternal(index + 1);
    this->array[index++] = e;
    return true;
}

// 是否需要調(diào)整當(dāng)前數(shù)組大小
template<class E>
void ArrayList<E>::ensureCapacityInternal(int minCapacity) {
    // 當(dāng)前數(shù)組是不是空,或者 len 是不是 0
    if (this->array == NULL) {
        minCapacity = 10;// 第一次初始化大小
    }

    // 判斷要不要擴(kuò)容
    if (minCapacity - len > 0) {
        grow(minCapacity);
    }
}

// 擴(kuò)容創(chuàng)建新的數(shù)組
template<class E>
void ArrayList<E>::grow(int capacity) {
    // 計(jì)算新數(shù)組大小的長度
    int new_len = len + (len >> 1);

    if (capacity - new_len > 0) {
        new_len = capacity;
    }

    // 創(chuàng)建新的數(shù)組
    E *new_arr = (E *) malloc(sizeof(E) * new_len);

    if (this->array) {
        // 拷貝數(shù)據(jù)
        memcpy(new_arr, array, sizeof(E) * index);
        // 釋放原來的內(nèi)存
        free(this->array);
    }

    array = new_arr;
    len = new_len;
}

#endif //MYAPPLICATION_ARRAYLIST_H
三.System.arraycopy 源代碼分析

java 中 ArrayList 數(shù)組的拷貝是通過 native 層去實(shí)現(xiàn)的,我看的是 jdk 1.8 的源碼,如果想進(jìn)一步了解其 native 層的實(shí)現(xiàn),我們需要下載 jdk 1.8 的源碼。

打開openjdk\hotspot\src\share\vm\prims\jvm.cpp可以看到一個(gè)方法JVM_ArrayCopy,但是該方法沒有真正實(shí)現(xiàn)復(fù)制的代碼,而是簡單的檢測源數(shù)組和目的數(shù)組是否為空,排除一些異常情況,方法都比較簡單,我們只要順著往下走就行了。

/* 
java.lang.System中的arraycopy方法 
*/  
JVM_ENTRY(void, JVM_ArrayCopy(JNIEnv *env, jclass ignored, jobject src, jint src_pos,  
                               jobject dst, jint dst_pos, jint length))  

  // 檢查源數(shù)組和目的數(shù)組不為空  
  if (src == NULL || dst == NULL) {  
    THROW(vmSymbols::java_lang_NullPointerException());  
  }  
   // 進(jìn)行解析轉(zhuǎn)換
  arrayOop s = arrayOop(JNIHandles::resolve_non_null(src));  
  arrayOop d = arrayOop(JNIHandles::resolve_non_null(dst));  
  assert(s->is_oop(), "JVM_ArrayCopy: src not an oop");  
  assert(d->is_oop(), "JVM_ArrayCopy: dst not an oop");  

  //真正調(diào)用復(fù)制的方法  
  s->klass()->copy_array(s, src_pos, d, dst_pos, length, thread);  
}

/* 
java.lang.System中的arraycopy方法具體實(shí)現(xiàn) 
*/  
void ObjArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d,  
                               int dst_pos, int length, TRAPS) {  
  //檢測s是數(shù)組  
  assert(s->is_objArray(), "must be obj array");  
  
  //目的數(shù)組不是數(shù)組對象的話,則拋出ArrayStoreException異常  
  if (!d->is_objArray()) {  
    THROW(vmSymbols::java_lang_ArrayStoreException());  
  }  
  
  // Check is all offsets and lengths are non negative  
  //檢測下標(biāo)參數(shù)非負(fù)  
  if (src_pos < 0 || dst_pos < 0 || length < 0) {  
    THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException());  
  }  
  // Check if the ranges are valid  
  //檢測下標(biāo)參數(shù)是否越界  
  if  ( (((unsigned int) length + (unsigned int) src_pos) > (unsigned int) s->length())  
     || (((unsigned int) length + (unsigned int) dst_pos) > (unsigned int) d->length()) ) {  
    THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException());  
  }  
  
  // Special case. Boundary cases must be checked first  
  // This allows the following call: copy_array(s, s.length(), d.length(), 0).  
  // This is correct, since the position is supposed to be an 'in between point', i.e., s.length(),  
  // points to the right of the last element.  
  //length==0則不需要復(fù)制  
  if (length==0) {  
    return;  
  }  
  //UseCompressedOops只是用來區(qū)分narrowOop和oop,具體2者有啥區(qū)別需要再研究  
  //調(diào)用do_copy函數(shù)來復(fù)制  
  if (UseCompressedOops) {  
    narrowOop* const src = objArrayOop(s)->obj_at_addr<narrowOop>(src_pos);  
    narrowOop* const dst = objArrayOop(d)->obj_at_addr<narrowOop>(dst_pos);  
    do_copy<narrowOop>(s, src, d, dst, length, CHECK);  
  } else {  
    oop* const src = objArrayOop(s)->obj_at_addr<oop>(src_pos);  
    oop* const dst = objArrayOop(d)->obj_at_addr<oop>(dst_pos);  
    do_copy<oop> (s, src, d, dst, length, CHECK);  
  }  
}  

// Either oop or narrowOop depending on UseCompressedOops.  
template <class T> void ObjArrayKlass::do_copy(arrayOop s, T* src,  
                               arrayOop d, T* dst, int length, TRAPS) {  
  
  BarrierSet* bs = Universe::heap()->barrier_set();  
  // For performance reasons, we assume we are that the write barrier we  
  // are using has optimized modes for arrays of references.  At least one  
  // of the asserts below will fail if this is not the case.  
  assert(bs->has_write_ref_array_opt(), "Barrier set must have ref array opt");  
  assert(bs->has_write_ref_array_pre_opt(), "For pre-barrier as well.");  
  
  if (s == d) {  
    // since source and destination are equal we do not need conversion checks.  
    assert(length > 0, "sanity check");  
    bs->write_ref_array_pre(dst, length);  
    //復(fù)制的函數(shù)  
    Copy::conjoint_oops_atomic(src, dst, length);  
  } else {  
    // We have to make sure all elements conform to the destination array  
    Klass* bound = ObjArrayKlass::cast(d->klass())->element_klass();  
    Klass* stype = ObjArrayKlass::cast(s->klass())->element_klass();  
    if (stype == bound || stype->is_subtype_of(bound)) {  
      // elements are guaranteed to be subtypes, so no check necessary  
      //stype對象是bound,或者stype是bound的子類抑或stype實(shí)現(xiàn)bound接口  
      bs->write_ref_array_pre(dst, length);  
      Copy::conjoint_oops_atomic(src, dst, length);  
    } else {  
      // slow case: need individual subtype checks  
      // note: don't use obj_at_put below because it includes a redundant store check  
      T* from = src;  
      T* end = from + length;  
      for (T* p = dst; from < end; from++, p++) {  
        // XXX this is going to be slow.  
        T element = *from;  
        // even slower now  
        bool element_is_null = oopDesc::is_null(element);  
        oop new_val = element_is_null ? oop(NULL)  
                                      : oopDesc::decode_heap_oop_not_null(element);  
        if (element_is_null ||  
            (new_val->klass())->is_subtype_of(bound)) {  
          bs->write_ref_field_pre(p, new_val);  
          *p = element;  
        } else {  
          // We must do a barrier to cover the partial copy.  
          const size_t pd = pointer_delta(p, dst, (size_t)heapOopSize);  
          // pointer delta is scaled to number of elements (length field in  
          // objArrayOop) which we assume is 32 bit.  
          assert(pd == (size_t)(int)pd, "length field overflow");  
          bs->write_ref_array((HeapWord*)dst, pd);  
          THROW(vmSymbols::java_lang_ArrayStoreException());  
          return;  
        }  
      }  
    }  
  }  
  bs->write_ref_array((HeapWord*)dst, length);  
}  

// oops, conjoint, atomic on each oop  
static void conjoint_oops_atomic(oop* from, oop* to, size_t count) {  
  assert_params_ok(from, to, LogBytesPerHeapOop);  
  pd_conjoint_oops_atomic(from, to, count);  
}

//檢測是否是k的子類,或者是實(shí)現(xiàn)k接口  
bool is_subtype_of(Klass* k) const {  
  juint    off = k->super_check_offset();  
  Klass* sup = *(Klass**)( (address)this + off );  
  const juint secondary_offset = in_bytes(secondary_super_cache_offset());  
  if (sup == k) {  
    return true;  
  } else if (off != secondary_offset) {  
    return false;  
  } else {  
    return search_secondary_supers(k);  
  }  
}  

static void pd_conjoint_oops_atomic(oop* from, oop* to, size_t count) {  
  // Do better than this: inline memmove body  NEEDS CLEANUP  
  if (from > to) {  
    while (count-- > 0) {  
      // Copy forwards  
      *to++ = *from++;  
    }  
  } else {  
    from += count - 1;  
    to   += count - 1;  
    while (count-- > 0) {  
      // Copy backwards  
      *to-- = *from--;  
    }  
  }  
}  

視頻地址:https://pan.baidu.com/s/1A-1pG6IwrtR8WrxpZ75gyw
視頻密碼:acw5

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,973評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內(nèi)容

  • 天黑了,屋外飄著雨加雪。上午老爹本想把水管接通,結(jié)果因?yàn)橐ビH戚家喝酒,耽擱了?;丶視r(shí)已是黑夜的王國。 老爹叫上我...
    南桑子閱讀 247評論 1 2
  • 對于辭職出去旅行這件事,當(dāng)初看到新聞的時(shí)候,我只是一笑置之,沒想到?jīng)]過多久,自己竟然也走了這條路。 是什么讓我下決...
    青裳MG閱讀 720評論 2 1
  • 我們終此一生,就是要擺脫他人的期待,找到真正的自己。 莉迪亞死了。 小說以這句直白的文字開始。一層層抽絲剝繭,解開...
    君小陶閱讀 703評論 4 3