Hướng dẫn sử dụng luồng vào ra nhị phân trong Java
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Tổng quan luồng vào ra nhị phân

Luồng nhị phân dẫn đầu bởi 2 class InputStream OutputStream dưới 2 class này là rất nhiều các class con , nếu so sánh về lực lượng theo cách này mà nói thì họ hàng luồng nhị phân nhiều và phức tạp hơn hẳn so với họ hàng luồng ký tự.Với JDK1.5 trong luồng nhị phân có 2 class được khuyến cáo là không nên sử dụng là LineNumberInputStream StringBufferInputStream vì đã bị lỗi thời .

2- Class InputStream & OutputStream

Class InputStream là một class trìu tượng vì vậy bạn không thể khởi tạo đối tượng InputStream thông qua chính class InputStream .Tuy nhiên class này rẽ ra nhiều nhánh thông qua các class con thừa kế nó.Tùy vào các tình huống bạn có thể tạo đối tượng InputStream từ các cấu tử của các class con.
// java.io.InputStream là một class trìu tượng (abstract class)
// Không thể khởi tạo trực tiếp đối tượng InputStream thông qua class InputStream
// Nên khởi tạo đối tượng InputStream thông qua các class con của nó ..

InputStream fileStream =new FileInputStream("C:/test.txt");

// Luồng đầu vào từ bàn phím..
InputStream is = System.in;
Class OutputStream là một class trìu tượng vì vậy bạn không thể khởi tạo đối tượng OutputStream thông qua chính class OutputStream .Tuy nhiên class này rẽ ra nhiều nhánh thông qua các class con thừa kế nó và quan trọng .Tùy vào các tình huống bạn có thể tạo đối tượng InputStream từ cấu tử của các class con.
// java.io.OutputStream là một class trìu tượng (abstract class)
// Không thể khởi tạo trực tiếp đối tượng OutputStream thông qua class OutputStream
// Nên khởi tạo đối tượng OutputStream thông qua các class con của nó ..

// Luồng ghi dữ liệu vào file
OutputStream os=new FileOutputStream("D:/outData.txt");

// Luồng ghi ra màn hình Console.
OutputStream w=System.out;
  • HelloInputStream.java
package org.o7planning.tutorial.javaio.stream;

import java.io.FileInputStream;
import java.io.InputStream;

public class HelloInputStream {

   public static void main(String[] args) {
       try {

           // Tạo một đối tượng InputStream theo class con của nó.
           // Đây là luồng đọc một file.
           InputStream is = new FileInputStream("data.txt");

           int i = -1;

           // Đọc lần lượt các byte trong luồng.
           // Mỗi lần đọc ra 8bit, chuyển nó thành số int.
           // Khi đọc ra giá trị -1 nghĩa là kết thúc luồng.

           while ((i = is.read()) != -1) {
               System.out.println(i + "  " + (char) i);
           }
           is.close();
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
}
Kết quả:
  • HelloOutputStream.java
package org.o7planning.tutorial.javaio.stream;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;

public class HelloOutputStream {

   public static void main(String[] args) {
       try {
           File dir = new File("C:/Test");
           // Tao thu muc C:/Test
           dir.mkdirs();
           // Tạo một luồng ký tự đầu ra với mục đích ghi thông tin vào file
           OutputStream w = new FileOutputStream(
                   "C:/Test/test_outputStream.txt");
           
           
           // Tạo một mảng byte ,ta sẽ ghi các byte này vào file nói trên .
           byte[] by = new byte[] { 'H', 'e', 'l', 'l', 'o' };

           // Ghi lần lượt các ký tự vào luồng
           for (int i = 0; i < by.length; i++) {
               byte b = by[i];
               // Ghi ký tự vào luồng
               w.write(b);
           }
           // Đóng luồng đầu ra lại việc ghi xuống file hoàn tất.
           w.close();
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
}
Kết quả:
2 Ví dụ phía trên là đơn giản, chúng đọc hoặc ghi từng byte, trong ví dụ tiếp theo chúng ta sẽ đọc hoặc ghi đồng  loạt nhiều byte, việc này làm tăng tốc việc sử lý.
  • InputStreamExample2.java
package org.o7planning.tutorial.javaio.stream;

import java.io.FileInputStream;
import java.io.InputStream;

public class InputStreamExample2 {

   public static void main(String[] args) {
       try {
           // Tạo một luồng đầu vào bằng cách đọc một file
           InputStream in = new FileInputStream("data.txt");

           // Mảng để mỗi lần đọc các byte từ luồng thì tạm thời để lên đó
           // Ta dùng mảng 10 byte

           byte[] temp = new byte[10];
           int i = -1;

           // Đọc các byte trong luồng và gán lên các phần tử của mảng.
           // Giá trị i là số đọc được của 1 lần. (i sẽ <= 10).
           // Khi không còn phần tử trong luồng i sẽ = -1
           while ((i = in.read(temp)) != -1) {
               // Tạo String từ các byte đọc được
               String s = new String(temp, 0, i);
               System.out.println(s);
           }
           in.close();
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
}
Đây là hình ảnh minh họa các lần đọc đồng loạt của ví dụ trên:
  • OutputStreamExample2.java
package org.o7planning.tutorial.javaio.stream;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;

public class OutputStreamExample2 {

   public static void main(String[] args) {
       try {
           File dir = new File("C:/Test");
           // Tao thu muc C:/Test
           dir.mkdirs();            
           
           // Tạo một luồng nhị phân đầu ra với mục đích ghi thông tin vào file
           OutputStream os = new FileOutputStream("C:/Test/test_writerOutputStream.txt");
           
           // Tạo một mảng byte ,ta sẽ ghi các byte này vào file nói trên .
           byte[] by = new byte[] { 'H', 'e', 'l', 'l', 'o', ' ', 31, 34, 92 };
           byte[] by2 = new byte[] { 'H', 'e', 'l', 'l', 'o', ' ', 'b', 'o',
                   'y' };
           
           // Ghi cả các byte trong mảng byte[] by vào luồng
           os.write(by);
           
           // Đẩy các byte hiện có trên luồng xuống file .
           os.flush();
           
           // Tiếp tục ghi các byte trong mảng thứ 2 vào luồng
           os.write(by2);
           
           // Đóng luồng vào công việc ghi thành công .
           os.close();
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
}

3- Class ByteArrayInputStream & ByteArrayOutputStream

ByteArrayInputStream bao bọc một mảng các byte (byte[] buf) và thông qua ByteArrayInputStream truy cập phần tử mảng ..
 
ByteArrayOutputStream là một luồng các byte, bên trong đối tượng này chứa một mảng các byte (byte[] buf) có khả năng tự tăng kích cỡ khi số byte của luồng tăng lên.Mỗi khi luồng được ghi vào các byte thì chính là gán tiếp byte đó vào các vị trí mảng chưa được gán ..
  •  Khi mảng đầy phần tử thì chương trình tạo mảng mới có độ dài lớn hơn và copy các phần tử của mảng cũ vào ...(Đó là cách tự lớn lên của mảng ký tự như đã nói trên)
Vài method của ByteArrayOutputStream:
 
// Mảng các byte chứa các byte của luồng ..
  - byte[] toByteArray();

// Chuyển về một String mô tả dẫy các byte trong luồng.
  - String toString() ;

// Trả về số vị trí được gán của mảng byte[] buf .
  - int size();
  • ByteArrayInputStreamExample.java
package org.o7planning.tutorial.javaio.bytestream;

import java.io.ByteArrayInputStream;
import java.io.IOException;

public class ByteArrayInputStreamExample {

   public static void main(String args[]) throws IOException {

       // Một mảng byte.
       byte[] bytes = new byte[] { 'H', 'e', 'l', 'l', 'o', ' ', 'I', 'O' };

       // Sử dụng ByteArrayInputStream để đọc dữ liệu mảng trên.
       ByteArrayInputStream bInput = new ByteArrayInputStream(bytes);

       System.out.println("Converting characters to Upper case ");
       int c = 0;

       // Đọc lần lượt các byte trong luồng.
       // Con trỏ sẽ di chuyển từ đầu mảng tới cuối mảng.
       // Mỗi lần đọc một byte con trỏ sẽ tiến 1 bước về cuối.
       while ((c = bInput.read()) != -1) {
           char ch = (char) c;
           ch = Character.toUpperCase(ch);
           System.out.println(ch);
       }

       // Kiểm tra xem stream này có hỗ trợ đánh dấu (mark) không.
       boolean markSupport = bInput.markSupported();

       System.out.println("Mark Support? " + markSupport);

       // Đưa con trỏ về vị trí mặc định
       // Trong ví dụ này nó sẽ đưa về vị trí 0.
       bInput.reset();

       char ch = (char) bInput.read();
       System.out.println(ch);

       // Đọc byte kế tiếp
       ch = (char) bInput.read();
       System.out.println(ch);

       System.out.println("Skip 4");
       // Nhẩy qua 4 vị trí
       bInput.skip(4);
       ch = (char) bInput.read();
       System.out.println(ch);

   }
}
Kết quả
  • ByteArrayOutputStreamExample.java
package org.o7planning.tutorial.javaio.bytestream;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class ByteArrayOutputStreamExample {

   public static void main(String args[]) throws IOException {

       // Tạo một đối tượng ByteArrayOutputStream.
       // Đối tượng chứa bên trong nó một mảng các byte.
       // Khởi tạo mảng các byte 12 phần tử.
       // Nếu số phẩn tử ghi vào luồng nhiều hơn 12 mảng sẽ được thay thế bằng
       // mảng mới có nhiều phần tử hơn, và copy các phần tử mảng cũ sang.
       ByteArrayOutputStream bOutput = new ByteArrayOutputStream(12);

       String s = "Hello ByteArrayOutputStream";

       for (int i = 0; i < s.length(); i++) {

           char ch = s.charAt(i);

           if (ch != 'a' && ch != 'e') {
               bOutput.write(ch);
           }
       }

       // Kiểm tra độ dài của luồng
       int size = bOutput.size();
       System.out.println("Size = " + size);

       byte[] bytes = bOutput.toByteArray();

       String ss = new String(bytes);

       System.out.println("New String = " + ss);
   }
}
Kết quả:

4- Class ObjectInputStream và ObjectOutputStream

ObjectInputStream, ObjectOutputStream cho phép bạn đọc hoặc ghi một Object vào luồng. Các Object này phải là kiểu Serializable (Nghĩa là có thể sắp hàng).
Sau đây là một số class ví dụ:
  • Student.java
package org.o7planning.tutorial.javaio.objstream;

import java.io.Serializable;

public class Student implements Serializable {
 
    private static final long serialVersionUID = -5074534753977873204L;

    private String firstName;

    private String lastName;

    public Student(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

}
  • Pupil.java
package org.o7planning.tutorial.javaio.objstream;

import java.io.Serializable;

public class Pupil implements Serializable {
   
    private static final long serialVersionUID = -8501383434011302991L;
    
    private String fullName;

    public Pupil(String fullName)  {
        this.fullName= fullName;
    }
    
    public String getFullName() {
        return fullName;
    }

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

}
  • ObjectOutputStreamExample.java
package org.o7planning.tutorial.javaio.objstream;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;

public class ObjectOutputStreamExample {

   public static void main(String[] args) throws IOException {
       File dir = new File("C:/Test");
       // Tao thu muc C:/Test.
       dir.mkdirs();

       // Tao mot luong ghi vào file ...
       FileOutputStream fos = new FileOutputStream(
               "C:/Test/testObjectStream.txt");

       // Tạo đối tượng ObjectOutputStream bao lấy 'fos'.
       // Những gì ghi vào luồng này sẽ được đẩy xuống 'fos'.
       ObjectOutputStream oos = new ObjectOutputStream(fos);
       
       
       // Ghi một String vào luồng.
       oos.writeUTF("This is student, pupil profiles");
       
       // Chú ý: Các đối tượng ghi được vào luồng phải
       // là kiểu Serializable.
       
       // Ghi một đối tượng Date vào luồng.
       oos.writeObject(new Date());

       Student student1 = new Student("Thanh", "Phan");
       Student student2 = new Student("Ngan", "Tran");
       Pupil pupil1 = new Pupil("Nguyen Van Ba");

       oos.writeObject(student1);
       oos.writeObject(pupil1);
       oos.writeObject(student2);

       oos.close();
       System.out.println("Write successful");
   }

}
Kết quả chạy:
Đây là hình ảnh minh họa việc ghi các Object xuống file, việc ghi là lần lượt, sau này khi đọc ra bạn cũng phải nhớ thứ tự đã ghi trước đó để đọc cho đúng.
Và ví dụ với ObjectInputStream đọc file vừa được ghi ra tại ví dụ trên:
  • ObjectInputStreamExample.java
package org.o7planning.tutorial.javaio.objstream;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Date;

public class ObjectInputStreamExample {

   public static void main(String[] args) throws IOException,
           ClassNotFoundException {

       // Tạo một luồng đọc file..
       FileInputStream fis = new FileInputStream(
               "C:/Test/testObjectStream.txt");

       // Tạo đối tượng ObjectInputStream bao lấy 'fis'.
       ObjectInputStream ois = new ObjectInputStream(fis);

       // Đọc ra đối tượng String:
       String s = ois.readUTF();

       System.out.println(s);

       // Đọc ra đối tượng Date.
       Date date = (Date) ois.readObject();
       System.out.println("Date = " + date);

       Student student1 = (Student) ois.readObject();

       System.out.println("Student " + student1.getFirstName());

       Pupil pupil = (Pupil) ois.readObject();

       System.out.println("Pupil " + pupil.getFullName());

       Student student2 = (Student) ois.readObject();

       System.out.println("Student " + student2.getFirstName());

       ois.close();
   }
}
Kết quả:

5- Class DataInputStream và DataOutputStream

  • DataInputStream
// Cấu tử
public DataOutputStream(OutputStream out)

// Ghi một ký tự 16 bit (2 byte)
public void writeChar(int val)
// Ghi một số double 64 bit (8-byte)
public void writeDouble(double val)
// Ghi một số float 32 bit (4-byte)
public void writeFloat(float val)
// Ghi một số tự nhiên 32 bit  (4-byte)
public void writeInt(int val)
// Ghi một String mã hóa dạng UTF-8.
public void writeUTF(String obj)
....
  • DataInputStream
// Cấu tử
public DataInputStream(InputStream in)

// Đọc ra một ký tự (16 bit)
public char readChar()
// Đọc ra một số double (64 bit)
public double readDouble()
// Đọc ra một số float (32 bit)
public float readFloat()
// Đọc ra một số int (16 bit)
public int readInt()
// Đọc ra một String (mã hóa dạng UTF-8).
public String readUTF()
....
  • DataOutputStreamExample.java
package org.o7planning.tutorial.javaio.datastream;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class DataOutputStreamExample {

   public static void main(String[] args) throws IOException {
       int cityIdA = 1;
       String cityNameA = "Green Lake City";
       int cityPopulationA = 500000;
       float cityTempA = 15.50f;

       int cityIdB = 2;
       String cityNameB = "Salt Lake City";
       int cityPopulationB = 250000;
       float cityTempB = 10.45f;

       File dir = new File("C:/Test");
       dir.mkdirs();

       //
       // Tạo đối tượng FileOutputStream để ghi xuống file.
       //
       FileOutputStream fos = new FileOutputStream("C:/Test/cities.txt");

       // Tạo đối tượng DataOutputStream bao lấy 'fos'.
       // Dữ liệu ghi vào 'dos' sẽ được đẩy sang 'fos'.
       DataOutputStream dos = new DataOutputStream(fos);

       //
       // Ghi các dữ liệu vào luồng.
       //
       dos.writeInt(cityIdA);
       dos.writeUTF(cityNameA);
       dos.writeInt(cityPopulationA);
       dos.writeFloat(cityTempA);

       dos.writeInt(cityIdB);
       dos.writeUTF(cityNameB);
       dos.writeInt(cityPopulationB);
       dos.writeFloat(cityTempB);

       dos.flush();
       dos.close();
   }

}
Chạy class DataOutputStreamExample và nhận được một file dữ liệu được ghi ra.
  • DataInputStreamExample.java
package org.o7planning.tutorial.javaio.datastream;

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class DataInputStreamExample {
   
   
   
   public static void main(String[] args) throws IOException {
       
        // Luồng đọc dữ liệu từ file.
        FileInputStream fis = new FileInputStream("C:/Test/cities.txt");
        // Tạo đối tượng DataInputStream bao lấy 'fis'.
        DataInputStream dis = new DataInputStream(fis);

        //
        // Đọc dữ liệu.
        //
        int cityId1 = dis.readInt();
        System.out.println("Id: " + cityId1);
        String cityName1 = dis.readUTF();
        System.out.println("Name: " + cityName1);
        int cityPopulation1 = dis.readInt();
        System.out.println("Population: " + cityPopulation1);
        float cityTemperature1 = dis.readFloat();
        System.out.println("Temperature: " + cityTemperature1);

        //
        // Đọc dữ liệu.
        //
        int cityId2 = dis.readInt();
        System.out.println("Id: " + cityId2);
        String cityName2 = dis.readUTF();
        System.out.println("Name: " + cityName2);
        int cityPopulation2 = dis.readInt();
        System.out.println("Population: " + cityPopulation2);
        float cityTemperature2 = dis.readFloat();
        System.out.println("Temperature: " + cityTemperature2);
       
        dis.close();
   }
}
Kết quả:

6- Dẫy luồng đầu vào nhị phân - SequenceInputStream

Thông thường bạn đã quen thuộc với việc đọc một file nào đó và thu được một luồng đầu vào .Nhưng trong thực tế đôi khi bạn cần đọc từ nhiều file và lấy các dữ liệu đó ghép với nhau để ghi thành 1 file khác chẳng hạn .Vậy là ý tưởng ghép nhiều luồng đầu vào với nhau để thành một luồng lớn hơn nối đuôi nhau . Chúng ta đang nói đến class java.io.SequenceInputStream . Khái niệm này không có tương ứng cho luồng đầu ra ...
// Cấu tử :

  // Tạo một luồng đầu vào từ việc ghép luồng s2 nối tiếp với s1  
   public SequenceInputStream(InputStream s1,InputStream s2)  
   
   // Tạo một luồng đầu vào từ việc ghép một tập hợp các luồng đầu vào khác.
   public SequenceInputStream(Enumeration<? extends InputStream> e)

// Một vài method :
   // - Không có gì mới so với danh sách các method thấy trong class cha InputStream .
Ví dụ:
// Một luồng nhị phân đầu vào đọc từ một file File1.txt .
InputStream is1=new FileInputStream("File1.txt");
// Luồng mới đọc từ file File2.txt  
InputStream is2=new FileInputStream("File2.txt");
// Nối luồng is2 nối tiếp với luồng is1 thành một luồng nhị phân mới .
SequenceInputStream sis=new SequenceInputStream(is1,is2);
// Thao tác trên luồng đầu vào nhị phân SequenceInputStream sis như bình thường ...

7- Luồng dữ liệu đường ngầm nhị phân (PipedInputStream,PipedOutputStream)

Đặt ra một tình huống bạn có 2 luồng một luồng đầu vào và một luồng đầu ra ...Chẳng hạn luồng đầu vào dữ liệu A đọc một file , lấy thông tin từ luồng này ghi vào luồng dữ liệu B đầu ra là một file khác .. Hai luồng A và B trong tình huống này là tách riêng nhau... Vì vậy trong ứng dụng bạn phải có 3 thao tác:
  1. Tạo luồng dữ liệu đọc A
  2. Tạo luồng ghi dữ liệu B
  3. Đọc từ A ghi vào B ...
Hai thao tác đầu phải có, nhưng bạn muốn bỏ đi thao tác thứ 3 ... nghĩa là có một cái gì đó liên hệ ngầm với nhau giữa 2 luồng (vào-ra) ,để sao cho những byte xuất hiện trên luồng đầu đọc A lập tức được ghi tự động vào B.... Đó được gọi là liên hệ đường ngầm giữa 2 luồng vào và ra ..
  • TODO

8- Class PrintStream


// PrintStream là class con trực tiếp của FilterOutputStream .
// Nó có thể bao bọc một luồng đầu ra nhị phân (OutputStream) , ..
// Cấu tử :

   // Bao bọc một luồng nhị phân
   public PrintStream(OutputStream out)

   public PrintStream(OutputStream out,boolean autoFlush)
   // Ghi thông tin vào file ..
   public PrintStream(String fileName)
   // ...(Xem javadoc) .

// Một số method
   public void println(String s)
   public void print(char ch)
   // Ghi một đối tượng vào luồng .
   public void print(Object obj)
   // Ghi một số tự nhiên 64 bit vào luồng .  
   public void print(long n)
   public PrintStream append(java.lang.CharSequence csq) .
   // ... (chi tiết xem javadoc) .
Bạn đã quen biết với bắt ngoại lệ Exception thông qua khối try ,catch .
try {    
 // Làm một điều gì đó trong khối try ...  
 // Lỗi chia cho 0
 int i=10/0;
}
// Có điều gì đó sai xót trong khi chạy khối try khối catch được chạy
catch(Exception e) {  
 // In ra lý do sai xót trong khi chạy    
 System.out.println("Error on try..."+e.getMessage());
 // In ra thông tin quá trình chạy lỗi xuất hiện ở các vị trí nào ra màn hình Console
 // Làm sao để lấy được đoạn text "stack trace" ?
 e.printStackTrace();
}
Đây là 'Stack Trace' bạn thường thấy khi có lỗi gì đó.
Ví dụ sau đây lấy ra String "Stack Trace"
  • GetStackTraceString.java
package org.o7planning.tutorial.javaio.printstream;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

public class GetStackTraceString {

   private static String getStackTraceString(Exception e) {
       // Tạo một đối tượng ByteArrayOutputStream.
       ByteArrayOutputStream baos = new ByteArrayOutputStream();

       // Những gì ghi vào 'printStream' sẽ được ghi sang 'baos'.
       PrintStream printStream = new PrintStream(baos);

       // Ghi thông tin lỗi sang 'printStream'
       e.printStackTrace(printStream);

       printStream.close();
       
       byte[] bytes = baos.toByteArray();

       String s = new String(bytes);
       return s;
   }

   public static void main(String[] args) {

       try {
           // Làm một điều gì đó trong khối try ...
           // Lỗi chia cho 0
           int i = 10 / 0;
       }
       // Có điều gì đó sai xót trong khi chạy khối try khối catch được chạy
       catch (Exception e) {
           // In ra lý do sai xót trong khi chạy
           System.out.println("Error on try..." + e.getMessage());
           // In ra thông tin quá trình chạy lỗi xuất hiện ở các vị trí nào ra
           // màn hình Console
           // Lấy được đoạn text "stack trace":
           String s = getStackTraceString(e);

           System.out.println("Stack Trace String " + s);
       }
   }
}
Đoạn code dưới đây ghi thông tin dò lỗi ra màn hình Console nhưng lại sử dụng method printStackTrace(PrintStream ps) của Exception thay vì sử dụng method printStackTrace() vốn mặc định dùng để ghi dò lỗi ra Console .
// Luồng PrintStream ghi ra màn hình Console.
PrintStream os = System.out;
// Exception e ..
// Ghi thông tin dò lỗi vào luồng PrintStream os .Hay nói cách khác ghi ra màn hình Console.
e.printStackTrace(os);

// Về bản chất nó tương đương với gọi:
e.printStackTrace();