Requirements

Prerequisite knowledge

You need to be familiar with ActionScript 3 variables, objects, loops, and arrays.

User level

Intermediate

In ActionScript 3 fundamentals: Associative arrays, maps, and dictionaries, you were introduced to arrays and maps based on both Dictionary and Object as ways to store data in your applications. There are two additional types of objects provided by ActionScript 3 that will allow you to do more aggressive type checking and very low-level data manipulation. This article introduces Vector and ByteArray and builds upon the knowledge introduced in ActionScript 3 fundamentals: Array.

Vectors are arrays that contain one and only one predefined type of data. Vectors in ActionScript 3 can be of a fixed or dynamic size and, as you will see, this choice restricts the way you access and manipulate a vector's contents. Vectors have two key advantages: they can be optimized in Flash Player and hence perform better than standard arrays, and they can help catch coding errors by only allowing a single data type to be added to the data structure.

ByteArrays are literally an array structure of the bytes composing data in ActionScript 3. They are very useful for rapid access to low-level data such as that required to do graphic or sound manipulation.

Vectors

Vectors are a type of object, and therefore must be instantiated. Because a vector can contain only one type of data, the data type must be declared when you create the vector. The syntax to both declare a data type and instantiate a vector with that type is different than any you have encountered in ActionScript 3 so far and may look strange at first. The first line of code below declares a variable that will reference a vector containing numbers. The second line of code declares a reference variable and then instantiates a vector that will contain only Number data.

var myVector:Vector.<Number>; var myVector:Vector.<Number> = new Vector.<Number>();

You can think about the type of element contained in a vector as being part of its type. So, the vector above is a vector of numbers. A vector of sprites is a completely different type of object and hence the following code will cause an error:

var myVector:Vector.<Number> = new Vector.<Sprite>(); TypeError: Error #1034: Type Coercion failed: cannot convert __AS3__.vec::Vector.<flash.display::Sprite>@6318e71 to __AS3__.vec.Vector.<Number>.

You may create a Vector of any type, complex or simple, or of any interface in ActionScript 3. All of the following are legal:

var vector1:Vector.<String> = new Vector.<String>(); var vector2:Vector.<Sprite> = new Vector.<Sprite>(); var vector3:Vector.<IEventDispatcher> = new Vector.<IEventDispatcher>();

Like arrays, all ActionScript 3 vectors have a length property. That length property corresponds to the number of elements stored in the vector at any given time. Executing this code will show that the vector currently has 0 elements.

var myVector:Vector.<Number> = new Vector.<Number>(); trace(myVector.length); //output: 0

When you create a new Vector , you have two additional choices to make. You can choose to specify an initial size for the vector and whether the size is fixed. For example, this vector will hold have a space for each day of the week.

var days:Vector.<String> = new Vector.<String>( 7 ); trace( days.length ); //output: 7

In this case, the vector is created with seven places to be defined in the future. A Vector of seven items could be visualized as:

When a vector is created in this way, it can grow or shrink in size, like arrays. However, there are many times where this is unnecessary. Continuing with the day of the week example, it is unlikely that the number of days in a week will change anytime soon. It is a relatively static number.

When dealing with a static number of things, you can tell the vector that the size is fixed. This allows the vector to further optimize access, making updating and retrieving data faster. To tell a vector that it is a fixed size, you simply pass true to the second constructor argument during instantiation.

var days:Vector.<String> = new Vector.<String>( 7, true );

When a Vector is set to a fixed size, attempting to change its length in any way will cause an error. You are allowed to change the fixed nature of a vector at any time by altering its fixed property:

var days:Vector.<String> = new Vector.<String>( 7, true ); days.fixed = false; //you can change the length days.fixed = true; //you can NOT change the length

Manipulating data in vectors

Data can be added to and removed from ActionScript 3 vectors at runtime using the same methods you use with arrays. You can replace existing elements at any time and, so long as the vector is not fixed, it will grow or shrink as needed.

There are a number of methods available in the Vector class to manipulate data in the vector. Generally, they are very similar to methods of the Array class. The two most common ones are push() and pop().

push() and pop() methods

The push() method adds one or more new elements to the end of a vector. The method returns the new length of the vector after the new item(s) have been added.

var letters:Vector.<String> = new Vector.<String>(); letters.push( "C" );
letters.push( "D", "E", "F" );
var len:uint = letters.push( "G" ); trace(len); //5 trace(letters.length); //5

However, remember that Vector only accepts one type of data. The following code will cause an error.

var letters:Vector.<Sprite> = new Vector.<Sprite>(); letters.push("C"); //Error TypeError: Error #1034: Type Coercion failed: cannot convert "C" to flash.display.Sprite.

As with an array, items can be removed from the end of a vector with the pop() method. The pop() method removes one element from the end of a vector and returns it. Look at the following example:

var letters:Vector.<String> = new Vector.<String>(); letters.push( "C" );
letters.push( "D" );
var letter:String = letters.pop(); trace( letter ); //output: D
letters.push( "E" );

For those of you familiar with a Stack data structure, the push() and pop() methods of Vector provide an easy way to implement a typed stack in ActionScript 3.

Index

While adding data with push() and removing it with pop() is an effective way to manipulate the end of variable sized vectors, they are most commonly manipulated and accessed via index. In ActionScript 3, a vector index begins at 0, meaning the first element of the vector is the 0th element.

Individual values of the vector are accessed by using the same syntax as you would use with an array—the name of the vector, followed by square brackets and the index you wish to retrieve. For example:

var days:Vector.<String> = new Vector.<String>(); days[ 0 ] = "Sun"; days[ 1 ] = "Mon";

You can also use this same technique to modify the contents of a vector. For example, you could modify the string "Thu" to say "Hi".

var days:Vector.<String> = new Vector.<String>( 7, true ); days[ 0 ] = "Sun"; days[ 1 ] = "Mon";

As vector indexes are 0-based, the last element of the vector is always one less than the length. If you have a vector of length 7, then the last element of the vector is at index 6, Sat in the example above.

You can also use the index to populate the vector with data, just as you did with the push() method.

var days:Vector.<String> = new Vector.<String>(); days[ 0 ] = "Sun"; days[ 1 ] = "Mon";

If you use a fixed-length Vector, this will be your primary way to populate data.

var days:Vector.<String> = new Vector.<String>( 7, true ); days[ 0 ] = "Sun"; days[ 1 ] = "Mon";

Dense vectors

In ActionScript 3, Array instances can be dense (all elements have value) or sparse (missing elements). This is not true with Vector instances. Vectors must be dense. This means that every element has to have a value.

While this is legal with Array :

var elements:Array = new Array(); elements[ 1 ] = "B";

The same technique will cause an error to be thrown with Vector . All elements must exist for a Vector, so in this example, index 0 must exist before index 1 can:

var elements:Array = new Array(); elements[ 1 ] = "B";

Note that this is different than having null values:

var elements:Vector.<String> = new Vector.<String>(); elements[ 1 ] = "B"; //Error RangeError: Error #1125: The index 1 is out of range 0.

When you insert null into index 0 you are creating a vector with both positions defined. One of elements simply contains a null value.

var elements:Vector.<String> = new Vector.<String>(); elements[ 0 ] = null; elements[ 1 ] = "B";

When you insert null into index 0 you are creating a vector with both positions defined. One of elements simply contains a null value.

Looping over vectors

Just like with arrays, one of the most common practices when working with vectors involves iterating through the elements of a vector searching for a given value. The function below shows how you can move through a vector element by element and search for a particular value. If the function finds the value, it returns the index of the element in the vector. If it fails to find the value, it returns a -1.

function findIndexOfValue( vector:Vector.<String>, value:String ):int { var length:uint = vector.length; for ( var i:uint=0; i<length; i++ ) { if (vector[ i ] == value ) { return i; } return -1; } } var elements:Vector.<String> = new Vector.<String>(); elements[ 0 ] = [ "A" ]; elements[ 1 ] = [ "B" ]; elements[ 2 ] = [ "C" ]; trace( findIndexOfValue( elements, "C" ) ); //2 trace( findIndexOfValue( elements, "Z" ) ); //-1

ByteArray

ByteArray is a class that allows advanced developers to work directly with binary data in ActionScript 3. It is very useful when the need arises to work with low-level data such as that inside of images or arriving directly from the network.

var bytes:ByteArray = new ByteArray();

ByteArrays concern themselves with two general types of operation: reading and writing. One of the simplest things we can do with a ByteArray is to write a value into it. The following code writes the values true, false, true into the ByteArray.

var bytes:ByteArray = new ByteArray(); bytes.writeBoolean( true ); bytes.writeBoolean( false ); bytes.writeBoolean( true );

This looks relatively similar to when you pushed items into a vector or array. There are indeed some similarities but also some important differences.

Reading and writing data

First, you will notice that you used writeBoolean() when you added data to the ByteArray . The ByteArray needs to know what type of data is being added to it. ByteArray supports writing with the following methods:

Table 1. Read and write methods of ByteArray

Data type

Read method

Write method

Note

Boolean

readBoolean()

writeBoolean()

read or write a true or false

Numeric

readDouble()

writeDouble()

read or write a 64-bit floating point number

readFloat()

writeFloat()

read or write a 32-bit floating point number

readShort()

writeShort()

read or write a 16-bit integer

readUnsignedShort()


read or write a 16-bit unsigned integer

readInt()

writeInt()

writes a 32 bit signed integer

readUnsignedInt()

writeUnsignedInt()

read or write a 32-bit unsigned integer

String

readMultiByte()

writeMultiByte()

read or write a multi-byte string of a specified length in a specified character set

readUTFBytes()

writeUTFBytes()

read or write a UTF-8 string of a specified length

readUTF()

writeUTF()

read or write a UTF-8 strings

Objects

readObject()

writeObject()

read or write an entire object into the ByteArray using Action Message Format encoding

Raw data

readByte()

writeByte()

read or write a single byte of data

readBytes()

writeBytes()

read or write a predetermined number of discrete bytes

var bytes:ByteArray = new ByteArray(); bytes.writeBoolean( true ); bytes.position=0; bytes.readBoolean(); //Output: true

Storage

The second major difference between Array and ByteArray is the way data is stored. In regular arrays, you think of one value existing in each slot. In a ByteArray, each slot is a fixed size—a byte—and the items you write into the ByteArray span multiple bytes. For example, the following code writes an unsigned integer (32 bits long), a Boolean value and another unsigned integer (32 bits long) into the ByteArray. Each 32-bit integer spans four bytes in the ByteArray. The Boolean value is stored in one byte. The ByteArray occupies a total of nine bytes and thus has a length of nine.

var bytes:ByteArray = new ByteArray(); bytes.writeUnsignedInt(10); bytes.writeBoolean(true); bytes.writeUnsignedInt(26); trace( bytes.length ); //output: 9

The image above shows where each of those values is stored along with the binary bits stored in each byte that make up the stored value.

Position

This reveals another significant difference between the way ByteArrays and standard arrays work: the concept of position versus index. Standard arrays and vectors allow you to retrieve data at any given index with array syntax, for example myArray[2] . In the example above, what is at index 2? It is somewhere in the middle of the number 10—specifically it is a series of eight 0s. That isn't very helpful. Thus, the concept of index isn't usable with ByteArray.

Instead, ByteArray uses the concept of position. Each byte within the ByteArray is a position. When you execute an operation that reads or writes, it does so at the current position and then moves to the next position. If you read one byte, the position moves forward by one. Likewise if you read four bytes, the position moves forward by four. You can query and set the current position via the position property. In the diagrams below, the position after each line of code is executed is shown by an arrow. Examine each line of the code below and the impact it has on the ByteArray:

var bytes:ByteArray = new ByteArray(); //Position is 0 bytes.writeUnsignedInt(10); //After write method, position is 4
bytes.writeBoolean(true); //After write method, position is 5
bytes.writeUnsignedInt( 26 ); //After write method, position is 9
bytes.position = 0; //Position is 0
trace( bytes.readUnsignedInt() ); //Output 10, After read method, position is 4
trace( bytes.readBoolean() ); //Output true, After read method, position is 5
trace( bytes.readUnsignedInt() ); //Output 26, After read method, position is 9

This sequential access method is much faster than the array index method you use with Array and Vector, but it is more complicated. If you know the correct position of an item you wish to read, you can access it by using a combination of the position property and reading. For example, you could set the position to four and read the Boolean directly:

bytes.position = 4; //Position is 4 trace( bytes.readBoolean() ); //Output true, After read method, position is 5

Looping over ByteArrays

As you may have noticed from the previous examples, you need to know the type and order of the data added to the ByteArray in order to read it back out correctly. There are however some times when you simply want to iterate through each byte. To that end, the ByteArray allows you to determine the remaining number of bytes that can be read for this purpose.

The ByteArray bytesAvailable property always gives you the number of bytes from the current position to the end of the ByteArray. Using this property, you can create a simple while loop that will get each byte of the ByteArray.

var bytes:ByteArray = new ByteArray(); bytes.writeUnsignedInt( 10 ); bytes.writeUnsignedInt( 26 ); bytes.position = 0; while ( bytes.bytesAvailable ) { trace( bytes.readByte() ); } //Output 0 0 0 10 0 0 0 26

Compressing ByteArrays

ByteArray instances have the ability to compress and decompress themselves using either the deflate or zlib compression algorithms. Beginning with Flash Player 10.0 and AIR 1.5, a ByteArray can be compressed using the deflate algorithm by calling the deflate() method. Decompressing the ByteArray is accomplished by calling the corresponding inflate() method.

To use the zlib compression algorithm, you must call compress() and uncompress() . In Flash Player, this method always uses zlib compression. In AIR, you can choose between zlib or deflate by calling compress() and specifying the compression algorithm to use when compressing. Valid values are defined as constants in the CompressionAlgorithm class. To decompress a ByteArray you compressed using compress() , you simply call the uncompress() method.

The following code example populates the ByteArray with a series of numbers. It traces the length of the ByteArray then deflates it, shows the new length, and then inflates it to the original size.

var bytes:ByteArray = new ByteArray(); bytes.writeUnsignedInt( 10 ); bytes.writeUnsignedInt( 26 ); bytes.writeUnsignedInt( 11 ); bytes.writeUnsignedInt( 12 ); bytes.writeUnsignedInt( 11 ); bytes.writeUnsignedInt( 23 ); bytes.writeUnsignedInt( 01 ); bytes.writeUnsignedInt( 16 ); bytes.writeUnsignedInt( 02 ); bytes.writeUnsignedInt( 17 ); bytes.writeUnsignedInt( 03 ); bytes.writeUnsignedInt( 16 ); trace( bytes.length ); //48 bytes.deflate(); trace( bytes.length ); //32 bytes.inflate(); trace( bytes.length ); //48

For more information about compressing and decompressing ByteArrays see compress(), uncompress(), deflate(), and inflate() in the ActionScript 3.0 Reference.

Object Serialization

One of the most powerful operations that can be performed on a ByteArray instance is writing the contents of an entire object into a ByteArray. The object's data and type persist, so that later, a fully populated object can be read back out of the ByteArray.

Doing this involves a process called serializing an object with Action Message Format (AMF). This is actually the same process required if you wish to send objects back and forth to a Java, ColdFusion or PHP server. The specific details of how objects are serialized with AMF is beyond the scope of this article. However, how to work with objects in a ByteArray is covered below.

AMF natively supports core ActionScript data types. When instances of ActionScript classes like Sprite or TextField or Date are written into a ByteArray, they are stored as a Sprite or TextField or Date . If you are writing an object of a custom type into a ByteArray, it will be stored as a generic Object . This means that when you read it, it will be of type Object and you will not be able to cast it back to its correct type. To avoid this problem, you must register the class. The example that follows demonstrates how to write and read an object of a custom type into and out of a ByteArray.

As a first step, consider this simple object named Person:

package vo { public class Person { public var firstName:String; public var lastName:String; public var age:Number; public function Person() { } } }

It's a very simple object with three custom properties. If you want to be able to store this object as a Person object and not a generic Object in a ByteArray, you must first register a class alias. You only need to do this once in your application. Register the class alias anytime before you use it.

registerClassAlias( "vo.Person", vo.Person );

This method simply gives Flash Player a convenient string to use as an alias for this class. By convention you use the dot path and class name to create the alias; however, technically you could use any unique alias, for example:

registerClassAlias( "nonIntuitiveAlias", vo.Person );

Once you have registered the class alias you can create a new Person object, fill it with data and write it to the ByteArray using the writeObject() method.

registerClassAlias( "vo.Person", vo.Person ); var bytes:ByteArray = new ByteArray(); var person:Person = new Person(); person.firstName = "Lee"; person.lastName = "Alan"; person.age = 37; bytes.writeObject( person );

To read the data back from the ByteArray, you must first reset the position to where the Person object begins, in this case position 0. You can then use the readObject() to read the Person instance back out of the Array.

bytes.position = 0; var person1:Person = bytes.readObject(); trace( person1 is Person ); //true trace( person1.firstName ); //Lee trace( person === person1 ); //false

The object read back from the ByteArray is actually a Person instance. It will have all of the same properties and values of the original Person instance. However, it is not the same instance. It is a copy. As the last trace statement shows, person1 is a copy of the original person object, not a reference to it.

Where to go from here

The Vector class provides an optimized array for situations when you have a homogenous data set. Vectors can be further optimized by declaring a fixed length. Attempting to add a different type of data than specified when creating the vector or attempting to modify the length of a fixed-length vector will cause an error.

ByteArray provides low-level binary access to data in ActionScript 3. It is extremely powerful and extremely fast; however, it requires a firm understanding of data types and serialization to use effectively.

To learn more about each of these topics please refer to their entries in the ActionScript 3.0 Reference—Vector and ByteArray.