หยิบเซกเมนต์ของอาร์เรย์ใน Java โดยไม่ต้องสร้างอาร์เรย์ใหม่บนฮีป


181

ฉันกำลังมองหาวิธีใน Java ที่จะกลับส่วนของอาร์เรย์ ตัวอย่างจะได้รับอาร์เรย์ไบต์ที่มีไบต์ที่ 4 และ 5 ของอาร์เรย์ไบต์ ฉันไม่ต้องการสร้างอาร์เรย์ไบต์ใหม่ในหน่วยความจำฮีปเพื่อทำเช่นนั้น ตอนนี้ฉันมีรหัสต่อไปนี้:

doSomethingWithTwoBytes(byte[] twoByteArray);

void someMethod(byte[] bigArray)
{
      byte[] x = {bigArray[4], bigArray[5]};
      doSomethingWithTwoBytes(x);
}

ฉันต้องการทราบว่ามีวิธีที่จะทำdoSomething(bigArray.getSubArray(4, 2))โดยที่ 4 คือออฟเซ็ตและ 2 คือความยาวตัวอย่างเช่น


1
แล้วการทำเวทมนตร์ JNI ใน C ++ ล่ะ? อาจเป็นหายนะจาก GC POV หรือไม่
AlikElzin-kilaka

มันต้องเป็นอาร์เรย์ของไบต์ดั้งเดิมหรือไม่?
MP Korstanje

คำตอบ:


185

คำปฏิเสธ: คำตอบนี้ไม่สอดคล้องกับข้อ จำกัด ของคำถาม:

ฉันไม่ต้องการสร้างอาร์เรย์ไบต์ใหม่ในหน่วยความจำฮีปเพื่อทำเช่นนั้น

( สุจริตฉันรู้สึกว่าคำตอบของฉันมีค่าควรถูกลบคำตอบโดย @ unique72 ถูกต้อง Imma ให้การแก้ไขนี้นั่งสักครู่แล้วฉันจะลบคำตอบนี้ )


ฉันไม่รู้วิธีที่จะทำโดยตรงกับอาร์เรย์โดยไม่มีการจัดสรรฮีปเพิ่มเติม แต่คำตอบอื่น ๆ ที่ใช้ wrapper รายการย่อยมีการจัดสรรเพิ่มเติมสำหรับ wrapper เท่านั้น - แต่ไม่ใช่อาเรย์ - ซึ่งจะเป็นประโยชน์ในกรณีของ อาร์เรย์ขนาดใหญ่

ที่กล่าวว่าหากมีใครกำลังมองหาช่วงเวลาสั้น ๆ วิธีการอรรถประโยชน์Arrays.copyOfRange()ถูกนำมาใช้ใน Java 6 (ปลายปี 2006?):

byte [] a = new byte [] {0, 1, 2, 3, 4, 5, 6, 7};

// get a[4], a[5]

byte [] subArray = Arrays.copyOfRange(a, 4, 6);

10
สิ่งนี้ยังคงจัดสรรเซ็กเมนต์หน่วยความจำใหม่แบบไดนามิกและคัดลอกช่วงนั้น
Dan

4
ขอบคุณแดน - ฉันละเลย OP copyOfRangeที่ไม่ได้ต้องการที่จะสร้างแถวใหม่และฉันไม่ได้มองไปที่การดำเนินงานของ ถ้ามันเป็นโอเพนซอร์ซบางทีมันอาจผ่านไปได้ :)
David J. Liszewski

7
ฉันคิดว่าผู้คนจำนวนมากต้องการสร้างอาเรย์ย่อยจากอาเรย์และไม่ต้องกังวลว่ามันจะใช้หน่วยความจำเพิ่มขึ้นบ้าง พวกเขาเจอคำถามนี้และรับคำตอบที่ต้องการดังนั้นโปรดอย่าลบเนื่องจากมันมีประโยชน์ - ฉันคิดว่าไม่เป็นไร
Lonely Coder

2
ในความเป็นจริง copyOfRange ยังคงจัดสรรเซ็กเมนต์หน่วยความจำใหม่
Kevingo Tsai

167

Arrays.asList(myArray)มอบหมายให้ใหม่ArrayList(myArray)ซึ่งไม่ได้คัดลอกอาร์เรย์ แต่เพียงเก็บข้อมูลอ้างอิง การใช้List.subList(start, end)หลังจากนั้นจะทำให้ a SubListซึ่งเพิ่งอ้างอิงรายการเดิม (ซึ่งยังคงเพียงแค่อ้างอิงอาร์เรย์) ไม่มีการคัดลอกอาเรย์หรือเนื้อหาเพียงแค่การสร้าง wrapper และรายชื่อทั้งหมดที่เกี่ยวข้องจะได้รับการสนับสนุนโดยอาเรย์ดั้งเดิม (ฉันคิดว่ามันจะหนักกว่านี้)


9
ชี้แจงก็มอบหมายให้ชั้นเอกชนในการArraysเรียกพลุกพล่านArrayListแต่ที่จริงๆเป็นListรอบอาร์เรย์เมื่อเทียบกับการjava.util.ArrayListที่จะทำสำเนา ไม่มีการจัดสรรใหม่ (จากเนื้อหาของรายการ) และไม่มีการขึ้นต่อกันของบุคคลที่สาม ฉันเชื่อว่านี่เป็นคำตอบที่ถูกต้องที่สุด
dimo414

28
ที่จริงแล้วมันจะไม่ทำงานสำหรับอาร์เรย์แบบดั้งเดิมตามที่ OP ต้องการ ( byte[]ในกรณีของเขา) สิ่งที่คุณจะได้รับList<byte[]>คือ และการเปลี่ยนbyte[] bigArrayไปใช้Byte[] bigArrayอาจทำให้โอเวอร์เฮดของหน่วยความจำมีความสำคัญ
Dmitry Avtonomov

2
วิธีเดียวที่จะบรรลุสิ่งที่ต้องการอย่างแท้จริงคือผ่านsun.misc.Unsafeชั้นเรียน
Dmitry Avtonomov

39

หากคุณกำลังมองหาแนวทางนามแฝงของรูปแบบตัวชี้เพื่อให้คุณไม่จำเป็นต้องจัดสรรพื้นที่และคัดลอกข้อมูลจากนั้นฉันเชื่อว่าคุณไม่มีโชค

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


3
ใช่ฉันหวังว่าจะมีวิธีชี้บางอย่างเนื่องจากฉันไม่ต้องการจัดสรรหน่วยความจำแบบไดนามิก แต่ดูเหมือนว่าเป็นสิ่งที่ฉันจะต้องทำ
jbu

1
ดังที่ @ unique72 แนะนำดูเหมือนว่าจะมีวิธีการทำสิ่งที่คุณต้องการโดยการใช้ประโยชน์จากรายละเอียดปลีกย่อยในการใช้งานรายการ java / array หลายประเภท ดูเหมือนว่าจะเป็นไปได้ แต่ไม่ใช่ในลักษณะที่ชัดเจนและนั่นทำให้ฉันลังเลที่จะพึ่งพามันมากเกินไป ...
Andrew

เหตุใดจึงควรarray*copy*()ใช้หน่วยความจำเดียวกันซ้ำ ไม่ตรงข้ามกับสิ่งที่ผู้โทรคาดหวังหรือไม่
Patrick Favre

23

วิธีหนึ่งคือการล้อมอาร์เรย์ใน java.nio.ByteBufferใช้ฟังก์ชั่นใส่ / รับที่แน่นอนและแบ่งบัฟเฟอร์เพื่อทำงานใน subarray

ตัวอย่างเช่น

doSomething(ByteBuffer twoBytes) {
    byte b1 = twoBytes.get(0);
    byte b2 = twoBytes.get(1);
    ...
}

void someMethod(byte[] bigArray) {
      int offset = 4;
      int length = 2;
      doSomething(ByteBuffer.wrap(bigArray, offset, length).slice());
}

โปรดทราบว่าคุณต้องโทรหาทั้งคู่wrap()และslice()เนื่องจากwrap()ตัวมันเองจะมีผลกับฟังก์ชั่นการใส่ / รับแบบสัมพันธ์เท่านั้นไม่ใช่แบบสัมบูรณ์

ByteBuffer อาจเป็นเรื่องยากที่จะเข้าใจ แต่มีแนวโน้มที่จะนำไปใช้อย่างมีประสิทธิภาพและคุ้มค่ากับการเรียนรู้


1
นอกจากนี้ยังเป็นที่น่าสังเกตว่าวัตถุ ByteBuffer สามารถถอดรหัสธรรมได้อย่างง่ายดาย:StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buffer, 0, readBytes))
skeryl

@Soulman ขอบคุณสำหรับคำอธิบาย แต่คำถามหนึ่งมันมีประสิทธิภาพมากกว่าการใช้ Arrays.copyOfRange?
ucMedia

1
@ucMedia สำหรับอาร์เรย์สองไบต์Arrays.copyOfRangeน่าจะมีประสิทธิภาพมากกว่า โดยทั่วไปคุณจะต้องวัดกรณีการใช้งานเฉพาะของคุณ
Soulman

20

ใช้ java.nio.Buffer's มันเป็นเสื้อคลุมที่มีน้ำหนักเบาสำหรับบัฟเฟอร์ประเภทต่าง ๆ แบบดั้งเดิมและช่วยจัดการการแบ่งตำแหน่งการแปลงการสั่งไบต์เป็นต้น

หากไบต์ของคุณมาจากสตรีม NIO Buffers สามารถใช้ "โหมดตรง" ซึ่งสร้างบัฟเฟอร์ที่สำรองไว้โดยทรัพยากรดั้งเดิม สิ่งนี้สามารถปรับปรุงประสิทธิภาพได้ในหลายกรณี


14

คุณสามารถใช้ArrayUtils.subarrayใน apache คอมมอนส์ ไม่สมบูรณ์แบบ แต่ใช้งานง่ายกว่าSystem.arraycopy. ข้อเสียเล็กน้อยคือมันช่วยแนะนำการพึ่งพาอื่นให้กับโค้ดของคุณ


23
มันเหมือนกับ Arrays.copyOfRange () ใน Java 1.6
newacct

10

ฉันเห็นคำตอบย่อยรายการอยู่ที่นี่แล้ว แต่นี่คือรหัสที่แสดงให้เห็นว่ามันเป็นรายการย่อยจริงไม่ใช่สำเนา:

public class SubListTest extends TestCase {
    public void testSubarray() throws Exception {
        Integer[] array = {1, 2, 3, 4, 5};
        List<Integer> list = Arrays.asList(array);
        List<Integer> subList = list.subList(2, 4);
        assertEquals(2, subList.size());
        assertEquals((Integer) 3, subList.get(0));
        list.set(2, 7);
        assertEquals((Integer) 7, subList.get(0));
    }
}

ฉันไม่เชื่อว่ามีวิธีที่ดีในการทำเช่นนี้กับอาร์เรย์โดยตรง



7

Lists ช่วยให้คุณสามารถใช้งานและการทำงานร่วมกับsubListบางสิ่งบางอย่างโปร่งใส อะเรย์ดั้งเดิมจะทำให้คุณต้องติดตามการ จำกัด ออฟเซ็ทบางอย่าง ByteBufferมีตัวเลือกที่คล้ายกันตามที่ฉันได้ยิน

แก้ไข: หากคุณรับผิดชอบวิธีการที่มีประโยชน์คุณสามารถกำหนดด้วยขอบเขต (เช่นเดียวกับวิธีการที่เกี่ยวข้องกับอาร์เรย์ใน java เอง:

doUseful(byte[] arr, int start, int len) {
    // implementation here
}
doUseful(byte[] arr) {
    doUseful(arr, 0, arr.length);
}

ยังไม่ชัดเจน แต่ถ้าคุณทำงานกับองค์ประกอบของตัวเองเช่นคุณคำนวณบางอย่างและเขียนผลลัพธ์กลับมา?


6

ทางเลือกหนึ่งคือการส่งผ่านอาร์เรย์ทั้งหมดและดัชนีเริ่มต้นและจุดสิ้นสุดและวนซ้ำระหว่างค่าเหล่านั้นแทนที่จะวนซ้ำทั้งแถวที่ผ่านไป

void method1(byte[] array) {
    method2(array,4,5);
}
void method2(byte[] smallarray,int start,int end) {
    for ( int i = start; i <= end; i++ ) {
        ....
    }
}

6

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

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

ดังนั้นกลับไปที่จาวาคุณไม่สามารถทำได้ ดังที่ได้กล่าวไว้ NIO ByteBufferช่วยให้คุณสามารถห่ออาร์เรย์แล้วตัดมันได้ แต่ให้ส่วนต่อประสานที่ไม่สะดวก แน่นอนคุณสามารถคัดลอกซึ่งอาจเร็วกว่าที่คุณคิด คุณสามารถแนะนำStringสิ่งที่เป็นนามธรรมของคุณเองที่ช่วยให้คุณสามารถแบ่งอาเรย์ (การใช้งาน Sun ปัจจุบันของStringมีการchar[]อ้างอิงรวมทั้งการชดเชยเริ่มต้นและความยาวการดำเนินงานที่สูงขึ้นมีเพียงchar[]) byte[]อยู่ในระดับต่ำ แต่สิ่งที่เป็นนามธรรมของคลาสที่คุณใส่ลงไปนั้นจะทำให้เกิดความวุ่นวายของไวยากรณ์จนกระทั่ง JDK7 (อาจ)


ขอบคุณที่อธิบายว่าทำไมมันจึงเป็นไปไม่ได้ Btw ตอนนี้ String คัดลอกลงsubstringใน HotSpot (ลืมว่าบิลด์ใดเปลี่ยนแปลงสิ่งนี้) ทำไมคุณถึงบอกว่า JDK7 จะอนุญาตให้ใช้ไวยากรณ์ได้ดีกว่า ByteBuffer?
Aleksandr Dubinsky

@AleksandrDubinsky ในขณะที่เขียนมันดูเหมือน Java SE 7 กำลังจะอนุญาตให้อาร์เรย์[]สัญกรณ์เกี่ยวกับประเภทที่ผู้ใช้กำหนดเช่นและList ByteBufferยังรออยู่ ...
Tom Hawtin - tackline

2

@ unique72 คำตอบเป็นฟังก์ชั่นหรือสายอย่างง่ายคุณอาจจำเป็นต้องแทนที่วัตถุด้วยประเภทชั้นเรียนที่คุณต้องการ 'ชิ้น' มีให้เลือกสองแบบเพื่อให้เหมาะกับความต้องการที่หลากหลาย

/// Extract out array from starting position onwards
public static Object[] sliceArray( Object[] inArr, int startPos ) {
    return Arrays.asList(inArr).subList(startPos, inArr.length).toArray();
}

/// Extract out array from starting position to ending position
public static Object[] sliceArray( Object[] inArr, int startPos, int endPos ) {
    return Arrays.asList(inArr).subList(startPos, endPos).toArray();
}

1

แล้วListเสื้อคลุมบาง ๆล่ะ?

List<Byte> getSubArrayList(byte[] array, int offset, int size) {
   return new AbstractList<Byte>() {
      Byte get(int index) {
         if (index < 0 || index >= size) 
           throw new IndexOutOfBoundsException();
         return array[offset+index];
      }
      int size() {
         return size;
      }
   };
}

(ยังไม่ทดลอง)


สิ่งนี้จะเกิดการทะเลาะวิวาทกันของไบต์ อาจจะช้า
MP Korstanje

@mpkorstanje: ในByteวัตถุไลบรารี Orable Java สำหรับbyteค่าทั้งหมดจะถูกแคช ดังนั้นค่าใช้จ่ายในการชกมวยควรจะค่อนข้างช้า
Lii

1

ฉันต้องการวนซ้ำในตอนท้ายของอาร์เรย์และไม่ต้องการคัดลอกอาร์เรย์ วิธีการของฉันคือการทำให้ Iterable อยู่ในอาร์เรย์

public static Iterable<String> sliceArray(final String[] array, 
                                          final int start) {
  return new Iterable<String>() {
    String[] values = array;
    int posn = start;

    @Override
    public Iterator<String> iterator() {
      return new Iterator<String>() {
        @Override
        public boolean hasNext() {
          return posn < values.length;
        }

        @Override
        public String next() {
          return values[posn++];
        }

        @Override
        public void remove() {
          throw new UnsupportedOperationException("No remove");
        }
      };
    }
  };
}

-1

นี่คือน้ำหนักเบากว่า Arrays.copyOfRange เล็กน้อย - ไม่มีช่วงหรือค่าลบ

public static final byte[] copy(byte[] data, int pos, int length )
{
    byte[] transplant = new byte[length];

    System.arraycopy(data, pos, transplant, 0, length);

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