Java:API:Strings and IO

From Juneday education
Jump to: navigation, search

Introduction

This chapter has a few lectures about Strings in Java and also the IO API (IO as in Input/Output).

java.nio

Path is the new File!

A Path is an abstract representation of a path in the file system. Some useful ways for creating a Path instance (Path is an interface):

Path p = Paths.get("/", "home", "rikard", "yrgo", "file.txt");
Path home = Paths.get("/home/rikard");
Path yrgo = home.resolve("yrgo"); // /home/rikard/yrgo
Path chalmers = yrgo.resolveSibling("chalmers"); // /home/rikard/chalmers
Path fromYrgoToChalmers = yrgo.relativize(chalmers); // ../chalmers
// See also: getParent(), getFileName(), getRoot()

See also: Oracle tutorial - Path operations for more information on Path.

Using Paths to read a file with text

To read text from a file into Java, is quite simple using Path:

Path p = Paths.get("/home/rikard/yrgo/file.txt");
String fileContents = new String(Files.readAllBytes(p), StandardCharsets.UTF_8);

// Line-by-line with line numbers:
List<String> lines = Files.readAllLines(p, StandardCharsets.UTF_8);
int lineNumber = 1;
for (String line : lines) {
 System.out.println(lineNumber++ + line);
}

Reading lines of text from a text file has never been easier. No more Scanner or BufferedReader. Who misses Scanners, anyway? Yeah, that's right. Authors of Java text books and several university teachers. We've never understood the fascination with scanners, anyway.

If you have a Path to the file, and know the character encoding (like UTF_8 in the example), then you can get all lines as a List<String> by simply calling the static method <code>Files.readAllLines(path, charset). Of course, you must catch IOException, which may occur, but that's nothing new when using I/O.

In fact, printing all lines of text from a file can be shortened using the enhanced for loop (for-each-loop):

for (String line : Files.readAllLines(Paths.get(fileName), StandardCharsets.UTF_8)) {
  System.out.println(line);
}

Let’s write a text file!

We can write a text file from Java too, and it's not that hard. There's a very convenient method in java.nio.file.Files for that:

Path targetFile = Paths.get("/home/rikard/yrgo/my_target.txt");
String content = "First line.\nSecond line.\nThird line.";
Files.write(targetFile, content.getBytes(StandardCharsets.UTF_8));

// Append two more lines:
String extra = "\nFourth line.\nFifth line.\n";
Files.write(targetFile, extra.getBytes(StandardCharsets.UTF_8),
            StandardOpenOption.APPEND);

// Append a list of lines:
List<String> lastLines = new ArrayList<String>();
lastLines.add("Sixth line.");
lastLines.add("Seventh line.");

Files.write(targetFile, lastLines, StandardCharsets.UTF_8,
            StandardOpenOption.APPEND);

The interesting method calls hear are:

First we have the call to create a Path Path targetFile = Paths.get("/home/rikard/yrgo/my_target.txt");. We are using the static call to Paths.get() which takes a String argument with the path to the file we want to write to.

Next, we have
Files.write(targetFile, content.getBytes(StandardCharsets.UTF_8));
which is a static call to Files.write(), an overloaded version of the write method which accepts a Path, and a byte[]. We get the byte[] from a String, by calling the instance method getBytes(StandardCharsets.UTF_8). This creates an array of bytes from the String to write, using UTF_8 as the encoding. The reason for this, is that a Java String contains unicode characters, so we need to encode the String in a specified way to get the correct bytes, to write to the file. The result is UTF_8 encoded text in the file.

After that, we have Files.write(targetFile, extra.getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND);. Here, extra is also a Java String, from which we get the bytes as UTF_8. But since we are appending the bytes to the file (as opposed to overwriting the file), we use an overloaded version of write() which accepts an additional parameter, StandardOpenOption.APPEND which is a constant in the enum java.nio.file.StandardOpenOption. Other options to choose between in the enum are:

  • APPEND
  • CREATE
  • CREATE_NEW
  • DELETE_ON_CLOSE
  • DSYNC
  • READ
  • SPARSE
  • SYNC
  • TRUNCATE_EXISTING
  • WRITE

Please refer to the documentation of StandardOpenOption for more information.

The last line of interest in the example is
Files.write(targetFile, lastLines, StandardCharsets.UTF_8, StandardOpenOption.APPEND);
. This overloaded version of Files.write() takes four arguments:
  • a Path
  • an Iterable<? extends CharSequence>
  • a characterset (in this case UTF_8)
  • a StandardOpenOption (in this case APPEND)

The interesting part is the parameter Iterable<? extends CharSequence>. It allows us to write several character sequences (typically Strings) if they are contained in an iterable collection. We used a List<String> as the argument, which means that each String in the list will be written as one line at the time.

Convenience methods in java.nio.Files

Prior to java.nio (which was introduced in Java 7), creating readers and writers was quite explicit and wordy, using the decorator classes:

BufferedReader br = new BufferedReader(new FileReader(fileName));
PrintWriter out = new java.io.PrintWriter(
 new java.io.BufferedWriter(
   new java.io.FileWriter(filename + ".java")));

With the convenience methods in java.nio.file.Files, you can do this:

InputStream in = Files.newInputStream(path);
OutputStream out = Files.newOutputStream(path);
Reader reader = Files.newBufferedReader(path);
Writer writer = Files.newBufferedWriter(path);

The argument is of type java.nio.file.Path.

Copying, moving, deleting files, mkdir, touch

To copy, move or delete files is also very simple using java.nio.file.Files.

Files.copy(fromPath, toPath);
Files.move(fromPath, toPath);
Files.delete(path);
boolean deleted = Files.deleteIfExists(path);

You can also create directories (or trees of directories):

Files.createDirectory(path);
Files.createDirectories(path); // same as mkdir -p many/dirs/in/one/go
Files.createFile(path); // create new empty file

Implementing ls using DirectoryStream

A simple implementation of ls for listing files with the suffix ".java", can be achieved using java.nio.file.DirectoryStream<T>:

String dirName = args[0];
Path path = Paths.get(dirName);

try (DirectoryStream<Path> dirStream =
      Files.newDirectoryStream(path, "*.java")) { // filter on *.java
  for (Path file : dirStream) {
    System.out.println(file.getFileName());
  }
} catch (IOException e) {
  // Handle I/O-problems
}

Here, we're using an overloaded version of Files.newDirectoryStream() which takes a Path and a String as the arguments. The String is a globbing expression.

The globbing expression *.java matches all files whose file name ends with ".java".

We get a DirectoryStream back from the call to Files.newDirectoryStream(). We can iteratate over each Path in the DirectoryStream.

Read up on DirectoryStream:

Implementing cat in Java

Here's an implementation of cat in Java. It cats the arguments (if found) to standard out (if they are printable). If no arguments are given, it works interactively, just like cat does in bash.

import java.nio.file.*;
import java.io.*;
import java.util.Scanner;

public class Jcat {
  public static void main(String[] args) {
    try {
      if(args.length != 0) {
        for(int i = 0; i < args.length; i++){
          try {
            for (String line : Files.readAllLines(Paths.get(args[i]))) {
              System.out.println(line);
            }
          } catch (NoSuchFileException fne) {
            System.err.println("No such file or directory: " +
                               fne.getMessage());
          } catch (IOException e) {
            System.err.println(e);
          }
        }
      } else {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()) {
          System.out.println(sc.nextLine());
        }
      }
    } catch (Exception e) {
      System.err.println(e.getMessage());
    }
  }
}

Here are some example runs:

$ javac Jcat.java && java Jcat files/*
Here's file one.
With two lines of text.
This is file two. With three lines
of text.
This is the last line.

$ cat files/*
Here's file one.
With two lines of text.
This is file two. With three lines
of text.
This is the last line.

$ javac Jcat.java && java Jcat
This line will be echoed back by Jcat   <--- typed by user
This line will be echoed back by Jcat   <--- printed by Jcat
(user presses Ctrl-D)

$ cat
This line will be echoed back by cat    <--- typed by user
This line will be echoed back by cat    <--- printed by cat
(user presses Ctrl-D)

$ javac Jcat.java && java Jcat Jcat.java 
import java.nio.file.*;
import java.io.*;
import java.util.Scanner;

public class Jcat {
  public static void main(String[] args) {
    try {
      if(args.length != 0) {
        for(int i = 0; i < args.length; i++){
          try {
            for (String line : Files.readAllLines(Paths.get(args[i]))) {
              System.out.println(line);
            }
          } catch (NoSuchFileException fne) {
            System.err.println("No such file or directory: " +
                               fne.getMessage());
          } catch (IOException e) {
            System.err.println(e);
          }
        }
      } else {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()) {
          System.out.println(sc.nextLine());
        }
      }
    } catch (Exception e) {
      System.err.println(e.getMessage());
    }
  }
}
$

Slides and videos

So far, we have three lectures here:

Parsing a CSV file (going from Scanner to modern Java)

Java and Unicode

Using I/O Streams in Java

Links

Further reading