Setup del ejercicio Llamaremos setup al conjunto de recursos (clases) que se proveen para que el alumno pueda desarrollar el ejercicio. El setup incluye las siguientes clases utilitarias: • UTr ee – Permite navegar a través de las hojas de un árbol binario. Más adelante veremos ejemplos que ilustran cómo debemos usar cada una de estas clases. Se incluye la clase Node, con la que implementaremos la lista enlazada y el árbol binario. Su código fuente es el siguiente: public class class Node
{ private private private private private
int c; long n;
Node Node si g = null; Node Node i z q = null; Node Node der de r = null;
// : // setters y getters... // :
} sz zi zi p y szun szunzi p y de todas las clases en las El setup incluye también el código fuente de los programa s sz que se basa su codificación. Todo esto lo analizaremos más adelante.
Clases y objetos Hagamos un breve repaso de la estrategia que utilizaremos para implementar el compresor/descompresor de archivos destacando en negrita los objetos que intervienen en el proceso. Veamos: Recorreremos el para crear una en la que vamos a contar cuántas veces aparece cada carácter. Luego crearemos una donde cada nodo representará a cada uno los diferentes caracteres, ordenada de menor a mayor según su probabilidad de aparición. La lista la convertiremos en un . Los diferentes caminos hacia las hojas del árbol nos darán los que nos permitirán recodificar cada carácter. Luego asignaremos en cada registro de la tabla su código Huffman correspondiente y, a partir de esto, generaremos el . Finalmente, con los códigos Huffman que tenemos en la tabla recorreremos el archivo que se va a comprimir para sustituir cada byte por su nuevo código, generando así el . En el proceso intervienen los siguientes objetos: • Archivo que se va a comprimir comprimir • Tabla de ocurrencias • Lista enlazada • Árbol binario • Código Huffman • Archivo de códigos • Archivo comprimido El análisis de las relaciones entre estos objetos nos ayudará a definir sus interfases. Veamos: Recorreremos el archivo que queremos comprimir para crear una tabla de ocurrencias en la que vamos a contar cuántas veces aparece cada carácter. Tabl Tabl aOcur r enci as t abl a = ar chi voAC voACompr i mi r . cr ear Tabl Tabl aOcur r enci as( ) ;
UTN.BA | ALGORITMOS Y ESTRUCTURA DE DATOS | ING. PABLO AUGUSTO SZNAJDLEDER (2015)
A
Luego crearemos una lista enlazada con los diferentes caracteres, ordenada de menor a mayor según su probabilidad de aparición. Li st aOr denada l i st a = t abl a. crear Li st a( ) ;
La lista la convertiremos en un árbol binario. Ar bol Huf f man ar bol = l i st a. conver t i r EnAr bol ( ) ;
Los diferentes caminos hacia las hojas del árbol nos darán los códigos Huffman que nos permitirán recodificar cada carácter. Luego asignaremos en cada registro de la tabla su código Huffman correspondiente. t abl a. car gar Codi gosHuf f man( ar bol ) ;
A partir de esto generaremos el archivo de códigos. ar chi voDeCodi gos. gr abar ( t abl a) ;
Finalmente, con los códigos Huffman que tenemos en la tabla r ecorreremos el archivo que se va a comprimir para sustituir cada byte por su nuevo código, generando así el archivo comprimido. ar chi voCompr i mi do. gr abar ( archi voACompr i mi r , t abl a) ;
Los tipos de estos objetos se presentan en la siguiente tabla; la implementación de sus métodos quedará a cargo del alumno.
Programa compresor (s zz i p. j ava) Veamos el código fuente del compresor. El lector verá que es lineal. Comenzamos instanciando la clase que representa al archivo de entrada. Luego le pedimos crear la tabla de ocurrencias. A la tabla le pedimos que la lista enlazada y a esta le pedimos el árbol Huffman. Luego utilizamos el árbol para cargar los códigos en la tabla. Por último, instanciamos la clase que representa el archivo de códigos y lo generamos utilizando la información contenida en la tabla. Más adelante instanciamos el archivo comprimido y lo generamos en función del archivo de entrada y de la tabla que contiene los códigos Huffman asociados a cada byte. public class s z zi p
{ public static void mai n( St r i ng[ ] ar gs)
{ // abrimos el archivo original que vamos a comprimir I Fi l eI nput i nput Fi l e = new I Fi l eI nput ( ) ;
i nput Fi l e. set Fi l ename( ar gs[ 0] ) ; // obtenemos la tabla de ocurrencias
I Tabl e t abl e = i nput Fi l e. creat eTabl e( ) ; // obtenemos la lista enlazada
I Li s t l i s t = t abl e. c r eat eSor t edLi s t ( ) ; // convertimos la lista en arbol
I Tr ee t r e e = l i s t . t oTr ee( ) ; // asignamos los codigos en la tabla
t abl e. l oadHuf f manCodes( t r ee) ; // abrimos el archivo de codigo I Fi l eCode codeFi l e = new I Fi l eCode( ) ;
codeFi l e. set Fi l ename( ar gs[ 0] + ". szcod") ;
B
UTN.BA | ALGORITMOS Y ESTRUCTURA DE DATOS | ING. PABLO AUGUSTO SZNAJDLEDER (2015)
// grabamos el archivo tomando los codigos del arbol
codeFi l e. save( t abl e) ; // abrimos el archivo comprimido I Fi l eCompr essed compr essFi l e = new I Fi l eCompr essed( ) ;
compr essFi l e. set Fi l ename( ar gs[ 0] + ". szdat ") ; // grabamos el archivo comprimido
compr essFi l e. save( i nput Fi l e, t abl e) ; } }
Programa descompresor (szunzi p. j ava) El descompresor es más simple. Instanciamos el archivo de códigos Huffman y le pedimos que restaure el árbol. Luego utilizamos el árbol y el archivo comprimido para restaurar el archivo original. public class szunzi p
{ mai n( St r i ng[ ] ar gs) public static void { // abrimos el archivo de codigos I Fi l eCode codeFi l e = new I Fi l eCode( ) ;
codeFi l e. set Fi l ename( ar gs[ 0] + ". szcod") ; // leemos el archivo y generamos el arbol ITree tree = codeFile.load(); // abrimos el archivo comprimido I Fi l eCompr essed compr essFi l e= new I Fi l eCompr essed( ) ;
compr essFi l e. set Fi l ename( ar gs[ 0] +". szdat ") ; // abrimos el archivo original (el que vamos a restaurar) I Fi l eI nput i nput Fi l e = new I Fi l eI nput ( ) ;
i nput Fi l e. set Fi l ename( ar gs[ 0] ) ; // recuperamos el archivo original
c ompr es s Fi l e. r es t o r e( i nput Fi l e, t r ee) ; } }
Clases e implementaciones Llegó el momento de programar. Ahora definiremos los métodos de cada una de las clases para que el alumno, como programador, complete sus implementaciones y haga que los programas s zz i p y szunzi p funcionen correctamente. La especificación de la tarea que cada método debe realizar está documentada en el mismo código fuente. Por esto, sugiero prestar especial atención a dicha información. I Code. j ava – Clase de los códigos Huffman
Esta clase representa a un código Huffman, es decir, una secuencia de, a lo sumo, 128 bits. public class I Code
{ // retorna el i-esimo bit (contando desde cero, de izquierda a derecha) public int get Bi t At ( int i )
{ return 0;
}
UTN.BA | ALGORITMOS Y ESTRUCTURA DE DATOS | ING. PABLO AUGUSTO SZNAJDLEDER (2015)
C
// retorna la longitud del codigo Huffman (valor entre 1 y 128) public int get Lengt h( )
{ return 0;
} // inicializa el codigo Huffman con los caracteres de sCod, seran 1s o 0s public void f r omSt r i ng( St r i ng sCod)
{ } } I Tr e e. j ava – Clase del árbol binario o árbol Huffman
La clase I Tr ee representa al árbol Huffman. public class I Tr ee
{ // asigna la raiz del arbol (es decir: primer y unico nodo de la lista) public void set Root ( Node r oot )
{ } // retorna la raiz del arbol public Node get Root ( )
{ return null;
} // en cada invocacion retorna la siguiente hoja del arbol // comenzando desde la que esta "mas a la izquierda" // NOTA:utilizar el metodo next de la clase UTree public Node next ( I Code cod)
{ return null;
} } I Li st . j ava - Clase de la lista enlazada
La clase I Li st representa una lista enlazada, ordenada de menor a mayor según la cantidad de ocurrencias de cada carácter. Si dos caracteres aparecen la misma cantidad de veces entonces se considera menor al que tenga menor código ASCII o valor numérico. Los caracteres ficticios se consideran alfabéticamente mayores que los demás. public class I Li st
{ // desenlaza y retorna el primer nodo de la lista public Node r emoveFi r st Node( )
{ return null;
} // agrega el nodo n segun el critero de ordenamiento explicado mas arriba public void addNode( Node n)
{ }
D
UTN.BA | ALGORITMOS Y ESTRUCTURA DE DATOS | ING. PABLO AUGUSTO SZNAJDLEDER (2015)
// convierte la lista en un arbol y lo retorna // NOTA:usar el metodo removeFirstNode public I Tr ee t oTr ee( )
{ return null;
} // retorna negativo, 0 o positivo segun n1 sea menor, igual o mayor que n2 public i nt compar e( Node n1, Node n2)
{ return 0;
} } I Tabl e. j ava - Clase de la tabla de ocurrencias
La clase I Tabl e representa la tabla de ocurrencias que indicará cuantas veces aparece cada carácter dentro del archivo que vamos a comprimir. public class I Tabl e
{ // incrementa el contador relacionado al caracter (o byte) c public void addCount ( int c)
{ } // retorna el valor del contador asociado al caracter (o byte) c public long get Count ( int c)
{ return 0;
} // crea la lista enlazada public I Li s t c r eat eSor t edLi s t ( )
{ return null;
} // almacena en la tabla el codigo Huffman asignado a cada caracter // NOTA:usar el metodonext del parametro tree l oadHuf f manCodes( I Tr ee t r ee) public void
{ } // retorna el codigo Huffman asignado al caracter c public I Code get Code( int c)
{ return null;
} } I Fi l eI nput . j ava – Clase del archivo que vamos a comprimir o a restaurar public class I Fi l eI nput
{ // asigna el nombre del archivo set Fi l ename( St r i ng f i l ename) public void
{ }
UTN.BA | ALGORITMOS Y ESTRUCTURA DE DATOS | ING. PABLO AUGUSTO SZNAJDLEDER (2015)
E
// retorna el nombre del archivo public St r i ng get Fi l ename( )
{ return null;
} // crea y retorna la tabla de ocurrencias con los contadores de los // diferentes bytes que aparecen en el archivo public I Tabl e cr eat eTabl e( )
{ return null;
} // retorna la longitud del archivo public long get Lengt h( )
{ return 0;
} } I Fi l eCode. j ava – Clase del archivo de códigos Huffman
Representa al archivo . szcod que se genera durante la compresión y que contiene los códigos Huffman asociados a cada carácter del archivo original. public class I Fi l eCode
{ // asigna el nombre del archivo public void set Fi l ename( St r i ng f )
{ } // graba el archivo tomando los codigos Huffman de la tabla // NOTA: usar el metodo writeBitde la clase UFile save( I Tabl e t abl e) public void
{ } // lee el archivo (ya generado) y construye el arbol Huffman // NOTA:usar el metodo readBitde la clase UFile. // Es probable que se necesite utilizar el metodo parseInt de la clase // Integer para convertir un numero binario en un valor entero, asi: // int v = Integer.parseInt("10101",2); // retorna el valor 21 public I Tr ee l oad( )
{ return null;
} } I Fi l eCompr essed. j ava – Clase del archivo comprimido
Representa el archivo . szdat que contiene la información comprimida. public class I Fi l eCom pr essed
{ // asigna el nombre del archivo public void set Fi l ename( St r i ng f i l ename)
{ }
F
UTN.BA | ALGORITMOS Y ESTRUCTURA DE DATOS | ING. PABLO AUGUSTO SZNAJDLEDER (2015)
// retorna el nombre del archivo public St r i ng get Fi l ename( )
{ return null;
} // graba el archivo comprimido recorriendo el archivo de entrada // reemplazando cada caracter por su correspondiente codigo Huffman. // Al principio del archivo debe grabar un long con la longitud en bytes // que tenia el archivo original // NOTA: usar el metodo writeBit de la clase IBitWriter. public void save( I Fi l eI nput i nput Fi l e, I Tabl e t abl e)
{ return null;
} // restaura el archivo original recorriendo el archivo comprimido y, // por cada bit leido, se desplaza por las ramas del arbol hasta llegar // a la hoja que contiene el caracter por escribir. Recordar que los // primeros bytes del archivo .szdat indican la longitud en bytes del // archivo original. // NOTA: usar los metodos readBit de la clase IBitReader. r es t or e ( I Fi l eI nput i nput Fi l e, I Tr ee t r ee) public void
{ } }
I Bi t Wr i t er . j ava – Grabar bits public class I Bi t Wr i t er
{
I Bi t Reader . j ava – Leer bits public class I Bi t Reader
{ // escribe un bit en fos wr i t eBi t ( public void
// retorna el 1 o 0 segun cual sea // el bit leido. Si no hay mas bits // para leer (ultimo bit del ultimo // byte) retorna -1 public int r eadBi t (
Fi l eOut put St r eam f os , int bi t ) { return;
}
Fi l eI nput St r eam f i s ) { return 0;
// completa a 8 bits el ultimo byte public void f l us h(
Fi l eOut put St r eam f os)
} }
{ return;
} }
Manejo de archivos en Java Para implementar los métodos save y r e s t o r e de la clase I Fi l eCompr essed, el método save de la clase I Fi l eCode y el método cr eat eTabl e de la clase I Fi l eI nput será imprescindible conocer parte de la API a través de la cual los programas Java pueden leer y escribir archivos. En esta sección veremos algunos ejemplos simples que nos permitirán comprender cómo funciona la administración de archivos en Java.
UTN.BA | ALGORITMOS Y ESTRUCTURA DE DATOS | ING. PABLO AUGUSTO SZNAJDLEDER (2015)
G
Leer un archivo (clase Fi l eI nput St r eam) La clase Fi l eI nput St r eam provee el método r ead a través del cual podemos leer “byte por byte” el contenido de un archivo que abriremos “para lectura”. En el siguiente ejemplo abrimos un archivo y, por cada byte, incrementamos un contador. Al finalizar mostramos el valor del contador que coincidirá con el tamaño (en bytes) del archivo que leímos. public class Test Fi l eI nput St r eam
{ public static void mai n( St r i ng[ ] ar gs)
{ Fi l eI nput St r eam f i s = null; try
{ // el nombre del archivo se debe pasar en linea de comandos
St r i ng nombr eAr chi vo = args[ 0] ; // abrimos el archivo f i s = new Fi l eI nput St r eam( nombr eAr chi vo) ; // contador para contar cuantos bytes tiene el archivo int cont =0; // leemos el primer byte int c = f i s . r ead( ) ; // iteramos mientras no llegue el EOF, representado por -1 while( c! =- 1 )
{ cont ++; // leemos el siguiente byte
c = f i s . r e ad( ) ; } Syst em. out . pr i nt l n( nombr eAr chi vo+" t i ene " +cont +" byt es" ) ; } catch( Except i on ex)
{ ex. pr i nt St ackTr ace( ) ; throw new Runt i meExcept i on( ex) ; } finally
{ try
{ // cerramos el archivo if( f i s! =null) f i s. cl ose( ) ;
} catch( Except i on ex)
{ ex. pr i nt St ackTr ace( ) ; throw new Runt i meExcept i on( ex) ; } } } }
El método r ead lee un byte y retorna su valor, que será mayor o igual que cero y menor o igual que 255. Al llegar el end of file, para indicar que no hay más bytes para leer, el método retornará -1.
H
UTN.BA | ALGORITMOS Y ESTRUCTURA DE DATOS | ING. PABLO AUGUSTO SZNAJDLEDER (2015)
Para asegurarnos de que el archivo quede correctamente cerrado, invocamos al método c l os e dentro de la sección f i nal l y de un gran bloque try-catch-finally . Bytes sin bit de signo Java provee el tipo de datos byt e que permite representar un valor numérico entero en 1 byte de longitud. El problema es que este tipo de dato es signado y, al no existir el modificador unsigned, una variable de tipo byt e solo puede contener valores comprendidos entre -128 y 127. Para trabajar con archivos binarios necesitaremos leer bytes sin bit de signo, es decir, valores numéricos enteros comprendidos entre 0 y 255. Dado que el tipo byte no soporta este rango de valores, en Java se utiliza el tipo i nt para representar unsigned bytes. Escribir un archivo (clase Fi l eOut put St r eam) La clase Fi l eOut put St r eamrepresenta un archivo que se abrirá para escritura. El método wr i t e permite escribir “byte por byte” en el archivo. En el siguiente programa escribiremos una cadena de caracteres (una sucesión de bytes) en un archivo. Tanto el nombre del archivo como el texto de la cadena que vamos a escribir deben especificarse en línea de comandos. public class Test Out put St r eam
{ public static void mai n( St r i ng[ ] ar gs)
{ Fi l eOut put St r eam f os = null; try
{ St r i ng nombr eAr chi vo = args[ 0] ; St r i ng t ext AGr abar = ar gs[ 1] ; // abrimos el archivo para escritura f os = new Fi l eOut put St r eam( nombr eAr chi vo) ; // recorremos la cadena for( int i =0; i
i ++ )
{ int c = t ext AGr abar . char At ( i ) ;
// grabamos el siguiente byte
f os . wr i t e( c) ; } } catch( Except i on ex)
{ ex. pr i nt St ackTr ace( ) ; throw new Runt i meExcept i on( ex) ; } finally
{ try
{ if( f o s ! =null) f os . c l os e( ) ;
} catch( Except i on ex)
{ ex. pr i nt St ackTr ace( ) ; throw new Runt i meExcept i on( ex) ; } } } }
UTN.BA | ALGORITMOS Y ESTRUCTURA DE DATOS | ING. PABLO AUGUSTO SZNAJDLEDER (2015)
I
Buffers (clases Buf f er edI nput St r eamy Buf f er r edOut put St r eam) Como estudiamos en su momento, el uso de buffers incrementa notablemente el rendimiento de las operaciones de entrada y salida. En Java, las clases Buf f er r edI nput St r eamy Buf f er r edOut put St r eamimplementan los mecanismos de buffers de lectura y escritura respectivamente. El siguiente programa graba en un archivo de salida el contenido de un archivo de entrada, es decir, copia el archivo. public class Test Buf f er sI O
{ public static void mai n( St r i ng[ ] ar gs)
{ Buf f er edI nput St r eam bi s = null; Buf f er edOut put St r eam bos = null; try
{ // nombre de archivo origen
St r i ng desde = ar gs[ 0] ; // nombre de archivo destino
St r i ng hast a = ar gs[ 1] ; bi s = new Buf f er edI nput St r eam( new Fi l eI nput St r eam( desde) ) ; bos = new Buf f er edOut put St r eam( new Fi l eOut put St r eam( hast a) ) ; // leemos el primer byte desde el buffer int c = bi s. r ead( ) ; while( c! =- 1 )
{ // escribimos el byte en el buffer de salida
bos . wr i t e( c) ; // leemos el siguiente byte
c = bi s. r ead( ) ; } // vaciamos el contenido del buffer
bos . f l us h( ) ; } catch( Except i on ex)
{ ex. pr i nt St ackTr ace( ) ; throw new Runt i meExcept i on( ex) ; } finally
{ try
{ if( bos! =null) bos . c l os e( ) ; if( bi s! =null) bi s . c l os e( ) ;
} catch( Except i on ex)
{ ex. pr i nt St ackTr ace( ) ; throw new Runt i meExcept i on( ex) ; } } } }
J
UTN.BA | ALGORITMOS Y ESTRUCTURA DE DATOS | ING. PABLO AUGUSTO SZNAJDLEDER (2015)
Por defecto, el tamaño de buffer tanto de lectura como de escritura es de 8192 bytes (8 KB). Podemos especificar el tamaño de buffer con el que queremos trabajar pasándolo como segundo argumento en el constructor. Por ejemplo, en las siguientes líneas de código utilizamos como tamaño de buffer el valor que el usuario indique como tercer argumento en línea de comandos. i nt buf f er Si ze = I nt eger . par seI nt ( ar gs[ 2] ) ; bi s = new Buf f er edI nput St r eam( new Fi l eI nput St r eam( ar gs[ 0] ) , buf f er Si ze) ; bos = new Buf f eredOut put St r eam( new Fi l eOut put St r eam( ar gs[ 1] ) , buf f erSi ze) ;
Clases utilitarias Las clases utilitarias son recursos que se incluyen en el setup del ejercicio con el objetivo de abstraer al programador sobre determinadas cuestiones relacionadas con los siguientes temas:
Recorrer el árbol binario, tema que corresponde a otras materias.
Leer y escribir bits en archivos.
A continuación, veremos cómo usar cada una de estas clases. Clase UTr ee - Recorrer el árbol binario La clase UTr ee permite recorrer el árbol Huffman a través de sus hojas. Para esto, provee el método next que, en cada invocación, retornará la siguiente hoja o nul l cuando no tenga más hojas para retornar. Veamos un ejemplo: public class UTr eeDemo
{ public static void mai n( St r i ng[ ] ar gs)
{ // obtenemos el arbol Huffman
Node r oot = cr ear Ar bol Huf f man( ) ; // instancio un objeto de la clase UTree a partir de la raiz del arbol UTr ee uTr ee = new UTr ee( r oot ) ; // buffer donde el metodo next asignara los codigos Huffman St r i ngBuf f er codHuf f man = new St r i ngBuf f er ( ) ; // obtenemos la primera hoja
Node hoj a = uTr ee. next ( codHuf f man) ; // iteramos mientras el metodo no retorne null while( hoj a! =null)
{ // mostramos la hoja leida char c = hoj a. get C( ) ; int n = hoj a. get N( ) ;
St r i ng cod = codHuf f man. t oSt r i ng( ) ; Syst em. out . pr i nt l n( c+" , " +n+" , " +cod) ; // obtenemos la siguiente hoja
hoj a = uTr ee. next ( codHuf f man) ; } } // sigue mas abajo // :
La primera vez que invocamos al método next retornará la hoja del árbol ubicada “más a la izquierda”. Cada invocación subsiguiente avanzará hacia la derecha hasta llegar a la última hoja. Luego retornará nul l . El UTN.BA | ALGORITMOS Y ESTRUCTURA DE DATOS | ING. PABLO AUGUSTO SZNAJDLEDER (2015)
K
método, además, asigna en el stringBuffer que recibe como parámetro el código Huffman correspondiente a la hoja que retornó. La salida de este programa será: C( 7) codi go = {000} M( 5) codi go = {001} O( 11) codi go = {01} ( 5) codi go = {100} S( 1) codi go = {10100} R( 1) codi go = {10101} N( 1) codi go = {10110} I ( 1) codi go = {10111} T( 2) codi go = {1100} E( 2) codi go = {1101} A( 2) codi go = {1110} U( 1) codi go = {1111}
Veamos el método cr ear Ar bol Huf f man donde se hardcodea el árbol correspondiente al texto “COMO COME COCORITO COME COMO COSMONAUTA”. // : // viene de mas arriba private static Node cr ear Ar bol Huf f man( )
{ // nivel 5 (ultimo nivel) Node nS = node( ' S' , 1, null, Node nR = node( ' R' , 1, null, Node nN = node( ' N' , 1, null, Node nI = node( ' I ' , 1, null,
null) ; null) ; null) ; null) ;
// nivel 4
Node Node Node Node Node Node
a2 a1 nT nE nA nU
= node( 256+2, 2, nS, = node( 256+1, 2, nN, = node( ' T' , 2, null, = node( ' E' , 2, null, = node( ' A' , 2, null, = node( ' U' , 1, null,
nR) ; nI ) ; null) ; null) ; null) ; null) ;
// nivel 3
Node Node Node Node Node Node
nC = nM = nESP a5 = a4 = a3 =
node( ' C' , 7, null, null) ; node( ' M' , 5, null, null) ; = node( ' ' , 5, null, null) ; node( 256+5, 4, a2, a1) ; node( 256+4, 4, nT, nE) ; node( 256+3, 3, nA, nU) ;
// nivel 2
Node Node Node Node
a8 nO a7 a6
= = = =
node( 256+8, 12, nC, nM) ; node( ' O' , 11, null, null) ; node( 256+7, 9, nESP, a5) ; node( 256+6, 7, a4, a3) ;
// nivel 1
Node a10 = node( 256+10, 23, a8, nO) ; Node a9 = node( 256+9, 16, a7, a6) ; // nivel 0 (raiz)
Node a11 = node( 256+11, 39, a10, a9) ; return a11;
} private static Node node( int c,
long n,
Node i zq, Node der )
{
L
UTN.BA | ALGORITMOS Y ESTRUCTURA DE DATOS | ING. PABLO AUGUSTO SZNAJDLEDER (2015)
Node node = new Node( ) ; node. set C( c) ; node. set N( n) ; node. s et I z q( i z q) ; node. set Der ( der ) ; node. s et Si g( nul l ) ; return node; } }
Clases I Bi t Reader y I Bi t Wr i t er - Leer y escribir bits La lógica del compresor de archivos basado en el algoritmo de Huffman consiste en reemplazar cada byte del archivo original por su correspondiente código Huffman que, generalmente, será una secuencia de menos de 8 bits. El problema que surge es el siguiente: la mínima unidad de información que podemos escribir (o leer) en un archivo es 1 byte (8 bits). No podemos escribir o leer “bit por bit” a menos que lo simulemos. Por ejemplo: podemos desarrollar un método es cr i bi r Bi t que reciba el bit (1 o 0) que queremos escribir y lo guarde en una variable de instancia hasta juntar un paquete de 8 bits. En cada invocación juntará un bit más. Así, cuando haya reunido los 8 bits podrá grabar un byte completo en el archivo. Para leer, el proceso es inverso: deberíamos desarrollar el método l eer Bi t que en la primera invocación lea un byte desde el archivo y retorne el primero de sus bits. Durante las próximas 7 invocaciones deberá retornar cada uno de los siguientes bits del byte que ya tiene leído. En la invocación número 9 no tendrá más bits para entregar, por lo que deberá volver al archivo para leer un nuevo byte. Las clases I Bi t Wr i t er y I Bi t Reader hacen exactamente esto. Veamos un ejemplo: public class Bi t Demo
{ public static void mai n( St r i ng[ ] ar gs)
{ // grabamos en DEMO.dat los bits: 1011101101110
gr abar Bi t s( " DEMO. dat " , " 1011101101110" ) ; // recorremos DEMO.dat imprimimos cada bit leido leerBits("DEMO.dat");
} private static void gr abar Bi t s( St r i ng nomAr ch, St r i ng bi t s)
{ Fi l eOut put St r eam f os = null; try
{ // abrimos el archivo para grabar los bits f os = new Fi l eOut put St r eam( nomAr ch) ; // instanciamos IBitWriter I Bi t Wr i t er bw = new I Bi t Wr i t er ( ) ; // recorremos los bits que queremos escribir for( int i =0; i
{ // obtenemos el i-esimo bit (1 o 0) int bi t = bi t s . char At ( i ) - ' 0' ; // lo grabamos en el archivo
bw. wr i t eBi t ( f os , bi t ) ; } // si quedo un byte sin completar, lo completo con ceros y lo grabo
bw. f l us h( f os ) ;
UTN.BA | ALGORITMOS Y ESTRUCTURA DE DATOS | ING. PABLO AUGUSTO SZNAJDLEDER (2015)
M
} catch( Except i on ex)
{ ex. pr i nt St ackTr ace( ) ; throw new Runt i meExcept i on( ex) ; } finally
{ try
{ if( f o s ! =null) f os. cl ose( ) ;
} catch( Except i on ex)
{ ex. pr i nt St ackTr ace( ) ; throw new Runt i meExcept i on( ex) ; } } } private static void l eer Bi t s( St r i ng nomAr ch)
{ Fi l eI nput St r eam f i s = null; try
{ // abrimos el archivo para grabar los bits f i s = new Fi l eI nput St r eam( nomAr ch) ; // instanciamos IBitReader I Bi t Reader br = new I Bi t Reader ( ) ; // recorremos la cadena de bits int bi t = br . r eadBi t ( f i s ) ; // cuando no haya mas bits retornara negativo while( bi t >=0 )
{ // mostramos el bit
Sys t e m. out . pr i nt l n( bi t ) ; // leemos el proximo bit
bi t = br . r eadBi t ( f i s ) ; } } catch( Except i on ex)
{ ex. pr i nt St ackTr ace( ) ; throw new Runt i meExcept i on( ex) ; } finally
{ try
{ if( f i s ! =null) f i
s. cl ose( ) ;
} catch( Except i on ex) {
ex. pr i nt St ackTr ace( ) ; throw new Runt i meExcept i on( ex) ; } } } }
N
UTN.BA | ALGORITMOS Y ESTRUCTURA DE DATOS | ING. PABLO AUGUSTO SZNAJDLEDER (2015)