Java數(shù)組擴(kuò)容的三大小結(jié)
方案1:新建數(shù)組
這種方法新建的數(shù)組必須要比原先的長(zhǎng)度要長(zhǎng),然后將原來(lái)的數(shù)組內(nèi)容移到新的數(shù)組中
<!--more--> int[] a = {1, 2, 3, 4, 5}; ? // 創(chuàng)建新數(shù)組,長(zhǎng)度為源數(shù)組的兩倍 int[] b = new int[a.length * 2]; ? // 將舊數(shù)組內(nèi)容復(fù)制到新數(shù)組 for (int i = 0; i < a.length; i++) { ? ? b[i] = a[i]; } ? // 新數(shù)組內(nèi)容賦值給源數(shù)組 a = b; ? // 打印結(jié)果 System.out.println(Arrays.toString(a));
輸出結(jié)果
[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
方案2:Arrays.copyOf
int[] a = {1, 2, 3, 4, 5}; ? // 第一個(gè)參數(shù)是拷貝的數(shù)組,第二個(gè)參數(shù)是擴(kuò)容長(zhǎng)度,且返回一個(gè)新數(shù)組 a = Arrays.copyOf(a, a.length * 2); ? // 打印結(jié)果 System.out.println(Arrays.toString(a));
輸出結(jié)果
[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
Arrays.copyof是用于數(shù)組進(jìn)行復(fù)制時(shí)常使用的方法,本身在Arrays類(lèi)里面,而之所以能這么使用而不用創(chuàng)建對(duì)象在于該方法本身由static修飾,被static修飾的方法可以在該類(lèi)創(chuàng)建對(duì)象前載入jvm。
public static long[] copyOf(long[] original, int newLength) { long[] copy = new long[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
通過(guò)上面的代碼可以看出,其本質(zhì)是調(diào)用了System.arraycopy方法。
先產(chǎn)生一個(gè)新的數(shù)組然后調(diào)用arraycopy方法最后在返回產(chǎn)生的新數(shù)組。但是我們進(jìn)行數(shù)組擴(kuò)容的時(shí)候禪城了新數(shù)組,但是原數(shù)組依然存在,造成了內(nèi)存的浪費(fèi)。
方案3:System.arraycopy
int[] a = {1, 2, 3, 4, 5}; ? // 定義新數(shù)組,長(zhǎng)度為源數(shù)組的兩倍 int[] b = new int[a.length * 2]; ? /** * src 需要拷貝的源數(shù)組 * srcPos 源數(shù)組中的起始位置 * dest 目標(biāo)數(shù)組 * destPos 目標(biāo)數(shù)組中的起始位置 * length 要復(fù)制的數(shù)組元素?cái)?shù)量 */ System.arraycopy(a, 0, b, 0, a.length); ? // 新數(shù)組內(nèi)容賦值給原數(shù)組 a = b; ? // 打印結(jié)果 System.out.println(Arrays.toString(a));
輸出結(jié)果
[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
arraycopy源碼
/** * @param src the source array. 源數(shù)組 * @param srcPos starting position in the source array. 要復(fù)制的源數(shù)組的起始位置 * @param dest the destination array. 目標(biāo)數(shù)組 * @param destPos starting position in the destination data. 目標(biāo)數(shù)組的起始位置 * @param length the number of array elements to be copied. 要復(fù)制的長(zhǎng)度 * @throws IndexOutOfBoundsException if copying would cause * access of data outside array bounds. * 如果復(fù)制會(huì)導(dǎo)致數(shù)據(jù)的訪問(wèn)超出數(shù)組邊界。 * 則會(huì)報(bào)IndexOutOfBoundsException索引越界異常 * @throws 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. * 如果由于類(lèi)型不匹配而無(wú)法將src數(shù)組中的元素存儲(chǔ)到dest數(shù)組中。 * 則會(huì)報(bào) ArrayStoreException數(shù)組存儲(chǔ)異常 * @throws NullPointerException if either <code>src</code> or * <code>dest</code> is <code>null</code>. * 如果src或dest為null。 * 則會(huì)報(bào)NullPointerException空指針異常 */ ? public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
System.arraycopy()的幾種常用方法(普通for循環(huán)和arraycopy方法對(duì)比)
1. 從舊數(shù)組拷貝到新數(shù)組
//從舊數(shù)組拷貝到新數(shù)組 for (int i=0;i<size;i++){ arrayNew[i]=array[i]; } System.arraycopy(array, 0, arrayNew, 0, array.length);
2. 從左向右循環(huán),逐個(gè)元素向左挪一位。
//從左向右循環(huán),逐個(gè)元素向左挪一位。 for (int i = index; i < size - 1; i++) { array[i] = array[i + 1]; } System.arraycopy(array, index + 1, array, index, size - 1 - index);
3. 從右向左循環(huán),逐個(gè)元素向右挪一位。
//從右向左循環(huán),逐個(gè)元素向右挪一位。 for (int i = size - 1; i >= index; i--) { array[i + 1] = array[i]; } System.arraycopy(array, index, array, index + 1, size - index);
System.arraycopy()深層理解
深復(fù)制還是淺復(fù)制
先說(shuō)結(jié)論 :
- 當(dāng)數(shù)組為一維數(shù)組,且元素為基本類(lèi)型或String類(lèi)型時(shí),屬于深拷貝,即原數(shù)組與新數(shù)組的元素不會(huì)相互影響。
- 當(dāng)數(shù)組為多維數(shù)組,或一維數(shù)組中的元素為引用類(lèi)型時(shí),屬于淺拷貝,原數(shù)組與新數(shù)組的元素引用指向同一個(gè)對(duì)象。
引用對(duì)象
構(gòu)建一個(gè)User類(lèi)型源數(shù)組,復(fù)制后至target數(shù)組,比較第一個(gè)元素的內(nèi)存地址,判斷結(jié)果是相同的,證明為淺復(fù)制;后修改target數(shù)組數(shù)組的隨機(jī)元素,發(fā)現(xiàn)原來(lái)的值也變了。
public class SystemArrayCopyTestCase { ? public static void main(String[] args) { User[] users = new User[] { new User(1), new User(2), new User(3) };// 初始化對(duì)象數(shù)組 User[] target = new User[users.length];// 新建一個(gè)目標(biāo)對(duì)象數(shù)組 System.arraycopy(users, 0, target, 0, users.length);// 實(shí)現(xiàn)復(fù)制 System.out.println("源對(duì)象與目標(biāo)對(duì)象的物理地址是否一樣:" + (users[0] == target[0] ? "淺復(fù)制" : "深復(fù)制")); //淺復(fù)制 target[0].setId(5); System.out.println("修改目標(biāo)對(duì)象的屬性值后源對(duì)象users:"); for (User user : users) { System.out.println(user); } } } ? class User { } ?
System.arraycopy() 在拷貝數(shù)組的時(shí)候,采用的使用潛復(fù)制,復(fù)制結(jié)果是一維的引用變量傳遞給新數(shù)組的一維數(shù)組,修改新數(shù)組時(shí),會(huì)影響原來(lái)的數(shù)組。
一維數(shù)組和多維數(shù)組
將一維數(shù)組作源數(shù)組,進(jìn)行拷貝,產(chǎn)生target數(shù)組;然后修改target數(shù)組中的元素,新數(shù)組沒(méi)變,證明是值拷貝,修改新數(shù)組不會(huì)影響原來(lái)的值。
將多維數(shù)組作源數(shù)組,進(jìn)行拷貝至目標(biāo)數(shù)組,修改至目標(biāo)數(shù)組的元素,新數(shù)組也變了,說(shuō)明是二者是相同的引用,而這時(shí)改變其中任何一個(gè)數(shù)組的元素的值,其實(shí)都修改了共同數(shù)組元素的值,所以原數(shù)組和新數(shù)組的元素值都一樣了
線程是否安全(摘自網(wǎng)絡(luò))
System.ayyaycopy是不安全的。
public class ArrayCopyThreadSafe { private static int[] arrayOriginal = new int[1024 * 1024 * 10]; private static int[] arraySrc = new int[1024 * 1024 * 10]; private static int[] arrayDist = new int[1024 * 1024 * 10]; private static ReentrantLock lock = new ReentrantLock(); ? private static void modify() { for (int i = 0; i < arraySrc.length; i++) { arraySrc[i] = i + 1; } } ? private static void copy() { System.arraycopy(arraySrc, 0, arrayDist, 0, arraySrc.length); } ? private static void init() { for (int i = 0; i < arraySrc.length; i++) { arrayOriginal[i] = i; arraySrc[i] = i; arrayDist[i] = 0; } } ? private static void doThreadSafeCheck() throws Exception { for (int i = 0; i < 100; i++) { System.out.println("run count: " + (i + 1)); init(); Condition condition = lock.newCondition(); ? new Thread(new Runnable() { @Override public void run() { lock.lock(); condition.signalAll(); lock.unlock(); copy(); } }).start(); ? ? lock.lock(); // 這里使用 Condition 來(lái)保證拷貝線程先已經(jīng)運(yùn)行了. condition.await(); lock.unlock(); ? Thread.sleep(2); // 休眠2毫秒, 確??截惒僮饕呀?jīng)執(zhí)行了, 才執(zhí)行修改操作. modify(); ? if (!Arrays.equals(arrayOriginal, arrayDist)) { throw new RuntimeException("System.arraycopy is not thread safe"); } } } ? public static void main(String[] args) throws Exception { doThreadSafeCheck(); } } ?
這個(gè)例子的具體操作是:
arrayOriginal 和 arraySrc 初始化時(shí)是相同的, 而 arrayDist 是全為零的.
啟動(dòng)一個(gè)線程運(yùn)行 copy() 方法來(lái)拷貝 arraySrc 到 arrayDist 中.
在主線程執(zhí)行 modify() 操作, 修改 arraySrc 的內(nèi)容. 為了確保 copy() 操作先于 modify() 操作, 我使用 Condition, 并且延時(shí)了兩毫秒, 以此來(lái)保證執(zhí)行拷貝操作(即System.arraycopy) 先于修改操作.
根據(jù)第三點(diǎn), 如果 System.arraycopy 是線程安全的, 那么先執(zhí)行拷貝操作, 再執(zhí)行修改操作時(shí), 不會(huì)影響復(fù)制結(jié)果, 因此 arrayOriginal 必然等于 arrayDist; 而如果 System.arraycopy 是線程不安全的, 那么 arrayOriginal 不等于 arrayDist.
run count: 1
Exception in thread "main" java.lang.RuntimeException: System.arraycopy is not thread safe
at ArrayCopyThreadSafe.doThreadSafeCheck(ArrayCopyThreadSafe.java:54)
at ArrayCopyThreadSafe.main(ArrayCopyThreadSafe.java:60)
結(jié)論 :System.ayyaycopy是不安全的。
System.arraycopy()和for()相比誰(shuí)更高效
當(dāng)測(cè)試數(shù)組的范圍比較小的時(shí)候,兩者相差的時(shí)間無(wú)幾,當(dāng)測(cè)試數(shù)組的長(zhǎng)度達(dá)到百萬(wàn)級(jí)別,System.arraycopy的速度優(yōu)勢(shì)就開(kāi)始體現(xiàn)了,根據(jù)對(duì)底層的理解,System.arraycopy是對(duì)內(nèi)存直接進(jìn)行復(fù)制,減少了for循環(huán)過(guò)程中的尋址時(shí)間,從而提高了效能。
到此這篇關(guān)于Java數(shù)組擴(kuò)容的三大小結(jié)的文章就介紹到這了,更多相關(guān)Java數(shù)組擴(kuò)容內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于maven install報(bào)錯(cuò)原因揭秘:parent.relativePath指向錯(cuò)誤的本地POM文件
在使用Maven進(jìn)行項(xiàng)目構(gòu)建時(shí),如果遇到'parent.relativePath'指向錯(cuò)誤的本地POM文件的問(wèn)題,可能會(huì)導(dǎo)致構(gòu)建失敗,這通常是由于父項(xiàng)目POM文件的相對(duì)路徑設(shè)置錯(cuò)誤、本地POM文件與父項(xiàng)目POM文件版本或內(nèi)容不一致所致,解決方法包括檢查并修正父項(xiàng)目POM文件中的相對(duì)路徑設(shè)置2024-09-09Java Thread之Sleep()使用方法及總結(jié)
這篇文章主要介紹了Java Thread之Sleep()使用方法及總結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11HttpMessageConverter報(bào)文信息轉(zhuǎn)換器的深入講解
在Spring中內(nèi)置了大量的HttpMessageConverter,通過(guò)請(qǐng)求頭信息中的MIME類(lèi)型,選擇相應(yīng)的HttpMessageConverter,這篇文章主要給大家介紹了關(guān)于HttpMessageConverter報(bào)文信息轉(zhuǎn)換器的相關(guān)資料,需要的朋友可以參考下2022-01-01MyBatis直接執(zhí)行SQL的工具SqlMapper
今天小編就為大家分享一篇關(guān)于MyBatis直接執(zhí)行SQL的工具SqlMapper,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12java去除中文括號(hào)小括號(hào),或者英文括號(hào)的實(shí)例代碼
這篇文章主要介紹了java去除中文括號(hào)小括號(hào),或者英文括號(hào)的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09spring aop之鏈?zhǔn)秸{(diào)用的實(shí)現(xiàn)
這篇文章主要介紹了spring aop之鏈?zhǔn)秸{(diào)用的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-02-02Spring注解驅(qū)動(dòng)之關(guān)于@Bean注解指定初始化和銷(xiāo)毀的方法
這篇文章主要介紹了Spring注解驅(qū)動(dòng)之關(guān)于@Bean注解指定初始化和銷(xiāo)毀的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09