gianluca.aguzzi@unibo.it
angelo.filaseta@unibo.it
Questo materiale è ampiamente basato su quello realizzato dai Prof. Mirko Viroli e Roberto Casadei, che ringrazio.
Ogni errore riscontratovi è esclusiva responsabilità degli autori di questo documento.
java.io.*
java.nio
java.io.File
java.io.File
: pt1 public class File implements Serializable, Comparable<File> {
public File(String pathname) {...}
public File(String parent, String child) {...}
public File(File parent, String child) {...}
/* -- Path-component accessors -- */
public String getName() {...}
public String getParent() {...}
public File getParentFile() {...}
public String getPath() {...}
/* -- Path operations -- */
public boolean isAbsolute() {...}
public String getAbsolutePath() {...}
public File getAbsoluteFile() {...}
public String getCanonicalPath() throws IOException {...}
public File getCanonicalFile() throws IOException {...}
/* -- Attribute accessors -- */
public boolean canRead() {...}
public boolean canWrite() {...}
public boolean exists() {...}
public boolean isDirectory() {...}
public boolean isFile() {...}
public boolean isHidden() {...}
public long lastModified() {...}
public long length() {...}
java.io.File
: pt2 /* -- File operations -- */
public boolean createNewFile() throws IOException {...}
public boolean delete() {...}
public void deleteOnExit() {...}
public String[] list() {...}
public String[] list(FilenameFilter filter) {...}
public boolean mkdir() {...}
public boolean renameTo(File dest) {...}
public boolean setLastModified(long time) {...}
public boolean setReadOnly() {...}
public boolean setWritable(boolean writable, boolean ownerOnly) {...}
public boolean setWritable(boolean writable) {...}
public boolean setReadable(boolean readable, boolean ownerOnly) {...}
public boolean setReadable(boolean readable) {...}
public boolean setExecutable(boolean executable, boolean ownerOnly) {...}
public boolean setExecutable(boolean executable) {...}
public boolean canExecute() {...}
/* -- Disk usage -- */
public long getTotalSpace() {...}
public long getFreeSpace() {...}
public long getUsableSpace() {...}
}
package it.unibo.io.files;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class FileUtil {
private static final String USER_HOME = System.getProperty("user.home");
public static List<File> findFilesInHomeDirectoryByExtension(final String ext) {
final var userHome = new File(USER_HOME);
final var allFiles = userHome.listFiles();
if (!userHome.isDirectory()) {
throw new IllegalStateException("The user home is not a directory!");
}
final var result = new ArrayList<File>();
for (final var file: allFiles) {
if (file.isFile() && file.getName().endsWith("." + ext)) {
result.add(file);
}
}
return result;
}
}
InputStream
e OutputStream
in JavaInputStream
e OutputStream
byte
) leggibili vs. scrivibiliFile
(In
|Out
)putStream
)ByteArray
(In
|Out
)putStream
)Data
(In
|Out
)putStream
)Object
(In
|Out
)putStream
)Zip
(In
|Out
)putStream
)Reader
, Writer
, e specializzazioni)In
|Out
)putStream
java.io.InputStream
public abstract class InputStream implements Closeable {
// Reads the next byte (0 to 255, -1 is end-of-stream)
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException {...}
public int read(byte b[], int off, int len) throws IOException {...}
public long skip(long n) throws IOException {...}
public int available() throws IOException {...}
public void close() throws IOException {...}
public synchronized void mark(int readlimit) {...}
public synchronized void reset() throws IOException {...}
public boolean markSupported() {...}
}
FileInputStream
e ByteArrayInputStream
ByteArrayInputStream
ByteArrayInputStream
InputStream
a partire da un byte[]
package it.unibo.io.files;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
public class UseByteArrayStream {
public static void main(String[] args) throws IOException {
final byte[] b = new byte[] { 10, 20, -1, 40, -58 };
final InputStream in = new ByteArrayInputStream(b);
try {
int c = in.read();
while (c != -1) {
System.out.println(c);
c = in.read();
}
System.out.println("End of stream");
} finally { // assicura la chiusura anche con eccezioni
in.close(); // problema: può tirare eccezione!
}
}
}
try-with-resources
java.lang.AutoCloseable
come primo argomentocatch
di eccezionipackage it.unibo.io.files;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
public class UseTryWithResources {
public static void main(String[] args) throws IOException {
final byte[] b = new byte[] { 10, 20, 30, 40, 50 };
try (final InputStream in = new ByteArrayInputStream(b)) {
int c = in.read();
while (c != -1) { // C-style
System.out.println(c);
c = in.read();
}
System.out.println("End of stream");
}
}
}
StreamDumper
package it.unibo.io.files;
import java.io.IOException;
import java.io.InputStream;
public class StreamDumper {
// rendo inaccessibile il costruttore
private StreamDumper() {}
public static void dump(final InputStream input) throws IOException {
for (int value = input.read(); value != -1; value = input.read()) {
System.out.print(value + "\t");
}
}
}
UseStreamDumper
– uso uniforme di vari InputStream
package it.unibo.io.files;
import ...
public class UseStreamDumper {
public static void main(String[] args) throws IOException {
final byte[] bytes = new byte[]{ 10, 20, 30 };
// Cerco un file txt nella home folder dell'utente
final var txtFiles = FileUtil.findFilesInHomeDirectoryByExtension("txt");
if (txtFiles.isEmpty()) {
throw new IllegalStateException("No txt files on the user home directory!");
}
final var file = txtFiles.get(0);
try(
final InputStream input = new ByteArrayInputStream(bytes);
final InputStream input2 = new FileInputStream(file);
) {
System.out.println(txtFiles.get(0).getAbsolutePath());
System.out.println("First stream (from memory):");
StreamDumper.dump(input);
System.out.println("\nSecond stream (from file " + file.getPath() + ":");
StreamDumper.dump(input2);
System.out.println();
}
}
}
java.io.OutputStream
public abstract class OutputStream implements Closeable, Flushable{
/**
* ...
* The byte to be written is the eight low-order bits of the argument <code>b</code>.
* The 24 high-order bits of <code>b</code> are ignored.
*
* 0x000000FF writes byte 0xFF (255)
* 0xFFFFFFFF writes byte 0xFF (255)
* 0x12345678 writes byte 0x78 (120)
*/
public abstract void write(int b) throws IOException;
public void write(byte b[]) throws IOException {...}
public void write(byte b[], int off, int len) throws IOException {...}
public void flush() throws IOException {...}
public void close() throws IOException {...}
}
InputStream
ByteArrayOutputStream
e FileOutputStream
UseOutputStream
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
public class UseOutputStream {
public static void main(final String[] args) throws IOException {
try (
final OutputStream output = new FileOutputStream(UseFile.FILE_NAME);
) {
// Aggiungo byte random
final Random r = new Random();
for (int i = 0; i < 100; i++) {
output.write(r.nextInt(256));
}
// Aggiungo un array di byte
final byte[] b = new byte[] { 10, 20, 30, 40 };
for (int i = 0; i < 10; i++) {
output.write(b);
}
}
}
}
byte
?Per ottenere questo risultato, si “circonda” l’(In
|Out
)putStream
con un altro,
che si occupa di aggiungere funzionalità:
Ad sempio, si definisce Buffered
(In
|Out
)putStream
che estende (In
|Out
)putStream
aggiungendo un buffer intermedio
Buffered
(In
|Out
)putStream
fa da wrapper per un altro (In
|Out
)putStream
al quale delega le operazioniBuffered
(In
|Out
)putStream
è quindi un decoratore per (In
|Out
)putStream
Con questa tecnica è possibile decorare qualunque (In
|Out
)putStream
!
BufferedInputStream
In
|Out
)putStream
, è possibile comporre:File
(In
|Out
)putStream
, ByteArray
(In
|Out
)putStream
Buffer
(In
|Out
)putStream
, ..PrintStream
, formato stringhe testualiDataInputStream
, dati grezzi (non visto)ObjectInputStream
, interi oggetti (non visto)Scrittura di testo su file
Scrittura di testo su file in modo bufferizzato
Stesse chiamate lato cliente, basta costruire la catena di decoratori in modo opportuno!
Supponiamo utilizziate un FileOutputStream
per scrivere in sequenza i numeri da 0 a 20(escluso)
0x000102030405060708090A0B0C0D0E0F10111213
012345678910111213141516171819
Per fare input/output occorre stabilire:
public class ReadWriteWithoutEncoding {
private static final String FILE_NAME = "test-output.txt";
public static void main(String[] args) throws IOException {
try (final var out = new FileOutputStream(FILE_NAME)) {
System.out.println("Writing...");
for (int b = 0; b < 1000; b++) {
System.out.print(b + " ");
out.write(b);
}
System.out.println("Done.");
}
try (final var in = new FileInputStream(FILE_NAME)) {
System.out.println("Reading...");
for (var read = in.read(); read != -1; read = in.read()) {
System.out.print(read + " ");
}
System.out.println("Done.");
}
// Quick method to read the file as a text string
System.out.println("Actual file content:");
System.out.println(new String(Files.readAllBytes(Path.of(FILE_NAME))));
}
}
Writing...
0 1 2 3 4 ... 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 ... Done.
Reading...
0 1 2 3 4 ... 255 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ... Done.
Actual file content:
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ
[\]^_`abcdefghijklmnopqrstuvwxyz{|}~����������������������������������������������������
����������������������������������������������������������������������������
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ
[\]^_`abcdefghijklmnopqrstuvwxyz{|}~����������������������������������������������������
����������������������������������������������������������������������������
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ
[\]^_`abcdefghijklmnopqrstuvwxyz{|}~����������������������������������������������������
����������������������������������������������������������������������������
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ
[\]^_`abcdefghijklmnopqrstuvwxyz{|}~����������������������������������������������������
����������������������������������������������������
Non avete scritto testo!
Avete scritto il valore in byte
della parte meno significativa degli interi che avete passato
0
dopo 255
(massimo valore di un byte senza segno)Per interpretare i byte come sequenza di caratteri,
occorre usare una tabella di conversione
(character encoding, Charset
in Java)
byte
, se vediamo delle stringhe di testo è perchè avviene un encodingchar
String
in JavaReader
e Writer
(In
|Out
)putStream
s sono pensati per gestire direttamente byte, non caratteri.
Molto spesso abbiamo però bisogno di leggere o scrivere stringhe di testo!
Reader
e Writer
sono pensati per gestire “stream di caratteri” invece che stream di byte
Reader
:
per la scrittura in formato carattere possiamo infatti usare PrintStream
InputStreamReader
public class UseBufferedPrintStream {
private static final File FILE = new File("test-output.txt");
public static void main(String[] args) throws IOException {
// string -> buffer -> compress -> file
try (final PrintStream fileOut = new PrintStream(
new BufferedOutputStream(new FileOutputStream(FILE)), // better performance
false,
StandardCharsets.UTF_8 // Always specify how to translate text into bytes and viceversa
)) {
fileOut.println("Hey, this is my first printed line in a text file!");
fileOut.println("Here is another text line");
}
System.out.println("Writing completed!");
// To read, we must reverse the operations:
// file -> decompress -> string (optionally, with a buffer)
try (
final var inputReader = new BufferedReader(
new InputStreamReader(new FileInputStream(FILE), StandardCharsets.UTF_8)
)
) {
for (String line = inputReader.readLine(); line != null; line = inputReader.readLine()) {
System.out.println("Read line: " + line);
}
}
}
}
Files
Nella maggior parte dei casi, vogliamo salvare e caricare i nostri dati scrivendo e leggendo un intero file.
byte[]
(ad esempio un’immagine)La classe Files
contiene diversi metodi convenienti per leggere e scrivere file con poco codice:
Paths.get(String)
ottiene un oggetto di tipo Path
da un nome
File
File(String)
Files.write(Path, byte[])
scrive un array di byte
dentro il Path
Files.write(Path, Iterable<? extends Charsequence>, Charset)
scrive una collezione di stringhe dentro il Path
,
traducendo le stringhe in byte
usando il Charset
fornitoFiles.writeString(Path, String, Charset)
scrive una collezione di stringhe dentro il Path
,
traducendo le stringhe in byte
usando il Charset
fornitobyte[] Files.readAllBytes(Path)
legge il contenuto di Path
e lo restituisce come byte[]
List<String> Files.readAllLines(Path, Charsets)
legge il contenuto di Path
e lo restituisce come List<String>
,
una stringa per ogni linea,
interpretando i byte
del file come testo usando il Charset
fornitoString Files.readString(Path, Charsets)
legge il contenuto di Path
e lo restituisce come singola String,
interpretando i byte
del file come testo usando il Charset
fornitoFiles
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
public class UseFilesUtilities {
public static void main(final String[] args) throws IOException {
final var bytes = new byte[] { 1, 2, -128, 99 };
final Path binary = Paths.get("binary-file.dat");
Files.write(binary, bytes);
System.out.println("Read bytes: " + Arrays.toString(Files.readAllBytes(binary)));
final List<String> dante = List.of(
"Tanto gentile e tanto onesta pare",
"la donna mia, quand'ella altrui saluta",
"ch'ogne lingua deven tremando muta"
);
final Path text = Paths.get("text-file.txt");
Files.write(text, dante, StandardCharsets.UTF_8); // Always specify the Charset!
System.out.println("Read all file lines:\n" + Files.readAllLines(text, StandardCharsets.UTF_8));
System.out.println("Read the file as string:\n" + Files.readString(text, StandardCharsets.UTF_8));
Files.writeString(text, "this is\na multiline string!", StandardCharsets.UTF_8);
System.out.println("Read all file lines:\n" + Files.readAllLines(text, StandardCharsets.UTF_8));
System.out.println("Read the file as string:\n" + Files.readString(text, StandardCharsets.UTF_8));
}
}
├── src
│ ├── main
│ │ ├── java
│ │ └── resources
│ └── test
│ ├── java
│ └── resources
├── build.gradle.kts
└── settings.gradle.kts
src/[main|test]/resources
contengono le risorse del progetto opportunamente organizzate
resources
diventa parte del classpath!
Abbiamo visto finora il classpath come l’insieme dei percorsi dove la virtual machine va a cercare le classi da caricare
-cp
di java
e javac
, il classpath può contenere indifferentemente dei path o dei JAR (o anche degli zip)Esso includerà tipicamente anche le risorse del progetto, i JAR delle dipendenze importate, etc.
Come possiamo accedere a queste risorse in modo uniforme?
Java fornisce un’utilità per caricare risorse dal classpath
ClassLoader.getSystemResource
(AsStream
)(String)
public abstract class ClassLoader {
public static ClassLoader getSystemClassLoader();
public static URL getSystemResource(String name);
public static InputStream getSystemResourceAsStream(String name);
public URL getResource(String name);
// ...
ClassLoader
) è un’oggetto responsabile del caricamento di classi e risorse
CLASSPATH
getSystemResource
e getSystemResourceAsStream
è il nome di una risorsa (non un percorso del filesystem!), che è una stringa separata da /
che identifica la risorsa
ClassLoader.getSystemResource()
equivale a ClassLoader.getSystemClassLoader().getResource()
final InputStream in = ClassLoader.getSystemResourceAsStream("/settings/settings");
final BufferedReader br = new BufferedReader(new InputStreamReader(in));
final String line = br.readLine();
in.close();
final URL imgURL = ClassLoader.getSystemResource("/images/gandalf.jpg");
final ImageIcon icon = new ImageIcon(imgURL);
final JLabel lab1 = new JLabel(icon);
Progetto di esempio: https://github.com/unibo-oop/example-with-get-resources
Spesso un software ha necessità di caricare al primo avvio delle impostazioni di default, quindi lasciare l’utente libero di modificarle e, se avviato successivamente caricare quelle scelte dall’utente. In caso di sistema multiutente, le impostazioni saranno diverse per ciascuno.
.nomeprogramma
.getResource()
.Per scrivere oggetti (a parte le String
),
abbiamo bisogno di convertire gli oggetti in byte[]
o in String
(serializzare),
ossia nei formati che sappiamo scrivere.
Allo stesso modo, sapendo leggere solo byte[]
oppure String
,
dovremo avere modo di deserializzare questi flussi in oggetti
Esistono sistemi di conversione sia binaria
(oggetto $\xrightarrow{\text{serializzazione}}$ byte[]
$\xrightarrow{\text{deserializzazione}}$ oggetto)
che testuale
(oggetto $\xrightarrow{\text{serializzazione}}$ String
$\xrightarrow{\text{deserializzazione}}$ oggetto)
Standard testuali sono più facilmente leggibili, e, laddove le prestazioni e lo spazio non siano stringenti, preferiti