Reading and writing a ByteArray

The ByteArray class is part of the openfl.utils package. To create a ByteArray object in Haxe, import the ByteArray class and invoke the constructor, as shown in the following example:

import openfl.display.Sprite;
import openfl.utils.ByteArray;

class CreateByteArrayExample extends Sprite {
    public function new() {
        super();

        var stream:ByteArray = new ByteArray();
    }
}

ByteArray methods

Any meaningful data stream is organized into a format that you can analyze to find the information that you want. A record in a simple employee file, for example, would probably include an ID number, a name, an address, a phone number, and so on. An MP3 audio file contains an ID3 tag that identifies the title, author, album, publishing date, and genre of the file that's being downloaded. The format allows you to know the order in which to expect the data on the data stream. It allows you to read the byte stream intelligently.

The ByteArray class includes several methods that make it easier to read from and write to a data stream. Some of these methods include readBytes() and writeBytes(), readInt() and writeInt(), readFloat() and writeFloat(), readObject() and writeObject(), and readUTFBytes() and writeUTFBytes(). These methods enable you to read data from the data stream into variables of specific data types and write from specific data types directly to the binary data stream.

For example, the following code reads a simple array of strings and floating-point numbers and writes each element to a ByteArray. The organization of the array allows the code to call the appropriate ByteArray methods (writeUTFBytes() and writeFloat()) to write the data. The repeating data pattern makes it possible to read the array with a loop.

// The following example reads a simple Array (groceries), made up of strings
// and floating-point numbers, and writes it to a ByteArray.
import openfl.display.Sprite;
import openfl.utils.ByteArray;

class WriteByteArrayExample extends Sprite {
    public function new() {
        super();

        // define the grocery list Array
        var groceries:Array<Dynamic> = ["milk", 4.50, "soup", 1.79, "eggs", 3.19, "bread", 2.35];
        // define the ByteArray
        var bytes:ByteArray = new ByteArray();
        // for each item in the array
        var i = 0;
        while (i < groceries.length) {
            bytes.writeUTFBytes(groceries[i++]); // write the string and position to the next item
            bytes.writeFloat(groceries[i++]); // write the float
            trace("bytes.position is: " + bytes.position); // display the position in ByteArray
        }
        trace("bytes length is: " + bytes.length); // display the length
    }
}

The position property

The position property stores the current position of the pointer that indexes the ByteArray during reading or writing. The initial value of the position property is 0 (zero) as shown in the following code:

var bytes:ByteArray = new ByteArray();
trace("bytes.position is initially: " + bytes.position);     // 0

When you read from or write to a ByteArray, the method that you use updates the position property to point to the location immediately following the last byte that was read or written. For example, the following code writes a string to a ByteArray and afterward the position property points to the byte immediately following the string in the ByteArray:

var bytes:ByteArray = new ByteArray();
trace("bytes.position is initially: " + bytes.position);     // 0
bytes.writeUTFBytes("Hello World!");
trace("bytes.position is now: " + bytes.position);    // 12

Likewise, a read operation increments the position property by the number of bytes read.

var bytes:ByteArray = new ByteArray();

trace("bytes.position is initially: " + bytes.position);     // 0
bytes.writeUTFBytes("Hello World!");
trace("bytes.position is now: " + bytes.position);    // 12
bytes.position = 0;
trace("The first 6 bytes are: " + (bytes.readUTFBytes(6)));    //Hello
trace("And the next 6 bytes are: " + (bytes.readUTFBytes(6)));    // World!

Notice that you can set the position property to a specific location in the ByteArray to read or write at that offset.

The bytesAvailable and length properties

The length and bytesAvailable properties tell you how long a ByteArray is and how many bytes remain in it from the current position to the end. The following example illustrates how you can use these properties. The example writes a String of text to the ByteArray and then reads the ByteArray one byte at a time until it encounters either the character "a" or the end (bytesAvailable <= 0).

import openfl.display.Sprite;
import openfl.utils.ByteArray;

class BytesAvailableAndLengthExample extends Sprite {
    public function new() {
        super();

        var bytes:ByteArray = new ByteArray();
        var text:String = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vivamus etc.";

        bytes.writeUTFBytes(text); // write the text to the ByteArray
        trace("The length of the ByteArray is: " + bytes.length); // 70
        bytes.position = 0; // reset position
        while (bytes.bytesAvailable > 0 && (bytes.readUTFBytes(1) != 'a')) {
            // read to letter a or end of bytes
        }
        if (bytes.position < bytes.bytesAvailable) {
            trace("Found the letter a; position is: " + bytes.position); // 23
            trace("and the number of bytes available is: " + bytes.bytesAvailable); // 47
        }
    }
}

The endian property

Computers can differ in how they store multibyte numbers, that is, numbers that require more than 1 byte of memory to store them. An integer, for example, can take 4 bytes, or 32 bits, of memory. Some computers store the most significant byte of the number first, in the lowest memory address, and others store the least significant byte first. This attribute of a computer, or of byte ordering, is referred to as being either big endian (most significant byte first) or little endian (least significant byte first). For example, the number 0x31323334 would be stored as follows for big endian and little endian byte ordering, where a0 represents the lowest memory address of the 4 bytes and a3 represents the highest:

Big Endian Big Endian Big Endian Big Endian
a0 a1 a2 a3
31 32 33 34
Little Endian Little Endian Little Endian Little Endian
a0 a1 a2 a3
34 33 32 31

The endian property of the ByteArray class allows you to denote this byte order for multibyte numbers that you are processing. The acceptable values for this property are either "bigEndian" or "littleEndian" and the Endian class defines the constants BIG_ENDIAN and LITTLE_ENDIAN for setting the endian property with these strings.

The compress() and uncompress() methods

The compress() method allows you to compress a ByteArray in accordance with a compression algorithm that you specify as a parameter. The uncompress() method allows you to uncompress a compressed ByteArray in accordance with a compression algorithm. After calling compress() and uncompress(), the length of the byte array is set to the new length and the position property is set to the end.

The CompressionAlgorithm class defines constants that you can use to specify the compression algorithm. The ByteArray class supports the deflate (AIR-only), zlib, and lzma algorithms. The zlib compressed data format is described at http://www.ietf.org/rfc/rfc1950.txt. The lzma algorithm is described at http://www.7-zip.org/7z.html.

The deflate compression algorithm is used in several compression formats, such as zlib, gzip, and some zip implementations. The deflate compression algorithm is described at http://www.ietf.org/rfc/rfc1951.txt.

The following example compresses a ByteArray called bytes using the lzma algorithm:

bytes.compress(CompressionAlgorithm.LZMA);

The following example uncompresses a compressed ByteArray using the deflate algorithm:

bytes.uncompress(CompressionAlgorithm.LZMA);

Reading and writing objects

The readObject() and writeObject() methods read an object from and write an object to a ByteArray, encoded in a serialized format, such as Haxe Serialization Format, JSON, or Action Message Format (AMF). These object serialization formats are used by various OpenFL classes, including NetConnection, NetStream, LocalConnection, and Shared Objects.

The ByteArray.objectEncoding property specifies the serialization format that is used to encode the object data. The openfl.net.ObjectEncoding class defines constants for specifying the serialization format:

  • ObjectEncoding.HXSF
  • ObjectEncoding.JSON
  • ObjectEncoding.AMF0
  • ObjectEncoding.AMF3

The following example calls writeObject() to write an XML object to a ByteArray, which it then compresses using the Deflate algorithm and writes to the order file on the desktop. The example uses a label to display the message "Wrote order file to desktop!" in the OpenFL window when it is finished.

import openfl.filesystem.*;
import openfl.display.Sprite;
import openfl.text.TextField;
import openfl.utils.ByteArray;
import openfl.utils.CompressionAlgorithm;

class WriteObjectExample extends Sprite {
    public function new() {
        super();

        var bytes:ByteArray = new ByteArray();
        var myLabel:TextField = new TextField();
        myLabel.x = 150;
        myLabel.y = 150;
        myLabel.width = 200;
        addChild(myLabel);

        var order:Dynamic = {
            items: [
                {id: '1', menuName: 'burger', price: 3.95},
                {id: '2', menuName: 'fries', price: 1.45},
            ]
        };

        // Write order object to ByteArray
        bytes.writeObject(order);
        bytes.position = 0; // reset position to beginning
        bytes.compress(CompressionAlgorithm.DEFLATE); // compress ByteArray
        writeBytesToFile("order.dat", bytes);
        myLabel.text = "Wrote order file to desktop!";
    }

    function writeBytesToFile(fileName:String, data:ByteArray):Void {
        var outFile:File = File.desktopDirectory; // dest folder is desktop
        outFile = outFile.resolvePath(fileName); // name of file to write
        var outStream:FileStream = new FileStream();
        // open output file stream in WRITE mode
        outStream.open(outFile, FileMode.WRITE);
        // write out the file
        outStream.writeBytes(data, 0, data.length);
        // close it
        outStream.close();
    }
}

The readObject() method reads an object in serialized format from a ByteArray and stores it in an object of the specified type. The following example reads the order file from the desktop into a ByteArray (inBytes), uncompresses it, and calls readObject() to store it in the XML object orderXML. The example uses a loop to add each node to a text area for display. The example also displays the value of the objectEncoding property along with a header for the contents of the order file.

import openfl.filesystem.*;
import openfl.display.Sprite;
import openfl.text.TextField;
import openfl.utils.ByteArray;
import openfl.utils.CompressionAlgorithm;

class ReadObjectExample extends Sprite {
    public function new() {
        super();

        var inBytes:ByteArray = new ByteArray();
        // define text area for displaying XML content
        var myTxt:TextField = new TextField();
        myTxt.width = 550;
        myTxt.height = 400;
        addChild(myTxt);
        // display objectEncoding and file heading
        myTxt.text = "Object encoding is: " + inBytes.objectEncoding + "\n\n" + "order file: \n\n";
        readFileIntoByteArray("order.dat", inBytes);

        inBytes.position = 0; // reset position to beginning
        inBytes.uncompress(CompressionAlgorithm.DEFLATE);
        inBytes.position = 0; // reset position to beginning
        // read order object
        var order:Dynamic = inBytes.readObject();

        // for each node in orderXML
        var items:Array<Dynamic> = order.items;
        for (item in items) {
            // append item node to text area
            myTxt.text += item.id + " " + item.menuName + " " + item.price + "\n";
        }
    }

    // read specified file into byte array
    function readFileIntoByteArray(fileName:String, data:ByteArray):Void {
        var inFile:File = File.desktopDirectory; // source folder is desktop
        inFile = inFile.resolvePath(fileName); // name of file to read
        var inStream:FileStream = new FileStream();
        inStream.open(inFile, FileMode.READ);
        inStream.readBytes(data);
        inStream.close();
    }
}

results matching ""

    No results matching ""