Tema 6

Anuncio
Tema 6. Lectura y escritura
Autor: José C. Riquelme
1. Lectura: la clase Scanner
1.1 Definición
Java proporciona en el paquete java.util una clase que se denomina Scanner que nos permitirá
leer datos desde ficheros de texto o incluso desde teclado. Los objetos de tipo Scanner
mediante la invocación de distintos métodos permitirán leer datos de cualquier tipo (String,
enteros, reales, etc) con diversas posibilidades de separadores. Asimismo permitirá leer un
fichero de texto línea a línea, guardando cada línea en un objeto de tipo String.
Para construir un objeto de tipo Scanner se invocará al constructor de la clase pasándole como
argumento un objeto de tipo File (que se encuentra en el paquete java.io). Los objetos de tipo
File relacionan un fichero con su nombre y path en el sistema de archivos del ordenador. Un
objeto de tipo File se crea mediante un constructor al que se le pasa como argumento una
cadena de caracteres con el nombre y el path del fichero que se quiere leer o escribir. Por
ejemplo:
File f = new File("palabras.txt");
Indica que el fichero palabras.txt se encuentra en el directorio raíz de la carpeta que contenga
nuestro proyecto Java. Si quisiéramos que el fichero estuviera en una carpeta concreta
podríamos poner un constructor como:
File f = new File("c:\Usuarios\pedro\clases\poo\tema6\palabras.txt");
Y en la carpeta del paquete test del proyecto podríamos poner:
File f = new File(".\src\test\palabras.txt");
Una vez creado el fichero f se puede invocar al constructor de la clase Scanner:
Scanner sc = new Scanner(f);
Sin embargo lo usual es hacerlo todo en la misma sentencia ya que el objeto de tipo File
normalmente no se va a usar:
Scanner sc = new Scanner(new File("palabras.txt"));
1.2 Métodos de la clase Scanner
La clase Scanner proporciona un conjunto de métodos para leer el contenido del fichero de
texto que se ha conectado mediante la invocación del constructor. En la siguiente tabla se
exponen algunos de los más interesantes, como siempre se puede ver la relación completa en
la documentación de Java: http://docs.oracle.com/javase/7/docs/api/java/util/Scanner.html
2
Introducción a la Programación
void
close()
Closes this scanner.
boolean
hasNext()
Returns true if this scanner has another token in its input.
boolean
hasNextDouble()
Returns true if the next token in this scanner's input can be interpreted as a double
value using the nextDouble() method.
boolean
hasNextInt()
Returns true if the next token in this scanner's input can be interpreted as an int value
in the default radix using the nextInt() method.
boolean
hasNextLine()
Returns true if there is another line in the input of this scanner.
boolean
hasNextLong()
Returns true if the next token in this scanner's input can be interpreted as a long
value in the default radix using the nextLong() method.
String
next()
Finds and returns the next complete token from this scanner.
double
nextDouble()
Scans the next token of the input as a double.
int
nextInt()
Scans the next token of the input as an int.
String
nextLine()
Advances this scanner past the current line and returns the input that was skipped.
long
nextLong()
Scans the next token of the input as a long.
Scanner
useDelimiter(String pattern)
Sets this scanner's delimiting pattern to a pattern constructed from the specified
String.
Antes de explicar el uso de estos métodos hay que reseñar un par de cuestiones:
 Java permite crear objetos de tipo Scanner invocando distintos constructores, no solo
con argumentos de tipo File, sino también InputStream, Readable o incluso un String.
Estos constructores se mantienen por compatibilidad entre versiones de Java y para
permitir lecturas a nivel de byte.
 El método useDelimiter sobre un objeto Scanner lleva como argumento un objeto de
tipo Pattern: http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html
que es una representación como cadena de una expresión regular:
http://es.wikipedia.org/wiki/Expresi%C3%B3n_regular
En esta asignatura sólo lo vamos a usar para indicar separadores entre los datos a leer
distintos del espacio en blanco que es el separador por defecto de la clase Scanner.
 Hay que tener en cuenta que el constructor de Scanner lanza la excepción de tipo
FileNotFoundException, por tanto el método que contenga la invocación debe
lanzar (throws) una excepción.
6. Lectura y escritura
1.3 Uso de la clase Scanner
Como hemos podido observar en la tabla anterior la clase Scanner nos permite leer un
fichero de texto de diversas maneras: línea a línea, String a String (con algún carácter
separador) o incluso leer otros tipos de datos como enteros, reales, etc.
Ejemplo 1
Supongamos que queremos leer un fichero de texto línea a línea guardando cada línea en
un String:
Scanner sc=new Scanner(new File("palabras.txt"));
while (sc.hasNextLine()) {
String s=sc.nextLine();
//tratamiento de s, por ejemplo:
mostrar(s);
}
sc.close();
Como podemos ver los métodos usados son hasNextLine que nos devuelve cierto
mientras haya más líneas por leer y falso cuando el fichero se acaba y nextLine que
devuelve la siguiente línea del fichero en un String. De esta forma si el fichero palabras.txt
tuviera este contenido:
cinco cinco cinco
seis seis
siete siete
ocho
La salida del programa anterior sería una copia del fichero línea a línea:
cinco cinco cinco
seis seis
siete siete
ocho
Ejemplo 2
Supongamos que queremos leer un fichero de texto palabra a palabra separadas por un
blanco guardando cada palabra en un String:
Scanner sc=new Scanner(new File("palabras.txt"));
while (sc.hasNext()) {
String s=sc.next();
//tratamiento de s, por ejemplo:
mostrar(s);
}
sc.close();
3
4
Introducción a la Programación
Como podemos ver ahora los métodos usados son hasNext que nos devuelve cierto
mientras haya más palabras por leer y falso cuando el fichero se acaba y next que
devuelve la siguiente palabra del fichero en un String. De esta forma si el fichero
palabras.txt tuviera este contenido:
cinco cinco cinco
seis seis
siete siete
ocho
La salida del programa anterior sería:
cinco
cinco
cinco
seis
seis
siete
siete
ocho
Ejemplo 3
Supongamos que queremos leer un fichero de texto palabra a palabra separadas por una
coma guardando cada palabra en un String:
Scanner sc=new Scanner(new File("palabrascomas.txt")).useDelimiter(",");
while (sc.hasNext()) {
String s=sc.next();
//tratamiento de s, por ejemplo:
mostrar(s);
}
sc.close();
Como podemos ver la diferencia con el anterior ejemplo es el uso del método
useDelimiter que permite leer separando por otros caracteres distintos de blanco. Si el
fichero palabrascomas.txt tuviera este contenido:
cinco,cinco,cinco,seis,seis,siete,siete,ocho
La salida del programa anterior sería:
cinco
cinco
cinco
seis
6. Lectura y escritura
seis
siete
siete
ocho
Ejemplo 4
Si nuestro fichero contuviera datos de tipo numérico también se podrían leer con la clase
Scanner. Por ejemplo si quisiéramos construir una lista de tipo Integer con el contenido del
siguiente fichero enteros.txt:
23 24 35 45
36 34
37
Escribiríamos el siguiente código:
List<Integer> ls = new LinkedList<Integer>();
Scanner sc = new Scanner(new File("numeros.txt"));
while (sc.hasNextInt()) {
ls.add(sc.nextInt());
}
sc.close();
mostrar("los números leídos: ",ls);
y la salida sería
los números leídos: [23, 24, 35, 45, 36, 34, 37]
Si en vez de leer números enteros quisiéramos leer números reales sustituiríamos los
métodos hasNextInt y nextInt por hasNextDouble y nextDouble.
Ejemplo 5
Finalmente la clase Scanner también nos permite leer datos desde teclado. Para ello en el
constructor pasamos como argumento el objeto System.in, atributo de la clase System para
señalar la entrada estándar o teclado. Así por ejemplo si quisiéramos leer números enteros
desde teclado y añadirlos a una lista hasta leer uno negativo, esto es si introducimos por
teclado la secuencia:
23 45 65 23 45 67 -6 78 76 45
La salida sería:
los números leídos: [23, 45, 65, 23, 45, 67]
El código para esto es:
5
6
Introducción a la Programación
List<Integer>ls = new LinkedList<Integer>();
Scanner sc = new Scanner(System.in);
boolean fin=false;
while (sc.hasNext() && !fin) {
Integer i= sc.nextInt();
if (i>=0)
ls.add(i);
else
fin=true;
}
sc.close();
mostrar("los números leídos: ",ls);
Si quisiéramos leer una secuencia completa, para terminar introduciríamos en la consola de
entrada Ctrl-Z que es la secuencia que indica fin de lectura.
1.4 Encapsulación de la lectura
Los trozos de código de los ejemplos anteriores no están situados en ningún método en
concreto. Como ya sabemos de temas anteriores, la programación orientada a objetos debe
encapsular las líneas de código de una determinada acción en un método, bien sea un
método de una clase, bien sea un método de utilidad. Por tanto, si estamos codificando un
método de una clase que necesita realizar una lectura de datos, en el método
correspondiente escribiríamos las sentencias anteriores. También hay que señalar que en
los trozos de código anteriores no se ha incluido el tratamiento de la excepción que puede
generar la construcción del objeto de tipo Scanner. Por tanto, el método donde se incluya
la invocación del constructor de Scanner debe lanzar (throws) o capturar (try/catch) la
excepción FileNotFoundException. Sin embargo, una forma habitual de usar la lectura de
datos es encapsularla en un método de utilidad que devuelva un objeto de tipo List con los
objetos leídos. Incluso se puede suponer que los objetos siempre serán de tipo String ya
que a partir de estos es fácil obtener los tipos numéricos habituales mediante la invocación
de los correspondientes constructores.
Ejemplo 6
Supongamos que queremos obtener datos de un fichero, sin importarnos el tipo, sólo
sabemos que están separados por comas y guardarlos en un List de String. Vamos a
construir un método estático en una clase Utiles de la siguiente forma:
public static List<String> leeFichero(String fileName, String del) {
List<String> listaleida= new LinkedList<String>();
try {
Scanner sc = new Scanner(new
File(fileName)).useDelimiter("\\s*"+del+"\\s*");
while (sc.hasNext()) {
listaleida.add(sc.next());
}
sc.close();
} catch (FileNotFoundException e) {
6. Lectura y escritura
System.out.println("Fichero no encontrado "+fileName);
}
return listaleida;
}
Algunas cuestiones respecto al código anterior:




El método leeFichero devuelve una lista de String independientemente de cuales
sean los datos contenidos en el fichero.
El método recibe un String con el nombre del fichero a leer y una cadena con el
carácter separador.
La invocación al método useDelimiter se hace concatenando el carácter separador
con cualquier combinación de blancos, tabuladores o saltos de línea delante o
detrás. Esa combinación es indicada por la expresión regular “\\s*”.
La construcción del objeto Scanner está incluida en el cuerpo de una sentencia
try/catch para capturar una posible excepción de fichero no encontrado, que
indicará que el fichero o no existe o no está donde se espera.
La invocación a este método por ejemplo para leer los enteros de este fichero:
23, 24, 35, 45,
36, 34, 37, 56, 45,
67
Sería en una clase TestLectura como sigue:
public static void main(String[] args) {
List<String> lst = Util.leeFichero("numeros_comas.txt",",");
List<Integer> li = new LinkedList<Integer>();
for(String s: lst){
li.add(new Integer(s));
}
mostrar("Los números leídos son ",li);
}
Donde la salida sería
Los números leídos son [23, 24, 35, 45, 36, 34, 37, 56, 45, 67]
Para leer datos separados por blancos podríamos usar una sobrecarga del método que no
tuviera parámetro delimitador. Su código sería:
public static List<String> leeFichero(String fileName) {
List<String> listaleida= new LinkedList<String>();
try {
7
8
Introducción a la Programación
Scanner sc = new Scanner(new
File(fileName)).useDelimiter("\\s+");
while (sc.hasNext()) {
listaleida.add(sc.next());
}
sc.close();
} catch (FileNotFoundException e) {
System.out.println("Fichero no encontrado");
}
return listaleida;
}
Dónde la expresión "\\s+" indica cualquier combinación de al menos un
blanco, un tabulador o un salto de línea.
Ejemplo 7
Supongamos ahora que se necesita leer un fichero de texto con los valores de los atributos
de una clase, de forma que la salida sea una colección de objetos creados a partir de esos
valores. Así si tenemos el fichero personas.txt con el siguiente contenido:
Pedro Gómez,11111111A,25
Luisa Espinel,222222222B,24
Pedro Gómez,33333333C,24
Mariana Guerrero,44444444D,28
Vamos a crear un método de utilidad que devuelva un List<Persona> con los cuatro objetos
de tipo Persona, que estarán organizados por filas, de forma que cada fila contiene los
valores de los atributos de un objeto separados por comas. El método recibirá un String con
el nombre del fichero y lo leerá línea a línea. Cada una de estas líneas contiene los valores
de los atributos (nombre, DNI y edad) necesarios para construir un objeto Persona. Para
separar estos valores se va a usar un método auxiliar que hemos denominado
separaElementos para a partir de otro objeto de tipo Scanner segmentar la línea en una List
de String con los atributos. El código es:
public static List<Persona> leePersonas(String nomFich) throws
FileNotFoundException{
List <Persona> lp = new LinkedList<Persona>();
Scanner sc=new Scanner(new File(nomFich));
while (sc.hasNextLine()) {
String linea = sc.nextLine();
List<String> lisat= separaElementos(linea);
Persona p=new PersonaImpl(lisat.get(0),lisat.get(1),new
Integer(lisat.get(2)));
lp.add(p);
}
return lp;
}
public static List<String> separaElementos(String s){
List<String> ls = new LinkedList<String>();
Scanner sc1=new Scanner(s).useDelimiter(",");
while (sc1.hasNext()){
6. Lectura y escritura
ls.add(sc1.next());
}
return ls;
}
Algunas cuestiones respecto al código anterior:
 La excepción FileNotFoundException no ha sido tratada mediante un try/catch para
no dificultar la lectura del código. Por eso sólo se ha propagado (throws) hacia el
método invocante. En un código “bien hecho” debería estar la sentencia try/catch.
 El método separaElementos no depende de Persona o del tipo que estemos
construyendo. Es un método de propósito general que recibe un String s y devuelve
un List de String con los “trozos” de s que se encuentran separados por comas.
 Como se puede observar el tipo Scanner también es capaz de “leer” de una cadena
de caracteres, simplemente poniéndola en el lugar del objeto de tipo File en la
invocación al constructor.
 El constructor de PersonaImpl que estamos invocando recibe dos String (nombre y
DNI) y un Integer (edad) que es convertido a Integer en la invocación al constructor.
 También debería incluirse el lanzamiento de una excepción si el número de
elementos de la lista lisat de atributos n tiene los tres elementos que debería tener
si el fichero está bien construido. Esto es, se debe lanzar una excepción que
advierta si el fichero tiene tres valores separados por comas en cada línea.
2. Escritura: la clase printWriter
2.1 Definición
Java proporciona dos clases básicas para escribir en un fichero de texto: PrintStream y
PrintWriter. Su uso es muy parecido y las diferencias son más internas que externas para un
programador no avanzado. La principal diferencia es que PrintWriter (que es posterior a
printStream) no puede ser usado para escribir bytes “en crudo” (raw en inglés), y por tanto
su uso habitual es para datos que tengan una representación en texto o dicho de otra
manera para tipos que tengan el método toString. Nosotros vamos a usar la clase
PrintWriter aunque sus métodos para escribir objetos en formato carácter son similares a
los de PrintStream. Una ventaja de PrintWriter es que sus métodos no lanzan excepciones,
aunque sí alguno de sus constructores.
El constructor de la clase PrintWriter recibe un objeto de File y puede lanzar la excepción
FileNotFoundException si se produce algún problema para su apertura (normalmente disco
lleno o protegido). Por tanto un posible código para la creación de un objeto PrintWriter es:
File file = new File(filename);
try {
PrintWriter ps = new PrintWriter(file);
} catch (FileNotFoundException e) {
System.out.println("Fichero no encontrado "+filename);
}
9
10
Introducción a la Programación
2.2 Métodos de la clase PrintWriter
Una selección de los métodos que java proporciona a la clase PrintWriter son los de la
siguiente tabla. Como siempre se pueden consultar todos en:
http://docs.oracle.com/javase/7/docs/api/java/io/PrintWriter.html
void
close()
cierra el fichero
void
print(Tipo
t)
Escribe un valor t de tipo Tipo, donde Tipo puede ser char, boolean, char[ ],
double, int, String, long, float u Object
void
println()
Salta de línea
void
println(Tipo t)
Escribe un objeto de tipo boolean, char, char [ ], String, double, float, int, long u
Object y después salta de línea
2.3 Uso de la clase PrintWriter
Como se puede ver de la tabla anterior escribir en un fichero de texto es igual de sencillo
que en la consola. Basta con definir un objeto de la clase PrintWriter e invocar con él a los
métodos print o println según queramos saltar o no de línea al final del objeto a escribir
que pasaremos como argumento.
Ejemplo 7
Para escribir en un fichero de texto los valores de una lista de números enteros de manera
que estén uno por línea, el código sería:
File file = new File("enteros.txt");
PrintWriter ps = null;
try {
ps = new PrintWriter(file);
for (Integer elem : lista) {
ps.println(elem);
}
ps.close();
} catch (FileNotFoundException e) {
System.out.println("Fichero no encontrado "+filename);
}
2.4 Encapsulación de la escritura
Igual que se hizo en la lectura de ficheros, podemos hacer un método estático que reciba
una Collection de elementos de cualquier tipo y los escriba en un fichero de texto. Para ello
podemos usar los tipos genéricos que se estudiaron al final del Tema 3. El código podría ser:
public static <T> void escribeFichero(Collection<T> it, String
filename) {
6. Lectura y escritura
File file = new File(filename);
try {
PrintWriter ps = new PrintWriter(file);
for (T elem : it) {
ps.println(elem);
}
ps.close();
} catch (FileNotFoundException e) {
System.out.println("Fichero no encontrado "+filename);
}
}
Si quisiéramos que no escribiera cada dato en una nueva línea sino separar los objetos por
un espacio en blanco o por una coma, bastaría con sustituir:
ps.println(elem);
por
ps.print(elem+ " "); // ps.print(elem+ ",");
3. Ejercicios
1. Escriba en una clase de utilidad los métodos que nos permiten encapsular la lectura y
escritura de ficheros, añadiendo cuando sea necesario el tratamiento de Excepciones
mediante sentencias try/catch.
2. Lea desde teclado una lista de números reales y escríbala en un fichero de texto
separados por blancos y en otro cada uno en una línea.
3. Lea una lista de palabras desde teclado, ordénela alfabéticamente y escriba el resultado
en un fichero de texto de forma que cada palabra esté en una línea y éstas estén
numeradas.
11
Descargar