Monday, December 19, 2016

Bab 5. Java Struktur Data dan Pemrograman GUI



Bab.5 Masukan dan Keluaran File

Tujuan Instruksional
·         Mengetahui bagaimana I/O diproses dalam JAVA.
·         Membedakan antara I/O teks dan I/O biner.
·         Menulis dan membaca byte menggunakan FileInputStream dan FileOutputStream.
·         Membaca dan menulis nilai primitif dan string menggunakan DataInputStream/DataOutputStream.
·         Mengimplementasikan antarmuka Serializable untuk membuat objek dapat diserialkan.
·         Menyimpan dan merestorasi objek menggunakan ObjectOutputStream dan ObjectInputStream, dan untuk memahami bagaimana objek-objek diserialkan dan apa jenis objek yang bisa diserialkan.
·         Menyerialkan array.
·         Menulis dan menulis file menggunakan kelas RandomAccessFile.


5.1 Introduksi

Data yang disimpan dalam file teks direpresentasikan dalam format yang dapat dibaca manusia. Data yang disimpan dalam file biner disimpan dalam format biner. Anda tidak bisa membaca file biner, karena hanya didesain untuk dibaca oleh program. Sebagai contoh, program sumber JAVA disimpan dalam file teks dan dapat dibaca oleh editor teks, tetapi kelas JAVA disimpan dalam file biner dan hanya bisa dibaca oleh JVM. Keuntungan file biner adalah lebih efisien diproses daripada file teks.

Meskipun secara teknis tidak presisi dan tepat, Anda bisa menganggap suatu file teks memuat runtun karakter dan suatu file biner memuat runtun biner. Sebagai contoh, integer desimal 199 disimpan sebagai runtun tiga karakter ‘1’, ‘9’, ‘9’, di dalam file teks, dan integer yang sama sebagai nilai tipe-byte C7 di dalam file biner, karena desimal 199 sama dengan C7 dalam heksadesimal .

JAVA menawarkan banyak kelas untuk melaksanakan masukan dan keluaran file, yang dapat dikategorikan sebagai kelas I/O teks dan kelas I/O biner.


5.2 Bagaimana I/O Ditangani JAVA
Ingat bahwa objek File mengenkapsulasi properti-properti suatu file tetapi tidak memuat metode-metode untuk menulis/membaca data ke/dari suatu file. Untuk melaksanakan tugas I/O, Anda perlu menciptakan objek dari kelas I/O JAVA yang sesuai. Objek tersebut memuat metode-metode untuk menulis/membaca data ke/dari suatu file. Sebagai contoh, untuk menulis teks ke suatu file bernama temp.txt, Anda perlu menciptakan suatu objek menggunakan kelas PrintWriter sebagai berikut:

PrintWriter keluaran = new PrintWriter("temp.txt");

Anda sekarang dapat memanggil metode print dari objek tersebut untuk menulis suatu string ke dalam file. Sebagai contoh, statemen berikut menulis “JAVA itu Tangguh!” ke dalam file:

keluaran.print("JAVA itu Tangguh!");

Statemen berikut ini menutup file tersebut:

keluaran.close();

Terdapat banyak kelas I/O untuk berbagai tujuan. Pada umumnya, dapat diklasifikasikan menjadi kelas masukan dan kelas keluaran. Kelas masukan memuat metode-metode untuk membaca data, dan kelas keluaran mamuat metode-metode untuk menulis data. PrintWriter merupakan suatu contoh kelas keluaran, dan Scanner adalah suatu contoh kelas masukan. Kode berikut menciptakan suatu objek masukan untuk file temp.txt dan membaca data dari file itu:

Scanner masukan = new Scanner(new File("temp.txt"));
System.out.println(masukan.nextLine());

Jika file temp.txt memuat “JAVA itu Tangguh!”, maka masukan.nextLine() menghasilkan string “JAVA itu Tangguh!”.

Gambar 5.1 mengilustrasikan pmerograman I/O JAVA. Suatu objek masukan membaca aliran data dari suatu file, dan suatu objek keluaran menulis aliran data ke dalam suatu file. Objek masukan disebut dengan aliran masukan dan objek keluaran disebut dengan aliran keluaran.


Gambar 5.1 Program menerima data dari suatu objek masukan dan mengirimkan data melalui suatu objek keluaran


5.3 I/O Teks dan I/O Biner
Komputer tidak membedakan file biner dan file teks. Semua file disimpan dalam format biner, jadi semua file secara esensial adalah file biner. I/O teks dibangun berdasarkan I/O biner untuk menyediakan suatu level abstraksi dalam pengkodean dan pendekodean, seperti tertampil pada Gambar 5.2a. Pengkodean dan pendekodean secara otomatis dilakukan untuk I/O teks. JVM mengubah Unicode menjadi pengkodean berbasis-file ketika menulis suatu karakter dan mengubah pengkodean berbasis-file menjadi Unicode ketika membaca suatu karakter. Sebagai contoh, dimisalkan Anda menulis string “199” menggunakan I/O teks ke dalam file. Setiap karakter ditulis ke dalam file. Karena Unicode untuk ‘1’ adalah 0x0031, Unicode 0x0031 dikonversi menjadi suatu kode yang bergantung dari skema pengkodean untuk file terkait. (Perhatikan bahwa 0x menandakan bilangan heksadesimal). Pengkodean yang umum dipakai adalah ASCII. Kode ASCII untuk karakter ‘1’ adalah 49 (0x31 dalam heksadesimal) dan untuk karakter ‘9’ adalah 57 (0x39 dalam heksadesimal). Jadi, menulis karakter-karakter “199” adalah sama dengan mengirimkan tiga byte—0x31, 0x39, 0x39— pada keluaran, seperti tertampil pada Gambar 5.2a.

I/O biner tidak memerlukan konversi. Jika Anda menulis suatu nilai numerik ke dalam file menggunakan I/O biner, nilai eksaknya di dalam memori disalin ke dalam file tersebut. Sebagai contoh, suatu nilai tipe-byte 199 direpresentasikan sebagai 0xC7  di dalam memori dan muncul persis sama di dalam file, seperti tertampil pada Gambar 5.2b. Ketika Anda membaca satu byte menggunakan I/O biner, satu nilai byte dibaca dari masukan.



Gambar 5.2 I/O teks memerlukan pengkodea dan pendekodean, sedangkan I/O biner tidak


5.4 Kelas I/O Biner
Perancangan kelas-kelas I/O JAVA merupakan contoh penerapan pewarisan, dimana operasi-operasi umum digeneralisasi di dalam superkelas, dan sub-subkelas menyediakan operasi-operasi yang lebih spesifik. Gambar 5.3 menampilkan beberapa kelas yang melaksanakan I/O biner. InputStream merupakan akar untuk semua kelas-kelas masukan biner, dan OutputStream merupakan akar untuk semua kelas-kelas keluaran biner. Gambar 5.4 dan Gambar 5.5 mencantumkan semua metode di dalam InputStream dan OutputStream.



Gambar 5.3 InputStream, OutputStream, dan sub-subkelasnya untuk I/O biner




Gambar 5.4 Kelas abstrak InputStream mendefinisikan metode-metode aliran byte masukan



Gambar 5.5 Kelas abstrak OutputStream mendefinisikan metode-metode aliran byte keluaran


5.4.1 FileInputStream/FileOutputStream
FileInputStream dan FileOutputStream digunakan untuk membaca/menulis dari/ke file. Semua metode di dalam kedua kelas ini diwarisi dari InputStream dan OutputStream. FileInputStream dan FileOutputStream tidak mengintroduksi metode-metode baru. Untuk menciptakan suatu FileInputStream, digunakan konstruktor-konstruktor berikut, seperti tertampil pada Gambar 5.6.



Gambar 5.6 FileInputStream memasukkan aliran byte dari file


Ekspesi java.io.FileNotFoundException akan terjadi bila Anda mencoba untuk menciptakan suatu FileInputStream dari file yang tidak ada. Untuk menciptakan suatu FileOutputStream, digunakan konstruktor-konstruktor berikut, seperti tertampil pada Gambar 5.7.



Gambar 5.7 FileOutputStream mengeluarkan aliran byte ke dalam file


Jika file tidak ada, suatu file baru akan diciptakan. Jika file sudah ada, maka dua konstruktor pertama akan menghapus isi semula dari file itu. Untuk tetap mempertahankan isi semula file dan menyambungnya dengan data baru, digunakan dua konstruktor terakhir dengan melewatkan parameter true dan append.

Hampir semua metode di dalam kelas-kelas I/O melempar java.io.IOException. Oleh karena itu, Anda harus mendeklarasikan java.io.IOException untuk melemparkan eksepsi di dalam metode atau menempatkan kode di dalam blok try-catch, seperti ini:



Kode5.1 menggunakan I/O biner untuk menulis nilai-nilai sepuluh byte dari 1 sampai 10 ke dalam suatu file bernama temp.dat dan membacanya kembali dari file tersebut.

Kode5.1 UjiAliranFile.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.io.*;

public class UjiAliranFile {
  public static void main(String[] args) throws IOException{
    // Menciptakan suatu alitan keluaran ke file
    FileOutputStream keluaran = new FileOutputStream("temp.dat");

    // Mengeluarkan nilai-nilai ke file
    for (int i = 1; i <= 10; i++)
      keluaran.write(i);

    // Menutup aliran keluaran
    keluaran.close();

    // Menciptakan suatu alitan masukan
    FileInputStream masukan = new FileInputStream("temp.dat");

    // Membaca nilai-nilai dari file
    int nilai;
    while ((nilai = masukan.read()) != -1)
      System.out.print(nilai + " ");

    // Menutup aliran keluaran
    masukan.close();
  }
}

Keluaran

1 2 3 4 5 6 7 8 9 10


Suatu FileOutputStream diciptakan untuk file temp.dat pada baris 6. Loop for menulis sepuluh nilai byte ke dalam file (baris 9-10). Pemanggilan write(i) sama dengan pemanggilan write((byte)i). Baris 13 menutup aliran keluaran. Baris 16 menciptakan suatu FileInputStream untuk file temp.dat. Nilai-nilai dibaca dari file dan ditampilkan pada konsol pada baris 19-21. Ekspresi ((nilai = masukan.read()) != -1) (baris 20) membaca suatu byte dari masukan.read(), menugaskannya kepada nilai, dan memeriksa apakah bernilai -1. Jika bernilai -1, maka menandakan akhir suatu file.

File yang diciptakan dalam contoh ini adalah file biner, yang dapat dibaca oleh program JAVA tetapi tidak dari suatu editor teks, seperti tertampil pada Gambar 5.8.


Gambar 5.8 Suatu file biner tidak bisa ditampilkan dalam mode teks


5.4.2 FilterInputStream/FilterOutputStream
Aliran filter merupakan aliran yang memilter byte-byte untuk tujuan tertentu. Aliran masukan byte menyediakan metode read yang dapat dipakai hanya untuk membaca. Jika Anda membaca integer, double, atau string, Anda memerlukan suatu kelas filter menyatukan aliran masukan byte. Penggunaan suatu kelas filter memampukan Anda untuk membaca integer, double, atau string, bukan byte maupun karakter. FilterInputStream dan FilterOutputStream merupakan dua kelas basis untuk memilter data. Jika Anda perlu memproses tipe-tipe numerik primitif, Anda bisa menggunakan DataInputStream dan DataOutputStream untuk memilter byte. 

5.4.3 DataInputStream/DataOutputStream
DataInputStream membaca data byte dari aliran dan mengkonversinya menjadi nilai tipe primitif atau menjadi string. DataOutputStream mengkonversi nilai tipe primitif atau string menjadi byte dan menempatkan byte tersebut ke aliran.

DataInputStream mewarisi FilterInputStream dan mengimplementasikan antarmuka DataInput, seperti tertampil pada Gambar 5.9. DataOutputStream mewarisi FilterOutputStream dan mengimplementasikan antarmuka DataOutput, seperti tertampil pada Gambar 5.10.

DataInputStream mengimplementasikan metode-metode yang didefinisikan di dalam
antarmuka DataInput untuk membaca nilai-nilai tipe primitif dan string. DataOtuputStream mengimplementasikan metode-metode yang didefinisikan di dalam  antarmuka DataOutput untuk menulis nilai-nilai tipe primitif dan string. Nilai-nilai primitif disalin dari memori ke keluaran tanpa adanya konversi. Karakter-karakter di dalam suatu string dapat dituliskan dalam beberapa cara, seperti didiskusikan pada bagian selanjutnya.


Gambar 5.9 DataInputStream memilter suatu aliran byte masukan menjadi nilai tipe primitif dan string


Gambar 5.10 DataOutputStream memampukan Anda untuk menulis nilai tipe primitif dan string ke dalam suatu aliran keluaran


Karakter dan String di dalam I/O Biner
Suatu Unicode terdiri-dari dua byte. Metode writeChar(char c) menulis Unciode atas karakter c ke keluaran. Metode writeString(String s) menulis Unicode atas setiap karakter di dalam string s ke keluaran. Metode writeBytes(String s) menulis byte rendah dari Unicode atas setiap karakter di dalam string s ke keluaran. Byte tinggi dari Unicode dibuang. Metode writeBytes cocok untuk string yang memuat karakter-karakter ASCII, karena suatu kode ASCII disimpan hanya di dalam byte rendah dari Uncode. Jika suatu string memuat karakter-karakter non-ASCII, maka Anda harus menggunakan metode writeChar untuk menulis string.

Metode writeUTF(String s) menulis dua byte dari informasi ke aliran keluaran, diikuti dengan  representasi UTF-8 termodifikasi atas setiap karakter di dalam string s. UTF-8 merupakan suatu skema pengkodean yang mengijinkan sistem untuk beroperasi dengan ASCII maupun dengan Unicode. Kebanyakan sistem operasi menggunakan ASCII. JAVA menggunakan Unicode. Himpunan karakter ASCII merupakan subhimpunan dari himpunan karakter Unicode. Karena kebanyakan aplikasi hanya membutuhkan himpunan karakter ASCII, hal yang percuma untuk merepresentasikan suatu karakter ASCII 8-bit sebagai suatu karakter Unicode 16-bit. Skema pengkodean UTF-8 termodifikasi menyimpan suatu karakter menggunakan satu, dua, tiga byte. Karakter-karakter dikodekan dalam satu byte jika kodenya lebih rendah atau sama dengan 0x7F, dalam dua byte jika kodenya lebih besar dari 0x7F dan lebih kecil dari atau sama dengan 0x7FF, dalam tiga byte jika kodenya lebih besar dari 0x7FF.

Biti-bit awal dari suatu karakter UTF-8 mengindikasikan apakah suatu karakter disimpan dalam satu byte, dua byte, atau tiga byte. Jika bit pertama adalah 0, maka karakternya disimpan dalam satu byte. Jika bit-bit pertama adalah 110, maka maka karakternya disimpan dalam dua byte. Jika bit-bit pertama adalah 1110, maka maka karakternya disimpan dalam tiga byte. Informasi yang mengindikasikan jumlah karakter di dalam suatu string disimpan dalam dua byte pertama yang mendahului karakter-karakter UTF-8. Sebagai contoh, writeUTF(“ABCDEF”) sebenarnya menulis delapan byte (00 06 41 42 43 44 45 46) ke dalam file, karena dua byte pertama menyimpan jumlah karakter di dalam string.

Metode writeUTF(String s) mengkonversi suatu string menjadi suatu runtun byte di dalam format UTF-8 dan menuliskannya ke dalam suatu aliran biner. Metode readUTF() membaca suatu string yang telah ditulis menggunakan metode writeUTF().

Format UTF-8 memiliki keuntungan karena menghemat satu byte untuk setiap karakter ASCII, karena karakter Unicode memerlukan dua byter dan suatu karakter ASCII dalam UTF-8 hanya memerlukan satu byte. Jika kebanyakan karakter di dalam suatu string panjang adalah karakter-karakter ASCII biasa, maka penggunaan UTF-8 sangat efisien.


Menggunakan DataInputStream/DataOutputStream
Aliran masukan dan aliran keluaran diciptakan menggunakan konstruktor-konstruktor berikut ini (lihat Gambar 5.9 dan 5.10):

public DataInputStream(InputStream instream)
public DataOutputStream(OutputStream outstream)

Statemen-statemen berikut menciptakan aliran-aliran data. Statemen pertama menciptakan suatu aliran masukan untuk file in.dat; statemen kedua menciptakan suatu aliran keluaran untuk file out.dat:

DataInputStream input =
new DataInputStream (new FileInputStream("in.dat"));
DataOutputStream output =
new DataOutputStream (new FileOutputStream("out.dat"));

Kode5.2 nama dan skor mahasiswa ke dalam suatu file bernama temp.dat dan membaca data kembali dari file itu.

Kode5.2 UjiAliranData.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.io.*;

public class UjiAliranData {
  public static void main(String[] args) throws IOException {
    // Menciptakan suatu aliran keluaran untuk file temp.dat
    DataOutputStream keluaran =
      new DataOutputStream(new FileOutputStream("temp.dat"));

    // Menulis nama dan skor mahasiswa ke file
    keluaran.writeUTF("Robert");
    keluaran.writeDouble(85.5);
    keluaran.writeUTF("Sintong");
    keluaran.writeDouble(185.5);
    keluaran.writeUTF("Rico");
    keluaran.writeDouble(105.25);

    // Menutup aliran keluaran
    keluaran.close();

    // Menciptakan suatu aliran masukan untuk file temp.dat
    DataInputStream masukan =
      new DataInputStream(new FileInputStream("temp.dat"));
   
    // Membaca nama skor mahasiswa dari file
    System.out.println(masukan.readUTF() + " " + masukan.readDouble() );
    System.out.println(masukan.readUTF() + " " + masukan.readDouble());
    System.out.println(masukan.readUTF() + " " + masukan.readDouble());
  }
}

Keluaran

Robert 85.5
Sintong 185.5
Rico 105.25

Suatu DataOutputStream diciptakan untuk file temp.dat pada baris 6-7. Nama dan skor mahasiswa ditulis ke dalam file pada baris 10-15. Baris 18 menutup aliran keluaran. Suatu DataInputStream diciptakan untuk file yang sama pada baris 21-22. Nama dan skor mahasiswa dibaca kembali dari file dan ditampilkan pada konsol pada baris 25-27.

DataInputStream dan DataOutputStream membaca dan menulis nilai tipe primitif dan string JAVA dalam teknik bebas-mesin, sehingga memampukan Anda untuk menulis suatu file data pada suatu mesin dan membacanya pada mesin yang lain yang memiliki sistem operasi atau struktur file yang berbeda. 


Mendeteksi Akhir Suatu File
Jika Anda tetap membaca data pada akhir dari suatu InputStream, maka EOFException akan terjadi. Eksepsi ini digunakan untuk mendeteksi akhir dari suatu file, seperti tertampil pada kode5.3.

Kode5.3 DeteksiAkhirFile.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.io.*;

public class DeteksiAkhirFile {
  public static void main(String[] args) {
    try {
      DataOutputStream keluaran = new DataOutputStream
        (new FileOutputStream("uji.dat"));
      keluaran.writeDouble(4.5);
      keluaran.writeDouble(43.25);
      keluaran.writeDouble(3.2);
      keluaran.close();

      DataInputStream masukan = new DataInputStream
        (new FileInputStream("uji.dat"));
      while (true) {
        System.out.println(masukan.readDouble());
      }
    }
    catch (EOFException ex) {
      System.out.println("Semua data terbaca");
    }
    catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

Keluaran

4.5
43.25
3.2
Semua data terbaca

Program menulis tiga nilai double ke dalam file menggunakan DatOutputStream (baris 6-10), dan membaca data menggunakan DataInputStream (baris 13-17). Ketika pembacaan melewati akhir file, suatu eksepsi EOFException dilempar. Eksepsi ini ditangkap pada baris 19.

5.4.4 BufferedInputStream/BufferedOutputStream
BufferedInputStream/BufferedOutputStream dapat digunakan untuk mempercepat masukan dan keluaran dengan mengurangi jumlah pembacaan dan penulisan. BufferedInputStream/BufferedOutputStream tidak memuat metode-metode baru. Semua metode di dalam BufferedInputStream/BufferedOutputStream diwariskan dari kelas InputStream/OutputStream. BufferedInputStream /Buffered- OutputStream menambah suatu penyangga di dalam aliran untuk menyimpan byte-byte agar pemrosesan berjalan lebih efisien.

Anda bisa menciptakan objek BufferedInputStream/BufferedOutputStream menggunakan konstruktor-konstruktor, seperti tertampil pada Gambar 5.11 dan Gambar 5.12.


Gambar 5.11 BufferedInputStream menyangga aliran masukan


Gambar 5.12 BufferedOutputStream menyangga aliran keluaran


Jika tidak ada ukuran penyangga yang ditentukan, ukuran defaultnya adalah 512 byte. Suatu aliran masukan tersangga membaca sebanyak mungkin data ke dalam penyangga di dalam suatu pemanggilan tunggal. Sebaliknya, suatu aliran keluaran tersangga memanggil metode write hanya bila penyangga penuh atau bila metode flush() dipanggil.

Anda dapat memperbaiki kinerja program UjiAliranData.java dalam contoh terdahulu dengan menambahkan penyangga-penyangga di dalam aliran pada baris 6-7 dan 13-14 sebagai berikut:

DataOutputStream keluaran = new DataOutputStream(
  new BufferedOutputStream (new FileOutputStream("temp.dat")));

DataInputStream masukan = new DataInputStream(
  new BufferedInputStream (new FileInputStream("temp.dat")));


5.5 Masalah: Menyalin File
Bagian ini akan mengembangkan suatu program untuk menyalin file. Pengguna perlu menyediakan suatu file sumber dan suatu file target sebagai argumen-argumen command-line menggunakan perintah sebagai berikut:

java Salin sumber target

Program menyalin suatu file sumber ke file target dan menambilkan jumlah byte yang ada di dalam file. Jika file sumber tidak ada, maka pengguna diberitahu bahwa file tidak dapat ditemukan. Jika file target sudah ada, maka pengguna diberitahu bahwa file tersebut ada. Contoh keluaran program ditampilkan pada Gambar 4.13 berikut:

Gambar 5.13 Program menyalin suatu file


Untuk menyalin isi dari file sumber ke file target, sangat cocok bila menggunakan aliran masukan biner untuk membaca byte dari file sumber dan aliran keluaran biner untuk mengirim byte ke file target, apapun isi file sumber tersebut. File sumber dan file target ditentukan dari command line. Suatu InputFileStream diciptakan untuk file sumber dan suatu OutputFileStream diciptakan untuk file target. Metode read() dipakai untuk membaca suatu byte dari aliran masukan, dan metode write(b) digunakan untuk menulis byte ke aliran keluaran. BufferedInputStream dan BufferedOutputStream digunakan untuk memperbaiki kinerja. Kode5.4 memberikan solusi terhadap masalah ini.


Kode5.4 Salin.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
9
40
41
42
43
44
45
46
47
48
49
50
51
52
import java.io.*;

public class Salin {
  /** Metode utama
    @param args[0] untuk filesumber
    @param args[1] untuk filetarget
  */
  public static void main(String[] args) throws IOException {
    // Memeriksa penggunaan parameter command-line
    if (args.length != 2) {
      System.out.println(
        "Usage: java Salin fileSumber fileTarget");
      System.exit(0);
    }

    // Memeriksa apakah file sumber ada
    File fileSumber = new File(args[0]);
    if (!fileSumber.exists()) {
      System.out.println("Source file " + args[0] + " not exist");
      System.exit(0);
    }

    // Memeriksa apakah file target ada
    File fileTarget = new File(args[1]);
    if (fileTarget.exists()) {
      System.out.println("Target file " + args[1] + " already
       exists");
      System.exit(0);
    }

    // Menciptakan suatu aliran masukan
    BufferedInputStream masukan =
      new BufferedInputStream(new FileInputStream(fileSumber));

    // Menciptakan suatu aliran keluaran
    BufferedOutputStream keluaran =
      new BufferedOutputStream(new FileOutputStream(fileTarget));

    // Secara kontinyu membaca suatu byte dari aliran masukan,menulisnya ke aliran keluaran
    int r; int jumlahByteDisalin = 0;
    while ((r = masukan.read()) != -1) {
      keluaran.write((byte)r);
      jumlahByteDisalin++;
    }

    // Menutup aliran-aliran
    masukan.close();
    keluaran.close();

    // Menampilkan hasil
    System.out.println(jumlahByteDisalin + " bytes copied");
  }
}

Keluaran

Usage: java Salin fileSumber fileTarget

Program pertama-tama memeriksa apakah pengguna telah melewatkan dua argumen yang diperlukan dari command line pada baris 10-14.

Program menggunakan kelas File untuk memeriksa apakah file sumber dan file target sudah atau belum. Jika file sumber belum ada (baris 18-21) atau jika file target sudah ada, maka program akan berhenti.

Suatu aliran masukan diciptakan menggunakan BufferedInputStream pada baris 32-33, dan suatu aliran keluaran diciptakan menggunakan BufferedOutputStream pada baris 36-37.

Ekspresi ((r = masukan.read()) != -1) (baris 41) membaca suatu byte dari masukan.read(), menugaskannya kepada r, dan memeriksa apakah r bernilai -1. Nilai masukan -1 menandakan akhir suatu file. Program secara kontinyu membaca byte demi byte dari aliran masukan dan mengirimkannya ke aliran keluaran sampai semua byte terbaca.


5.6 I/O Object
DataInputStream/DataOutputStream memampukan Anda untuk melakukan operasi I/O untuk nilai tipe primitif dan string. ObjectInputStream/ObjectOutputStream memampukan Anda untuk melakukan I/O untuk objek, nilai primitif, dan string. Karena ObjectInputStream/ObjectOutputStream memuat semua fungsi dari DataInputStream/DataOutputStream, Anda bisa menggantikan DataInputStream /DataOutputStream dengan ObjectInputStream/ObjectOutputStream.

ObjectInputStream mewarisi InputStream dan mengimplementasikan ObjectInput dan ObjectStreamConstants, seperti tertampil pada Gambar 5.14. ObjectInput merupakan sub-antarmuka dari DataInput. DataInput ditampilkan pada Gambar 5.9. ObjectStreamConstants memuat konstanta-konstanta untuk mendukung ObjectInputStream/ObjectOutputStream.

ObjectOutputStream mewarisi OutputStream dan mengimplementasikan ObjectOutput dan ObjectStreamConstants, seperti tertampil pada Gambar 5.15. ObjectOutput merupakan sub-antarmuka dari DataOutput. DataOutput ditampilkan pada Gambar 5.10.
  

Gambar 5.14 ObjectInputStream dapat membaca objek, nilai primitif, dan string


Gambar 5.15 ObjectOutputStream dapat membaca objek, nilai primitif, dan string


Kode5.5 menulis nama, skor mahasiswa, dan tanggal saat ini ke dalam suatu file bernama objek.dat.

Kode5.5 UjiAliranKeluaranObjek.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.*;

public class UjiAliranKeluaranObjek {
  public static void main(String[] args) throws IOException {
    // Menciptakan suatu aliran keluaran untuk fil objek.dat
    ObjectOutputStream keluaran =
      new ObjectOutputStream(new FileOutputStream("objek.dat"));

    // Menulis suatu string, nilai double, dan objek ke dalam file
    keluaran.writeUTF("Butet");
    keluaran.writeDouble(85.5);
    keluaran.writeObject(new java.util.Date());

    // Menutup aliran keluaran
    keluaran.close();
  }
}

Suatu ObjectOutputStream diciptakan untuk menulis data ke dalam file objek.dat pada baris 6-7. Suatu string, nilai double, dan objek ditulis ke file pada baris 10-12. Untuk meningkatkan kinerja, Anda bisa menambahkan suatu penyangga di dalam aliran menggunakan statemen berikut menggantikan baris 6-7:

ObjectOutputStream keluaran = new ObjectOutputStream(
  new BufferedOutputStream(new FileOutputStream("objek.dat")));

Objek jamak atau nilai primitif jamak dapat ditulis ke aliran. Objek-objek dapat dibaca kembali dari ObjectInputStream terkait dengan tipe sama dan dengan urutan sama seperti ketika ditulis. Casting pada JAVA perlu dilakukan untuk mendapatkan tipe yang diinginkan. Kode5.6 membaca data kembali dari file objek.dat.

Kode5.6 UjiAliranMasukanObjek.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.*;

public class UjiAliranMasukanObjek {
  public static void main(String[] args)
    throws ClassNotFoundException, IOException {
    // Menciptakan suatu aliran masukan untuk file objek.dat
    ObjectInputStream masukan =
      new ObjectInputStream(new FileInputStream("objek.dat"));

    // Membaca suatu string, nilai double, dan objek dari file
    String nama = masukan.readUTF();
    double skor = masukan.readDouble();
    java.util.Date tanggal = (java.util.Date)(masukan.readObject());
    System.out.println(nama + " " + skor + " " + tanggal);

    // Menutup aliran keluaran
    masukan.close();
  }
}

Keluaran

Butet 85.5 Tue Dec 04 17:41:57 ICT 2012


5.6.1 Antarmuka Serializable
Tidak semua objek dapat ditulis ke suatu aliran keluaran. Objek yang dapat ditulis seperti itu dikatakan dengan serializable atau terserialkan. Suatu objek terserialkan merupakan instans dari antarmuka java.io.Serializable, sehingga  objek kelas harus mengimplementasikan Serializable.

Antarmuka Serializable adalah antarmuka penanda. Karena tidak memiliki metode, Anda tidak perlu menambahkan kode tambahan di dalam kelas Anda yang mengimplementasikan Serializable. Mengimplementasikan antarmuka ini memampukan mekanisme serialisasi JAVA untuk mengotomasi proses penyimpanan objek dan array.  

Untuk menghargai keberadaan fitu otomasi ini, Anda perlu memakainya pada saat akan menyimpan objek. Dimisalkan Anda akan menyimpan suatu objek JButton. Untuk melakukannya, Anda perlu menyimpan semua nilai-nilai properti saat ini (misalnya, warna, huruf, teks, dan penyejajaran) di dalam objek. Karena JButton merupakan subkelas dari AbstractButton, nilai-nilai properti AbstractButton harus pula disimpan berikut dengan nilai-nilai properti semua superkelas dari AbstractButton. Jika suatu properti bertipe objek (misalnya, background dengan tipe Color), maka menyimpannya sama dengan menyimpan semua nilai-nilai properti di dalam objek tersebut. Seperti Anda bisa perhatikan, hal ini merupakan proses yang sangat melelahkan. Untungnya, Anda tidak perlu melakukannya secara manual. JAVA menyediakan suatu mekanisme built-in untuk mengotomasi proses penulisan objek. Proses ini dikenal dengan serialisasi objek,  yang diimplementasikan di dalam ObjectOutputStream. Sebaliknya, proses pembacaan objek dikenal dengan deserialisasi objek, yang diimplementasikan di dalam ObjectInputStream.

Banyak kelas di dalam JAVA API mengimplementasikan Serializable. Kelas-kelas utilitas, seperti java.util.Date, dan semua kelas-kelas komponen GUI Swing mengimplementasikan Serializable. Mencoba untuk menyimpan suatu objek yang tidak mengimplementasikan antarmuka Serializable akan menyebabkan eksepsi NotSerializableException.

Ketika suatu objek terserialkan disimpan, kelas dari objek tersebut dienkodekan; termasuk nama kelas, sidik kelas, dan nilai-nilai variabel instans objek. Sedangkan nilai-nilai variabel statik di dalam objek tidak disimpan.


5.6.2 Membuat Array Terserialkan
Suatu array terserialkan bila semua elemennya terserialkan. Keseluruhan array dapat disimpan menggunakan writeObject ke dalam suatu file dan kemudian direstorasi menggunakan readObject. Kode5.7 menyimpan suatu array yang memuat lima nilai int dan suatu array yang memuat tiga string dan membacanya kembali untuk ditampilkan pada konsol.

Kode5.7 UjiAliranObjekUntukArray.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.io.*;

public class UjiAliranObjekUntukArray {
  public static void main(String[] args)
      throws ClassNotFoundException, IOException {
    int[] angka = {1, 2, 3, 4, 5};
    String[] string = {"Robert", "Sintong", "Rico"};

    // Menciptakan suatu aliran keluaran untuk file array.dat
    ObjectOutputStream keluaran =
      new ObjectOutputStream(new FileOutputStream
       ("array.dat", true));

    // Menulis array ke aliran keluaran objek
    keluaran.writeObject(angka);
    keluaran.writeObject(string);

    // Menutup aliran
    keluaran.close();

    // Menciptakan suatu aliran masukan untuk file array.dat
    ObjectInputStream masukan =
     new ObjectInputStream(new FileInputStream("array.dat"));

    int[] angkaBaru = (int[])(masukan.readObject());
    String[] stringBaru = (String[])(masukan.readObject());

    // Menampilkan array
    for (int i = 0; i < angkaBaru.length; i++)
      System.out.print(angkaBaru[i] + " ");
      System.out.println();

    for (int i = 0; i < stringBaru.length; i++)
      System.out.print(stringBaru[i] + " ");
  }
}

Keluaran

1 2 3 4 5
Robert Sintong Rico


5.7 File Akses-Acak
Semua aliran yang telah Anda gunakan sejauh ini dikenal sebagai aliran hanya-baca atau hanya-tulis. File-file eksternal dari aliran-aliran ini adalah file-file sekuensial yang tidak bisa diperbarui tanpa menciptakan suatu file baru. Sangat sering diperlukan untuk memodifikasi file atau untuk menyisipkan sepotong data baru ke dalam file. JAVA menyediakan kelas RandomAccessFile untuk mengijinkan suatu file dibaca dari atau ditulis ke lokasi-lokasi acak.

Kelas RandomAccessFile mengimplementasikan antarmuka DataInput dan DataOutput, seperti tertampil pada Gambar 5.16. Antarmuka DataInput yang ditampilkan pada Gambar 5.9 mendefinisikan metode-metode (misalnya, readInt, readDouble, readChar, readBoolean, readUTF) untuk membaca nilai primitif dan string.


Gambar 5.16 RandomAccessFile mengimplementasikan antarmuka DataInput dan DataOutput dengan metode-metode tambahan untuk mendukung akses acak


Ketika menciptakan suatu RandomAccessFile, Anda harus menentukan satu dari dua mode (“r” atau “rw”). Mode “r” berarti bahwa aliran bersifat hanya-baca, mode “rw” berarti bahwa aliran mengijinkan pembacaan dan penulisan. Sebagai contoh, statemen berikut ini menciptakan suatu aliran baru, raf, yang mengijinkan program untuk membaca dari dan menulis ke file uji.dat:

RandomAccessFile raf = new RandomAccessFile("uji.dat", "rw");

Jika file uji.dat telah ada, maka raf diciptakan untuk mengaksesnya; jika uji.dat belum ada, maka suatu file bernama uji.dat diciptakan, dan raf  diciptakan untuk mengakses file baru tersebut. Metode raf.length() mengembalikan jumlah byte di dalam file uji.dat. Jika Anda menyisipkan data baru pada file uji.dat, maka nilai raf.length() akan bertambah.

Suatu file akses-acak terdiri-dari suatu runtun byte. Penanda khusus yang disebut dengan pointer file diposisikan  pada salah satu dari byte yang ada. Operasi penulisan dan pembacaan terjadi pada lokasi pointer file. Ketika suatu file dibuka, pointer file ditetapkan di awal file. Ketika Anda membaca dari atau menulis data ke file, pointer file bergerak maju ke item data selanjutnya. Sebagai contoh, jika Anda membaca suatu nilai int di menggunakan readInt(), maka JVM membaca 4 byte dari pointer file, dan sekarang pointer file lebih maju 4 byte daripada lokasi sebelumnya, seperti tertampil pada Gambar 5.17.

Untuk suatu RandomAccessFile raf, Anda dapat menggunakan metode  raf.seek(position) untuk menggerakkan pointer file ke lokasi tertentu. raf.seek(0) akan menggerakkan pointer file ke awal file,dan raf.seek(raf.length()) akan menggerakkan pointer file ke akhir file. Kode5.8 mendemonstrasikan RandomAccessFile.



Gambar 5.17 Setelah suatu nilai int dibaca, pointer file bergerak maju 4 byte


Kode5.8 UjiRandomAccessFile.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.io.*;

public class UjiRandomAccessFile {
  public static void main(String[] args) throws IOException {
    // Menciptakan suatu file akses-acak
    RandomAccessFile inout = new RandomAccessFile("inout.dat", "rw");

    // Membersihkan file dengan menghancurkan isi semula, jika ada
    inout.setLength(0);

    // Menulis integer-integer baru ke dalam file
    for (int i = 0; i < 200; i++)
      inout.writeInt(i);

    // Menampilkan panjang file saat ini
    System.out.println("Panjang file saat ini adalah "+ inout.length());

    // MengambilRetrieve the first number
    inout.seek(0);//angka pertama
    System.out.println("Angka pertama adalah "+ inout.readInt());

    // Mengambil angka kedua
    inout.seek(1 * 4);// Menggerakkan pointer file ke angka kedua
    System.out.println("Angka kedua adalah "+ inout.readInt());

    // Mengambil angka kesepuluh
    inout.seek(9 * 4);// Menggerakkan pointer file ke angka kesepuluh
    System.out.println("Angka kesepuluh adalah "+ inout.readInt());

    // Memodifikasi angka kesebelas
    inout.writeInt(555);

    // Menyambungkan suatu angka baru
    inout.seek(inout.length());// Menggerakkan pointer file ke akhir file
    inout.writeInt(999);

    // Menampilkan panjang baru
    System.out.println("Panjang baru file adalah "+ inout.length());

    // Mengambil angka kesebelas yang baru
    inout.seek(10 * 4);// Menggerakkan pointer file ke angka selanjutnya
    System.out.println("Angka kesebelas adalah "+ inout.readInt());

    inout.close();
  }
}

Keluaran

Panjang file saat ini adalah 800
Angka pertama adalah 0
Angka kedua adalah 1
Angka kesepuluh adalah 9
Panjang baru file adalah 804
Angka kesebelas adalah 555

Suatu RandomAccessFile diciptakan untuk file bernama inout.dat dengan mode “rw”untuk mengijnkan operasi pembacaan dan penulisan pada file.

inout.setLength(0) menetapkan panjang file menjadi 0 pada baris 9. Ini akan menghapus semua isi semula dari file tersebut.

Loop for menulis 200 int mulai 0 sampai 199 ke dalam file pada baris 12-13. Karena setiap int memerlukan 4 byte, maka panjang total file sekarang yang didapatkan dari inout.length() adalah 800 (baris 16), seperti ditunjukkan pada keluaran program.

Pemanggilan inout.seek(0) pada baris 19 menetapkan pointer file ke awal file. inout.readInt() membaca nilai pertama pada baris 20 dan menggerakkan pointer file ke angka kedua. Angka kedua dibaca pada baris 23.

inout.seek(9*4) pada baris 27 menggerakkan pointer file ke angka kesepuluh. inout.readInt() membaca angka kesepuluh dan menggerakkan pointer file ke angka kesebelas pada baris 28. inout.write(555) menulis angka kesebelas yang baru pada posisi saat ini (baris 31). Angka kesebelas sebelumnya telah dihapus.

inout.seek(inout.length()) menggerakkan pointer file ke akhir file pada baris 34. inout.write(999) menulis 999 pada akhir file. Sekarang panjang file bertambah 4, sehingga inout.length() mengembalikan 804 pada baris 38.

inout.seek(10*4) menggerakkan pointer ke angka kesebelas pada baris 41. Angka kesebelas yang baru, 555, ditampilkan pada baris 42.




No comments:

Post a Comment