1. 程式人生 > >java.lang.System下的arraycopy和java.util.Arrays.copyOf方法

java.lang.System下的arraycopy和java.util.Arrays.copyOf方法

(1) java.lang.System.arraycopy

public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos, int length);

問題:方法沒有任何的實現,具體是如何實現的呢?

以下是關於該方法具體的說明:

* @param      src      the source array.

* @param      srcPos   starting position in the source array.

* @param      dest     the destination array.

* @param      destPos  starting position in the destination data.

* @param      length   the number of array elements to be copied.

* @exception  IndexOutOfBoundsException  if copying would cause

*               access of data outside array bounds.

* @exception  ArrayStoreException  if an element in the <code>src</code>

*               array could not be stored into the <code>dest</code> array

*               because of a type mismatch.

* @exception  NullPointerException if either <code>src</code> or

*               <code>dest</code> is <code>null</code>.

(2) java.util.Arrays.copyOf

	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) {
		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;
	}

兩者的最大區別是:

    arraycopy 方法會因為新陣列大小比舊陣列大小小而報IndexOutOfBoundsException

    copyOf 則不會因此報錯,因為copyOf 的返回值是在內部new好的copy 陣列,而該copy陣列new的大小就等於newLength,故即使在客戶端指定好了新陣列newArray 的大小,接收到返回值後也是指向底層new出來的陣列copy換句話說,在客戶端程式碼中即使不給新陣列new物件,如:String[] newStr = null; 那麼對於arraycopy 是會報NullPointerException 的錯誤的,而對於java.util.Arrays 中的copyOf 方法則由於jdk 底層已經new 出了物件而不會報該錯誤!

    不過需要特別注意的是:copyOf 方法最後也是呼叫System.arraycopy的方法,不過由於前面的準備,異常情況就不會出現了。

下面是一個具體例項:

public class ArrayCopyOf {
    public static void main(String[] args) {
        String[] oldStr = new String[6];
        setValues(oldStr);
        String[] newStr = null;
        newStr = java.util.Arrays.copyOf(oldStr, 6);
        //System.arraycopy(oldStr, 0, newStr, 0, oldStr.length);
        // 會報錯NullPointerException ,如果改為newStr = new String[5],
        // 那麼呼叫Arrays.copyOf 方法不會報錯,而呼叫System.arraycopy 方法
        // 則回報IndexOutOfBoundsException
        print(oldStr);
        print(newStr);
        System.out.println(oldStr.length);
        System.out.println(newStr.length);
    }
 
    private static void print(String[] newStr) {
        // TODO Auto-generated method stub
        for (int i = 0; i < newStr.length; i++) {
            System.out.print(newStr[i] + " : ");
        }
        System.out.println();
    }
 
    private static void setValues(String[] oldStr) {
        // TODO Auto-gemmnerated method stub
        for (int i = 0; i < oldStr.length; i++) {
            oldStr[i] = "str " + i;
        }
    }
}

與此同時,java.util.Arrays 還過載了很多copyOf 方法:

	public static byte[] copyOf(byte[] original, int newLength) {
        byte[] copy = new byte[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
	public static int[] copyOf(int[] original, int newLength) {
        int[] copy = new int[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
		return copy;
	}

等等;

現在再來討論下ArrayList 中對該兩個方法的使用情況:

    由於ArrayList 底層是用陣列實現的,那麼就必然會面臨兩個問題:

    (1) 一開始必須要指定一個初始化的陣列大小;

    java.util.ArrayList 中初始化ArrayList 大小是10 :

private transient Object[] elementData;// 為什麼要設定成transient? 不想被序列化?
public ArrayList(int initialCapacity) {
    super();
     if (initialCapacity < 0)
     throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    this.elementData = new Object[initialCapacity];
}
public ArrayList() {
    this(10);
}

    (2) 當陣列容量不夠時,能夠動態增加容量。

java.util.ArrayList 中的add(E e) 方法
public boolean add(E e) {
    ensureCapacity(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
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);
    }
}

    可見,每次新增元素時,JDK 都會先檢查陣列elementData 的容量是否已經滿了,如果滿了,就會呼叫Arrays.copyOf 方法,用elementData 中的元素和新陣列的長度構造一個新的陣列,並重新賦值給elementData陣列。此處不難看出,由於需求是把長度已經增長了的elementData 陣列重新賦值給elementData ,故直接使用System.arraycopy 方法是不適合的。

    另外,新陣列長度的改變也是有講究的,JDK 的設計是每次有新的擴充套件陣列長度的需要到來時,就按int newCapacity = (oldCapacity * 3)/2 + 1; 的演算法構造新陣列的長度,不難看出,這種演算法構造出來的新的陣列長度的增量都會比上一次大( 而且是越來越大) ,即認為客戶需要增加的資料很多,而避免頻繁newInstance 的情況。

    下面再來看ArrayList 中的toArray 方法

	public Object[] toArray() {
		return Arrays.copyOf(elementData, size);
	}

	public <T> T[] toArray(T[] a) {
		if (a.length < size)
			// Make a new array of a's runtime type, but my contents:
			return (T[]) Arrays.copyOf(elementData, size, a.getClass());
		System.arraycopy(elementData, 0, a, 0, size);
		if (a.length > size)
			a[size] = null;
		return a;
	}

    此處主要看第二個toArray 方法,可見,客戶是想把ArrayList 中的元素轉換成陣列並存放到指定的a 陣列中,因此就要判斷a.length 與elementData 當前的大小了,如果a.length<size, 那麼呼叫System.arraycopy 方法是肯定會報錯的,故必須呼叫Arrays.copyOf 方法,而如果a.length>=size 那麼就比不上呼叫System.arraycopy 方法來得方便了( 因為根據上面說的,Arrays.copyOf 方法最終也是呼叫System.arraycopy方法的) 。

    綜上所述,可以總結:在允許的情況下,儘量呼叫System.arraycopy 方法,實在不行再呼叫Arrays.copyOf 方法。