User Tools

Site Tools


java:byte_streams_to_data

Μετασχηματισμός των ροών δυαδικών δεδομένων σε βασικούς τύπους δεδομένων

Ο προηγούμενος κώδικας δουλεύει εξαιρετικά με ροές από bytes, στην πραγματικότητα όμως τα δεδομένα που θέλουμε να αποθηκεύσουμε ή να διαβάσουμε μπορεί να είναι ακέραιοι, αριθμοί κινητής υποδιαστολής ή άλλοι βασικοί τύποι δεδομένων. Προκειμένου να μπορέσουμε να διαβάσουμε ή να γράψουμε μορφές πληροφορίας που απαιτούν περισσότερα του ενός bytes έρχεται σε βοήθεια η κλάση java.nio.ByteBuffer. Η κλάση αυτή παρέχει ένα buffer μέσω του οποίου μπορούμε να γράψουμε ή να διαβάσουμε σύνθετους τύπους πληροφορίας.

Για παράδειγμα, ας υποθέσουμε ότι έχουμε μία σειρά από δεδομένα που θέλουμε να γράψουμε με συγκεκριμένη σειρά σε ένα αρχείο, όλα σε δυαδική μορφή. Προκειμένου να το επιτύχουμε θα πρέπει να κάνουμε τα εξής:

  1. να δημιουργήσουμε ένα αντικείμενο της κλάσης java.nio.ByteBuffer ικανού μεγέθους ώστε να μπορεί να αποθηκεύσει τη συγκεκριμένη πληροφορία,
  2. να αποθηκεύσουμε τις επιμέρους πληροφορίες στο buffer με τη σειρά που θέλουμε να αποθηκευτούν.
  3. να εξάγουμε από το buffer ένα πίνακα από bytes που περιέχει τη σχετική πληροφορία και τον πίνακα αυτό να τον γράψουμε στο OutputStream που επιθυμούμε.

Η διαδικασία ανάγνωσης είναι ακριβώς η αντίστροφή, δηλαδή:

  1. διαβάζουμε από ένα InputStream την πληροφορία σε ένα πίνακα από bytes.
  2. από τον πίνακα αυτό δημιουργούμε ένα java.nio.ByteBuffer.
  3. από το java.nio.ByteBuffer εξάγουμε την πληροφορία με τη σειρά που γνωρίζουμε ότι αυτή είναι αποθηκευμένη.

Το παρακάτω πρόγραμμα καλεί αρχικά τη συνάρτηση write η οποία χρησιμοποιεί ένα java.nio.ByteBuffer για να αποθηκεύσει έναν ακέραιο, ένα double, ένα χαρακτήρα (τον ελληνικό χαρακτρήρα 'Θ' (κεφαλαίο) και ένα string (το αλφαριθμητικό “Πως είσαι;”) σε ένα αρχείο με όνομα my.bin. Στη συνέχεια, το πρόγραμμα καλεί τη συνάρτηση read η οποια ανοίγει ένα FileInputStream για διάβασμα από το συγκεκριμένο αρχείο αποθηκεύει την πληροφορία που διάβασε σε ένα byte array από αυτό δημιουργεί ένα java.nio.ByteBuffer. Το buffer αυτό το χρησιμοποιούμε για να διαβάσουμε τα στοιχεία που αποθηκεύσαμε στο αρχείο με τη σειρά που τα βάλαμε.

ByteBufferReadWriteTest.java
import java.nio.*;
import java.io.*;
import java.util.Arrays;
 
public class ByteBufferReadWriteTest {
 
  /* this method is only for debugging.
   * Comment out code, where method is called.
   */
  public static void print_array(byte[] array) {
    int i=0;
    for(byte b: array) {
      System.out.format("%x", b);
      if(++i % 2 == 0)
        System.out.print(" ");
    }
    System.out.println();
  }
 
  public static void main(String []args ) {
    File file = new File("file.bin");
    write(file);
    read(file);
  }
 
  public static void write(File file) {
    int a = -159954;
    double b = 125.128953;
    char c = 'θ';
    String str = "Πώς είσαι;";
 
    ByteBuffer buffer = ByteBuffer.allocate(512);
    buffer.order(ByteOrder.BIG_ENDIAN);
    buffer.putInt(a);
    buffer.putDouble(b);
    buffer.putChar(c);
    buffer.put(str.getBytes(java.nio.charset.StandardCharsets.UTF_8));
    int buffer_size = buffer.position();
 
    try(FileOutputStream out = new FileOutputStream(file)) {
      byte []array = buffer.array();
      array = Arrays.copyOf(array, buffer_size);
      out.write(array);
      // just for debugging purposes
      //print_array(array);
    }
    catch(IOException ex) {
      System.out.println("Cannot write to file: "+file.getName());  
    }
  }
 
  public static void read(File file) {
    int a;
    double b;
    double c;
 
    byte array[] = new byte[512];
    int read_size;
    try(FileInputStream in = new FileInputStream(file)) {
      read_size = in.read(array);
      array = Arrays.copyOf(array, read_size);
    }
    catch(IOException ex) {
       System.out.println("Cannot read from file: "+file.getName()); 
    }
    // just for debugging purposes
    //print_array(array);
 
    ByteBuffer buffer = ByteBuffer.wrap(array);
    buffer.order(ByteOrder.BIG_ENDIAN);
    System.out.println(buffer.getInt());
    System.out.println(buffer.getDouble());
    System.out.println("'"+buffer.getChar()+"'");
    int str_size = buffer.remaining();
    byte[] bytes = new byte[str_size];
    buffer.get(bytes);
    System.out.println(new String(bytes, java.nio.charset.StandardCharsets.UTF_8));
  }
}

H default υλοποίηση ενός ByteBuffer καταχωρεί τα δεδομένα ή διαβάζει τα δεδομένα κατά big-endian. Εάν θέλετε να αλλάξετε τη σειρά καταχώρησης των δεδομένων στο buffer σε little-endian, μπορείτε να χρησιμοποιήσετε τη μέθοδο order. H μέθοδος λαμβάνει ένα αντικείμενο της κλάσης java.nio.ByteOrder, το οποίο είναι ένα εκ των java.nio.ByteOrder.BIG_ENDIAN ή java.nio.ByteOrder.LITTLE_ENDIAN. Πληροφορίες για τη διαφορά μεταξύ big-endian και little-endian μπορείτε να βρείτε στο βίντεο.

  1. Αλλάξτε το παραπάνω παράδειγμα, ώστε τα δεδομένα να αποθηκεύονται κατά little-endian και όχι κατά big-endian που είναι το default.
  2. Βάλτε σε σχόλια τη στατική μέθοδο read και προσπαθήστε να διαβάσετε μέσα στη μέθοδο write από το buffer που μόλις γράψατε. (βοήθεια: θα χρειαστείτε τη μέθοδο rewind της γονικής κλάσης java.nio.Buffer.

DataInputStream & DataOutputStream

Στις περιπτώσεις που τα δεδομένα γνωρίζουμε ότι έχουν αποθηκευτεί κατά big-endian εναλλακτικά της χρήσης των παραπάνω κλάσεων μπορούμε να χρησιμοποιήσουμε τις απλούστερες κλάσεις DataInputStream και DataOutputStream. Οι κλάσεις αυτές δημιουργούν ένα InputStream μέσω του οποίου μπορούμε να διαβάσουμε τους βασικούς τύπους δεδομένων όπως byte, short, int, long, double, float κλπ.

Παρακάτω παρατίθεται το παραπάνω πρόγραμμα αλλαγμένο ώστε να χρησιμοποιεί τα DataInputStream και DataOutputStream.

DataInputOutputStreamTest.java
import java.nio.*;
import java.io.*;
 
public class DataInputOutputStreamTest {
 
  /* this method is only for debugging.
   * Comment out code, where method is called.
   */
  public static void print_array(byte[] array) {
    int i=0;
    for(byte b: array) {
      System.out.format("%x", b);
      if(++i % 2 == 0)
        System.out.print(" ");
    }
    System.out.println();
  }
 
  public static void main(String []args ) {
    File file = new File("file.bin");
    write(file);
    read(file);
  }
 
  public static void write(File file) {
    int a = -159954;
    double b = 125.128953;
    char c = 'Θ';
    String str = "Πώς είσαι;";
 
    try(DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) {
      out.writeInt(a);
      out.writeDouble(b);
      out.writeChar(c);
      byte [] bytes_str = str.getBytes(java.nio.charset.StandardCharsets.UTF_16);
      out.write(bytes_str, 0, bytes_str.length);
    }
    catch(IOException ex) {
      System.out.println("Cannot write to file: "+file.getName());  
    }
  }
 
  public static void read(File file) {
 
    try(DataInputStream in = new DataInputStream(new FileInputStream(file))) {
      int a = in.readInt();
      double b = in.readDouble();
      char c = in.readChar();
      byte [] bytes = new byte[512];
      int str_size = in.read(bytes);
 
      System.out.println(a);
      System.out.println(b);
      System.out.println("'"+c+"'");
      System.out.println(new String(bytes, 0, str_size, java.nio.charset.StandardCharsets.UTF_16));
    }
    catch(IOException ex) {
       System.out.println("Something bad happened!"); 
    }
 
  }
}

Η κλάσεις DataInputStream και DataOutputStream δεν δίνουν τη δυνατότητα να αλλάξετε το endianness των τύπων που αποθηκεύονται στο stream.

Παρατηρήστε ότι και στις δύο περιπτώσεις μπορείτε να αλλάξετε την κωδικοποίηση των Strings που αποθηκεύονται μέσα στο stream. Στα παραπάνω παραδείγματα αλλάξτε την κωδικοποίηση σε UTF-8 από UTF-16.

java/byte_streams_to_data.txt · Last modified: 2021/04/04 15:44 (external edit)