Sử dụng SparseArray để thay thế cho HashMap trong Android
Bài đăng này đã không được cập nhật trong 5 năm
Đặt vấn đề
Hôm trước trong khi code một project Android, mình chợt thấy Android Studio gợi ý mình nên chuyển từ HashMap sang SparseArray. Ban đầu mình không hiểu vì sao lại phải chuyển và chuyển như vậy có ích gì. Vì thế mình quyết định lên StackOverFlow tìm kiếm câu trả lời. Sau đó mình đọc được một topic của một bạn cũng đang thắc mắc giống như mình. Vì vậy mình chia sẻ lại để anh em cùng đọc để hiểu hơn nhé.
Câu hỏi
Tôi có thể kể ra vài lý do giúp các HashMap
với key có kiểu Integer
sẽ tốt hơn nhiều so với các SparseArray
:
- Tài liệu Android cho
SparseArray
nói rằng "Nó thường chậm hơnHashMap
truyền thống". - Nếu bạn viết code bằng
HashMap
chứ không phảiSparseArray
, mã của bạn sẽ hoạt động với các triển khai khác củaMap
và bạn sẽ có thể sử dụng tất cả các API Java được thiết kế choMap
. - Nếu bạn viết code bằng
HashMap
chứ không phảiSparseArray
, code của bạn sẽ hoạt động trong cả các dự án không phải là Android. Map
override các phương thứcequals()
vàhashCode()
trong khiSparseArray
thì không.
Tuy nhiên, bất cứ khi nào tôi cố gắng sử dụng HashMap
với các key có kiểu Integer
trong một dự án Android, IntelliJ luôn nói với tôi rằng tôi nên sử dụng SparseArray
thay 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 nào cho việc sử dụng SparseArray
không ?
Câu trả lời
SparseArray
có thể được dùng để thay thế cho HashMap
khi khoá là kiểu dữ liệu nguyên thuỷ. Có một vài biến thể cho những kiểu key/value khác, mặc dù không phải tất cả chúng đều có sẵn một cách công khai.
Lợi ích và hạn chế của SparseArray
Lợi ích của chúng là:
- Cấp phát tự do
- Không phải boxing
(*) Boxing hiểu đơn giản là việc đưa một giá trị vào bên trong một đối tượng (int -> Integer, long -> Long...)
Các hạn chế là:
- Thường sẽ chậm hơn, không dùng tốt cho các collection lớn
- Chúng không hoạt động trong các dự án ngoài Android.
Các SparseArray thay thế cho HashMap
HashMap
có thể được thay thế bởi SparseArray
như dưới đây:
HashMap | SparseArray |
---|---|
HashMap<Integer, Object> | SparseArray |
HashMap<Integer, Boolean> | SparseBooleanArray |
HashMap<Integer, Integer> | SparseIntArray |
HashMap<Integer, Long> | SparseLongArray |
HashMap<Long, Object> | LongSparseArray |
HashMap<Long, Long> | LongSparseLongArray |
(*) LongSparseLongArray được ẩn nhưng ta có thể đọc được source code.
So sánh kích thước SparseArray với HashMap
Về mặt bộ nhớ, đây là một ví dụ về SparseArray
so với HashMap<Integer, Integer>
cho 1000 phần tử:
SparseArray
class SparseIntArray {
int[] keys;
int[] values;
int size;
}
- Kích thước Class = 12 + 3 * 4 = 24 bytes
- Kích thước Array = 20 + 1000 * 4 = 4024 bytes (có 2 mảng)
- Tổng cộng = 8,072 bytes
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;
}
- Kích thước Class = 12 + 8 * 4 = 48 bytes
- Kích thước Entry = 32 + 16 + 16 = 64 bytes
- Kích thước Array = 20 + 1000 * 64 = 64024 bytes
- Tổng cộng = 64,136 bytes
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. Từ so sánh trên, ta dễ dàng thấy được SparseArray
đã giúp ta tiết kiệm gấp 8 lần bộ nhớ so với HashMap
.
Gói java.lang.instrument
chứa một số phương thức hữu ích cho các phép toán nâng cao như kiểm tra kích thước của một đối tượng bằng getObjectSize(Object objectToSize)
.
Cách tính kích thước bộ nhớ
- Kích thước Class = 12 bytes + (số lượng các biến thể hiện) * 4 bytes
- Kích thước Array = 20 bytes + (số lượng phần tử) * (kích thước mỗi phần tử)
- Kích thước Entry = 32 bytes + (kích thước phần tử đầu tiên) + (kích thước phần tử thứ 2)
Tổng kết
Đối với đa số trường hợp phải lưu một đối tượng chứa các ánh xạ mà kích thước không quá lớn trong Android, ta nên ưu tiên sử dụng SparseArray để tiết kiệm bộ nhớ. Hy vọng bài viết này sẽ hữu ích đối với các bạn.
All rights reserved