成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java ArrayList.add 的實現

raledong / 3461人閱讀

摘要:是平時相當常用的實現其中的實現比較直接有時候也使用把元素插入到指定的上在中的實現是略有差別需要保證當前數組容量夠用然后把從處一直到尾部的數組元素都向后挪一位最后把要插入的元素賦給數組的處一直以來我都認為這個方法它的實現是調用底層的直接方便

ArrayList是平時相當常用的List實現, 其中boolean add(E e) 的實現比較直接:

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return true (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

有時候也使用 void add(int index, E element) 把元素插入到指定的index上. 在JDK中的實現是:

/**
 * Inserts the specified element at the specified position in this
 * list. Shifts the element currently at that position (if any) and
 * any subsequent elements to the right (adds one to their indices).
 *
 * @param index index at which the specified element is to be inserted
 * @param element element to be inserted
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
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++;
}

略有差別, 需要保證當前elementData 數組容量夠用, 然后把從index處一直到尾部的數組元素都向后挪一位. 最后把要插入的元素賦給數組的index處.

一直以來, 我都認為 System.arraycopy 這個native方法, 它的c++實現是調用底層的memcpy, 直接方便, 效率也沒問題.

但今天看了openJDK的源碼發(fā)現并非如此.

以openJDK8u60 為例, 在 objArrayKlass.cpp 中:

void ObjArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d,
                               int dst_pos, int length, TRAPS) {
  assert(s->is_objArray(), "must be obj array");

  if (!d->is_objArray()) {
    THROW(vmSymbols::java_lang_ArrayStoreException());
  }

  // Check is all offsets and lengths are non negative
  if (src_pos < 0 || dst_pos < 0 || length < 0) {
    THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException());
  }
  // Check if the ranges are valid
  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.
  if (length==0) {
    return;
  }
  if (UseCompressedOops) {
    narrowOop* const src = objArrayOop(s)->obj_at_addr(src_pos);
    narrowOop* const dst = objArrayOop(d)->obj_at_addr(dst_pos);
    do_copy(s, src, d, dst, length, CHECK);
  } else {
    oop* const src = objArrayOop(s)->obj_at_addr(src_pos);
    oop* const dst = objArrayOop(d)->obj_at_addr(dst_pos);
    do_copy (s, src, d, dst, length, CHECK);
  }
}

可以看到copy_array在做了各種檢查之后, 最終copy的部分在do_copy方法中, 而這個方法實現如下:

// Either oop or narrowOop depending on UseCompressedOops.
template  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);
    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
      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);
}

可以看到, 在設定了heap barrier之后, 元素是在for循環(huán)中被一個個挪動的. 做的工作比我想象的要多.

如果有m個元素, 按照給定位置, 使用ArrayList.add(int,E)逐個插入到一個長度為n的ArrayList中, 復雜度應當是O(m*n), 或者O(m*(m+n)), 所以, 如果m和n都不小的話, 效率確實是不高的.

效率高一些的方法是, 建立m+n長度的數組或ArrayList, 在給定位置賦值該m個要插入的元素, 其他位置依次賦值原n長度List的元素. 這樣時間復雜度應當是O(m+n).

還有, 在前面的實現中, 我們可以看到有對ensureCapacityInternal(int) 的調用. 這個保證數組容量的實現主要在:

/**
 * 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);
    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);
}

大家知道由于效率原因, ArrayList容量增長不是正好按照要求的容量minCapacity來設計的, 新容量計算的主要邏輯是: 如果要求容量比當前容量的1.5倍大, 就按照要求容量重新分配空間; 否則按當前容量1.5倍增加. 當然不能超出Integer.MAX_VALUE了. oldCapacity + (oldCapacity >> 1) 實際就是當前容量1.5倍, 等同于(int) (oldCapacity * 1.5), 但因這段不涉及浮點運算只是移位, 顯然效率高不少.

所以如果ArrayList一個一個add元素的話, 容量是在不夠的時候1.5倍增長的. 關于1.5這個數字, 或許是覺得2倍增長太快了吧. 也或許有實驗數據的驗證支撐.

關于這段代碼中出現的Arrays.copyOf這個方法, 實現的是重新分配一段數組, 把elementData賦值給新分配的空間, 如果新分配的空間大, 則后面賦值null, 如果分配空間比當前數組小則截斷. 底層還是調用的System.arraycopy.

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://systransis.cn/yun/72033.html

相關文章

  • JAVA 集合框架

    摘要:接口下面包含等。但是接口并沒有繼承接口,因此無法迭代。分離出接口是迭代器模式。但是接口又提供了接口以后將轉換成集合來迭代。的增強循環(huán)也只適用于那些繼承了接口的。 Iterator接口是Collection接口的父接口。Collection接口下面包含List,Set,Queue等。 Map接口與Collection接口同級。但是Map接口并沒有繼承Iterator接口,因此無法迭代。 ...

    galaxy_robot 評論0 收藏0
  • 深入了解Java集合中ArrayList

    摘要:實現了接口,所以包含的基本方法新增,刪除,插入等都實現了。線程安全問題在多線程情況下有線程進行修改時,是線程不安全的。線程安全性問題,取決于如何應用。反映的是當前數組中存了多少數據,而則反映的是內部數組的容量。 什么是ArrayList ArrayList 是一個可擴容數組Resizable-array,它實現了List接口的所有方法。 從對ArrayList的簡單描述中我們可以得出...

    zeyu 評論0 收藏0
  • Java 中初始化 List 集合 6 種方式!

    摘要:是開發(fā)中經常會使用的集合,你們知道有哪些方式可以初始化一個嗎這其中不缺乏一些坑,今天棧長我給大家一一普及一下。好了,今天棧長就給大家介紹到這里了,這種,你知道幾種另外,也有類似的初始化的方法,大家有興趣的可以試一下。 List 是 Java 開發(fā)中經常會使用的集合,你們知道有哪些方式可以初始化一個 List 嗎?這其中不缺乏一些坑,今天棧長我給大家一一普及一下。 1、常規(guī)方式 List...

    Binguner 評論0 收藏0
  • java集合

    摘要:主要用于遍歷集合中的元素,對象也被稱為迭代器。使用迭代過程中,不可修改集合元素迭代器采用快速失敗機制。一旦迭代過程中檢測到該集合已經被修改,程序立即出發(fā)異常,而不是顯示修改后的結果,避免了共享資源而引發(fā)的潛在問題。 集合類和數組不一樣,數組元素既可以是基本類型的值,也可以是對象(實際上保存的是對象的引用變量);而集合里只能保存對象(實際上只是保存對象的引用變量,但通常習慣上認為集...

    JinB 評論0 收藏0
  • java-list-map-set 學習記錄

    摘要:集合類類型解釋的父類集集合中的元素不按特定方式排序,并且沒有重復對象。他的有些實現類能對集合中的鍵對象進行排序。 集合類 2017-07-10 22:24:57 blog site https://github.com/Fiz1994 類型解釋: Collection : Set,List 的父類 Set(集):集合中的元素不按特定方式排序,并且沒有重復對象。他的有些實現類能對集合中的...

    stackvoid 評論0 收藏0

發(fā)表評論

0條評論

raledong

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<