Lấy một đoạn của một mảng trong Java mà không tạo một mảng mới trên heap


181

Tôi đang tìm kiếm một phương thức trong Java sẽ trả về một phân đoạn của một mảng. Một ví dụ sẽ là lấy mảng byte chứa byte thứ 4 và thứ 5 của mảng byte. Tôi không muốn phải tạo một mảng byte mới trong bộ nhớ heap chỉ để làm điều đó. Ngay bây giờ tôi có mã sau đây:

doSomethingWithTwoBytes(byte[] twoByteArray);

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

Tôi muốn biết nếu có một cách để làm doSomething(bigArray.getSubArray(4, 2))trong đó 4 là phần bù và 2 là chiều dài chẳng hạn.


1
Còn về việc thực hiện một số phép thuật JNI trong C ++ thì sao? Có thể là một thảm họa từ POV GC?
AlikElzin-kilaka

Nó có phải là một mảng các byte nguyên thủy không?
MP Korstanje

Câu trả lời:


185

Tuyên bố miễn trừ trách nhiệm: Câu trả lời này không phù hợp với các ràng buộc của câu hỏi:

Tôi không muốn phải tạo một mảng byte mới trong bộ nhớ heap chỉ để làm điều đó.

( Thành thật mà nói, tôi cảm thấy câu trả lời của mình đáng bị xóa. Câu trả lời của @ unique72 là chính xác. Imma hãy để bản chỉnh sửa này ngồi một chút và sau đó tôi sẽ xóa câu trả lời này. )


Tôi không biết cách nào để thực hiện việc này trực tiếp với mảng mà không cần phân bổ heap bổ sung, nhưng các câu trả lời khác sử dụng trình bao bọc danh sách phụ chỉ có phân bổ bổ sung cho trình bao bọc - nhưng không phải là mảng - sẽ hữu ích trong trường hợp một mảng lớn.

Điều đó nói rằng, nếu một người đang tìm kiếm sự ngắn gọn, phương thức tiện ích Arrays.copyOfRange()đã được giới thiệu trong Java 6 (cuối năm 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
điều này vẫn tự động phân bổ một phân đoạn bộ nhớ mới và sao chép phạm vi vào đó.
Dan

4
Cảm ơn Dan - Tôi đã bỏ qua rằng OP không muốn tạo mảng mới và tôi đã không nhìn vào việc thực hiện copyOfRange. Nếu nó là nguồn đóng có lẽ nó đã vượt qua. :)
David J. Liszewski

7
Tôi nghĩ rằng nhiều người muốn tạo một mảng con từ một mảng và không phải lo lắng rằng nó sử dụng thêm một số bộ nhớ. Họ bắt gặp câu hỏi này và nhận được câu trả lời họ muốn - vì vậy xin đừng xóa vì nó hữu ích - tôi nghĩ điều đó ổn.
Bộ giải mã cô đơn

2
thực tế, copyOfRange vẫn phân bổ phân đoạn bộ nhớ mới
Kevingo Tsai

167

Arrays.asList(myArray)ủy quyền cho mới ArrayList(myArray), không sao chép mảng mà chỉ lưu trữ tham chiếu. Sử dụng List.subList(start, end)sau đó làm cho một SubListcái mà chỉ tham chiếu danh sách ban đầu (mà vẫn chỉ tham chiếu mảng). Không sao chép mảng hoặc nội dung của nó, chỉ tạo trình bao bọc và tất cả các danh sách liên quan đều được hỗ trợ bởi mảng ban đầu. (Tôi nghĩ nó sẽ nặng hơn.)


9
Để làm rõ, nó ủy nhiệm cho một lớp riêng Arraysđược gọi một cách khó hiểu ArrayList, nhưng thực sự là Listxung quanh một mảng, trái ngược với java.util.ArrayListđiều đó sẽ tạo ra một bản sao. Không phân bổ mới (trong nội dung của danh sách) và không phụ thuộc vào bên thứ ba. Đây là, tôi tin rằng, câu trả lời đúng nhất.
dimo414

28
Trên thực tế, điều này sẽ không hoạt động đối với các mảng kiểu nguyên thủy như OP muốn ( byte[]trong trường hợp của anh ta). Tất cả bạn sẽ nhận được List<byte[]>. Và thay đổi byte[] bigArrayđể Byte[] bigArraycó thể áp đặt một bộ nhớ đáng kể trên đầu.
Dmitry Avtonomov

2
Cách duy nhất để thực sự đạt được những gì mong muốn là thông qua sun.misc.Unsafelớp học.
Dmitry Avtonomov

39

Nếu bạn đang tìm kiếm một cách tiếp cận bí danh kiểu con trỏ, do đó bạn thậm chí không cần phân bổ không gian và sao chép dữ liệu thì tôi tin rằng bạn đã hết may mắn.

System.arraycopy() sẽ sao chép từ nguồn của bạn đến đích và hiệu quả được yêu cầu cho tiện ích này. Bạn cần phân bổ mảng đích.


3
vâng, tôi đã hy vọng cho một số loại phương pháp con trỏ vì tôi không muốn phân bổ động bộ nhớ. nhưng có vẻ như đó là những gì tôi sẽ phải làm.
jbu

1
Như @ unique72 gợi ý, dường như có nhiều cách để làm những gì bạn muốn bằng cách khai thác sự tinh tế trong việc thực hiện các loại danh sách / mảng java khác nhau. Điều này dường như là có thể, chỉ là không theo một cách rõ ràng và điều đó khiến tôi do dự khi dựa vào nó quá nhiều ...
Andrew

Tại sao nên array*copy*()sử dụng lại cùng một bộ nhớ? Đó không phải là điều hoàn toàn ngược lại với những gì người gọi mong đợi sao?
Patrick Favre

23

Một cách là bọc mảng trong java.nio.ByteBuffer , sử dụng các hàm put / get tuyệt đối và cắt bộ đệm để làm việc trên một phân đoạn.

Ví dụ:

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

Lưu ý rằng bạn phải gọi cả hai wrap()slice(), vì wrap()chính nó chỉ ảnh hưởng đến các hàm put / get tương đối, chứ không phải các hàm tuyệt đối.

ByteBuffer có thể hơi khó hiểu, nhưng rất có thể được thực hiện một cách hiệu quả và rất đáng để học hỏi.


1
Cũng đáng lưu ý rằng các đối tượng ByteBuffer có thể được giải mã khá dễ dàng:StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buffer, 0, readBytes))
skeryl

@Soulman cảm ơn vì lời giải thích, nhưng một câu hỏi là nó hiệu quả hơn sử dụng Arrays.copyOfRange?
ucMedia

1
@ucMedia cho một mảng hai byte, Arrays.copyOfRangecó lẽ hiệu quả hơn. Nói chung, bạn sẽ phải đo cho trường hợp sử dụng cụ thể của bạn.
Soulman

20

Sử dụng java.nio.Buffer. Đây là một trình bao bọc nhẹ dành cho bộ đệm thuộc nhiều kiểu nguyên thủy khác nhau và giúp quản lý việc cắt, vị trí, chuyển đổi, sắp xếp byte, v.v.

Nếu byte của bạn bắt nguồn từ Luồng, Bộ đệm NIO có thể sử dụng "chế độ trực tiếp" để tạo bộ đệm được hỗ trợ bởi tài nguyên bản địa. Điều này có thể cải thiện hiệu suất trong rất nhiều trường hợp.


14

Bạn có thể sử dụng ArrayUtils.subarray trong apache commons. Không hoàn hảo nhưng trực quan hơn một chút so với System.arraycopy. nhược điểm là nó giới thiệu một phụ thuộc khác vào mã của bạn.


23
Nó giống như Arrays.copyOfRange () trong Java 1.6
newacct

10

Tôi thấy câu trả lời của SubList đã có ở đây, nhưng đây là đoạn mã chứng minh rằng đó là một danh sách con thực sự, không phải là một bản sao:

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

Tuy nhiên, tôi không tin rằng có một cách tốt để làm điều này trực tiếp với mảng.


9
List.subList(int startIndex, int endIndex)

9
Trước tiên, bạn cần bọc Mảng dưới dạng Danh sách: Arrays.asList (...). Danh sách con (...);
camickr

6

Một tùy chọn sẽ là vượt qua toàn bộ mảng và các chỉ số bắt đầu và kết thúc, và lặp lại giữa các mảng thay vì lặp lại trên toàn bộ mảng được truyền.

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

6

Các Lists cho phép bạn sử dụng và làm việc với subListmột cái gì đó trong suốt. Mảng nguyên thủy sẽ yêu cầu bạn theo dõi một số loại giới hạn bù.ByteBuffers có các tùy chọn tương tự như tôi đã nghe.

Chỉnh sửa: Nếu bạn phụ trách phương thức hữu ích, bạn chỉ có thể định nghĩa nó bằng giới hạn (như được thực hiện trong nhiều phương thức liên quan đến mảng trong chính java:

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

Tuy nhiên, điều đó không rõ ràng nếu bạn tự làm việc với các phần tử mảng, ví dụ: bạn tính toán một cái gì đó và viết lại kết quả?


6

Các tham chiếu Java luôn trỏ đến một đối tượng. Đối tượng có một tiêu đề trong số những thứ khác xác định loại cụ thể (vì vậy phôi có thể thất bại vớiClassCastException ). Đối với các mảng, phần bắt đầu của đối tượng cũng bao gồm độ dài, dữ liệu sẽ xuất hiện ngay sau đó trong bộ nhớ (về mặt kỹ thuật, việc triển khai là tự do để làm những gì nó làm, nhưng nó sẽ không làm gì khác). Vì vậy, bạn không thể có một tham chiếu chỉ ra đâu đó thành một mảng.

Trong C con trỏ chỉ bất cứ nơi nào và bất cứ điều gì, và bạn có thể chỉ vào giữa một mảng. Nhưng bạn không thể sử dụng một cách an toàn hoặc tìm ra mảng dài bao nhiêu. Trong D, con trỏ chứa phần bù vào khối bộ nhớ và chiều dài (hoặc tương đương con trỏ đến cuối, tôi không thể nhớ việc thực hiện thực sự làm gì). Điều này cho phép D cắt các mảng. Trong C ++, bạn sẽ có hai vòng lặp trỏ đến điểm bắt đầu và kết thúc, nhưng C ++ hơi kỳ quặc như thế.

Vì vậy, quay trở lại Java, không có bạn không thể. Như đã đề cập, NIO ByteBuffercho phép bạn bọc một mảng và sau đó cắt nó, nhưng cung cấp một giao diện khó xử. Tất nhiên bạn có thể sao chép, có lẽ nhanh hơn rất nhiều so với bạn nghĩ. Bạn có thể giới thiệu sự Stringtrừu tượng giống như của riêng bạn , cho phép bạn cắt một mảng (triển khai Mặt trời hiện tại Stringchar[]tham chiếu cộng với độ lệch bắt đầu và độ dài, thực hiện hiệu suất cao hơn chỉ có char[]). byte[]ở mức độ thấp, nhưng bất kỳ sự trừu tượng hóa dựa trên lớp nào mà bạn đưa vào sẽ tạo ra một mớ hỗn độn khủng khiếp của cú pháp, cho đến khi JDK7 (có lẽ).


Cảm ơn đã giải thích lý do tại sao nó là không thể. Btw, String hiện sao chép trên substringHotSpot (quên bản dựng nào đã thay đổi cái này). Tại sao bạn nói rằng JDK7 sẽ cho phép cú pháp tốt hơn ByteBuffer?
Alexanderr Dubinsky

@AleksandrDubinsky Tại thời điểm viết, có vẻ như Java SE 7 sẽ cho phép []ký hiệu mảng trên các loại do người dùng định nghĩa, chẳng hạn như ListByteBuffer. Vẫn đang chờ ...
Tom Hawtin - tackline

2

@ unique72 trả lời dưới dạng một hàm hoặc dòng đơn giản, bạn có thể cần thay thế Object, với loại lớp tương ứng mà bạn muốn 'cắt'. Hai biến thể được đưa ra để phù hợp với nhu cầu khác nhau.

/// 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

Làm thế nào về một Listbọc mỏng ?

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

(Chưa được kiểm tra)


Điều này sẽ phát sinh quyền anh-unboxing byte. Có thể chậm.
MP Korstanje

@mpkorstanje: Trong các Byteđối tượng thư viện Java có thể chịu được cho tất cả các bytegiá trị được lưu trữ. Vì vậy, quyền anh trên đầu nên khá chậm.
Lii

1

Tôi cần lặp lại đến hết một mảng và không muốn sao chép mảng đó. Cách tiếp cận của tôi là tạo ra một Iterable trên mảng.

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

Đây là một chút nhẹ hơn Arrays.copyOfRange - không có phạm vi hoặc tiêu cực

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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.