I/O Stream Processing in Java


Topics interesting only for Java programmers

Link to this posting

Postby Ursego » 05 Mar 2019, 07:44

To see the keywords colored, save the following text in a text file and open it in a Java compiler (or in Notepad++ and select in the menu: Language > J > Java).

Code: Select all
// Byte Streams vs. Character Streams:
// The java.io API defines two sets of classes for reading and writing streams: those with InputStream/OutputStream in their name and those with Reader/Writer in their name. For example, the java.io API defines both a FileInputStream class as well as a FileReader class, both of which define a stream that reads a file.
// >>> The InputStream/OutputStream classes are used for inputting and outputting RAW (BINARY) DATA (use byte datatype).
// >>> The Reader/Writer classes are used for inputting and outputting ONLY TEXTUAL (CHARACTER AND STRING) DATA (use char datatype).
// The java.io library defines four abstract classes that are the parents of all stream classes defined within the API: InputStream, OutputStream, Reader, and Writer. For convenience, the authors of the Java API include the name of the abstract parent class as the suffix of the child class. For example, ObjectInputStream ends with InputStream, meaning it has InputStream as an inherited parent class. Although most stream classes in java.io follow this pattern, PrintStream, which is an OutputStream, does not. Let's repeat: Classes with "Stream" in their name are used to read and write bytes (raw data), and Readers and Writers are used to read and write text (characters).

// Low-Level (not buffered) vs. High-Level (buffered) Streams:
// A low-level stream connects directly with the source of the data, such as a file, an array, or a String. LOW-LEVEL STREAMS PROCESS THE RAW DATA or resource and are accessed in a direct and unfiltered manner. For example, a FileInputStream IS A CLASS THAT READS FILE DATA ONE BYTE AT A TIME. Alternatively, a high-level stream is built on top of another stream using wrapping. Wrapping is the process by which an instance is passed to the constructor of another class and operations on the resulting instance are filtered and applied to the original instance. BufferedReader is a high-level stream that reads character data from an existing FileReader (which interacts directly with the file) in a buffered manner, which improves efficiency and performance:
try (BufferedReader bufferedReader = new BufferedReader(new FileReader("zoo-data.txt"))) {
   System.out.println(bufferedReader.readLine());
}
// Buffered classes read or write data in groups, rather than a single byte or character at a time. The performance gain from using a Buffered class to access a low-level file stream cannot be overstated. The constructors of high-level streams often take a reference to the abstract class. For example, BufferedWriter takes a Writer object as input, which allows it to take any subclass of Writer.

// Flushing the Stream:
// When data is written to an OutputStream, the underlying operating system does not necessarily guarantee that the data will make it to the file immediately. In many operating systems, the data may be cached in memory, with a write occurring only after a temporary cache is filled or after some amount of time has passed. If the data is cached in memory and the application terminates unexpectedly, the data would be lost, because it was never written to the file system. To address this, Java provides a flush() method, which requests that all accumulated data be written immediately to disk. You do not need to call the flush() method explicitly when you have finished writing to a file, since the close() method will automatically do this.

// Marking the Stream:
// The InputStream and Reader classes include mark(int) and reset() methods to move the stream back to an earlier position. Before calling either of these methods, you should call the markSupported() method, which returns true only if mark() is supported. Assume that we have an InputStream instance whose next values are ABCD:
InputStream inputStream = . . .
System.out.print ((char)inputStream.read()); // 'A'
if (inputStream.markSupported()) {
   inputStream.mark(100);
   System.out.print((char)inputStream.read()); // 'B'
   System.out.print((char)inputStream.read()); // 'C'
   inputStream.reset(); // return the stream pointer to the position which it had when mark() was called
}
System.out.print((char)inputStream.read()); // 'B'
System.out.print((char)inputStream.read()); // 'C'
System.out.print((char)inputStream.read()); // 'D'

// The File class:
// An abstract representation of file and directory pathnames. Objects of type File are used to represent the actual files (but not the data in the files) or directories that exist on a computer’s physical disk. The File class isn’t used to actually read or write data; it’s used to work at a higher level, making new empty files, searching for files, deleting files, making directories, and working with paths. When you make a new instance of the class File, you’re not yet making an actual file on the disk; you’re just creating an object in the memory which represents filename.
File myFile = new File("Course.txt")
// That creates a File object and then does one of two things:
// 1. If the file does NOT exist on disk, no actual file is created.
// 2. If the file does exist on disk, the new File object refers to that existing file.
// Notice that File file = new File("...") NEVER creates an actual file. There are two ways to create a file:
// 1. Invoke the createNewFile() method on a File object. The createNewFile() method creates an actual file and returnes true, indicating that a new file was created and that one didn’t already exist. For example:
File myFile = new File("Course.txt")
boolean newFileCreated = myFile.createNewFile();
if (!newFileCreated) System.out.print("Such a file already existed!");
// 2. Create a Writer or a Stream. Specifically, create a FileWriter, a PrintWriter, or a FileOutputStream (they will be described soon). WHENEVER YOU CREATE AN INSTANCE OF ONE OF THESE CLASSES, YOU AUTOMATICALLY CREATE A FILE, UNLESS ONE ALREADY EXISTS.

// The FileInputStream and FileOutputStream Classes:
// When reading a single value of a FileInputStream instance, the read() method returns a primitive int value rather than a byte value. It does this so that it has an additional value available to be returned, specifically -1 , when the end of the file is reached. If the class did return a byte instead of an int, there would be no way to know whether the end of the file had been reached based on the value returned from the read() method, since the file could contain all possible byte values as data (including -1!). For compatibility, the FileOutputStream also uses int instead of byte for writing a single byte to a file:
import java.io.*;
public class CopyFileSample {
   public static void copy(File sourceFile, File destinationFile) throws IOException {
      try (InputStream in = new FileInputStream(sourceFile); OutputStream out = new FileOutputStream(destinationFile)) {
         int symbolAsInt;
         while ((symbolAsInt = in.read()) != -1) {
            out.write(symbolAsInt);
         }
      }
   }
   public static void main(String[] args) throws IOException {
      File sourceFile = new File("Zoo.class");
      File destinationFile = new File("ZooCopy.class");
      this.copy(sourceFile, destinationFile);
   }
}

// The BufferedInputStream and BufferedOutputStream Classes:
// Instead of reading the data one byte at a time, we use the underlying read(byte[]) method of BufferedInputStream, which returns the number of bytes read into the provided byte array. The number of bytes read is important for two reasons. First, if the value returned is 0, then we know that we have reached the end of the file and can stop reading from the BufferedInputStream. Second, the last read of the file will likely only partially fill the byte array, since it is unlikely for the file size to be an exact multiple of our buffer array size. For example, if the buffer size is 1,024 bytes and the file size is 1,054 bytes, then the last read will be only 30 bytes. The length value tells us how many of the bytes in the array were actually read from the file. The remaining bytes of the array will be filled with leftover data from the previous read that should be discarded.

// The data is written into the BufferedOutputStream using the write(buffer byte[], int offset, int length) method. The offset value is the number of values to skip before writing characters, and it is usually set to 0. The length value is the number of characters from the byte array to write.

// Here’s a modified form of our copy() method, which uses byte arrays and the Buffered stream classes:
import java.io.*;
public class CopyBufferFileSample {
   public static void copy(File sourceFile, File destinationFile) throws IOException {
      try (InputStream in = new BufferedInputStream(new FileInputStream(sourceFile)); OutputStream out = new BufferedOutputStream(new FileOutputStream(destinationFile))) {
         byte[] buffer = new byte[1024];
         int lengthRead;
         while ((lengthRead = in.read(buffer)) > 0) {
            out.write(buffer, 0, lengthRead);
            out.flush(); // ensure that the written data is actually written to disk before the next buffer of data is read!
         }
      }
   }
}

// The FileReader and FileWriter classes:
// Like the FileInputStream and FileOutputStream classes, the FileReader and FileWriter classes contain read() and write() methods, respectively. These methods read/write char values instead of byte values; although similar to what you saw with streams, the API actually uses an int value to hold the data so that -1 can be returned if EOF is detected. The FileReader and FileWriter classes contain other methods that you saw in the stream classes, including close() and flush(). THE Writer CLASS, WHICH FileWriter INHERITS FROM, OFFERS A write(String) METHOD THAT ALLOWS A String OBJECT TO BE WRITTEN DIRECTLY TO THE STREAM. Using FileReader also allows you to pair it with BufferedReader in order to use the very convenient readLine() method, which you will see in the next example. Although you can use InputStream / OutputStream instead of Reader / Writer to read and write text files, it is inappropriate to do so.

// The BufferedReader and BufferedWriter Classes:
// Let’s take a look at a sample program that makes use of both the BufferedReader and BufferedWriter classes using the associated readLine() and write(String) methods. It reads a text file, outputs each line to screen, and writes a copy of the file to disk one at a time:
import java.io.*;
import java.util.*;
public class CopyTextFileSample {
   public static List<String> readFile(File sourceFile) throws IOException {
      List<String> linesArrayList = new ArrayList<String>();
      try (BufferedReader reader = new BufferedReader(new FileReader(sourceFile))) {
         String oneLine;
         while ((oneLine = reader.readLine()) != null) { // in buffered classes, we check for null - not for -1 like in not buffered FileInputStream and FileReader
            linesArrayList.add(oneLine);
         }
      }
      return linesArrayList;
   }
   public static void writeFile(List<String> linesArrayList, File destinationFile) throws IOException {
      try (BufferedWriter writer = new BufferedWriter(new FileWriter(destinationFile))) {
         for (String oneLine: linesArrayList) {
            writer.write(oneLine); // in buffered classes, we can write the entire String in a single call!!!
            writer.newLine();
         }
      }
   }
   public static void main(String[] args) throws IOException {
      File sourceFile = new File("Zoo.csv");
      List<String> linesArrayList = this.readFile(sourceFile);
      for (String oneLine: linesArrayList) {
         System.out.println(oneLine);
      }

      File destinationFile = new File("ZooCopy.csv");
      this.writeFile(linesArrayList, destinationFile);
   }
}

// Filter Streams (Java.io.FilterInputStream and Java.io.FilterOutputStream)

// FilterInputStream class works almost like InputStream class but what it does is simply overriding the InputStream class methods, passing the request to the InputStream. The read() method of FilterInputStream Class filters the data and read it and passes on the data to the underlying stream filtering which is done depending on the Streams Declaration:
public class FilterInputStream extends InputStream
// Constructor:
protected FilterInputStream(InputStream in) // Creates a FilterInputStream by assigning the argument in to the field this.in so as to remember it for later use.
// Method:
public int read(byte[] buffer) // Reads up to buffer.length bytes of data from this input stream into an array of bytes; the next byte of data, or -1 if the end of the stream is reached;
                        // This method simply performs the call read(b, 0, b.length) and returns the result. It is important that it does not do in.read(b) instead.
// Java program illustrating the working of read(byte[] buffer) method:
import java.io.*;
public class NewClass {
    public static void main(String[] args) throws IOException {
        FilterInputStream filterInputStream = null;
        InputStream inputStream = null;
 
        try {
            char symbolAsChar;
            int symbolAsInt;
            byte[] buffer = new byte[6];
            int length = 1;
 
            inputStream = new FileInputStream("GEEKS.txt");
            filterInputStream = new BufferedInputStream(inputStream);
            symbolAsInt = inputStream.read(buffer);
             
            for (byte currByte : buffer)  {
                symbolAsChar = (char)currByte; // since read() method returns Integer value, we convert each integer value to char
                System.out.println("At position " + length + " : " + symbolAsChar);
                length++;
            }
        }
        catch(Exception e) {
            e.printStackTrace();
        }
        finally {
            if (inputStream != null) inputStream.close();
            if (filterInputStream != null) filterInputStream.close();
        }
    }
}

// FilterOutputStream class is the superclass of all those classes which filters output streams. The write() method of FilterOutputStream Class filters the data and write it to the underlying stream, filtering which is done depending on the Streams. Declaration:
public class FilterOutputStream extends OutputStream
// Constructor:
FilterOutputStream(OutputStream geekout) // Creates an output stream filter.
// Method:
public void write(int b) throws IOException // Writes the specified byte to this output stream. Implements the abstract write method of OutputStream.
// Java program illustrating the working of work(int arg) method:
import java.io.*;
import java.lang.*;
public class NewClass {
   public static void main(String[] args) throws IOException
   {
      OutputStream fileOutputStream = null;
      FilterOutputStream filterOutputStream = null;
      FileInputStream fileInputStream = null;

      char symbolAsChar;
      int symbolAsInt;

      try {
         fileOutputStream = new FileOutputStream("GEEKS.txt");
         filterOutputStream = new FilterOutputStream(fileOutputStream);

         filterOutputStream.write(77); // write 'M' to the file
         filterOutputStream.flush();
         fileInputStream = new FileInputStream("GEEKS.txt"); // Create Input Sream
         symbolAsInt = fileInputStream.read(); // read() method reads next byte and converts it to int
         symbolAsChar = (char)symbolAsInt; // Since, read() converts bytes to int, so we convert int to char for our program output
         System.out.println("Character written by FilterOutputStream : " + symbolAsChar);
      }
      catch(IOException excpt) {
         // if any I/O error occurs
         System.out.print("Write Not working properly");
      }
      finally {
         if (fileOutputStream != null) fileOutputStream.close();
         if (filterOutputStream != null) filterOutputStream.close();
      }
   }
}

// The Serializable Interface:
// The process of converting an in-memory object to a stored data format is referred to as serialization, with the reciprocal process of converting stored data into an object, which is known as deserialization. In order to serialize objects, the class they belong to must implement the java.io.Serializable interface. The Serializable interface is a tagging or marker interface, which means that it does not have any methods associated with it. Any class can implement the Serializable interface since there are no required methods to implement. The purpose of implementing the Serializable interface is to inform any process attempting to serialize the object that you have taken the proper steps to make the object serializable, which involves making sure that the classes of all instance variables within the object are also marked Serializable. The requirement for properly marking an object as Serializable may involve nested objects. For example, if a Cat class is marked as Serializable and contains a reference to a Tail object, then the class definition for the Tail object must also be marked as Serializable. A process attempting to serialize an object will throw a NotSerializableException if the class or one of its contained classes does not properly implement the Serializable interface. You can use the "transient" keyword on a instance variable (of a primitive type or pointing to an object), which should not be serialized. That will instruct the process, serializing the object, to skip that variable and avoid throwing a NotSerializableException. The data, stored in the variable, will be lost during the serialization process. The JVM initializes these variables with the default values based on the data types (null for objects, 0 for int etc.) - but only for vars declared in the class itself or in its ancestor which implements Serializable. If you are a serializable class but your superclass is not serializable, then any transient instance variables you inherit from that superclass will be reset to the values they were given during the original construction of the object. This is because THE NON-SERIALIZABLE CLASS CONSTRUCTOR WILL RUN! In fact, every constructor above the first non-serializable class constructor will also run, no matter what, because once the first super constructor is invoked (during deserialization), it, of course, invokes its super constructor and so on, up the inheritance tree.

// If you serialize a collection or an array, every element must be serializable! A single non-serializable element will cause serialization to fail. Note also that although the collection interfaces are not serializable, the concrete collection classes in the Java API are.

// Static variables are not serialized to disk (which makes sense: in the time between serialization and deserialization of the object, that var in the memory could be changed by another object of the same class, and we don't want to rollback that change when deserialize). When you deserialize an object, the constructor of the serialized class is not called. Java calls the first no-arg constructor for the first nonserializable parent class, skipping the constructors of any serialized class in between.

// ObjectInputStream (inherited from InputStream) and ObjectOutputStream (inherited from OutputStream) Classes:
// Classes for object serialization and deserialization directly to and from disk.

// ObjectOutputStream includes a method to serialize the object:
void writeObject(Object) throws NotSerializableException
// If the provided object is not Serializable, or it contains an embedded reference to a class that is not Serializable or not marked transient, a NotSerializableException will be thrown.

// For the reciprocal process, the ObjectInputStream class includes a deserialization method:
public final Object readObject() throws IOException, ClassNotFoundException
// Since the return type of this method is java.lang.Object, the object will have to be cast explicitly at runtime to be used.

// For performance reasons, we wrap each low-level file stream with a Buffered stream and then chain the result to an Object stream:
import java.io.*;
import java.util.*;

public class Animal implements Serializable {
   private static final long serialVersionUID = 1L; // not required, but is considered a good practice to declare this private static final long variable and update it anytime you modify the class
                                          // (see https://stackoverflow.com/questions/285793/what-is-a-serialversionuid-and-why-should-i-use-it)
   private String name;
   private int age;
   private char type;

   public Animal(String name, int age, char type) { this.name = name; this.age = age; this.type = type; }
   public String getName() { return name; }
   public int getAge() { return age; }
   public char getType() { return type; }
   public String toString() { return "Animal [name=" + name + ", age=" + age + ", type=" + type + "]"; }
}

public class ObjectStreamSample {
   public static List<Animal> getAnimals(File dataFile) throws IOException, ClassNotFoundException {
      List<Animal> animalsList = new ArrayList<Animal>();
      try (ObjectInputStream objectInputStream = new ObjectInputStream(new BufferedInputStream(new FileInputStream(dataFile)))) {
         while (true) {
            Object object = objectInputStream.readObject(); // deserialize the Animal object from disk
            if (object instanceof Animal)
               animalsList.add((Animal)object);
         }
      } catch (EOFException e) {
         // File end reached
      }
      return animalsList;
   }
   public static void createAnimalsFile(List<Animal> animalsList, File dataFile) throws IOException {
      try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(dataFile)))) {
         for(Animal animal: animalsList)
            objectOutputStream.writeObject(animal); // serialize the Animal object to disk
      }
   }
   public static void main(String[] args) throws IOException, ClassNotFoundException {
      // Create a list of Animal objects in memory that includes two Animal instances:
      List<Animal> animalsList = new ArrayList<Animal>();
      animalsList.add(new Animal("Tommy Tiger", 5, 'T'));
      animalsList.add(new Animal("Peter Penguin", 8, 'P'));

      // Write the list data from memory to an animal.data file saved in the current working directory:
      File dataFile = new File("animal.data");
      this.createAnimalsFile(animalsList, dataFile);

      // Read the data from the file and output the following text: "[Animal [name=Tommy Tiger, age=5, type=T], Animal [name=Peter Penguin, age=8, type=P]]":
      System.out.println(getAnimals(dataFile));
   }
}
// The Object stream classes do support reading and writing null objects. Therefore, it is important to check for null values when reading from a serialized data stream. In our example, we rely on the property of the instanceof operator always to return false for null values to skip explicitly needing to check for null values.

// The PrintStream and PrintWriter Classes:
// High-level stream classes that write formatted representation of Java objects to a text-based output stream.
// * PrintStream class operates on OutputStream instances and writes data as BYTES;
// * PrintWriter class operates on Writer instances and writes data as CHARACTERS.
// System.out is actually a PrintStream object. Because PrintStream inherits OutputStream and PrintWriter inherits from Writer, both support the write() method while providing a slew of print-based methods: print(), println(), format() and printf(). Unlike the underlying write() method, which throws a checked IOException that must be caught in your application, these print-based methods do not throw any checked exceptions. If they did, you would have been required to catch a checked exception anytime you called System.out.println() in your code! Both classes provide a method, checkError(), that can be used to detect the presence of a problem after attempting to write data to the stream.

// java.io.Console class - Interacting with Users:
// The System.in and System.out objects have been available since the earliest versions of Java. In Java 6, the java.io.Console class was introduced with far more features and abilities than the original techniques. System.in and System.out are just raw streams, whereas Console is a class with multiple convenience methods. The Console class provides access to an instance of Reader and PrintWriter using the methods reader() and writer(), respectively. Access to these classes is analogous to calling System.in and System.out directly, although they use the Reader/Writer classes instead of the InputStream/OutputStream classes, which are more appropriate for working with character and String data. In this manner, they handle the underlying character encoding automatically.
import java.io.*;
public class ConsoleReadInputSample {
   public static void main(String[] args) throws NumberFormatException, IOException {
      // Retrieve an instance of the Console singleton (there is only one version of the object available in the JVM). It is created automatically
      // by JVM and accessed by calling the System.console() method which returns null in environments where text interactions are not supported:
      Console console = System.console();
      if (console == null) throw new RuntimeException("Console not available");

      console.writer().print("How excited are you about your trip today?"); // console.writer() returns an instance of PrintWriter
      // It is recommended that you call console.flush() method prior to calling any readLine() or readPassword() methods in order to ensure that no data is pending
      // during the read. Failure to do so could result in a user prompt for input with no preceding text, as the text prior to the prompt may still be in a buffer:
      console.flush();
      // readLine() method retrieves a single line of text from the user, and the user presses the Enter key to terminate it:
      String excitementAnswer = console.readLine();
      // An overloaded version of readLine() with the signature readLine(String format, Object. . . args) displays a formatted prompt to the user prior to accepting text:
      String name = console.readLine("Please enter your name: ");
      console.writer().print("What is your age? ");
      console.flush();
      BufferedReader reader = new BufferedReader(console.reader()); // console.reader() returns an instance of Reader
      String ageAsString = reader.readLine();
      Integer ageAsInteger = Integer.valueOf(ageAsString);
      console.writer().println();
      console.format("Your name is " + name);
      console.writer().println();
      console.format("Your age is "+ ageAsInteger);
      console.printf("Your excitement level is: " + excitementAnswer);
   }
}
// The readPassword() method is similar to the readLine() method, except that echoing is disabled. Also like the readLine() method, the Console class offers an overloaded version of the readPassword() method with the signature readPassword(String format, Object. . . args) used for displaying a formatted prompt to the user prior to accepting text. Unlike the readLine() method, though, the readPassword() method returns an array of characters instead of a String (for security purposes - to prevent storing the password in the String pool):
import java.io.*;
import java.util.Arrays;
public class PasswordCompareSample {
   public static void main(String[] args) throws NumberFormatException, IOException {
      Console console = System.console();
      if (console == null) throw new RuntimeException("Console not available");

      char[] password = console.readPassword("Enter your password: ");
      console.format("Enter your password again: ");
      console.flush();
      char[] verify = console.readPassword();
      boolean match = Arrays.equals(password, verify);
      // Immediately clear passwords from memory:
      for (int i = 0; i < password.length; i++) { password[i] = 'x'; }
      for (int i = 0; i < verify.length; i++) { verify[i] = 'x'; }
      console.format("Your password was " + (match ? "correct" : "incorrect"));
   }
}

// NIO

// Java introduced a replacement for java.io streams in Java 1.4 called Non-blocking I/O, or NIO for short. The NIO API introduced the concepts of buffers and channels in place of java.io streams. The basic idea is that you load the data from a file channel into a temporary buffer that, unlike byte streams, can be read forward and backward without blocking on the underlying resource. Java 7 introduced the NIO.2 API. While the NIO API was intended as a replacement for java.io streams, the NIO.2 API is actually a replacement for the java.io.File class. People sometimes refer to NIO.2 as just NIO, although for clarity and to distinguish it from the first version of NIO, we will refer to it as NIO.2.

// Introducing Path:
// The java.nio.file.Path interface, is the primary entry point for working with the NIO.2 API. A Path object represents a hierarchical path on the storage system to a file or directory. Path is a direct replacement for the legacy java.io.File class, and conceptually it contains many of the same properties. For example, both File and Path objects may refer to a file or a directory. Both also may refer to an absolute path or relative path within the file system. Unlike the File class, the Path interface contains support for symbolic links (shortcuts to files and directories).

// You can create an instance of a Path interface using the static get() method available in the Paths factory class. Note the s at the end of the Paths class to distinguish it from the Path interface:
Path path = Paths.get("c:\\zooinfo\\November\\employees.txt");

// Another way to construct a Path using the Paths class is with a URI value. A uniform resource identifier (URI) is a string of characters that identify a resource. It begins with a schema that indicates the resource type, followed by a path value. Examples of schema values include file://, http://, https://, and ftp://:
Path path = Paths.get(new URI("file:///c:/zoo-info/November/employees.txt"));

// Path object is not a file but a representation of a location within the file system. In this manner, most operations available in the Path and Paths classes can be accomplished regardless of whether the underlying file (that the Path object references) actually exists. A handful of operations in the Path and Paths classes, such as Path.toRealPath(), do require the file to exist and will throw a checked exception if the file is not available.

// Viewing the Path: Files.toString(), Files.getNameCount(), Files.getName():
// Path.toString() returns a String representation of the entire path. In fact, it is the only method in the Path interface to return a String. Most of the other methods that we will discuss in this section return a new Path object. Path.getNameCount() and Path.getName(int), are often used in conjunction to retrieve the number of elements in the path and a reference to each element:
Path path = Paths.get("/land/hippo/harry.happy");
System.out.println("The Path Name is: " + path); // toString()
for (int i = 0; i < path.getNameCount(); i++) { System.out.println("Element " + i + " is: " + path.getName(i)); }
// The output of this code snippet is the following:
// The Path Name is: /land/hippo/harry.happy
// Element 0 is: land
// Element 1 is: hippo
// Element 2 is: harry.happy
// Notice that the root element / is not included in the list of names. If the Path object represents the root element itself, then the number of names in the Path object returned by getNameCount() will be 0.

// Accessing Path Components: Files.getFileName(), Files.getParent(), Files.getRoot():
// The Path interface contains numerous methods for retrieving specific subelements of a Path object, returned as Path objects themselves. Path.getFileName() returns a Path instance representing the filename, which is the farthest element from the root. Path.getParent() returns a Path instance representing the parent path or null if there is no such parent. If the instance of the Path object is relative, this method will stop at the top-level element defined in the Path object. In other words, it will not traverse outside the working directory to the file system root. Path.getRoot() returns the root element for the Path object or null if the Path object is relative:
import java.nio.file.*;
public class PathFilePathTest {
   public static void printPathInformation(Path path) {
      System.out.println("Filename is: " + path.getFileName());
      System.out.println("Root is: " + path.getRoot());
      Path currentParent = path;
      while ((currentParent = currentParent.getParent()) != null) {
         System.out.println("   Current parent is: " + currentParent);
      }
   }
   public static void main(String[] args) {
      printPathInformation(Paths.get("/zoo/armadillo/shells.txt"));
      System.out.println();
      printPathInformation(Paths.get("armadillo/shells.txt"));
   }
}
// Output:
// Filename is: shells.txt
// Root is: /
//    Current parent is: /zoo/armadillo
//    Current parent is: /zoo
//    Current parent is: /

// Filename is: shells.txt
// Root is: null
//     Current parent is: armadillo

// Files.exists(Path):
// Takes a Path object and returns true if it references a file (or a directory) that exists in the file system:
if Files.exists(Paths.get("/ostrich/feathers.png"))... // file
if Files.exists(Paths.get("/ostrich"))... // directory

// Files.isSameFile(Path, Path):
// Determines if two Path objects relate to the same file within the file system. The isSameFile() method first checks if the Path objects are equal in terms of equal() (i.e. the variables point to the same object), and if so, it automatically returns true without checking to see if either file exists. If equals() returns false, then it locates each file to which the path refers in the file system and determines if they are the same, throwing a checked IOException if either file does not exist.

// Files.createDirectory(), Files.createDirectories():
// The directory-creation methods can throw the checked IOException, such as when the directory cannot be created or already exists. For example, createDirectory() will throw an exception if the parent directory in which the new directory resides does not exist:
try {
   Files.createDirectory(Paths.get("/bison/field"));
   Files.createDirectories(Paths.get("/bison/field/pasture/green")); // it will fail because the diroectory "/bison/field/pasture/" doesn't exist
} catch (IOException e) {
   // Handle file I/O exception...
}

// Files.copy(Path, Path):
// Copies a file or directory from one location to another. Throws the checked IOException, such as when the file or directory does not exist or cannot be read. Directory copies are shallow rather than deep, meaning that files and subdirectories within the directory are not copied. To copy the contents of a directory, you would need to create a function to traverse the directory and copy each file and subdirectory individually:
try {
   Files.copy(Paths.get("/panda"), Paths.get("/panda-save")); // perform a shallow copy of the panda directory, creating a new panda-save directory
   Files.copy(Paths.get("/panda/bamboo.txt"), Paths.get("/panda-save/bamboo.txt"));
} catch (IOException e) {
   // Handle file I/O exception...
}
// There are two overloaded copy() methods for copying files using java.io streams. The first copy() method takes a source java.io.InputStream along with a target Path object. It reads the contents from the stream and writes the output to a file represented by a Path object. The second copy() method takes a source Path object and target java.io.OutputStream. It reads the contents of the file and writes the output to the stream:
try (InputStream myInputStream = new FileInputStream("source-data.txt"); OutputStream myOutputStream = new FileOutputStream("output-data.txt")) {
   Files.copy(myInputStream, Paths.get("c:\\mammals\\wolf.txt")); // copy stream data to file
   Files.copy(Paths.get("c:\\fish\\clown.xsl"), myOutputStream); // copy file data to stream
} catch (IOException e) {
   // Handle file I/O exception...
}

// Files.move(Path, Path):
// Moves or renames a file or directory within the file system. Like the copy() method, the move() method also throws the checked IOException in the event that the file or directory could not be found or moved:
try {
   Files.move(Paths.get("c:\\zoo"), Paths.get("c:\\zoo-new")); // rename the zoo directory to zoo-new directory, keeping all of the original contents from the source directory
   Files.move(Paths.get("c:\\user\\addresses.txt"), Paths.get("c:\\zoo-new\\addresses2.txt")); // move the addresses.txt file from the directory user to the directory zoo-new, and rename it to addresses2.txt
   
} catch (IOException e) {
   // Handle file I/O exception...
}

// Removing a file or directory with Files.delete() and Files.deleteIfExists()
// Delete a file or EMPTY directory. The delete() method throws the checked IOException under a variety of circumstances. For example, if the path represents a non-empty directory, the operation will throw the runtime DirectoryNotEmptyException. If the target of the path is a symbol link, then the symbolic link will be deleted, not the target of the link. The deleteIfExists(Path) method is identical to the delete(Path) method, except that it will not throw an exception if the file or directory does not exist, but instead it will return a boolean value of false. It will still throw an exception if the file or directory does exist but fails, such as in the case of the directory not being empty:
try {
   Files.delete(Paths.get("/vulture/feathers.txt"));
   Files.deleteIfExists(Paths.get("/pigeon"));
} catch (IOException e) {
   // Handle file I/O exception...
}

// Reading and Writing File Data with Files.newBufferedReader(Path, Charset) and Files.newBufferedWriter(Path, Charset)
// The NIO.2 API includes methods for reading and writing file contents using java.io streams. The first method, Files.newBufferedReader(Path, Charset), reads the file specified at the Path location using a java.io.BufferedReader object. It also requires a Charset value to determine what character encoding to use to read the file. For this chapter, you just need to know that characters can be encoded in bytes in a variety of ways. It may also be useful to know that Charset.defaultCharset() can be used to get the default Charset for the JVM. This example reads the contents of the files using a BufferedReader and outputs the contents to the user:
Path path = Paths.get("/animals/gopher.txt");
try (BufferedReader reader = Files.newBufferedReader(path, Charset.forName("US-ASCII"))) {
   String currentLine = null;
   while ((currentLine = reader.readLine()) != null) // read from the stream
      System.out.println(currentLine);
} catch (IOException e) {
   // Handle file I/O exception...
}
// The second method, Files.newBufferedWriter(Path, Charset), writes to a file specified at the Path location using a BufferedWriter. This code snippet creates a new file with the specified contents, overwriting the file if it already exists:
Path path = Paths.get("/animals/gorilla.txt");
try (BufferedWriter writer = Files.newBufferedWriter(path, Charset.forName("UTF-16"))) {
   writer.write("Hello World");
} catch (IOException e) {
   // Handle file I/O exception...
}
// The newBufferedWriter() method also supports taking additional enum values in an optional vararg, such as appending to an existing file instead of overwriting it. Note that both of these methods use buffered streams rather than low-level file streams. The buffered stream classes are much more performant, so NIO.2 API includes methods that specifically return these stream classes, in part to encourage you always to use buffered streams in your application.

// Files.readAllLines(Path [, Charset]):
// Reads all of the lines of a text file and returns the results as an ordered List of String values. The following sample code reads all of the lines of the file in one stroke, and then iterates over them, displaying to user line by line:
Path path = Paths.get("/fish/sharks.log");
try {
   final List<String> lines = Files.readAllLines(path);
   for(String line: lines) {
      System.out.println(line);
   }
} catch (IOException e) {
   // Handle file I/O exception...
}

// Discovering Basic File Attributes:

// Files.isDirectory(Path): true - directory or shortcut to directory, false - file or shortcut to file
// Files.isRegularFile(Path): true - file or shortcut to file, false - directory or shortcut to directory
// Files.isSymbolicLink(Path): true - shortcut (regardless of whether the file or directory it points to exists), false - regular file or directory (i.e. not a shortcut)
// Files.isHidden(Path): true - file or directory is hidden within the file system, false - visible
// Files.isReadable(Path): true - file's contents are readable (based on the permissions), false - file is prohibited to be read
// Files.isExecutable(Path): true - file is marked executable, false - file is not executable

// The listed methods do not throw an exception if the path does not exist but instead return false. So, the following code is redundant:
if (Files.exists(path) && Files.isDirectory(path)) ...
// This code could be replaced with a single Files.isDirectory() method call since the exists() call is unnecessary:
if (Files.isDirectory(path)) ...

// Files.size(Path) method returns the size of the file in bytes as a long value. That size represents the conceptual size of the data, and this may differ from the actual size on the disk due to file system compression and organization. The size() method throws the checked IOException if the file does not exist or if the process is unable to read the file information. The Files.size(Path) method is defined only on files. Calling Files.size(Path) on a directory is system dependent and undefined. If you need to determine the size of a directory and its contents, you’ll need to walk the directory tree.

// Files.getLastModifiedTime(Path) returns a FileTime object with the last file modification time. The FileTime class is a simple container class that stores the date/time information about when a file was accessed, modified, or created. FileTime has a toMillis() method (returns the epoch time) and a static fromMillis() method (converts from the epoch time to a FileTime object).

// Files.setLastModifiedTime(Path, FileTime) method changes that time without changing the file contents.
try {
   final Path path = Paths.get("/rabbit/food.jpg");
   FileTime nowMillis = System.currentTimeMillis()
   System.out.println(Files.getLastModifiedTime(path).toMillis()); // read and output the last-modified time of the food.jpeg file
   Files.setLastModifiedTime(path, FileTime.fromMillis(nowMillis)); // set the last-modifid date/time using the current time value
   System.out.println(Files.getLastModifiedTime(path).toMillis()); // read and output the updated last-modified time of the food.jpeg file
} catch (IOException e) {
   // Handle file I/O exception...
}

// Files.getOwner(Path) [returns UserPrincipal] and Files.setOwner(Path, UserPrincipal):
try {
   UserPrincipal owner;
   // Read owner of file:
   Path path = Paths.get("/chicken/feathers.txt");
   owner = Files.getOwner(path)
   System.out.println(owner.getName());
   // Change owner of file:
   owner = path.getFileSystem().getUserPrincipalLookupService().lookupPrincipalByName("jane");
   Files.setOwner(path, owner);
   // Output the updated owner information:
   System.out.println(Files.getOwner(path).getName());
} catch (IOException e) {
   // The operating system may intervene when you try to modify the owner of a file and block the operation.
   // For example, a process running under one user may not be allowed to take ownership of a file owned by another user.
   // Both the getOwner() and setOwner() methods can throw the checked exception IOException in case of any issues accessing or modifying the file.
}

// Comparing Legacy File and NIO.2 Methods:
//-----------------------------------------------------------------------
// Legacy Method:            NIO.2 Method:
//-----------------------------------------------------------------------
// File.exists()            Files.exists(Path)
// File.getName()            Path.getFileName()
// File.getAbsolutePath()      Path.toAbsolutePath()
// File.isDirectory()         Files.isDirectory(Path)
// File.isFile()            Files.isRegularFile(Path)
// File.isHidden()            Files.isHidden(Path)
// File.length()            Files.size(Path)
// File.lastModified()         Files.getLastModifiedTime(Path)
// File.setLastModified(time)   Files.setLastModifiedTime(Path, FileTime)
// File.delete()            Files.delete(Path)
// File.renameTo(otherFile)      Files.move(Path, Path)
// File.mkdir()               Files.createDirectory(Path)
// File.mkdirs()            Files.createDirectories(Path)
// File.listFiles()            Files.list(Path)
//-----------------------------------------------------------------------

// ####### Pipes

// Pipes provide the ability for two threads running in the same JVM to communicate. In a piped I/O, we create two streams representing two ends of the pipe: a readable source channel (input stream) and a writable sink channel (output stream). Once some bytes are written to the sink channel they can be read from source channel in exactly the order in which they were written. A piped I/O is based on the producer-consumer pattern, where the producer produces data and the consumer consumes the data. A java.io.PipedOutputStream object represents one end and a java.io.PipedInputStream object represents the other end.

// We connect the two ends using the connect() method on the either object (both PipedInputStream and PipedOutputStream has a connect() method):
PipedInputStream inputStream = new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream();
inputStream.connect(outputStream); // or, you could write outputStream.connect(inputStream);

// We can also connect them by passing one object to the constructor when we create another object:
PipedInputStream inputStream = new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream(inputStream); // or, you could write PipedInputStream inputStream  = new PipedInputStream(outputStream)

// We can produce and consume data after we connect the two ends of the pipe. We produce data by using one of the write() methods of the PipedOutputStream object. Whatever we write to the piped output stream automatically becomes available to the piped input stream object for reading. We use the read() method of PipedInputStream to read data from the pipe. The piped input stream is blocked if data is not available when it attempts to read from the pipe.

// Here is a simple example of how to connect a PipedInputStream to a PipedOutputStream:
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

public class PipeExample {
    public static void main(String[] args) throws IOException {
        final PipedOutputStream outputStream = new PipedOutputStream();
        final PipedInputStream inputStream  = new PipedInputStream(outputStream);

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    outputStream.write("Hello world, pipe!".getBytes());
                } catch (IOException e) {
               e.printStackTrace();
            }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    int data;
                    while (1 = 1) {
                        data = inputStream.read(); // read data from the pipe
                  if (data != -1) break;
                        System.out.print((char) data);
                    }
                } catch (IOException e) {
               e.printStackTrace();
            }
            }
        });

        thread1.start(); // that will write (or strart writing if there is a lot of data to pass to thread2) data to the pipe; written data will wait inside the pipe until thread2 reads it
        thread2.start();
    }
}
User avatar
Ursego
Site Admin
 
Posts: 143
Joined: 19 Feb 2013, 20:33



Ketones are a more high-octane fuel for your brain than glucose. Become a biohacker and upgrade yourself to version 2.0!



cron
Traffic Counter

eXTReMe Tracker