The java.nio.Files class has a static method named Files#newInputStream, which takes a Path instance as an input and returns an InputStream as an output. But it is unclear to me how this can be done without instantiating a File(InputStream) object. I would like to be able to make my own implementation of Path which can point to an InputStream which is not related to a File object. I would like to do this so that I can make a virtual filesystem which uses the notation of filesystems without actually depending on a filesystem. Is this possible?
The Java NIO file system API uses delegation and abstract factory patterns. At the lowest level there's the implementation of java.nio.file.spi.FileSystemProvider:
Service-provider class for file systems. The methods defined by the
Filesclass will typically delegate to an instance of this class. [emphasis added]
A FileSystemProvider provides instances of java.nio.file.FileSystem:
Provides an interface to a file system and is the factory for objects to access files and other objects in the file system.
The default file system, obtained by invoking the
FileSystems.getDefaultmethod, provides access to the file system that is accessible to the Java virtual machine. TheFileSystemsclass defines methods to create file systems that provide access to other types of (custom) file systems.A file system is the factory for several types of objects: [emphasis added]
The
getPathmethod converts a system dependent path string, returning aPathobject that may be used to locate and access a file.The
getPathMatchermethod is used to create aPathMatcherthat performs match operations on paths.The
getFileStoresmethod returns an iterator over the underlying file-stores.The
getUserPrincipalLookupServicemethod returns theUserPrincipalLookupServiceto lookup users or groups by name.The
newWatchServicemethod creates aWatchServicethat may be used to watch objects for changes and events.
And then there's the Path interface:
An object that may be used to locate a file in a file system. It will typically represent a system dependent file path.
Note that a Path is just that, a path. It does not necessarily represent an actual file, only the path of the file (whether it exists or not). It's similar to java.io.File in this regard.
When you invoke methods in the Files class it uses the Path#getFileSystem() and FileSystem#provider() methods to delegate to the FileSystemProvider. This API design is what allows a developer to use the Files class with any implementation of Path in a transparent manner. It doesn't matter to the developer how that InputStream is created, only that the InputStream is created in a way consistent with the API's contracts.
The methods Paths#get(String,String...) and Path#of(String,String...) delegate to the default file system to create the instance of Path. When you use those Path instances with the methods in Files you end up accessing the host platform's underlying file system.
To see the aforementioned delegation in action, check out the source code of Files#newInputStream(Path,OpenOption...) (link and code for Java 24):
public static InputStream newInputStream(Path path, OpenOption... options) throws IOException { return provider(path).newInputStream(path, options); } // [...] /** * Returns the {@code FileSystemProvider} to delegate to. */ private static FileSystemProvider provider(Path path) { return path.getFileSystem().provider(); }
It simply delegates to FileSystemProvider#newInputStream(Path,OpenOption...). It's up to the implementation of that method to determine how to open the input stream for the given path and options. For instance, the default file systems will return an input stream that reads from the native file system. Whereas an in-memory file system might return a ByteArrayInputStream or something similar.
If you want to create a virtual file system then you need to implement a FileSystemProvider along with all the related abstract classes and interfaces, such as FileSystem and Path. Ultimately, these implementations will likely store the files in one or more byte[] or ByteBuffer. The streams and channels will read from/write to these arrays/buffers.
Note some of the API is "optional". If your implementation doesn't provide the optional API then you can throw an UnsupportedOperationException. The Javadoc of the various classes/methods gives more information.
That said, there's already an in-memory file system implementation out there: https://github.com/google/jimfs
"But it is unclear to me how this can be done without instantiating a File(InputStream)". The NIO file API is not related to java.io.File.
If you want to see how it does what it does you can look at the source code. Your JDK should have come with a src.zip file which contains the Java source files; however, it will only contain the implementation for your host operating system and won't contain any of the native code (the default file systems ultimately use native code to communicate with the underlying OS' file system).
Browse the GitHub repository to see all the source code, native included, for all operating systems.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With