ActionScript: Read text file line by line, introducing readLine()
Posted on 20 December 2010
Working with Adobe AIR one often needs to read text files from the user’s file system. The available methods of the
FileStream
object do not provide a convenient way to achieve the functionality of reading the file line-by-line.
Thus, I came up with an extended implementation of the FileStream
class that supports reading line by line, aptly
called, FileStreamWithLineReader
.
To read a line from a text file,
var file:File = new File("fileToBeRead.txt");
var stream:FileStreamWithLineReader = new FileStreamWithLineReader();
stream.open(file, FileMode.READ);
while(stream.bytesAvailable) {
var line:String = stream.readLine();
trace(line);
}
The class does not have big detrimental effect on the performance of read operations, but still I would recommend using it only for files when one needs to read the entire file line by line.
/**
*
* as3extensions - ActionScript Utility Classes
* Copyright (C) 2010-2011, myJerry Developers
* http://www.myjerry.org/as3extensions
*
* The file is licensed under the the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.myjerry.as3extensions.io
{
import flash.filesystem.FileStream;
import flash.geom.PerspectiveProjection;
import flash.utils.ByteArray;
/**
* An extension of the <code>flash.filesystem.FileStream</code> object that supports
* reading lines from the inherent file stream. The objects uses an internal buffer so
* as not to hit the performance. The default buffer size is 512 bytes.
*
* The object supports only standard methods of the underlying <code>FileStream</code>
* object.
*
* Usage of the class is only recommended when one tends to read the data line by line
* from a file stream. If the <code>readUTFLine()</code> or <code>readMultiByteLine()</code>
* methods are not intended to be called, it will be preferable to use the <code>FileStream</code>
* object, for performance reasons only.
*
* @author Sandeep Gupta
* @version 1.0
* @since 17 Dec 2010
*/
public class FileStreamWithLineReader extends FileStream {
/**
* The internal buffer we maintain to be used when working with the
* readLine() method.
*/
private var buffer:ByteArray = new ByteArray();
/**
* The default buffer size.
*/
private var bufferSize:uint = 512;
/**
* Constructor
*/
public function FileStreamWithLineReader() {
super();
}
/**
* Set the buffer size to the given value. A value of <code>ZERO</code> will reset the buffer size
* to a default value of 512 bytes.
*/
public function setBufferSize(value:uint):void {
if(value == 0) {
// the trace line here is intentional to let developer's know
// of the default value in debug mode.
trace('FileStreamWithLineReader: Buffer set to default size of 512 bytes.');
this.bufferSize = 512;
return;
}
this.bufferSize = value;
}
/**
* Utility method to see if check if we have data in our internal buffer.
*/
protected function get hasBuffer():Boolean {
if(buffer.length == 0) {
return false;
}
return true;
}
/**
* Method that returns the number of bytes that can be read in the next read
* over the actual file stream. The value is the lesser amongst the buffer size
* and the actual number of bytes available in the file stream.
*/
protected function get bytesToRead():uint {
return Math.min(this.bufferSize, super.bytesAvailable);
}
/**
* Method to refill the buffer again with data from the file stream. The buffer is
* either filled with the number of bytes as returned by the <code>bytesToRead()</code>
* method.
*/
protected function refillBuffer():void {
if(!this.hasBuffer) {
super.readBytes(buffer, 0, this.bytesToRead);
}
}
/**
* Method to clean up the current buffer and move back in the original stream by the
* extra bytes that were buffered in.
*/
protected function undoBuffer():void {
if(this.hasBuffer) {
super.position = (super.position - this.buffer.length);
this.buffer = new ByteArray();
}
}
/**
* Reads a <code>UTF-8</code> encoded line from the inherent file stream. The presence of
* a line termination character '<code>\n</code>' indicates the end-of-line.
*/
public function readUTFLine():String {
return readMultiByteLine("utf-8");
}
/**
* Reads a line in the specified encoding from the inherent file stream. The presence of
* a line termination character '<code>\n</code>' indicates the end-of-line.
*/
public function readMultiByteLine(charSet:String):String {
var toReturn:String = readLine(charSet);
// the following check is a fix when on windows the buffer reads between the values of
// 13 and 10, which are used to indicate the end of line
if(toReturn != null && toReturn.charCodeAt(toReturn.length - 1) == 13) {
return toReturn.substr(0, toReturn.length - 1);
}
return toReturn;
}
/**
* Method that actually attempts to read a line from the inherent file stream using internal buffers
* in the given encoding. If no line termination character is found, the entire stream from the current
* position to the end-of-file is returned back.
*/
protected function readLine(charSet:String):String {
var joinedString:String = '';
var toReturn:String = '';
var extraReadString:String = '';
do {
refillBuffer();
var currentReadString:String = this.buffer.readMultiByte(this.buffer.length, charSet);
// try and see if current buffer has enough data to return a line
var index:int = currentReadString.indexOf('\n');
if(index != -1) {
if(index == 0) {
// indicates that the first character of the current read string is a new line
// return joined string
toReturn = joinedString;
} else {
toReturn = joinedString + currentReadString.substr(0, index - 1);
}
extraReadString = currentReadString.substr(index + 1);
this.buffer = new ByteArray();
this.buffer.writeMultiByte(extraReadString, charSet);
this.buffer.position = 0;
return toReturn;
}
// check if we have more data to read from the file
// if not, we can return from this point
if(this.bytesToRead == 0) {
this.buffer = new ByteArray();
return joinedString + currentReadString;
}
// no the current buffer does not has enough data
// move the current read string to joined string
joinedString += currentReadString;
this.buffer = new ByteArray();
} while(true);
return joinedString;
}
/**
* Returns the number of bytes of data available for reading in the input buffer.
*/
override public function get bytesAvailable():uint {
if(this.hasBuffer) {
return buffer.length + super.bytesAvailable;
}
return super.bytesAvailable;
}
/**
* The current position in the buffer/file where the next read will happen.
*/
override public function get position():Number {
if(this.hasBuffer) {
return this.position - buffer.length;
}
return super.position;
}
/**
* Resets the next reading position in the inherent stream and adjusts the internal buffers
* accordingly.
*/
override public function set position(value:Number):void {
if(this.hasBuffer) {
if(value > this.buffer.length) {
// empty the buffer
// and skip in original stream
buffer = new ByteArray();
value = value - buffer.length;
super.position = value;
return;
}
if(value == buffer.length) {
// empty the buffer
// no need to skip
buffer = new ByteArray();
return;
}
// we just need to skip inside the buffer
// get how many positions do we need to skip
value = this.buffer.length - value;
var tempBuffer:ByteArray = new ByteArray();
this.buffer.readBytes(tempBuffer, value);
this.buffer = tempBuffer;
return;
}
super.position = value;
}
/**
* Overriden parent class functions to support buffering
*/
override public function readBoolean():Boolean {
refillBuffer();
return this.buffer.readBoolean();
}
override public function readByte():int {
refillBuffer();
return this.buffer.readByte();
}
override public function readBytes(bytes:ByteArray, offset:uint=0, length:uint=0):void {
undoBuffer();
super.readBytes(bytes, offset, length);
}
override public function readDouble():Number {
refillBuffer();
return this.buffer.readDouble();
}
override public function readFloat():Number {
refillBuffer();
return this.buffer.readFloat();
}
override public function readInt():int {
refillBuffer();
return this.buffer.readInt();
}
override public function readObject():* {
undoBuffer();
return super.readObject();
}
override public function readShort():int {
refillBuffer();
return this.buffer.readShort();
}
override public function readUnsignedByte():uint {
refillBuffer();
return this.buffer.readUnsignedByte();
}
override public function readUnsignedInt():uint {
refillBuffer();
return this.buffer.readUnsignedInt();
}
override public function readUnsignedShort():uint {
refillBuffer();
return this.buffer.readUnsignedShort();
}
override public function readUTF():String {
undoBuffer();
return super.readUTF();
}
override public function readUTFBytes(length:uint):String {
undoBuffer();
return super.readUTFBytes(length);
}
}
}
Hope this helps.