Ficheros de Bytes
Nesta actividade imos ver como traballar con un tipo de ficheiros, o de bytes de maneira máis clásica e logo usando Java NIO. Para a continuación ver o uso de datos primitivos de Java con ficheiros.
Java con Ficheiros de Bytes
O fluxo de bytes é a maneira máis eficiente de traballar con entrada e saída de bytes, e tamén a máis simple e de baixo nivel. Neste caso traballarase con bytes directamente que se almacenan nos ficheiros en formato binario.
Java ofrécenos o paquete básico de Entrada/Saída java.io. Partindo deste paquete temos as dúas clases para a manipulación de ficheiros con fluxo de bytes:
- FileInputStream: clase que define as operacións básicas de entrada de bytes con ficheiros.
- FileOutputStream: clase que define as operacións básicas de saída de bytes con ficheiros.
Java sempre crea un obxecto para asignarlle un fluxo de bytes. As anteriores clases herdan das clases abstractas InputStream e OutputStream respectivamente.
Operacións básicas:
- Método read(): método da clase FileInputStream, que lee do fluxo de entrada un byte (8 bits) e devolve un int (tipo primitivo de java). Hai dúas posibilidades: Lectura normal: devolve un int (24 ceros á esquerda e 8 bits resultante da lectura). Lectura fin do stream: obtense o valor “-1”. int read(): Le un byte de data do fluxo de entrada.
- Método write(): método da clase FileOutputStream, que escribe no ficheiro un byte pero sempre a partires dun int (cast forzoso quédase cos últimos 8 bits) void write (int b): o mesmo pero escribe o número de bytes ó fluxo de saída.
- Método flush(): método que forza o gardado no stream o no writer de bytes. Exemplo de lectura e escritura:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FluxoBytes {
public static void main(String[] args) {
//Obtéñese o directorio home do sistema onde almacenarase o ficheiro
String directory = System.getProperty("user.home");
String fileName = "sample.txt";
String absolutePath = directory + File.separator + fileName;
// escritura do ficheiro
try(FileOutputStream fileOutputStream = new FileOutputStream(absolutePath)) {
String fileContent = "This is a sample text.";
//almacenamento de bytes
fileOutputStream.write(fileContent.getBytes());
} catch (FileNotFoundException e) {
System.err.println("Ficheiro non encontrado");
} catch (IOException e) {
System.err.println("Error na E/S");
}
// lendo o ficheiro
try(FileInputStream fileInputStream = new FileInputStream(absolutePath)) {
int ch = fileInputStream.read();
//ata a fin do stream
while(ch != -1) {
System.out.print((char)ch);
ch = fileInputStream.read();
}
} catch (FileNotFoundException e) {
System.err.println("Ficheiro non encontrado");
} catch (IOException e) {
System.err.println("Error na E/S");
} }
}
Java NIO
A partires da versión 1.4 de Java existe un novo paquete de funcionalidades de entrada e saída que evita as deficiencias de java.io e fai máis sinxelo o uso de streams (métodos máis directos). Centrándonos en ficheiros, mellora o rendemento das operacións, aparecen habilidades asíncronas (chamadas sen bloqueo), etc… Operacións básicas:
- Files.write: método estático de escritura de bytes. É necesario indicarlle a ruta, o contido a escribir e unha opción de escritura:clase que define as operacións básicas de entrada de bytes con ficheiros. Path Files.write(Path path,byte[] bytes, StandardOpenOption options) throws IOException Aspectos a ter en conta é que devolve a ruta do ficheiro a escribir. Para o terceiro argumento do método o máis frecuente é utilizar o enumerado StandardOpenOption, que ten múltiples opcións (enlace das opcións): hai que telas en conta para a forma de abrir o ficheiro.
- Files.readAllBytes: método estático para ler desde un ficheiro e envorcalo a unha matriz de bytes. Necesita unha ruta, é cerra o ficheiro cando chega o final ou hai un erro de entrada ou saída. byte[] readAllBytes(Path path) throws IOException Exemplo do seu uso:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class FluxoBytesNIO {
public static void main(String[] args) {
String directory = System.getProperty("user.home");
String fileName = "sample.txt";
String content = "This is a sample text.";
Path path = Paths.get(directory, fileName);
try {
Files.write(path, content.getBytes(), StandardOpenOption.CREATE);
} catch (IOException e) {
System.err.println("Error na E/S");
}
try {
byte[] data = Files.readAllBytes(path);
System.out.println(new String(data));
} catch (IOException e) {
System.err.println("Error na E/S");
}
}}
Interface Path e clase Paths: utilidades do paquete java.NIO para manexar rutas do sistema, no noso caso rutas de ficheiros. A clase Paths ten métodos estáticos que devolve a interface Path a partires dunha URI o cadea de caracteres. A interface Path é un argumento de diferentes métodos en java.NIO, que representa a fonte de información. Un posible uso co método readAllBytes() é usalo xunto co construtor de String. Se queremos obter unha cadea de alfanuméricos de todo o contido do ficheiro podes empregalo así:
String content = new String(Files.readAllBytes(Paths.get("readMe.txt")), StandardCharsets.UTF_8);
Manexo de datos primitivos en ficheiros
En ocasións temos a necesidade de utilizar datos primitivos en ficheiros; estes datos primitivos son os herdados de linguaxes máis clásicas e non se comportan como obxectos normais no paradigma orientación de obxectos. Son os seguintes:
- byte
- short
- int
- long
- float
- double
- boolean
- char
No uso con ficheiros cada tipo de dato primitivo ten a súa función de lectura e escritura, e é frecuente utilizar ficheiros binarios.
Escritura
O primeiro paso e obter o stream do ficheiro binario, do mesmo xeito que no apartado de “Java con ficheiros de bytes”: FileOutputStream fileOutputStream = new FileOutputStream(absolutePath);
Unha vez obtido o stream para a escritura (sexa por ruta de string ou obxecto da clase Path) é preciso crear un obxecto da clase DataOutputStream que recibe como parámetro un obxecto tipo FileOutputStream:
DataOutputStream salida = new DataOutputStream(fileOutputStream); Co obxecto DataOutputStream xa temos métodos de escritura por cada tipo de dato primitivo desta forma → writexXx() onde xXx é o nome do dato primitivo. Exemplo de escribir datos primitivos int (enteiro):
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;
public class DataOutputStreamInt {
public static void main(String[] args) {
escribirInt();
}
//Uso de recursos e excepcións clásica sen try with resources
private static void escribirInt() {
Scanner sc = new Scanner(System.in);
FileOutputStream fos = null;
DataOutputStream salida = null;
int n;
try {
fos = new FileOutputStream("datos.dat");
salida = new DataOutputStream(fos);
System.out.print("Introduce número enteiro. -1 para rematar: ");
n = sc.nextInt();
while (n != -1) {
salida.writeInt(n); //se escribe o número enteiro no ficheiro
System.out.print("Introduce número enteiro. -1 para rematar: ");
n = sc.nextInt();
}
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
} catch (IOException e) {
System.out.println(e.getMessage());
} finally {
try {
if (fos != null) {
fos.close();
}
if (salida != null) {
salida.close();
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}}
}
Lectura
Como pasa coa escritura, necesitamos dalgunha forma obter o Stream do ficheiro que imos ler: FileInputStream fileInputStream = new FileInputStream(String ruta);
Recordade que FileInputStream pódese obter tamén do obxecto File coma unha das formas máis usadas. A partires do obxecto anterior necesitamos crear un tipo DataInputStream, que é onde java nos ofrece os métodos de lectura. Un método de lectura por cada tipo de dato primitivo: readxXx() onde xXx é o nome do dato primitivo. Exemplo de ler datos primitivos int (enteiro):
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class DataInputStreamInt {
public static void main(String[] args) {
lerInt();
}
//Uso de recursos e excepcións clásica sen try with resources
public static void lerInt(){
FileInputStream fis = null;
DataInputStream entrada = null;
int n;
try {
fis = new FileInputStream("datos.dat");
entrada = new DataInputStream(fis);
while (true) {
n = entrada.readInt(); //se lee un enteiro do ficheiro
System.out.println(n); //se mostra na pantalla
}
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
} catch (EOFException e) {
System.out.println("Fin de fichero");
} catch (IOException e) {
System.out.println(e.getMessage());
} finally {
try {
if (fis != null) {
fis.close();
}
if (entrada != null) {
entrada.close();
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}}
As vantaxes de utilizar ficheiros binarios con datos primitivos son a inmediatez e a non necesidade de conversión da datos (casting). Por outra banda non nos da a liberdade e flexibilidade dos ficheiros de texto. Temos que ter claro qué tipo de dato imos ler.
Exercicio 1
Desenvolve un programa en Java que realice unha copia byte a byte dun arquivo pasado por parámetros. O nome do novo ficheiro sería: proba.txt -> proba_Copia.txt
Exercicio 2
Desenvolve un programa en Java que crea unha matriz de elementos de tipo double e lee por teclado o valor dos seus elementos. A continuación escribe o contido da matriz nun ficheiro. O principio do ficheiro escríbense dous enteiros cos valores do número de filas e columnas da matriz.