CSV (comma-separated values) menyimpan tabel dalam teks biasa: setiap baris mewakili satu catatan dan setiap kolom dipisah tanda koma. Banyak alat mengekspor dan menerima format ini, sehingga kemampuan membaca dan menulisnya tanpa library tambahan masih sering dibutuhkan. Topik ini membangun pengurai CSV dari nol, dimulai dari batas-batas split, lanjut ke mesin status kecil yang menghormati tanda kutip, dan ditutup dengan penulisan berkas yang aman dibaca ulang.
Struktur CSV dan batas split#
Untuk CSV yang benar-benar sederhana, yaitu nilai yang tidak mengandung koma maupun tanda kutip, metode split(",") sudah cukup. Karena isinya teks biasa, berkas CSV dapat dibuka di editor teks mana pun dan berpindah antar alat tanpa pengonversi khusus. Namun split menyimpan satu perilaku yang mudah terlewat: kolom kosong di ujung baris dibuang, kecuali batas negatifnya dinyatakan secara eksplisit. Program berikut mengurai baris dengan kolom nilai yang kosong pada posisi terakhir, lalu membandingkan kedua bentuk pemanggilan.
public class DemoCsvSplit {
public static void main(String[] args) {
String baris = "3,Dewi,";
String[] kolom = baris.split(",");
System.out.println("split(\",\") menghasilkan " + kolom.length + " kolom:");
for (String k : kolom) {
System.out.println(" [" + k + "]");
}
String[] kolomPenuh = baris.split(",", -1);
System.out.println("split(\",\", -1) menghasilkan " + kolomPenuh.length + " kolom:");
for (String k : kolomPenuh) {
System.out.println(" [" + k + "]");
}
}
}split(",") menghasilkan 2 kolom:
[3]
[Dewi]
split(",", -1) menghasilkan 3 kolom:
[3]
[Dewi]
[]Pemanggilan split(",") menghasilkan dua kolom karena kolom kosong di ujung dibuang, sedangkan split(",", -1) mempertahankannya sebagai kolom ketiga yang kosong. Angka -1 adalah batas yang menuntut semua pemisahan dilakukan tanpa pembuangan. Menjalankan programnya dari terminal cukup dengan satu perintah berikut.
# berada di folder tempat DemoCsvSplit.java disimpan
java DemoCsvSplit.javasplit(",") menghasilkan 2 kolom:
[3]
[Dewi]
split(",", -1) menghasilkan 3 kolom:
[3]
[Dewi]
[]Batas split berikutnya lebih serius, yaitu tanda kutip yang sama sekali tidak dipahaminya.
// tidak-dijalankan
String baris = "Ali,\"Rapat pagi, siang, dan sore\"";
String[] kolom = baris.split(",");
// hasilnya empat kolom: [Ali] ["Rapat pagi] [ siang] [ dan sore"]Nilai Rapat pagi, siang, dan sore seharusnya menjadi satu kolom, tetapi split membelahnya di setiap koma, termasuk koma yang berada di dalam tanda kutip. CSV yang sah boleh membungkus nilai dengan tanda kutip justru untuk kasus ini, sehingga pengurainya butuh mesin yang memahami tanda kutip, bukan sekadar pemisahan teks.
Pengurai dengan tanda kutip#
Pengurai CSV yang menghormati tanda kutip dapat dibangun dari mesin status (state machine) dengan dua keadaan, yaitu di luar kutip dan di dalam kutip. Karakter koma hanya membelah kolom saat berada di luar kutip. Tanda kutip pertama memindahkan keadaan ke dalam kutip, dan pasangan dua tanda kutip berturut-turut di dalam kutip berarti satu tanda kutip yang benar-benar milik nilai. Alur keputusannya digambarkan berikut ini.
Program berikut menerapkan mesin itu pada beberapa baris contoh, termasuk koma di dalam kutip dan kutip yang digandakan.
import java.util.ArrayList;
import java.util.List;
public class DemoCsvParser {
public static void main(String[] args) {
String[] baris = {
"nama,catatan",
"Ali,\"Rapat pagi, siang, dan sore\"",
"Citra,\"Sedia \"\"kopi\"\" untuk tamu\"",
"Dewi,Tepat waktu"
};
for (String s : baris) {
List<String> kolom = urai(s);
System.out.println(kolom.size() + " kolom: " + kolom);
}
}
static List<String> urai(String baris) {
List<String> kolom = new ArrayList<>();
StringBuilder sel = new StringBuilder();
boolean dalamKutip = false;
for (int i = 0; i < baris.length(); i++) {
char c = baris.charAt(i);
if (dalamKutip) {
if (c == '"') {
if (i + 1 < baris.length() && baris.charAt(i + 1) == '"') {
sel.append('"');
i++;
} else {
dalamKutip = false;
}
} else {
sel.append(c);
}
} else {
if (c == '"') {
dalamKutip = true;
} else if (c == ',') {
kolom.add(sel.toString());
sel.setLength(0);
} else {
sel.append(c);
}
}
}
kolom.add(sel.toString());
return kolom;
}
}2 kolom: [nama, catatan]
2 kolom: [Ali, Rapat pagi, siang, dan sore]
2 kolom: [Citra, Sedia "kopi" untuk tamu]
2 kolom: [Dewi, Tepat waktu]Setiap baris kembali menjadi dua kolom dengan isinya utuh, termasuk koma di dalam kutip dan tanda kutip di sekeliling kata kopi. Panggilan setLength(0) mengosongkan StringBuilder dengan cepat, sehingga objek yang sama dipakai ulang untuk sel berikutnya tanpa membuat objek baru. Demo berikut menerapkan pengurai yang sama di browser. Sunting teksnya, bungkus sebuah nilai dengan tanda kutip, dan amati tabel hasil penguraiannya berubah seketika.
Pengurai di atas bekerja per baris, dan kelemahan itu perlu diketahui sejak awal. CSV yang sah membolehkan pemisah baris berada di dalam nilai bertanda kutip, sehingga berkas seperti itu tidak lagi cocok dibaca baris demi baris. Pengurai yang lengkap membaca seluruh aliran karakter alih-alih satu baris, dan tugas semacam itulah yang biasanya diserahkan pada library CSV seperti Apache Commons CSV.
Menulis CSV yang aman dibaca ulang#
Arah penulisan menghadapi persoalan yang sama dari sisi sebaliknya: nilai yang mengandung koma, tanda kutip, atau pemisah baris harus dibungkus tanda kutip, dan tanda kutip di dalamnya digandakan. Aturan ini didokumentasikan dalam RFC 4180, dokumen spesifikasi yang menjadi acuan format CSV. Program berikut menyimpan tabel kecil ke berkas CSV dengan penerapan aturan itu, lalu mencetak isi berkasnya.
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class DemoCsvTulis {
public static void main(String[] args) throws IOException {
String[][] data = {
{"nama", "catatan"},
{"Ali", "Rapat pagi, siang, dan sore"},
{"Citra", "Sedia \"kopi\" untuk tamu"},
{"Dewi", "Tepat waktu"}
};
StringBuilder teks = new StringBuilder();
for (String[] baris : data) {
String[] kolomAman = new String[baris.length];
for (int i = 0; i < baris.length; i++) {
kolomAman[i] = amankan(baris[i]);
}
teks.append(String.join(",", kolomAman)).append("\n");
}
Path berkas = Path.of("kegiatan.csv");
Files.writeString(berkas, teks.toString(), StandardCharsets.UTF_8);
System.out.print(Files.readString(berkas, StandardCharsets.UTF_8));
}
static String amankan(String nilai) {
if (nilai.contains(",") || nilai.contains("\"") || nilai.contains("\n")) {
return "\"" + nilai.replace("\"", "\"\"") + "\"";
}
return nilai;
}
}nama,catatan
Ali,"Rapat pagi, siang, dan sore"
Citra,"Sedia ""kopi"" untuk tamu"
Dewi,Tepat waktuBaris kedua dan ketiga kini dibungkus tanda kutip karena nilainya memuat koma dan tanda kutip, sedangkan baris keempat ditulis polos. Berkas yang dihasilkan persis seperti contoh pada bagian penguraian, sehingga berkas itu, ketika diurai kembali dengan program DemoCsvParser, menghasilkan kolom yang benar. Penulisan dan penguraian yang saling melengkapi inilah yang menjaga data tetap utuh melintasi aplikasi mana pun.
Kesalahan umum#
- Memakai
split(",")untuk CSV yang berisi nilai bertanda kutip. Koma di dalam kutip ikut membelah, sehingga jumlah kolom menjadi salah. - Memanggil
split(",")tanpa batas -1. Kolom kosong di ujung baris dibuang, dan data kolom terakhir lenyap tanpa pesan error. - Menulis nilai yang mengandung koma tanpa membungkusnya tanda kutip. Kolom bergeser sejak nilai itu, dan alat pembaca lain menghitung kolom yang berbeda dari maksud penulisnya.
- Menganggap satu baris teks selalu satu catatan. Nilai bertanda kutip boleh memuat pemisah baris, sehingga batasan pengurai per baris perlu disadari.
Ringkasan#
- CSV menyusun tabel dari baris dan koma, dan
split(",", -1)menjaga kolom kosong di ujung baris tetap ada. splittidak memahami tanda kutip, sehingga penguraian yang benar memakai mesin status dengan keadaan di luar kutip dan di dalam kutip.- Saat menulis, nilai yang memuat koma, kutip, atau pemisah baris dibungkus tanda kutip dan kutip di dalamnya digandakan, sesuai RFC 4180.
- Pengurai per baris tidak menangani pemisah baris di dalam kutip, sehingga kebutuhan yang lebih besar diserahkan pada library CSV.
Referensi#
- RFC 4180 — IETF — spesifikasi format CSV beserta aturan tanda kutip dan penggandaannya.
- split (Java SE 21) — Oracle
— dokumentasi
String.splitbeserta makna batas -1.