Java I / O¶
Introduction¶
Java provides many mechanisms for handling input/output operations. They are, among others streams and advanced serialization mechanisms:
- streaming is provided by Java IO classes, contained in the
java.io
package, - caching mechanisms are implemented by Java NIO components in the
java.nio
package.
Streams and buffers¶
Java IO is oriented towards stream processing which consists of:
- a certain number of bytes (one or more) are read
- data is not cached anywhere
- the programmer, using the data from the stream, cannot "move" on it, i.e. it is not possible to read the data once read
Java NIO is caching oriented, which means that:
- data is first loaded into a buffer of a certain size
- then they are put at the disposal of the programmer who can "move" around the buffer
Thread blocking¶
Using Java IO streams causes blocking of the currently running thread, i.e. using the read ()
or write ()
methods from the classes from the java.io
package, we block further execution of the program. A thread on which such a method was executed will wait until the read data is prepared or the write data which is not fully written.
The non-blocking Java NIO mode allows a thread to request to read data from a channel and get only what is currently available, or not at all if no data is currently available. Rather than staying blocked until the data is readable, the thread can continue working with something else. The same mechanism is used for data recording. A thread in Java NIO can now manage multiple read and write channels.
Java IO¶
Streams of bytes¶
Programs use byte streams to input and output 8-bit bytes. All classes related to byte streams are derived from the InputStream
and OutputStream
classes, e.g. FileInputStream
, FileOutputStream
.
public class ByteStream {
public static void main(String[] args) throws IOException {
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("user.txt");
out = new FileOutputStream("user_output.txt");
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
Name: John
Age: 34
Email: john@oracle.com
Phone: 1234567890
All the logic is to copy the content bit by bit as in the graphic below.
Character streams¶
The Java platform stores character values using the Unicode convention. Using character streams, the data from the byte stream is translated into a local character set.
All character stream classes are derived from the Reader
andWriter
classes. As with byte streams, there are character stream classes that specialize in I/O files. They are FileReader
andFileWriter
, respectively.
public class CharacterStream {
public static void main(String[] args) throws IOException {
FileReader in = null;
FileWriter out = null;
try {
in = new FileReader("user.txt");
out = new FileWriter("user_output.txt");
int c;
int nextChar;
while ((nextChar = in.read()) != -1) {
out.append((char) nextChar);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
Data caching¶
The classes based on InputStream
andOutputStream
use non-buffered input/output. This means that any read or write request is handled directly by the underlying operating system. This can make the program much less efficient, as each request like that often allows disk access, network activity, or other costly operation.
In order to reduce this kind of overhead, the Java platform implements buffered I/O streams. Buffered input streams read data from an area of memory known as buffer.
The program can convert an unbuffered stream to a buffered stream using classes such as: BufferedReader
,BufferedWriter
, which enable caching of character streams. Classes such as BufferedInputStream
andBufferedOutputStream
can stream bytes.
public class BufferedStream {
public static void main(String[] args) throws IOException {
BufferedReader in = null;
BufferedWriter out = null;
try {
in = new BufferedReader(new FileReader("user.txt"));
out = new BufferedWriter(new FileWriter("user_output.txt"));
String line;
while ((line = in.readLine()) != null) {
out.write(line);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
Data processing¶
In Java IO, data is read byte by byte with InputStream
or Reader
.
According to the diagram, the program only proceeds when there is new data to be read.
Java NIO¶
Buffers¶
A buffer is a data container of a specific primitive type. A buffer is a linear, finite sequence of elements of a certain primitive type. In addition to the content, the basic properties of the buffer are:
- capacity - the number of elements it contains. The buffer capacity is never negative and never changes.
- limit - is the index of the first element that should not be read or written. The buffer limit is never negative and never greater than its capacity.
- position - is the index of the next read or write item. A buffer position is never negative and never exceeds its limit.
The Buffer
class performs the following operations:
clear()
- prepares the buffer for a new sequence of channel read operations or relativeput
operations.flip()
- prepares the buffer for a new sequence of write to channel or relative fetch operation.rewind()
- prepares the buffer to reread data that is already contained there.
Channels¶
A channel (from the java.nio.channels.Channel
package) represents an open connection to an entity such as a hardware device, file, network socket, or program component that is capable of performing one or more different I / O operations, such as reading or write.
The channel can be open or closed. Once a channel is created, it is open and remains closed when closed. After a channel is closed, any attempt to execute an I / O operation on it will throw an exception ClosedChannelException
.
Basically, channels are intended to be safe for multithreaded access, as described in the interface and class specifications that extend and implement this interface.
The most frequently used implementations include:
FileChannel
- allows you to write and read data from a fileDatagramChannel
- enables saving and reading data via the UDP protocolSocketChannel
- allows you to write and read data via the TCP protocolServerSocketChannel
- allows you to listen for incoming TCP connections
public class NIOExample {
public static void main(String[] args) throws IOException {
RandomAccessFile file = new RandomAccessFile("user.txt", "r");
FileChannel fileChannel = file.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
while (fileChannel.read(byteBuffer) > 0) {
byteBuffer.flip();
while (byteBuffer.hasRemaining()) {
System.out.print((char) byteBuffer.get());
}
byteBuffer.clear();
}
file.close();
}
}
Data processing¶
NIO allows you to manage multiple channels (e.g. network connections or files) using a certain number of threads. Parsing the data, however, can be a bit more complicated than reading data from the blocking stream.
In order to successively retrieve data from the buffer, it is necessary to query it frequently, in order to collect information about data availability, which may result in a decrease in performance when reading connections.
Java NIO - file operations¶
Java NIO introduced many simplified file handling mechanisms. These mechanisms have been placed in the java.nio.file
package, and the starting point for their implementation is theFiles
class.
Path¶
The Path
interface represents the path to the file. It includes methods that can be used, among others down:
- obtaining information about the path
- accessing path items
- extracting path elements
Path p1 = Paths.get("/nio/example/path");
Path p2 = Paths.get(URI.create("file:///nio/example/FileTest.java"));
Path p3 = Path.of("/tmp", "dir_a", "dir_b");
Checking files or directories¶
The Files
class contains mechanisms that enable file verification, including:
Files.isExecutable(Path)
- checking if a file is executableFiles.isReadable(Path)
- checking if the file is readableFiles.isWritable(Path)
- check if the file is writable.
Deleting files¶
Within the Files
class, it is possible to delete files using the following methods:
Files.delete (Path)
- the method deletes the file if it exists, or throws an exceptionFiles.deleteIfExists (Path)
- the method deletes the file if it exists, otherwise does not perform any operation
Copying files¶
The Files
class provides a mechanism for copying files or directories using the method:
Files.copy (Path, Path, CopyOption ...)
The CopyOption
may be one of the following:
REPLACE_EXISTING
- forces a file to be copied even if the file already exists in the specified locationCOPY_ATTRIBUTES
- includes copying file attributesNOFOLLOW_LINKS
- reduces copying to symbolic links
Moving files¶
The Files.move(Path, Path, CopyOption...)
method allows you to move files that will result in an exception until the REPLACE_EXISTING
flag is set.
Create file¶
The Files.createFile (Path, FileAttribute <?>)
Method is responsible for creating files with different permissions. If the file we want to create already exists, an exception will be thrown.
Read from file¶
Reading from a file is performed using:
Files.readAllBytes (Path)
Files.readAllLines (Path, Charset)
Write to file¶
It is possible to write to the file using the following methods:
write(Path, byte [], OpenOption...)
- write byteswrite(Path, Iterable <extends CharSequence>, Charset, OpenOption...)
- write multiple lines, e.g. all values fromList <String
When specifying OpenOption
, we use an implementation such as StandardOpenOption
. The available options allow you to control the saving behavior, e.g. they allow you to define whether, before saving the data to the file, first delete its contents or add to the existing one.
An example of how the Files class works¶
public class FilesExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("data.txt");
Path pathToCopy = Paths.get("data_copy.txt");
Files.delete(path);
Files.createFile(path);
Files.write(path, "test data\n".getBytes(), StandardOpenOption.WRITE);
Files.write(path, "first line\n".getBytes(), StandardOpenOption.APPEND); // APPEND dodaje tekst do istniejącej zawartości
Files.write(path, "second line\n".getBytes(), StandardOpenOption.APPEND);
for (String line : Files.readAllLines(path)){
System.out.println(line);
}
Files.copy(path, pathToCopy);
Files.delete(pathToCopy);
}
}
Serialization and deserialization¶
Serialization is a mechanism that converts the state of an object into a stream of bytes. Deserialization, on the other hand, is the inverse process that allows you to recover the state of an object in memory from a stream of bytes.
The resulting byte stream is platform independent, so an object serialized on one platform can be successfully deserialized on another platform.
Serializable¶
The Serializable
interface enables the serialization and deserialization of objects using, incl. the classes ObjectInputStream
and ObjectOutputStream
, which contain a high-level API for serializing and deserializing objects.
import java.io.Serializable;
public class Movie implements Serializable {
private int id;
private String title;
private String type;
public Movie(int id, String title, String type) {
this.id = id;
this.title = title;
this.type = type;
}
//getters and setters
}
Without implementing the java.io.Serializable
interface, the above classes will not be able to properly serialize and deserialize theMovie
class object, which will result in a java.io.NotSerializableException
exception. Also note that the Serializable
interface does not require us to implement any method.
ObjectInputStream¶
The ObjectInputStream
class contains a method that enables the object to be serialized and written to the stream:
public final void writeObject(Object x) throws IOException
Przykładowa serializacja wygląda jak poniżej:
try {
Movie movieToSerialize = new Movie(1123, "Star Wars", "Start Wars IX");
FileOutputStream fileOutputStream = new FileOutputStream("movies.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(movieToSerialize);
out.flush();
out.close();
} catch (Exception e) {
System.err.println(e);
}
ObjectOutputStream¶
The ObjectOutputStream
class contains a method that allows you to deserialize an object and read it from the stream:
public final Object readObject() throws IOException, ClassNotFoundException
An example deserialization looks like this:
try {
FileInputStream fileIn = new FileInputStream("movies.txt");
ObjectInputStream in = new ObjectInputStream(fileIn);
Movie movieToDeserialize = (Movie) in.readObject();
in.close();
fileIn.close();
System.out.println(movieToDeserialize);
} catch (Exception e) {
System.err.println(e);
}