flex - Analizar archivos de texto grandes con Adobe AIR
actionscript-3 parsing (5)
Estoy tratando de hacer lo siguiente en AIR:
- navegar a un archivo de texto
- lea el archivo de texto y guárdelo en una cadena (en última instancia en una matriz)
- divida la cadena por el delimitador / n y coloque las cadenas resultantes en una matriz
- manipular esa información antes de enviarla a un sitio web (base de datos mysql)
Los archivos de texto con los que estoy tratando tendrán un tamaño de 100 a 500mb. Hasta ahora, he podido completar los pasos 1 y 2, aquí está mi código:
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import flash.filesystem.*;
import flash.events.*;
import mx.controls.*;
private var fileOpened:File = File.desktopDirectory;
private var fileContents:String;
private var stream:FileStream;
private function selectFile(root:File):void {
var filter:FileFilter = new FileFilter("Text", "*.txt");
root.browseForOpen("Open", [filter]);
root.addEventListener(Event.SELECT, fileSelected);
}
private function fileSelected(e:Event):void {
var path:String = fileOpened.nativePath;
filePath.text = path;
stream = new FileStream();
stream.addEventListener(ProgressEvent.PROGRESS, fileProgress);
stream.addEventListener(Event.COMPLETE, fileComplete);
stream.openAsync(fileOpened, FileMode.READ);
}
private function fileProgress(p_evt:ProgressEvent):void {
fileContents += stream.readMultiByte(stream.bytesAvailable, File.systemCharset);
readProgress.text = ((p_evt.bytesLoaded/1048576).toFixed(2)) + "MB out of " + ((p_evt.bytesTotal/1048576).toFixed(2)) + "MB read";
}
private function fileComplete(p_evt:Event):void {
stream.close();
//fileText.text = fileContents;
}
private function process(c:String):void {
if(!c.length > 0) {
Alert.show("File contents empty!", "Error");
}
//var array:Array = c.split(//n/);
}
]]>
</mx:Script>
Aquí está el MXML
<mx:Text x="10" y="10" id="filePath" text="Select a file..." width="678" height="22" color="#FFFFFF" fontWeight="bold"/>
<mx:Button x="10" y="40" label="Browse" click="selectFile(fileOpened)" color="#FFFFFF" fontWeight="bold" fillAlphas="[1.0, 1.0]" fillColors="[#E2E2E2, #484848]"/>
<mx:Button x="86" y="40" label="Process" click="process(fileContents)" color="#FFFFFF" fontWeight="bold" fillAlphas="[1.0, 1.0]" fillColors="[#E2E2E2, #484848]"/>
<mx:TextArea x="10" y="70" id="fileText" width="678" height="333" editable="false"/>
<mx:Label x="10" y="411" id="readProgress" text="" width="678" height="19" color="#FFFFFF"/>
el paso 3 es donde estoy teniendo algunos problemas. Hay 2 líneas en mi código comentadas, ambas líneas causan que el programa se congele.
fileText.text = fileContents; intenta poner el contenido de la cadena en un área de texto
var array: Array = c.split (/ / n /); intenta dividir la cadena por delimitador nueva línea
Podría usar alguna información en este punto ... ¿Estoy yendo sobre esto de la manera correcta? ¿Puede manejar / manejar archivos de este tamaño? (Asumo que sí) Este es mi primer intento de hacer cualquier tipo de trabajo flexible, si ves otras cosas que he hecho mal o podrían hacerse mejor, ¡agradecería la información de primera mano!
¡Gracias!
Estoy de acuerdo.
Intenta dividir el texto en fragmentos mientras lo lees desde la transmisión.
De esta manera, no tiene que almacenar el texto en su archivo. Cadena de texto (lo que reduce el uso de memoria en un 50%)
Hacer una split
en un archivo de 500MB podría no ser una buena idea. Puede escribir su propio analizador para trabajar en el archivo, pero puede que tampoco sea muy rápido:
private function fileComplete(p_evt:Event):void
{
var array:Array = [];
var char:String;
var line:String = "";
while(stream.position < stream.bytesAvailable)
{
char = stream.readUTFBytes(1);
if(char == "/n")
{
array.push(line);
line = "";
}
else
{
line += char;
}
}
// catch the last line if the file isn''t terminated by a /n
if(line != "")
{
array.push(line);
}
stream.close();
}
No lo he probado, pero solo debe pasar por el archivo carácter por carácter. Si el personaje es una nueva línea, inserte la línea anterior en la matriz, de lo contrario, agréguela a la línea actual.
Si no desea que bloquee su IU mientras lo hace, deberá resumirlo en una idea basada en el temporizador:
// pseudo code
private function fileComplete(p_evt:Event):void
{
var array:Array = [];
processFileChunk();
}
private function processFileChunk(event:TimerEvent=null):void
{
var MAX_PER_FRAME:int = 1024;
var bytesThisFrame:int = 0;
var char:String;
var line:String = "";
while( (stream.position < stream.bytesAvailable)
&& (bytesThisFrame < MAX_PER_FRAME))
{
char = stream.readUTFBytes(1);
if(char == "/n")
{
array.push(line);
line = "";
}
else
{
line += char;
}
bytesThisFrame++;
}
// if we aren''t done
if(stream.position < stream.bytesAvailable)
{
// declare this in the class
timer = new Timer(100, 1);
timer.addEventListener(TimerEvent.TIMER_COMPLETE, processFileChunk);
timer.start();
}
// we''re done
else
{
// catch the last line if the file isn''t terminated by a /n
if(line != "")
{
array.push(line);
}
stream.close();
// maybe dispatchEvent(new Event(Event.COMPLETE)); here
// or call an internal function to deal with the complete array
}
}
Básicamente, usted elige una cantidad del archivo para procesar cada cuadro (MAX_PER_FRAME) y luego procesa muchos bytes. Si pasa el número de bytes, simplemente haga un temporizador para llamar de nuevo a la función de proceso en unos pocos cuadros y debería continuar donde lo dejó. Puede enviar un evento de llamada a otra función una vez que esté seguro de que está completo.
Trata de procesarlo en partes.
stream.position <stream.bytes Disponible No ¿Esta condición sería falsa después de que la posición llega al medio del archivo? Si el archivo es de 10 bytes, después de haber leído 5 bytes, entonces BytesAvailable será 5, almacené el valor inicial en otra variable y lo usé en la condición. Además de eso, creo que es bastante bueno
Con respecto al analizador casero de James, hay un problema si los archivos de texto contienen caracteres UTF multibyte (estaba tratando de analizar los archivos UTF de una manera similar cuando encontré este hilo). La conversión de cada byte a una cadena individual desintegrará caracteres de varios bytes, por lo que hice algunas modificaciones.
Para que este analizador sea compatible con varios bytes, puede almacenar las líneas crecientes en un ByteArray en lugar de una cadena. Luego, cuando tocas el final de una línea (o un fragmento, o el archivo), puedes analizarlo como una cadena UTF (si es necesario) sin ningún problema:
var
out :ByteArray,
line_out :String,
line_end :Number,
char :int,
line:ByteArray;
out = new ByteArray();
line = new ByteArray();
while( file_stream.bytesAvailable > 0 )
{
char = file_stream.readByte();
if( (String.fromCharCode( char ) == "/n") )
{
// Do some processing on a line-by-line basis
line_out = ProcessLine( line );
line_out += "/n";
out.writeUTFBytes( line_out );
line = new ByteArray();
}
else
{
line.writeByte( char );
}
}
//Get the last line in there
out.writeBytes( line );