ทำสำเนาของอาร์เรย์


345

ฉันมีอาร์เรย์aที่มีการปรับปรุงอยู่ตลอดเวลา a = [1,2,3,4,5]สมมติว่า ฉันจำเป็นต้องทำสำเนาซ้ำกันแน่นอนของและเรียกมันว่าa bหากaมีการเปลี่ยนแปลงไป[6,7,8,9,10], ควรจะยังคงb [1,2,3,4,5]วิธีที่ดีที่สุดในการทำเช่นนี้คืออะไร? ฉันลองforวนซ้ำเช่น:

for(int i=0; i<5; i++) {
    b[i]=a[i]
}

แต่ดูเหมือนว่าจะทำงานไม่ถูกต้อง โปรดอย่าใช้คำศัพท์ขั้นสูงเช่นการทำสำเนาลึก ฯลฯ เพราะฉันไม่ทราบความหมาย

คำตอบ:


559

คุณสามารถลองใช้System.arraycopy ()

int[] src  = new int[]{1,2,3,4,5};
int[] dest = new int[5];

System.arraycopy( src, 0, dest, 0, src.length );

แต่น่าจะดีกว่าถ้าใช้ clone () ในกรณีส่วนใหญ่:

int[] src = ...
int[] dest = src.clone();

9
+1 สำหรับการไม่หมุนล้ออีกครั้ง และเท่าที่ฉันรู้วิธีนี้เป็นวิธีที่เร็วกว่าที่คุณจะได้รับในการคัดลอกอาร์เรย์
Felipe Hummel

6
ทั้งโคลนและอาร์เรย์เป็นสำเนา ฉันคาดว่าการโคลนนิ่งจะเร็วขึ้นเล็กน้อย ไม่ใช่ว่าความแตกต่างสำคัญ
MeBigFatGuy

5
@Felipe, @MeBigFatGuy - สำหรับอาร์เรย์ขนาดใหญ่เท่านั้น สำหรับอาร์เรย์ขนาดเล็กการวนรอบการคัดลอกอาจเร็วขึ้นเนื่องจากการตั้งค่าโสหุ้ย หากคุณดูที่ javadoc System.arraycopyคุณจะเห็นว่าวิธีการนั้นจำเป็นต้องตรวจสอบสิ่งต่าง ๆ ก่อนที่จะเริ่ม การตรวจสอบบางอย่างไม่จำเป็นสำหรับการวนรอบการคัดลอกขึ้นอยู่กับชนิดของอาร์เรย์แบบคงที่
สตีเฟ่นซี

7
@FelipeHummel, @MeBigFatGuy, @StephenC - นี่คือการทดสอบประสิทธิภาพของวิธีการคัดลอกอาร์เรย์ที่กล่าวถึงในคำตอบที่นี่ ในการตั้งค่านั้นclone()จะกลายเป็นสิ่งที่เร็วที่สุดสำหรับองค์ประกอบ 250,000 รายการ
Adam

6
เป็นเรื่องน่าเสียดายที่การอภิปรายทั้งหมดที่นี่เป็นเรื่องเกี่ยวกับปัญหาด้านประสิทธิภาพการทำงานของไมโครซึ่งเวลา 99.999% นั้นไม่สำคัญ จุดสำคัญอีกเรื่องที่สามารถอ่านได้มากขึ้นและมีโอกาสน้อยห่างไกลสำหรับข้อผิดพลาดกว่าจัดสรรแถวใหม่และการทำsrc.clone() arraycopy(และยังเกิดขึ้นอย่างรวดเร็ว)
Brian Goetz

231

คุณสามารถใช้ได้

int[] a = new int[]{1,2,3,4,5};
int[] b = a.clone();

เช่นกัน


6
ฉันแค่เคลียร์ประเด็นของ OP ว่า: " ถ้า A เปลี่ยนเป็น [6,7,8,9,10], B ก็ควรจะเป็น [1,2,3,4,5] " OP บอกว่าเขาพยายามใช้ลูป แต่ไม่ได้ผลสำหรับเขา
แฮร์รี่จอย

15
การโยนนั้นไม่จำเป็น ตัววิเคราะห์แบบสแตติกที่ดีจะเตือนเกี่ยวกับมัน แต่การโคลนเป็นวิธีที่ดีที่สุดในการสร้างอาร์เรย์ใหม่
erickson

5
@MeBigFatGuy - กรณีการใช้งานของ OP ทำให้เกิดการคัดลอกซ้ำไปยังอาร์เรย์เดียวกันดังนั้นโคลนจึงไม่ทำงาน
สตีเฟนซี

4
@ สตีเฟนซีฉันไม่ได้อ่าน ฉันเพิ่งอ่านว่าเขาต้องการสำเนาและจากนั้นจะอัปเดตเวอร์ชันที่ไม่ได้ถูกจัดเก็บซ้ำ
MeBigFatGuy

4
@MeBigFatGuy - เขาพูดว่า"ฉันมีอาร์เรย์ A ซึ่งมีการปรับปรุงอยู่ตลอดเวลา" . บางทีฉันอาจจะอ่านมากเกินไป แต่ฉันคิดว่าเขากำลังคัดลอก A ไป B ซ้ำหลายครั้งเช่นกัน
สตีเฟ่นซี

184

หากคุณต้องการทำสำเนา:

int[] a = {1,2,3,4,5};

นี่คือวิธีที่จะไป:

int[] b = Arrays.copyOf(a, a.length);

Arrays.copyOfอาจเร็วกว่าa.clone()ในอาร์เรย์ขนาดเล็ก ทั้งสององค์ประกอบสำเนาอย่างเท่าเทียมกันอย่างรวดเร็ว แต่โคลน () ผลตอบแทนเพื่อให้คอมไพเลอร์ที่มีการแทรกหล่อส่อไปObject int[]คุณสามารถดูได้ในไบต์รหัสเช่นนี้:

ALOAD 1
INVOKEVIRTUAL [I.clone ()Ljava/lang/Object;
CHECKCAST [I
ASTORE 2

63

คำอธิบายที่ดีจากhttp://www.journaldev.com/753/how-to-copy-arrays-in-java

วิธีการคัดลอก Java Array

Object.clone () : คลาสวัตถุให้วิธีการโคลน () และเนื่องจากอาร์เรย์ใน java ยังเป็นวัตถุคุณสามารถใช้วิธีนี้เพื่อให้ได้สำเนาแบบเต็ม วิธีนี้จะไม่เหมาะกับคุณถ้าคุณต้องการคัดลอกอาร์เรย์บางส่วน

System.arraycopy () : System class arraycopy () เป็นวิธีที่ดีที่สุดในการทำสำเนาบางส่วนของอาเรย์ มันเป็นวิธีที่ง่ายในการระบุจำนวนองค์ประกอบทั้งหมดที่จะคัดลอกและตำแหน่งดัชนีต้นทางและปลายทาง ตัวอย่างเช่นSystem.arraycopy (ต้นทาง, 3, ปลายทาง, 2, 5)จะคัดลอก 5 องค์ประกอบจากต้นทางไปยังปลายทางโดยเริ่มจากดัชนีที่ 3 ของแหล่งที่มาไปยังดัชนีที่ 2 ของปลายทาง

Arrays.copyOf ():หากคุณต้องการคัดลอกองค์ประกอบสองสามตัวแรกของอาร์เรย์หรือสำเนาทั้งหมดของอาร์เรย์คุณสามารถใช้วิธีนี้ได้ เห็นได้ชัดว่ามันไม่หลากหลายเช่น System.arraycopy () แต่ก็ไม่สับสนและใช้งานง่าย

Arrays.copyOfRange () : หากคุณต้องการให้องค์ประกอบของอาเรย์ถูกคัดลอกโดยที่ดัชนีเริ่มต้นไม่ใช่ 0 คุณสามารถใช้วิธีนี้เพื่อคัดลอกอาเรย์บางส่วน


35

ฉันมีความรู้สึกว่า "วิธีที่ดีกว่าในการคัดลอกอาเรย์ทั้งหมด" เหล่านี้ไม่ได้ช่วยแก้ปัญหาของคุณ

คุณพูด

ฉันลองใช้ for loop เช่น [... ] แต่ดูเหมือนว่าจะไม่ทำงานอย่างถูกต้องหรือไม่

ดูที่วงนั้นไม่มีเหตุผลที่ชัดเจนว่าจะไม่ทำงาน ... เว้นแต่:

  • คุณมีaและbอาร์เรย์ยุ่งเหยิงอย่างใด(เช่นaและbอ้างถึงอาร์เรย์เดียวกัน) หรือ
  • แอปพลิเคชันของคุณเป็นแบบมัลติเธรดและเธรดต่างกันกำลังอ่านและอัปเดตaอาร์เรย์พร้อมกัน

ไม่ว่าในกรณีใดวิธีการทำสำเนาทางเลือกจะไม่ช่วยแก้ปัญหาพื้นฐาน

การแก้ไขสำหรับสถานการณ์แรกนั้นชัดเจน สำหรับสถานการณ์ที่สองคุณจะต้องหาวิธีการซิงโครไนซ์เธรด คลาสอาเรย์ของอะตอมไม่ได้ช่วยเพราะมันไม่มีตัวสร้างสำเนาของอะตอมหรือวิธีการโคลน แต่การซิงโครไนซ์โดยใช้ mutex ดั้งเดิมจะทำการหลอก

(มีคำแนะนำในคำถามของคุณที่ทำให้ฉันคิดว่ามันเกี่ยวข้องกับหัวข้อจริง ๆ เช่นคำพูดของคุณที่aเปลี่ยนแปลงอยู่ตลอดเวลา)


2
ตกลง .. อาจเป็นจริง
MeBigFatGuy


9

วิธีการแก้ปัญหาทั้งหมดที่โทรมาจากอาเรย์เพิ่มตัวอย่างโค้ด null checkersconsider ที่ซ้ำซ้อนของคุณ:

int[] a = {1,2,3,4,5};
int[] b = Arrays.copyOf(a, a.length);
int[] c = a.clone();

//What if array a comes as local parameter? You need to use null check:

public void someMethod(int[] a) {
    if (a!=null) {
        int[] b = Arrays.copyOf(a, a.length);
        int[] c = a.clone();
    }
}

ฉันขอแนะนำให้คุณอย่าประดิษฐ์วงล้อและใช้คลาสยูทิลิตี้ที่การตรวจสอบที่จำเป็นทั้งหมดได้ดำเนินการไปแล้ว พิจารณา ArrayUtils จาก apache ทั่วไป รหัสของคุณสั้นลง:

public void someMethod(int[] a) {
    int[] b = ArrayUtils.clone(a);
}

คอมมอนส์ Apache คุณสามารถหามี


8

Arrays.copyOfRangeนอกจากนี้คุณยังสามารถใช้

ตัวอย่าง :

public static void main(String[] args) {
    int[] a = {1,2,3};
    int[] b = Arrays.copyOfRange(a, 0, a.length);
    a[0] = 5;
    System.out.println(Arrays.toString(a)); // [5,2,3]
    System.out.println(Arrays.toString(b)); // [1,2,3]
}

วิธีนี้คล้ายกับArrays.copyOfแต่มีความยืดหยุ่นมากกว่า ทั้งคู่ใช้System.arraycopyภายใต้ประทุน

ดู :


3

สำหรับการคัดลอกอาเรย์ที่มีค่าไม่เป็นโมฆะคุณสามารถใช้ทางเลือกพร้อมกับObject.clone()วิธีการที่ให้ไว้ในคำตอบนี้

int[] arrayToCopy = {1, 2, 3};
int[] copiedArray = Optional.ofNullable(arrayToCopy).map(int[]::clone).orElse(null);

แม้จะมีความจริงที่ว่าวิธีนี้มีความซับซ้อนมากกว่าก็ยังแนะนำหน่วยความจำเสียและถ้าอาร์เรย์มีความลับ (เช่นไบต์อาร์เรย์ด้วยรหัสผ่าน) ก็ยังแนะนำข้อบกพร่องด้านความปลอดภัยเพราะวัตถุกลางจะอยู่ในกองจนกว่าจะเก็บขยะอาจ เพื่อโจมตี
Weltraumschaf

1
ฉันไม่เห็นด้วยกับอาร์เรย์ที่อยู่ในฮีปพิเศษสำหรับโครงสร้างนี้ แน่นอนมันเรียกโคลนเมื่อจำเป็นเท่านั้นและOptionalวัตถุเป็นเพียงวัตถุว่างเปล่าที่มีการอ้างอิงถึงอาร์เรย์ที่มีอยู่ เกี่ยวกับผลกระทบด้านประสิทธิภาพฉันจะบอกว่ามันเร็วเกินไปที่จะบอกว่าจริง ๆ แล้วมันเป็นผลกระทบเนื่องจากการสร้างประเภทนี้เป็นตัวเลือกที่ดีสำหรับการทำอินไลน์ภายใน JVM แล้วไม่ส่งผลกระทบมากกว่าวิธีอื่น ๆ มันเป็นเรื่องของรูปแบบ (ฟังก์ชั่นการเขียนโปรแกรมการทำงานกับการเขียนโปรแกรมตามขั้นตอน แต่ไม่เพียง แต่) ที่จะพิจารณาว่ามีความซับซ้อนมากขึ้นหรือไม่
Nicolas Henneaux

3

ถ้าคุณต้องทำงานกับอาร์เรย์ดิบและไม่ได้ArrayListแล้วArraysมีสิ่งที่คุณต้องการ หากคุณดูซอร์สโค้ดสิ่งเหล่านี้เป็นวิธีที่ดีที่สุดในการคัดลอกอาเรย์ พวกเขามีการเขียนโปรแกรมการป้องกันที่ดีเพราะSystem.arraycopy()วิธีการนี้มีข้อยกเว้นมากมายที่ไม่ได้ตรวจสอบหากคุณป้อนพารามิเตอร์ที่ไม่มีเหตุผล

คุณสามารถใช้อย่างใดอย่างหนึ่งArrays.copyOf()ซึ่งจะคัดลอกจากNthองค์ประกอบแรกไปยังอาร์เรย์ที่สั้นกว่าใหม่

public static <T> T[] copyOf(T[] original, int newLength)

คัดลอกอาร์เรย์ที่ระบุการตัดปลายหรือขยายด้วย nulls (ถ้าจำเป็น) ดังนั้นการคัดลอกจึงมีความยาวที่ระบุ สำหรับดัชนีทั้งหมดที่ใช้ได้ทั้งในอาร์เรย์ดั้งเดิมและสำเนาทั้งสองอาร์เรย์จะมีค่าเหมือนกัน สำหรับดัชนีใด ๆ ที่ใช้ได้ในการคัดลอก แต่ไม่ใช่ต้นฉบับการคัดลอกจะมีค่าเป็นศูนย์ ดัชนีดังกล่าวจะมีอยู่ถ้าหากความยาวที่ระบุนั้นมากกว่าความยาวของอาร์เรย์เดิม อาร์เรย์ที่ได้นั้นมีคลาสเดียวกันกับอาร์เรย์ดั้งเดิม

2770
2771    public static <T,U> T[] More ...copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
2772        T[] copy = ((Object)newType == (Object)Object[].class)
2773            ? (T[]) new Object[newLength]
2774            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
2775        System.arraycopy(original, 0, copy, 0,
2776                         Math.min(original.length, newLength));
2777        return copy;
2778    }

หรือArrays.copyOfRange()จะทำเคล็ดลับ:

public static <T> T[] copyOfRange(T[] original, int from, int to)

คัดลอกช่วงที่ระบุของอาร์เรย์ที่ระบุลงในอาร์เรย์ใหม่ ดัชนีเริ่มต้นของช่วง (จาก) จะต้องอยู่ระหว่างศูนย์และดั้งเดิมความยาวรวม ค่าที่ต้นฉบับ [จาก] ถูกวางลงในองค์ประกอบเริ่มต้นของการคัดลอก (ยกเว้นจาก == original.length หรือจาก == ถึง) ค่าจากองค์ประกอบที่ตามมาในอาร์เรย์เดิมจะถูกวางไว้ในองค์ประกอบที่ตามมาในการคัดลอก ดัชนีสุดท้ายของช่วง (ถึง) ซึ่งจะต้องมากกว่าหรือเท่ากับอาจสูงกว่าความยาวเดิมซึ่งในกรณีนี้ null จะถูกวางไว้ในองค์ประกอบทั้งหมดของสำเนาที่มีดัชนีมากกว่าหรือเท่ากับต้นฉบับ ความยาว - จาก ความยาวของอาร์เรย์ที่ส่งคืนจะเป็นไป - จาก อาร์เรย์ที่ได้นั้นมีคลาสเดียวกันกับอาร์เรย์ดั้งเดิม

3035    public static <T,U> T[] More ...copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {
3036        int newLength = to - from;
3037        if (newLength < 0)
3038            throw new IllegalArgumentException(from + " > " + to);
3039        T[] copy = ((Object)newType == (Object)Object[].class)
3040            ? (T[]) new Object[newLength]
3041            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
3042        System.arraycopy(original, from, copy, 0,
3043                         Math.min(original.length - from, newLength));
3044        return copy;
3045    }

อย่างที่คุณเห็นทั้งสองฟังก์ชั่นนี้เป็นเพียงแค่ wrapper function ที่System.arraycopyมีตรรกะการป้องกันซึ่งสิ่งที่คุณพยายามทำนั้นถูกต้อง

System.arraycopy เป็นวิธีที่เร็วที่สุดในการคัดลอกอาร์เรย์


0

ฉันมีปัญหาที่คล้ายกันกับอาร์เรย์ 2 มิติและสิ้นสุดที่นี่ ฉันกำลังคัดลอกอาร์เรย์หลักและเปลี่ยนค่าอาร์เรย์ภายในและรู้สึกประหลาดใจเมื่อค่าเปลี่ยนแปลงในทั้งสองสำเนา โดยพื้นฐานแล้วสำเนาทั้งสองนั้นเป็นอิสระ แต่มีการอ้างอิงไปยังอาร์เรย์ภายในเดียวกันและฉันต้องทำสำเนาอาร์เรย์ภายในเพื่อให้ได้สิ่งที่ฉันต้องการ

มันอาจไม่ใช่ปัญหาของ OP แต่ฉันหวังว่ามันจะเป็นประโยชน์ได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.