Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

วิธีตั้งค่าความจุขั้นต่ำ ArrayList สำหรับ ListView บน Android

การตั้งค่าความจุขั้นต่ำ (Initial Capacity) ของ ArrayList สำหรับ ListView

ตัวอย่างนี้สาธิตการใช้ ensureCapacity() เพื่อจองพื้นที่หน่วยความจำล่วงหน้าสำหรับ ArrayList ลดการ Resize (ขยาย Array) ซ้ำๆ เมื่อเพิ่มข้อมูลมากๆ ก่อนแสดงใน ListView

ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่

สร้างโปรเจกต์ Android ใหม่ใน Android Studio

ขั้นตอนที่ 2: ออกแบบ Layout (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical"
    android:padding="16dp">

    <EditText
        android:id="@+id/etName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="ป้อนชื่อ" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:orientation="horizontal">
        <Button android:id="@+id/btnSave" android:text="บันทึก" android:layout_width="wrap_content" android:layout_height="wrap_content" />
        <Button android:id="@+id/btnRefresh" android:text="รีเฟรช" android:layout_width="wrap_content" android:layout_height="wrap_content" />
    </LinearLayout>

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp">
    </ListView>
</LinearLayout>

ขั้นตอนที่ 3: MainActivity.java

package com.example.myapplication;

import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;

public class MainActivity extends AppCompatActivity {
    private EditText etName;
    private ListView listView;
    private ArrayAdapter<String> adapter;
    private ArrayList<String> arrayList;
    private Set<String> uniqueSet = new HashSet<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // จุดสำคัญ: สร้าง ArrayList พร้อมกำหนด Initial Capacity
        arrayList = new ArrayList<>();
        arrayList.ensureCapacity(20); // จองพื้นที่สำหรับ 20 Elements ล่วงหน้า

        etName = findViewById(R.id.etName);
        listView = findViewById(R.id.listView);

        findViewById(R.id.btnRefresh).setOnClickListener(v -> {
            if (adapter != null) {
                adapter.notifyDataSetChanged();
                listView.invalidateViews();
                listView.refreshDrawableState();
            }
        });

        findViewById(R.id.btnSave).setOnClickListener(v -> {
            String input = etName.getText().toString().trim();
            if (!input.isEmpty()) {
                uniqueSet.add(input);
                arrayList.clear();
                arrayList.addAll(uniqueSet);

                Collections.sort(arrayList, new Comparator<String>() {
                    @Override
                    public int compare(String o1, String o2) {
                        return o1.compareTo(o2);
                    }
                });

                adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, arrayList);
                listView.setAdapter(adapter);

                Toast.makeText(this, "บันทึกสำเร็จ (Capacity: " + getCapacity(arrayList) + ")", Toast.LENGTH_SHORT).show();
            } else {
                etName.setError("กรุณาป้อนชื่อ");
            }
        });
    }

    // Helper: ดู Capacity จริงของ ArrayList (ใช้ Reflection)
    private int getCapacity(ArrayList<?> list) {
        try {
            java.lang.reflect.Field field = ArrayList.class.getDeclaredField("elementData");
            field.setAccessible(true);
            Object[] elementData = (Object[]) field.get(list);
            return elementData.length;
        } catch (Exception e) {
            return -1;
        }
    }
}

คำอธิบาย ensureCapacity()

arrayList.ensureCapacity(20);
  • เพิ่ม Capacity (ขนาด Internal Array) ให้มากกว่าหรือเท่ากับค่าที่ระบุ
  • ไม่เปลี่ยน Size (จำนวนข้อมูลจริง) - ยังคงเป็น 0
  • หาก Capacity ปัจจุบัน >= minCapacity → ไม่ทำอะไร
  • ช่วยลด Overhead ของการ Resize Array (Copy Array ใหม่ใหญ่ขึ้น)

ArrayList Internal: Size vs Capacity

PropertyคำอธิบายMethod
Sizeจำนวน Element จริงใน Listsize()
Capacityขนาด Array ภายใน (elementData.length)ไม่มี Public API (ใช้ Reflection)

พฤติกรรมการ Resize (Growth Policy)

// Java 8+ (Android)
newCapacity = oldCapacity + (oldCapacity >> 1); // oldCapacity * 1.5
// ตัวอย่าง: 10 → 15 → 22 → 33 → 49 → 73 → 109...
  • ทุกครั้งที่ Size เกิน Capacity → สร้าง Array ใหม่ 1.5x → Copy ข้อมูลเก่า → ชี้ไป Array ใหม่
  • Operation นี้มี Cost: O(n) Time + Memory Allocation + GC Pressure
  • ensureCapacity() ช่วยลดจำนวนครั้ง Resize

ตัวอย่างการทำงาน

ArrayList<String> list = new ArrayList<>();     // Capacity = 10 (Default)
list.ensureCapacity(20);                        // Capacity = 20
list.add("A"); // Size=1, Capacity=20
list.add("B"); // Size=2, Capacity=20
// ... เพิ่ม 20 ตัว → Size=20, Capacity=20 (ไม่ Resize)
list.add("U"); // Size=21 → Resize! Capacity=30 (20 * 1.5)

Constructor Alternatives

// 1. Constructor ระบุ Initial Capacity (แนะนำ)
ArrayList<String> list = new ArrayList<>(20); // Capacity = 20

// 2. ensureCapacity() หลังสร้าง
ArrayList<String> list = new ArrayList<>();
list.ensureCapacity(20); // ผลลัพธ์เหมือนกัน

// 3. จาก Collection อื่น
ArrayList<String> list = new ArrayList<>(existingCollection); // Capacity = size + α

เมื่อไหร่ควรใช้ ensureCapacity()?

  • ทราบจำนวนข้อมูลโดยประมาณ: เช่น โหลด 1000 รายการจาก API/Database
  • Batch Insert: เพิ่มข้อมูลจำนวนมากใน Loop
  • Performance Critical: UI List ที่อัปเดตบ่อย (RecyclerView Adapter)
  • Memory Constrained: หลีกเลี่ยง Overallocation (จองมากเกินไป)

ข้อควรระวัง

  • Over-allocation: ensureCapacity(10000) แต่เพิ่มแค่ 10 ตัว → เหลือพื้นที่ว่าง 9990 slots (Memory Waste)
  • ไม่ลด Capacity: ensureCapacity() เพิ่มได้อย่างเดียว ไม่ลด (ใช้ trimToSize() เพื่อลด)
  • Thread Safety: ArrayList ไม่ Thread-Safe - ใช้ Collections.synchronizedList() หรือ CopyOnWriteArrayList
  • Default Capacity: Java 8+ เริ่มต้นที่ 10 (Java 7: 10, แต่ lazy initialization)

trimToSize() - คู่หูของ ensureCapacity()

// หลังลบข้อมูลเยอะ Capacity อาจใหญ่กว่า Size มาก
arrayList.trimToSize(); // Capacity = Size (ปล่อย Memory คืน)
  • เหมาะหลัง: ลบข้อมูลมาก, โหลดข้อมูลเสร็จและไม่เพิ่มแล้ว
  • ใช้ร่วมกับ ensureCapacity(): จอง → ใช้ → ปล่อย

Benchmark: ensureCapacity vs No ensureCapacity

// เพิ่ม 100,000 items
// ไม่ใช้ ensureCapacity: ~17 Resizes (10→15→22→33→50→75→112→168→252→378→567→850→1275→1912→2868→4302→6453→9679→14518→21777→32665→48997→73495→110242)
// ใช้ ensureCapacity(100000): 0 Resizes
// เวลา: ลดลง ~30-50% (ขึ้นอยู่กับ JVM/ART)

กรณีการใช้งานจริงใน Android

  • RecyclerView Adapter: ensureCapacity(itemCount) ใน onCreate/onBindViewHolder
  • Data Loading: โหลดจาก Database/API → ensureCapacity(cursor.getCount())
  • Search/Filter: ArrayList สำหรับ Filter Results → ensureCapacity(originalSize)
  • Batch Operations: เพิ่ม/ลบหลายรายการพร้อมกัน

สรุป

ensureCapacity() เป็น Optimization Technique เรียบง่ายสำหรับ ArrayList เมื่อทราบจำนวนข้อมูลโดยประมาณ ช่วยลด GC Pressure และเพิ่ม Performance สำหรับ ListView/RecyclerView ที่จัดการข้อมูลจำนวนมาก