Sie sind auf Seite 1von 13

TEMA FICHEROS II

Realizar tareas con ficheros de forma secuencial y de forma aleatoria.

ABRIENDO Y TRABAJANDO CON FICHEROS DE FORMA SECUENCIAL.

Despus de la teora expuesta hasta ahora acerca del trabajo con ficheros, habr
observado que la metodologa de trabajo se repite.

Es decir, para escribir datos en un fichero:

- Definimos un flujo hacia el fichero en el que deseamos escribir datos.


- Leemos los datos del dispositivo de entrada o de otro fichero y los
escribimos en nuestro fichero. Este proceso se hace normalmente registro a
registro. Para ello, utilizaremos los mtodos proporcionados por la interfaz
del flujo.
- Cerramos el flujo.

Para leer datos de un fichero existente:

- Abrimos un flujo desde el fichero del cual queremos leer los datos.
- Leemos los datos del ficheros y los almacenamos en variables de nuestro
programaron el fin de trabajar con ellos. Este proceso se hace normalmente
registro a registro. Para ello, utilizaremos los mtodos proporcionados por la
interfaz del flujo.
- Cerramos el flujo.

EJEMPLOS:

Escribir en un fichero:

public static void crearFicheroFile (File fichero) throws IOException


{
PrintStream flujoS = System.out; //Salida estndar
DataOutputStream dos = null; //salida de datos hacia el fichero
char resp;

try
{
//Crear un flujo hacia el fichero que permita escribir
//datos de tipos primitivos y que utilice un buffer.
dos = new DataOutputStream (new BufferedOutputStream(new
FileOutputstream(fichero)));

//Declarar los datos a escribir en el fichero


String nombre, direccion;
long telefono;

//Leer datos de la entrada estndar y escribirlos en el fichero


do
{
flujoS.print ("nombre: "); nombre = Leer.dato();
flujoS.print ("direccin: "); direccion = Leer.dato();
flujoS.print ("telfono: "); telefono = Leer.datoLong();
//Almacenar un nombre, una direccin y un telfono en el fichero
dos.writeUTF(nombre);
dos.writeUTF(direccion);
dos.writeLong(telefono);

flujoS.print("desea escribir otro registro? (s/n) ");


resp= charSystem.in.read();
//Saltar kis caracteres disponibles en el flujo de entrada
System.in.skip (System.in.avaliable());
}
while (resp == 's');
}
finally
{
//Cerrar el flujo
if (dos != null) dos.close();
}
}

El mtodo expuesto anteriormente recibe como parmetro un objeto File que define el
nombre del dichero que se desea crear y realiza las tareas siguientes:

- Crea un flujo hacia el fichero especificado por el objeto File que permite
escribir datos de tipos primitivos utilizando un buffer.

- Lee grupos de datos nombre, direccin y telfono de la entrada estndar y los


escribe en el fichero.

- Si durante su ejecucin, alguno de los mtodos invocados por l lanza una


excepcin del tipo IOException no atrapada por ellos. La vuelve a lanzar
para que pueda ser atrapada por el mtodo que le invoque. Con esto, hemos
dado a CrearFichero un comportamiento, en cuanto a excepciones se refiere,
igual al de los mtodos read y write.

EJEMPLO:

Leer de un fichero

Para leer de un fichero creado por la aplicacin anterior, vamos a escribir otra
aplicacin basada en una clase de MostrarListaTfnos. Esta clase definir dos mtodos;

El mtodo mostrarFichero recibe como parmetro un objeto String que almacena el


nombre del fichero que se desea leer y realiza las tareas siguientes:

- Crea un objeto File para identificar el fichero


- Si el fichero especificado existe, crea un flujo desde el mismo que permite
leer datos de tipos primitivos utilizando un buffer.
- Lee un grupo de datos nombre, direccin y telfono desde el fichero y los
muestra. Cuando se alcance el final del fichero Java lanzar una excepcin
del tipo EOFException, instante en el que finalizar la ejecucin de este
mtodo.
- Si durante su ejecucin alguno de los mtodos invocados lanza un excepcin
IOException, la pasa para que sea atrapada por algn mtodo en la pila de
llamadas.

import java.io.*; //Libreras de Entrada/Salida

public class MostrarListaTfnos


{
public static void mostrarFichero (String nombreFichero) throws IOException
{
PrintStream flujoS= System.out; //Salida estndar
DataInputStream dis = null; //entrada de datos desde el fichero
File fichero = null; // objeto que identifica el fichero

try
{
//Crear un objeto File que identifique al fichero
fichero = new File (nombreFichero);
//Verificar si el fichero existe
if (fichero.exists())
{
//Si existe, abrir un flujo desde el mismo
dis = new DataInputStream (new BufferedInputStream( new
FileInputStream(fichero)));
//Declarar los datos a leer desde el fichero
String nombre, direccin;
long telfono;
do
{
//Leer un nombre, una direccin y un telfono desde el
//fichero
//Cuando se alcance el final del fichero Java lanzar una
//excepcin EOF, End Of File.
nombre = dis.readUTF();
direccion = dis.readUTF();
telefono = dis.readLong();
//Mostrar los datos nombre, direccion y telefono.
flujoS.println(nombre);
flujoS.println(direccion);
flujoS.println(telefono);
flujoS.println();
}
while (true);
}
else
{
flujoS.print("El fichero no existe");
}
}
catch (EOFException e)
{
flujoS.println ("Fin del listado");
}
finally
{
//Cerrar el flujo
if (dis != null)
dis.close();
}
}

ABRIENDO FICHEROS PARA ACCESO ALEATORIO

Hasta este punto, hemos trabajado con ficheros de acuerdo con el siguiente esquema:
abrir el fichero, leer o escribir hasta el final del mismo, y cerrar el fichero. Pero no
hemos ledo o escrito a partir de una determinada posicin dentro del fichero. Esto esto
es particularmente importante cuando necesitamos modificar algunos de los valores
contenidos en el fichero o cuando necesitemos extraer una parte concreta dentro del
fichero.

El paquete java.io contiene la clase RandomAccessFile que proporciona las


capacidades que permite este tipo de acceso directo. Adems, un flujo de esta clase
permite realizar tanto operaciones de lectura como de escritura sobre el fichero
vinculado con el mismo.

Un fichero accedido aleatoriamente es comparable a una matriz. En una matriz para


acceder a uno de sus elementos utilizamos un ndice. En un fichero accedido
aleatoriamente el ndice es sustituido por un puntero de lectura o escritura (R/W). Dicho
puntero es situado automticamente al principio del fichero cuando ste se abre para
leer y/o escribir. Por lo tanto, una operacin de lectura o de escritura comienza en la
posicin donde ste el puntero dentro del fichero; finalmente, su posicin coincidir
justo a continuacin del ltimo byte ledo o escrito.

La clase RandomAccessFile

Un flujo de esta clase permite acceder directamente a cualquier posicin dentro del
fichero vinculado con l.

La clase RandomAccessFile proporciona dos constructores:

RandomAccessFile (String nombre-fichero, String modo)


RandomAccessFile (File objeto-File, String modo)

El primer constructor abre un flujo vinculado con el fichero especificado por nombre-
fichero, mientras que el segundo hace lo mismo, pero a partir de un objeto File. El
argumento modo puede ser:

R  read. Slo lectura.


RW  read/write. Lectura y Escritura.

Por ejemplo, el siguiente fragmento de cdigo construye un objeto File para verificar si
el nombre especificado para el fichero existe como tal. Si existe y no corresponde a
unfichero se lanza una excepcin; si existe y se trata de un fichero, se crea un flujo para
escribir y leer a y desde ese fichero; y si no existe, tambin se crea el flujo y el fichero.

File fichero = new File ("listatelefonos.dat");


if (fichero.exists() && !fichero.isFile())
throw new IOException (fichero.getName() + " no es un fichero");
RandomAccessFile listaTelefonos = new RandomAccessFile (fichero, "rw");

La sentencia throw permite a un mtodo lanzar una excepcin en funcin de los hechos
que hayan ocurrido. Dicha excepcin podr ser atrapada por el propio mtodo o por
algn otro que se encuentre en el retorno por la pila de llamadas; si no se encuentra un
mtodo que pueda atraparla, la aplicacin ser interrumpida.

Asimismo, la clase RandomAccessFile, que se deriva directamente de Object,


implementa la interfaces DataInput y DataOutput; esto significa que proporciona los
mismos mtodos que expusimos para las clases DataInputStream y DataOutputStream.
Adems proporciona los mtodos getFilePointer, length y seek que se definen de la
forma siguiente:

public long getFilePointer() throws IOException

- Este devuelve la posicin actual en bytes del puntero. Entienda puntero como
el ndice de una matriz y tenga siempre presente este puntero marca siempre
la posicin donde se iniciar la siguiente operacin de lectura o de escritura.

public void length() throws IOException

- Este otro devuelve la longitud del fichero en bytes.

public void seek(long pos) throws IOException

- El ultimo mueve el puntero de R/W a una nueva localizacin desplazazada


una cierta cantidad de bytes (determinada en la variable pos) del principio
del fichero. No se permiten desplazamientos negativos. El desplazamiento
requerido puede ir ms all del final del fichero, esta accin no cambia la
longitud del fichero; la longitud del fichero slo cambiar si a continuacin,
realizamos una operacin de escritura.

Segn lo expuesto, las dos lneas de cdigo siguientes sitan el puntero de R/W, la
primera desp bytes antes del final del fichero y la segunda desp bytes despus de la
posicin actual.

listaTelfonos.seek (listaTelfonos.length() - desp);


listaTelfonos.seek (listaTelfonos.getFilePointer() + desp);

Igual que ocurra al trabajar con ficheros accedidos secuencialmente, los datos que
deseemos escribir en un fichero accedido aleatoriamente deben guardarse uno a uno
utilizando el mtodo adecuado de la clase del flujo vinculado. Por ejemplo, las
siguientes lneas de cdigo escriben en le fichero datos a partir de la posicin desp
dentro del mismo, los atributos nombre, direccin y telfono relativos a un objeto
CPersona:

CPersona objeto;
//..
RandomAccessFile ges = new RandomAccessFile ("datos", "rw");
fes.seek(desp);
fes.writeUTF(objeto.obtenerNombre());
fes.writeUTF(objeto.obtenerDireccion());
fes.writeLong(objeto.obtenerTelefono());

Si para nuestros propsitos, pensamos en los atributos nombre, direccin y telfono


como si de un registro se tratara, Cal es el tamao en bytes de ese registro? Si
escribimos ms registros todos tienen el mismo tamao? Evidentemente no; el tamao
de cada registro depender del nmero de caracteres almacenados en los String nombre
y direccin (telfono es un dato de tamao fijo, 8 bytes, puesto que se trata de un long)
A cuento de qu viene esta exposicin?

Al principio de este apartado dijimos que el acceso aleatorio a ficheros es


particularmente importante cuando necesitamos extraer una parte concreta dentro del
fichero. Esto puede resultar bastante complicado si las unidades de grabacin que
hemos denominado registros no son todas iguales, ya que intervienen los factores de:
posicin donde comienza un registro y longitud del registro. Tenga presente que cuando
necesite reemplazar el registro n de un fichero por otro, no debe sobrepasarse el nmero
de bytes que actualmente tiene. Todo esto es viable llevando la cuenta en una matriz de
la posicin de inicio de cada uno de los registros y de cada uno de los campos si fuera
preciso (esta informacin se almacenara en un fichero ndice para su utilizacin
posterior), pero resulta mucho ms fcil si todos los registros tienen la misma longitud.

Como ejemplo supongamos que deseamos mantener una lista de telfonos. Cada
elemento de la lista ser un objeto persona con los atributos nombre, direccion y
telfono. A su vez, la lista de telfonos ser otro objeto que encapsule un flujo
vinculado con un fichero y muestre una interfaz que permita aadir, eliminar y buscar
una persona en la lista. Cada registro del fichero contendr los atributos nombre
direccin y telfono de cada uno de los objetos persona que forman la lista.

La clase CPersona

La clase de objetos persona (que denominaremos CPersona) encapsular el nombre, la


direccin y el telfono de cada una de las personas de la lista; asimismo proporcionar
la funcionalidad necesaria para establecer u obtener los datos de cada persona
individual.

El listado siguiente muestra un ejemplo de una clase CPersona que define los atributos
privados nombre, direccin y telfono relativos a una persona, y los mtodos pbllicos
que forma la interfaz de esta clase de objetos. Esto es:

- Constructores, con y sin argumentos, para iniciar un objeto persona.


- Mtodos de acceso (asignar y obtener ) para cada uno de los atributos.
- Un mtodo denominado tamao que devuelve la longitud en bytes
correspondiente a los atributos de un objeto CPersona.

/**********************************************************
********************DEFINICIN CLASE PERSONA***************
**********************************************************/

public class CPersona


{
//Atributos
private String nombre;
private String direccion;
private long telefono;

//Constructores
public CPerona()
{
nombre=null;
direccion=null;
telefono=null;
}

public Cpersona (String nom, String dir, long tel)


{
this.nombre=nom;
this.direccion=dir;
this.telefono=tel;
}

//Metodos

public void asignarNombre(String nom)


{nombre = nom}
public String obtenerNombre ()
{return nom}
public void asignarDireccion (String dir)
{direccion = dir}
public String obtenerDireccion ()
{return direccion}
public void asignarTelefono(long tel)
{telefono = tel}
public long obtenerTelefono ()
{return telefono}
public int tamao()
{
//Longitud en bytes de los atributos (un long = 8 bytes)
//Cada caracter de un String ocupa 16 bits... 2 bytes, (unicode)
return nombre.length()*2 + direccion.length()*2+8
}

Un mtodo como asignarNombre simplemente asigna el nombre pasado como


argumento al atributo nombre del objeto que recibe el mensaje. Y un mtodo como
obtenerNombre devuelve el atributo nombre del objeto que recibe el mensaje. La
explicacin para los otros mtodos es anloga. Por ejemplo:

CPersona obj = new CPersona();


obj.asignarNombre ("Javier");
System.out.println (obj.obtenerNombre()); //escribe: Javier

La clase CListaTfnos

El listado siguiente muestra un ejemplo de lo que puede ser la clase lista de telfonos,
que denominaremos CListaTfnos. Define los atributos privados fes, flujo vinculado con
el fichero, nregs, nmero de registros, tamaoReg, tamao del registro en bytes, y
regsEliminados, indica si se elimin algn registro, as como los mtodos que se
describen a continuacin.
Constructor CListaTfnos

Cuando desde algn mtodo se cree un objeto CListaTfnos qu esperamos que ocurra?
Lgicamente que se cargue la lista de telfonos especificada, o bien que se cree una
nueva cuando el fichero especificado no exista. Por ejemplo:

File fichero 0 new File ("listatfnos.dat");


CListaTfnos listafnos = new CListaTfnos (fichero);

Segn lo expuesto, la lista de telfonos especificada se cargar desde un fichero


almacenado en el disco denominado listatfnos.dat y si este fichero no existe, se crear
uno nuevo con ese nombre. Para ello, el constructor de la clase CListaTfnos abrir un
flujo para acceso aleatorio desde el fichero especificado, almacenar una referencia al
mismo en el atributo fes de la clase, y en nregs, almacenar una referencia al mismo en
el atributo fes de la clase, y en nregs, almacenar el nmero de registros existentes en el
fichero. Un atributo ms, tamaoRegs, especificar el tamao que hayamos previsto
para cada registro y otro, regsEliminados, tomar el valor true cuando se marque algn
registro para eliminar. En nuestro caso, la informacin almacenada en un registro se
corresponde con el nombre, direccin y telfono de un objeto CPersona.

Atenindonos a lo explicado, veamos a continuacin el esqueleto de la clase


CListaTfnos y el constructor de la misma:

/**********************************************************
********************DEFINICIN CLASE CLISTATFNOS***********
**********************************************************/

import java.io.*;

public class CListaTfnos


{
//Atributos
private RandomAccessFile fes; //Flujo
private int nregs; //nmero de registros
private int tamaoReg = 140; //tamao del registro en bytes
private boolean regsEliminados = false; //true si se eliminaron registros

public CListaTfnos (File fichero) throws IOException


{
if (fichero.exists() && !fichero.isFile())
throws new IOException (fichero.getName() + " no es un fichero");
fes = new RandomAccessFile (fichero, "rw");

// Como es casi seguro que el ltimo registro no ocupe el tamao fijado


//utilizamos ceil para redondear por encima.

nregs = (int)Math.ceil((double)fes.length() / (double) tamaoReg);


}
Observe que el constructor de la clase verifica si el argumento pasado corresponde a un
nombre existente en el directorio actual de trabajo y, encaso de que exista, si realmente
se trata de un nombre de fichero; si no es as, lanzar una excepcin del tipo
IOException (throws IOException) para que sea atrapada por el mtodo que lo invoque;
en otro caso, abre un flujo que permite acceder aleatoriamente al fichero especificado
para leer y escribir, y calcula el nmero de registros de dicho fichero.

El resto de los mtodos en los que se puede producir un error de E/S, tambin lanzarn
una excepcin del tipo IOException para que sea atrapada por algubno de los mtodos
de la pila de llamadas. Esta es la tcnica que se sigue cuando no se desea manipular una
excepcin en el mtodo invocado, sino en el que invoca directa o indirectamente.

Para entender el clculo de la variable nregs, recuerde que el desplazamiento realizado


con seek puede ir ms all del final del fichero; esta accin no cambiar la longitud del
fichero, a no ser que a continuacin, realicemos una operacin de escritura; por eso, el
tamao del ltimo registro ser menor o igual que tamao-Reg. Esto es, para que todos
los registros del fichero tengan la misma longitud, como veremos en el mtodo
escribirReg, antes de escribir el siguiente registro realizaremos un desplazamiento
mltiplo de tamaoRed:

fes.seek (i * tamaoReg); //Situar el puntero L/E en el registro i


//A continuacin Escribir en el registro i

Escribir un registro en el fichero.

El mtodo escribirRed se ha diseado para que permita escribir los atributos de un


objeto CPersona dentro del fichero a partir de una posicin determinada. Tiene dos
parmetros: el primero indica el nmero de registro que se desea escribir, que puede
coincidir con un registro existente, en cuyo caso se sobreescribir este ltimo, o bien
con el nmero del siguiente registro que se puede aadir al fichero; y el segundo, hace
referencia al objeto CPersona cuyos atributos deseamos escribir. El mtodo devolver
un valor true si se ejecuta satisfactoriamente y false en otro caso.

public boolean escribirReg (int i, CPersona objeto) throws IOException


{
if ( i >= 0 && i <= nregs)
{
if (objeto.tamao() + 4 > tamaoReg)
System.err.println("tamao del registro excedido");
else
{
fes.week(i*tamaoReg);
fes.writeUTF(objeto.obtenerNombre());
fes.writeUTF(objeto.obtenerDireccin());
fes.writeLong(objeto.obtenerTelfono());
return true;
}
}
else
{
System.err.println ("Nmero de registro fuera de lmites");
}
return false;
}

Se observa que lo primero que hace el mtodo es verificar si el nmero de registro es


vlido (cuando i sea igual a nregs es porque se quiere aadir un registro al final del
fichero). El primer registro es el cero. Despus comprueba que el tamao de los
atributos del objeto CPersona ms 4, no superen el tamao establecido para el registro
(ms 4 porque cada vez que writeUTF escribe un String, aade 2 bytes iniciales para
dejar constancia del nmero de bytes que se escriben; esto permitir posteriormente al
mtodo readUTF sabes cuntos bytes tiene que leer). Si el tamao est dentro de los
lmites permitidos, sita el puntero de L/E en la posicin de inicio correspondiente a ese
registro dentro del fichero y escribe los atributos del objeto uno a continuacin de otro
(vea la definicin de seek).

Aadir un registro al final del fichero.

El mtodo aadirReg tiene como misin aadir un nuevo registro al final del fichero.
Para ello invoca al mtodo escribirReg pasando como argumento la posicin que
ocupar el nuevo registro, que coincide con el valor de nregs, y el objeto cuyos atributos
se desean escribir.

public void aadirReg (CPersona obj) throws IOException


{
if (escribirReg (nregs, obj)) nregs++;
}

Leer un registro.

Para leer un registro del fichero que almacena la lista de telfonos, la clase ClistaTfnos
proporciona el mtodo leerReg. Este mtodo tiene un parmetro para identificar el
nmero de registro que se desea leer y devuelve el objeto CPersona creado a partir de
los datos nombre, direccin y telfono ledos desde el fichero.

public CPersona leerReg (int i) throws IOException


{
if (i >= 0 && i < nregs)
{
fes.seek (i * tamaoReg); //situar el puntero de L/E

String nombre, direccin;


long telefono;
nombre = fes.readUTF();
direccion = fes.readUTF();
telefono = fes.readLong();

return new CPersona(nombre , direccion, telefono);


}
else
{
System.out.println ("nmero de registro fuera de lmites");
return null;
}
}

Lo primero que hace el mtodo leerReg es verificar si el nmero de registro es vlido (el
primer registro es el 0). Si el nmero de registro est dentro de los lmites permitidos,
sita el puntero de L/E en la posicin de inicio correspondiente a ese registro dentro del
fichero y lee los datos nombre, direccin y telfono (esto se hace enviando al flujo fes
vinculando con el fichero, el mensaje read UTF, una vez por cada dato). Finalmente,
devuelve un objeto CPersona construido a partir de los datos ledos (el valor devuelto
ser null si el nmero de registro est fuera de lmites).

Eliminar un registro del fichero.

Puesto que el fichero manipulado con una lista de telfonos, parece lgico buscar el
registro que se desee eliminar por el nmero de telfono, ya que ste es nico. Para este
propsito escribiremos un mtodo eliminarReg con un parmetro que almacene el
nmero de telfono a eliminar y que devuelva un valor true si la operacin se realiza
con xito, o false en caso contrario.

public boolean eliminarReg (long tel) throws IOException


{
CPersona obj;

//Buscar el telfono y marcar el registro para posteriormente


//eliminarlo

for (int reg_i = 0; reg_i < nregs; reg_i++)


{
obj = leerReg(reg_i);
if (obj.obtenerTelefono() == tel)
{
obj.asignarTelefono(0);
escribirReg( reg_i, obj);
regsEliminados = true;
return true;
}
}
return false;
}

El proceso seguido por le mtodo eliminar es leer registros del fichero, empezando por
el registro cero, y comprobar por cada uno de ellos si el telfono coincide con el valor
pasado como argumento (este proceso recibe el nombre de bsqueda secuencial). Si
existe un registro con el nmero de telfono buscado, no se borra fsicamente del
fichero, sino que se marca el registro poniendo un cero como nmero de telfono
(borrado lgico). Esta forma de proceder deja libertad al usuario de la clase CListaTfnos
para eliminar de una sola vez todos los registros marcados al finalizar su aplicacin (lo
que redunda en velocidad de ejecucin), para restaurar un registro marcado para
eliminar, para crear un histrico, etc.

Hay registros marcados para eliminar?

Para saber si se marcaron registro para eliminar despus de haber trabajado con el
fichero, aadimos a la clase CListaTfnos el mtodo tieneRegsEliminados. Este mtodo
devuelve el valor del atributo privado regsEliminados, que inicialmente vale false y que
es cambiado a ture por el mtodo eliminarReg cuando se marca un registro para borrar.

public boolean tieneRegsEliminados


{
return regsEliminados;
}

Buscar un registro en el fichero.

Una operacin muy comn es localizar un determinado registro. Para ello lo ms normal
es buscar por el nombre del propietario de ese telfono, aunque tambin podra
realizarse la bsqueda por la direccin. El mtodo buscarReg que se expone a
continuacin permite realizar la bsqueda por cualquier subcadena perteneciente al
nombre. Para ello utiliza dos parmetros: la subcadena a buscar y a partir de qu registro
del fichero se desea buscar. Si la bsqueda termina con xito, el mtodo devuelve el
nmero del registro correspondiente; en otro caso devuelve el valor -1.

public int buscarReg (String str, int pos) throws IOException


{
//Buscar un registro por una subcadena del nombre
//a partir de un registro determinado

CPersona obj;
String nom;

if (str == null) return -l;


if (pos < 0) pos = 0;
for ( int reg_i = pos; reg_i < nregs; reg_i++ )
{
obj= leerReg(reg_i);
nom = obj.obtenerNombre();
//str est contenida en nom?
if (nom.indexOf(str) > -1)
return reg_i;
}
return -1
}

Se observa que el mtodo buscarReg, al igual que el mtodo eliminarReg, realiza una
bsqueda secuencial desde el registro pos, comprobando si el nombre de alguno de ellos
contiene la subcadena str. Lgicamente, al realizar una bsqueda secuencial, el
resultado ser el nmero del primer registro que contenga en su nombre la subcadena
pasada como argumento; pero tambin es evidente que es posible continuar la bsqueda
a partir del siguiente registro, invocando de nuevo al mtodo buscarReg, pasando como
argumentos la misma subcadena y el nmero de registro siguiente al devuelto en el
proceso de bsqueda anterior.

Das könnte Ihnen auch gefallen