SparseArray กับ HashMap


177

ฉันคิดได้หลายสาเหตุว่าทำไมHashMaps ที่มีคีย์จำนวนเต็มดีกว่าSparseArrays:

  1. เอกสาร Android สำหรับการSparseArrayพูดว่า "โดยทั่วไปช้ากว่าแบบดั้งเดิมHashMap"
  2. หากคุณเขียนโค้ดโดยใช้HashMaps แทนSparseArrayรหัสของคุณจะทำงานร่วมกับการใช้งานแผนที่อื่น ๆ และคุณจะสามารถใช้ Java APIs ทั้งหมดที่ออกแบบมาสำหรับแผนที่
  3. หากคุณเขียนรหัสโดยใช้HashMaps แทนSparseArrayรหัสของคุณจะทำงานในโครงการที่ไม่ใช่ Android
  4. การแทนที่แผนที่equals()และhashCode()ที่SparseArrayอื่น ๆ

แต่เมื่อใดก็ตามที่ฉันพยายามใช้HashMapคีย์ที่มีจำนวนเต็มในโครงการ Android IntelliJ บอกฉันว่าฉันควรใช้SparseArrayแทน ฉันพบว่ามันยากที่จะเข้าใจ ไม่มีใครรู้เหตุผลที่น่าสนใจสำหรับการใช้งานSparseArrayของ?

คำตอบ:


235

SparseArrayสามารถใช้เพื่อแทนที่HashMapเมื่อคีย์เป็นชนิดดั้งเดิม มีตัวแปรบางอย่างสำหรับประเภทคีย์ / ค่าที่แตกต่างกันแม้ว่าจะไม่สามารถใช้งานได้ทั้งหมด

ประโยชน์คือ:

  • การจัดสรรฟรี
  • ไม่มีมวย

ข้อเสีย:

  • โดยทั่วไปช้ากว่าไม่ระบุสำหรับคอลเลกชันขนาดใหญ่
  • พวกเขาจะไม่ทำงานในโครงการที่ไม่ใช่ Android

HashMap สามารถถูกแทนที่ด้วยสิ่งต่อไปนี้:

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 

ในแง่ของหน่วยความจำนี่คือตัวอย่างของSparseIntArrayvs HashMap<Integer, Integer>สำหรับองค์ประกอบ 1000 รายการ:

SparseIntArray:

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

Class = 12 + 3 * 4 = 24 ไบต์
อาร์เรย์ = 20 + 1,000 * 4 = 4024 ไบต์
รวม = 8,072 ไบต์

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 ไบต์
รายการ = 32 + 16 + 16 = 64 ไบต์
อาร์เรย์ = 20 + 1000 * 64 = 64024 ไบต์
รวม = 64,136 ไบต์

ที่มา: ความทรงจำ Android โดย Romain Guyจากสไลด์ 90

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

แพคเกจประกอบด้วยวิธีการที่เป็นประโยชน์บางอย่างสำหรับการดำเนินงานขั้นสูงเช่นการตรวจสอบขนาดของวัตถุด้วยjava.lang.instrumentgetObjectSize(Object objectToSize)

ข้อมูลเสริมไว้บริการอย่างเป็นทางการจากเอกสารของ Oracle

Class = 12 ไบต์ + (ตัวแปรอินสแตนซ์ n) * 4 ไบต์
อาร์เรย์ = 20 ไบต์ + (n องค์ประกอบ) * (ขนาดองค์ประกอบ)
รายการ = 32 ไบต์ + (ขนาดองค์ประกอบที่ 1) + (ขนาดองค์ประกอบที่ 2)


15
ใครสามารถแนะนำฉันในที่ที่ "12 + 3 * 4" และ "20 + 1,000 * 4" มาจากไหน
Marian Paździoch

5
@ MarianPaździochเขาแสดงงานนำเสนอ ( speakerdeck.com/romainguy/android-memories ) โดยที่ชั้นเรียนใช้ 12 ไบต์ + 3 ตัวแปร 4 ไบต์ตัวแปร (อ้างอิง) ใช้ 20 ไบต์ (dlmalloc - 4, วัตถุเหนือหัว - 8, ความกว้างและการเติมเต็ม - 8)
CoolMind

1
สำหรับบันทึกข้อเสียที่สำคัญอีกข้อหนึ่งของ SparseArray ก็คือในฐานะที่เป็นวัตถุ Android จะต้องมีการเยาะเย้ยสำหรับการทดสอบหน่วย ที่เป็นไปได้ตอนนี้ฉันใช้วัตถุของ Java เพื่อทำการทดสอบ
David G

@DavidG คุณสามารถใช้unmock pluginเพื่อจำลองการอ้างอิง android
พายุหิมะ

1
แม้ว่าคุณจะไม่ได้ทำ Android การคัดลอกคลาสไปที่โปรเจ็กต์ของคุณนั้นไม่ยาก แต่ขึ้นอยู่กับคลาสอื่น ๆ 3 คลาสเท่านั้น ใบอนุญาต APL หมายถึงการทำเช่นนั้นไม่ว่าคุณจะทำงานกับอะไร
Yann TM

35

SparseArrayผมมาที่นี่เพียงแค่ต้องการตัวอย่างของวิธีการใช้งาน นี่คือคำตอบเพิ่มเติมสำหรับสิ่งนั้น

สร้าง SparseArray

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

SparseArrayแผนที่จำนวนเต็มบางส่วนObjectเพื่อให้คุณสามารถใช้ทดแทนในตัวอย่างข้างต้นกับคนอื่นString ๆ หากคุณกำลังทำแผนที่จำนวนเต็มจำนวนเต็มแล้วการใช้งานObjectSparseIntArray

เพิ่มหรืออัปเดตรายการ

ใช้put(หรือappend) เพื่อเพิ่มองค์ประกอบให้กับอาร์เรย์

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

โปรดทราบว่าintไม่จำเป็นต้องใช้ปุ่มตามลำดับ นอกจากนี้ยังสามารถใช้เพื่อเปลี่ยนค่าที่intคีย์เฉพาะ

ลบรายการ

ใช้remove(หรือdelete) เพื่อลบองค์ประกอบออกจากอาร์เรย์

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

intพารามิเตอร์เป็นกุญแจสำคัญในจำนวนเต็ม

ค้นหาค่าสำหรับคีย์ int

ใช้getเพื่อรับค่าสำหรับคีย์จำนวนเต็ม

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

คุณสามารถใช้get(int key, E valueIfKeyNotFound)หากคุณไม่ต้องการรับnullกุญแจที่หายไป

วนซ้ำรายการ

คุณสามารถใช้keyAtและvalueAtบางดัชนีเพื่อวนผ่านคอลเลกชันได้เนื่องจากการSparseArrayเก็บรักษาดัชนีแยกต่างหากจากintคีย์

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

โปรดทราบว่าคีย์จะเรียงลำดับจากน้อยไปหามากไม่ใช่ในลำดับที่ถูกเพิ่มเข้ามา


18

แต่เมื่อใดก็ตามที่ฉันพยายามใช้ HashMap กับคีย์จำนวนเต็มในโครงการ Android intelliJ บอกฉันว่าฉันควรใช้ SparseArray แทน

มันเป็นเพียงคำเตือนจากเอกสารนี้ของมันหร็อมแหร็มอาร์เรย์:

มันมีวัตถุประสงค์เพื่อให้มีประสิทธิภาพหน่วยความจำมากกว่าการใช้ HashMap เพื่อแมปจำนวนเต็มกับวัตถุ

สิ่งSparseArrayนี้ถูกทำให้เป็นหน่วยความจำที่มีประสิทธิภาพมากกว่าการใช้ HashMap ปกติซึ่งไม่อนุญาตให้มีหลายช่องว่างภายในอาเรย์ที่ไม่เหมือน HashMap ไม่มีอะไรต้องกังวลเกี่ยวกับมันคุณสามารถใช้ HashMap แบบดั้งเดิมหากคุณไม่ต้องการกังวลเกี่ยวกับการจัดสรรหน่วยความจำให้กับอุปกรณ์


5
จุดเกี่ยวกับการบันทึกหน่วยความจำนั้นถูกต้อง แต่ฉันไม่เคยเข้าใจเลยว่าทำไม android ไม่สามารถทำให้ SparseArray ใช้แผนที่ <Integer, T> เพื่อให้คุณได้รับการใช้งานแผนที่ที่มีประสิทธิภาพของหน่วยความจำ - ดีที่สุดของทั้งสองโลก
พอล Boddington

3
@ PaulBoddington ยังจำได้ว่าSparseArrayช่วยป้องกันจำนวนเต็มสำคัญที่จะเป็นกล่องอัตโนมัติซึ่งเป็นการดำเนินงานอื่นและประสิทธิภาพค่าใช้จ่าย แทนที่จะใช้ Map มันจะเปลี่ยนค่าจำนวนเต็มดั้งเดิมเป็นInteger
Rod_Algonquin

เป็นจริงเช่นกัน แต่หากพวกเขาใส่วิธีใส่มากเกินไปโดยใส่หนึ่งรายการไว้ด้วยการใส่ลายเซ็นต์ (int a, T t) คุณยังคงสามารถใส่คู่ของค่าคีย์ลงในแผนที่โดยไม่ต้องใส่กุญแจอัตโนมัติ ฉันคิดว่า Collections Framework นั้นมีประสิทธิภาพมาก (หนึ่งในเหตุผลที่ดีที่สุดสำหรับการใช้ Java) ซึ่งมันบ้าคลั่งที่จะไม่ใช้ประโยชน์จากมัน
พอล Boddington

6
@PaulBoddington คอลเล็กชันขึ้นอยู่กับวัตถุที่ไม่ได้อยู่บนพื้นฐานดังนั้นมันจะไม่ทำงานภายใน Collections API
Rod_Algonquin

10

Sparse Array ใน Java เป็นโครงสร้างข้อมูลที่จับคู่คีย์กับค่า แนวคิดเดียวกันกับแผนที่ แต่มีการนำไปใช้ที่แตกต่างกัน:

  1. แผนที่จะแสดงภายในเป็นอาร์เรย์ของรายการโดยที่แต่ละองค์ประกอบในรายการเหล่านี้เป็นคู่ที่สำคัญคู่กัน ทั้งคีย์และค่าเป็นอินสแตนซ์ของวัตถุ

  2. อาร์เรย์ที่กระจัดกระจายนั้นทำจากสองอาร์เรย์เท่านั้น: อาร์เรย์ของคีย์ (ดั้งเดิม) และอาร์เรย์ของค่า (วัตถุ) อาจมีช่องว่างในดัชนีอาร์เรย์เหล่านี้ดังนั้นคำว่า "กระจัดกระจาย" อาร์เรย์

ความสนใจหลักของ SparseArray ก็คือมันช่วยประหยัดหน่วยความจำโดยการใช้ primitives แทนวัตถุเป็นกุญแจ


10

หลังจาก googling ฉันพยายามเพิ่มข้อมูลบางอย่างไปยัง anwers ที่โพสต์แล้ว:

Isaac Taylorทำการเปรียบเทียบประสิทธิภาพของ SparseArrays และ Hashmaps เขากล่าวว่า

Hashmap และ SparseArray นั้นคล้ายกันมากสำหรับขนาดโครงสร้างข้อมูลต่ำกว่า 1,000

และ

เมื่อขนาดเพิ่มขึ้นเป็น 10,000 เครื่องหมาย [... ] Hashmap จะมีประสิทธิภาพมากกว่าด้วยการเพิ่มวัตถุในขณะที่ SparseArray มีประสิทธิภาพที่ดีกว่าเมื่อดึงวัตถุ [... ] ที่ขนาด 100,000 [... ] Hashmap จะสูญเสียประสิทธิภาพอย่างรวดเร็ว

การเปรียบเทียบกับEdgblogแสดงให้เห็นว่า SparseArray ต้องการหน่วยความจำน้อยกว่า HashMap มากเนื่องจากคีย์ที่เล็กกว่า (int vs Integer) และความจริงที่ว่า

อินสแตนซ์ HashMap.Entry ต้องติดตามการอ้างอิงสำหรับคีย์ค่าและรายการถัดไป นอกจากนี้ยังจำเป็นต้องจัดเก็บแฮชของรายการด้วยเช่นกัน

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


4

เอกสาร Android สำหรับ SparseArray บอกว่า "โดยทั่วไปช้ากว่า HashMap แบบดั้งเดิม"

ใช่มันถูกต้อง แต่เมื่อคุณมีเพียง 10 หรือ 20 รายการความแตกต่างของประสิทธิภาพควรไม่มีนัยสำคัญ

หากคุณเขียนรหัสโดยใช้ HashMaps แทนที่จะเป็น SparseArrays รหัสของคุณจะทำงานร่วมกับการใช้งานแผนที่อื่น ๆ และคุณจะสามารถใช้ Java API ทั้งหมดที่ออกแบบมาสำหรับแผนที่

ฉันคิดว่าบ่อยครั้งที่เราใช้HashMapเพื่อค้นหาค่าที่เกี่ยวข้องกับกุญแจเท่านั้นในขณะที่SparseArrayทำได้ดีจริงๆ

หากคุณเขียนรหัสโดยใช้ HashMaps แทนที่จะเป็น SparseArrays รหัสของคุณจะทำงานในโครงการที่ไม่ใช่ Android

ซอร์สโค้ดของ SparseArray นั้นค่อนข้างง่ายและเข้าใจง่ายเพื่อให้คุณใช้ความพยายามเพียงเล็กน้อยในการย้ายไปยังแพลตฟอร์มอื่น ๆ (ผ่าน COPY & Paste แบบง่าย)

การแทนที่แผนที่เท่ากับ () และ hashCode () ในขณะที่ SparseArray ไม่ได้

ทั้งหมดที่ฉันสามารถพูดได้คือ (สำหรับนักพัฒนาส่วนใหญ่) ที่สนใจ?

อีกด้านที่สำคัญของการSparseArrayก็คือว่ามันจะใช้อาร์เรย์เพื่อเก็บองค์ประกอบทั้งหมดในขณะที่HashMapการใช้งานEntryเพื่อให้SparseArrayค่าใช้จ่ายอย่างมีนัยสำคัญหน่วยความจำน้อยกว่าHashMapให้ดูนี้


1

น่าเสียดายที่คอมไพเลอร์ออกคำเตือน ฉันเดาว่า HashMap ใช้วิธีเก็บข้อมูลมากเกินไป

SparseArrays มีสถานที่ของพวกเขา ให้พวกเขาใช้อัลกอริทึมการค้นหาแบบไบนารีเพื่อค้นหาค่าในอาร์เรย์คุณต้องพิจารณาสิ่งที่คุณกำลังทำ การค้นหาแบบไบนารีคือ O (log n) ในขณะที่การค้นหา hash คือ O (1) นี่ไม่ได้แปลว่าการค้นหาแบบไบนารีจะช้ากว่าสำหรับชุดข้อมูลที่ระบุ อย่างไรก็ตามเมื่อจำนวนรายการเพิ่มขึ้นพลังของตารางแฮชก็จะเข้ามาแทนที่ ดังนั้นความคิดเห็นที่จำนวนรายการต่ำสามารถเท่ากันและอาจจะดีกว่าการใช้ HashMap

HashMap นั้นดีพอ ๆ กับแฮชและยังสามารถได้รับผลกระทบจาก load factor (ฉันคิดว่าในรุ่นต่อมาพวกเขาจะไม่สนใจตัวประกอบโหลดดังนั้นมันจึงเหมาะที่สุด) พวกเขายังเพิ่มแฮชรองเพื่อให้แน่ใจว่าแฮชดี ด้วยเหตุผลที่ SparseArray ทำงานได้ดีจริงๆสำหรับรายการที่ค่อนข้างน้อย (<100)

ฉันอยากจะแนะนำว่าถ้าคุณต้องการตารางแฮชและต้องการการใช้หน่วยความจำที่ดีขึ้นสำหรับจำนวนเต็มดั้งเดิม (ไม่มีการชกมวยอัตโนมัติ) ฯลฯ ลองใช้ขุมทรัพย์ ( http://trove.starlight-systems.com - ใบอนุญาต LGPL) (ไม่มีส่วนเกี่ยวข้องกับขุมทรัพย์เช่นเดียวกับห้องสมุดของพวกเขา)

ด้วยอาคารหลาย dex ที่เรียบง่ายเรามีคุณไม่จำเป็นต้องทำการบรรจุหีบห่อในสิ่งที่คุณต้องการ (trove มีคลาสจำนวนมาก)

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