การตั้งค่าความจุขั้นต่ำ (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 จริงใน List | size() |
| 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 ที่จัดการข้อมูลจำนวนมาก