SpzzyArray vs HashMap


177

Tôi có thể nghĩ ra một số lý do tại sao HashMaps với các khóa nguyên tốt hơn nhiều so với SparseArrays:

  1. Tài liệu Android cho SparseArraybiết "Nó thường chậm hơn truyền thống HashMap".
  2. Nếu bạn viết mã bằng HashMaps thay vì SparseArrays, mã của bạn sẽ hoạt động với các triển khai Bản đồ khác và bạn sẽ có thể sử dụng tất cả các API Java được thiết kế cho Bản đồ.
  3. Nếu bạn viết mã bằng HashMaps chứ không phải SparseArrays, mã của bạn sẽ hoạt động trong các dự án không phải là Android.
  4. Bản đồ ghi đè equals()hashCode()trong khi SparseArraykhông.

Tuy nhiên, bất cứ khi nào tôi cố gắng sử dụng một HashMapphím số nguyên trong một dự án Android, IntelliJ nói với tôi rằng tôi nên sử dụng một SparseArraythay thế. Tôi thấy điều này thực sự khó hiểu. Có ai biết bất kỳ lý do thuyết phục cho việc sử dụng SparseArrays?

Câu trả lời:


235

SparseArraycó thể được sử dụng để thay thế HashMapkhi khóa là loại nguyên thủy. Có một số biến thể cho các loại khóa / giá trị khác nhau, mặc dù không phải tất cả chúng đều có sẵn công khai.

Lợi ích là:

  • Phân bổ miễn phí
  • Không có quyền anh

Hạn chế:

  • Nói chung là chậm hơn, không được chỉ định cho các bộ sưu tập lớn
  • Họ sẽ không làm việc trong một dự án không phải Android

HashMap có thể được thay thế bằng cách sau:

SparseArray          <Integer, Object>
SparseBooleanArray   <Integer, Boolean>
SparseIntArray       <Integer, Integer>
SparseLongArray      <Integer, Long>
LongSparseArray      <Long, Object>
LongSparseLongArray  <Long, Long>   //this is not a public class                                 
                                    //but can be copied from  Android source code 

Về bộ nhớ, đây là một ví dụ về SparseIntArrayvs HashMap<Integer, Integer>cho 1000 phần tử:

SparseIntArray:

class SparseIntArray {
    int[] keys;
    int[] values;
    int size;
}

Class = 12 + 3 * 4 = 24 byte
Mảng = 20 + 1000 * 4 = 4024 byte
Tổng cộng = 8,072 byte

HashMap:

class HashMap<K, V> {
    Entry<K, V>[] table;
    Entry<K, V> forNull;
    int size;
    int modCount;
    int threshold;
    Set<K> keys
    Set<Entry<K, V>> entries;
    Collection<V> values;
}

Class = 12 + 8 * 4 = 48 byte
Entry = 32 + 16 + 16 = 64 byte
Array = 20 + 1000 * 64 = 64024 byte
Tổng cộng = 64.136 byte

Nguồn: Ký ức Android của Romain Guy từ slide 90.

Các số ở trên là dung lượng bộ nhớ (tính bằng byte) được phân bổ trên heap bởi JVM. Chúng có thể thay đổi tùy thuộc vào JVM cụ thể được sử dụng.

Các java.lang.instrumentgói chứa một số phương pháp hữu ích cho các hoạt động tiên tiến như kiểm tra kích thước của một đối tượng với getObjectSize(Object objectToSize).

Thông tin thêm có sẵn từ tài liệu chính thức của Oracle .

Class = 12 byte + (n biến thể hiện) * 4 byte
Array = 20 byte + (n phần tử) * (kích thước phần tử)
Entry = 32 byte + (kích thước phần tử thứ 1) + (kích thước phần tử thứ 2)


15
Ai đó có thể hướng dẫn cho tôi nơi "12 + 3 * 4" và "20 + 1000 * 4" đến từ đâu không?
Mary Paździoch

5
@ MarianPaździoch, anh ấy đã trình bày một bài thuyết trình ( loadeck.com/romainguy/android-memories ) trong đó một lớp chiếm 12 byte + 3 biến 4 byte, một mảng (tham chiếu) chiếm 20 byte (dlmalloc - 4, đối tượng - 8, chiều rộng & đệm - số 8).
CoolMind

1
Đối với bản ghi, một nhược điểm quan trọng khác của SpzzyArray là với tư cách là một đối tượng Android, nó cần phải được chế giễu để thử nghiệm đơn vị. Bây giờ tôi có thể sử dụng các đối tượng của Java để đơn giản hóa việc kiểm tra.
David G

@DavidG Bạn chỉ có thể sử dụng plugin unmock để giả định phụ thuộc Android.
bão tuyết

1
Ngay cả khi bạn không làm Android, việc sao chép lớp vào dự án của bạn không khó, nó chỉ phụ thuộc vào 3 lớp khác. Giấy phép APL có nghĩa là bạn có thể làm điều đó, bất kể giấy phép nào bạn đang làm việc.
Yann TM

35

Tôi đến đây chỉ muốn một ví dụ về cách sử dụng SparseArray. Đây là một câu trả lời bổ sung cho điều đó.

Tạo một SpzzyArray

SparseArray<String> sparseArray = new SparseArray<>();

Một SparseArraysố nguyên bản đồ cho một số Object, vì vậy bạn có thể thay thế Stringtrong ví dụ trên bằng bất kỳ cái nào khác Object. Nếu bạn đang ánh xạ số nguyên sang số nguyên thì hãy sử dụng SparseIntArray.

Thêm hoặc cập nhật các mục

Sử dụng put(hoặc append) để thêm các phần tử vào mảng.

sparseArray.put(10, "horse");
sparseArray.put(3, "cow");
sparseArray.put(1, "camel");
sparseArray.put(99, "sheep");
sparseArray.put(30, "goat");
sparseArray.put(17, "pig");

Lưu ý rằng các intphím không cần phải theo thứ tự. Điều này cũng có thể được sử dụng để thay đổi giá trị tại một intkhóa cụ thể .

Xóa các mục

Sử dụng remove(hoặc delete) để loại bỏ các phần tử khỏi mảng.

sparseArray.remove(17); // "pig" removed

Các inttham số là chìa khóa số nguyên.

Tra cứu giá trị cho khóa int

Sử dụng getđể lấy giá trị cho một số khóa nguyên.

String someAnimal = sparseArray.get(99);  // "sheep"
String anotherAnimal = sparseArray.get(200); // null

Bạn có thể sử dụng get(int key, E valueIfKeyNotFound)nếu bạn muốn tránh bị nullmất chìa khóa.

Lặp lại các mục

Bạn có thể sử dụng keyAtvalueAtmột số chỉ mục để lặp qua bộ sưu tập vì SparseArrayduy trì một chỉ mục riêng biệt khác với các intkhóa.

int size = sparseArray.size();
for (int i = 0; i < size; i++) {

    int key = sparseArray.keyAt(i);
    String value = sparseArray.valueAt(i);

    Log.i("TAG", "key: " + key + " value: " + value);
}

// key: 1 value: camel
// key: 3 value: cow
// key: 10 value: horse
// key: 30 value: goat
// key: 99 value: sheep

Lưu ý rằng các khóa được sắp xếp theo giá trị tăng dần, không theo thứ tự mà chúng được thêm vào.


18

Tuy nhiên, bất cứ khi nào tôi cố gắng sử dụng HashMap với các khóa số nguyên trong một dự án Android, intelliJ nói với tôi rằng tôi nên sử dụng SpzzyArray thay thế.

Nó chỉ là một cảnh báo từ tài liệu này của mảng thưa thớt:

Nó được dự định là có hiệu quả bộ nhớ cao hơn so với sử dụng HashMap để ánh xạ các số nguyên sang các đối tượng

SparseArrayđược tạo ra để có hiệu quả bộ nhớ hơn so với sử dụng HashMap thông thường, điều đó không cho phép nhiều khoảng trống trong mảng không giống như HashMap. Không có gì phải lo lắng về điều đó, bạn có thể sử dụng HashMap truyền thống nếu bạn không muốn lo lắng về việc cấp phát bộ nhớ cho thiết bị.


5
Các điểm về lưu bộ nhớ rõ ràng là hợp lệ, nhưng tôi chưa bao giờ hiểu tại sao Android không thể tạo ra SpzzyArray <T> thực hiện Bản đồ <Integer, T> để bạn có được triển khai Bản đồ hiệu quả về bộ nhớ - tốt nhất trong cả hai thế giới.
Paul Boddington

3
@PaulBoddington cũng nhớ SparseArrayngăn số nguyên chính là hộp Tự động, đây là một hoạt động khác và hiệu suất chi phí. thay vì Map, nó sẽ tự động hộp số nguyên choInteger
Rod_Algonquin

Cũng đúng, nhưng nếu họ đã quá tải phương thức đặt bằng cách bao gồm một phương thức đặt chữ ký (int a, T t), bạn vẫn có thể đặt các cặp giá trị khóa vào bản đồ mà không cần khóa tự động. Tôi chỉ nghĩ rằng Bộ sưu tập Khung rất mạnh (một trong những lý do tốt nhất để sử dụng Java) đến nỗi thật điên rồ khi không tận dụng lợi thế của nó.
Paul Boddington

6
@PaulBoddington Bộ sưu tập dựa trên các đối tượng không dựa trên nguyên thủy nên nó sẽ không hoạt động trong Bộ sưu tập API
Rod_Algonquin

10

Một mảng thưa thớt trong Java là một cấu trúc dữ liệu ánh xạ các khóa tới các giá trị. Cùng một ý tưởng như một Bản đồ, nhưng thực hiện khác nhau:

  1. Bản đồ được thể hiện bên trong dưới dạng một mảng các danh sách, trong đó mỗi phần tử trong các danh sách này là một cặp khóa, giá trị. Cả khóa và giá trị là các thể hiện đối tượng.

  2. Một mảng thưa thớt chỉ đơn giản được tạo thành từ hai mảng: một mảng các khóa (nguyên thủy) và một mảng các giá trị (đối tượng). Có thể có những khoảng trống trong các chỉ số mảng này, do đó, thuật ngữ mảng thưa thớt.

Sự quan tâm chính của SpzzyArray là nó tiết kiệm bộ nhớ bằng cách sử dụng các nguyên hàm thay vì các đối tượng làm khóa.


10

Sau khi googling tôi cố gắng thêm một số thông tin vào anwers đã đăng:

Isaac Taylor đã thực hiện một so sánh hiệu suất cho SpzzyArrays và Hashmaps. Anh ấy khẳng định rằng

Hashmap và SpzzyArray rất giống nhau đối với kích thước cấu trúc dữ liệu dưới 1.000

khi kích thước đã được tăng lên 10.000 điểm [...] Hashmap có hiệu suất cao hơn khi thêm các đối tượng, trong khi SpzzyArray có hiệu suất cao hơn khi truy xuất các đối tượng. [...] Với kích thước 100.000 [...] Hashmap mất hiệu suất rất nhanh

Một so sánh trên Edgblog cho thấy SpzzyArray cần ít bộ nhớ hơn HashMap vì khóa nhỏ hơn (int vs Integer) và thực tế là

một phiên bản HashMap.Entry phải theo dõi các tham chiếu cho khóa, giá trị và mục tiếp theo. Thêm vào đó, nó cũng cần lưu trữ hàm băm của mục dưới dạng int.

Để kết luận tôi sẽ nói rằng sự khác biệt có thể quan trọng nếu bạn sẽ lưu trữ nhiều dữ liệu trong Bản đồ của mình. Nếu không, chỉ cần bỏ qua cảnh báo.


4

Tài liệu Android cho SpzzyArray nói rằng "Nó thường chậm hơn HashMap truyền thống".

Đúng rồi. Nhưng khi bạn chỉ có 10 hoặc 20 mục, sự khác biệt hiệu suất sẽ không đáng kể.

Nếu bạn viết mã bằng HashMaps chứ không phải SpzzyArrays, mã của bạn sẽ hoạt động với các triển khai Bản đồ khác và bạn sẽ có thể sử dụng tất cả các API java được thiết kế cho Bản đồ

Tôi nghĩ rằng hầu hết chúng ta chỉ sử dụng HashMapđể tìm kiếm một giá trị được liên kết với một khóa trong khi SparseArraythực sự tốt về điều này.

Nếu bạn viết mã bằng HashMaps chứ không phải SpzzyArrays, mã của bạn sẽ hoạt động trong các dự án không phải là Android.

Mã nguồn của SpzzyArray khá đơn giản và dễ hiểu để bạn chỉ mất ít công sức để chuyển nó sang các nền tảng khác (thông qua một BẢN SAO & Dán đơn giản).

Bản đồ ghi đè bằng () và hashCode () trong khi SpzzyArray không

Tất cả những gì tôi có thể nói là, (với hầu hết các nhà phát triển), những người quan tâm?

Một khía cạnh quan trọng khác SparseArraylà nó chỉ sử dụng một mảng để lưu trữ tất cả các phần tử trong khi HashMapsử dụng Entry, do đó SparseArraychi phí bộ nhớ ít hơn đáng kể so với a HashMap, xem điều này


1

Thật không may là trình biên dịch đưa ra một cảnh báo. Tôi đoán HashMap đã được sử dụng quá mức để lưu trữ các mặt hàng.

SpzzyArrays có vị trí của họ. Cho rằng họ sử dụng thuật toán tìm kiếm nhị phân để tìm giá trị trong một mảng bạn phải xem xét những gì bạn đang làm. Tìm kiếm nhị phân là O (log n) trong khi tra cứu băm là O (1). Điều này không nhất thiết có nghĩa là tìm kiếm nhị phân chậm hơn đối với một tập hợp dữ liệu nhất định. Tuy nhiên, khi số lượng mục tăng lên, sức mạnh của bảng băm sẽ thay thế. Do đó, các nhận xét có số lượng mục nhập thấp có thể bằng và có thể tốt hơn so với sử dụng HashMap.

HashMap chỉ tốt như hàm băm và cũng có thể bị ảnh hưởng bởi hệ số tải (tôi nghĩ trong các phiên bản sau họ bỏ qua hệ số tải để có thể tối ưu hóa tốt hơn). Họ cũng đã thêm một hàm băm thứ cấp để đảm bảo hàm băm tốt. Ngoài ra, lý do SpzzyArray hoạt động thực sự tốt cho tương đối ít mục (<100).

Tôi sẽ đề nghị rằng nếu bạn cần một bảng băm và muốn sử dụng bộ nhớ tốt hơn cho số nguyên nguyên (không có quyền anh tự động), v.v., hãy thử trove. ( http://trove.starlight-systems.com - Giấy phép LGPL). (Không liên kết với trove, giống như thư viện của họ)

Với tòa nhà đa dex đơn giản hóa, chúng tôi thậm chí không cần phải đóng gói lại trove cho những gì bạn cần. (trove có rất nhiều lớp)

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.