I
Inertia_sublimation
Hello everyone. Im developing a program the requires heavy usage of
recursive folder size computation, and need to speed it up immensely.
I thought the use of a cache to store the result of recursively
getting the size of a folder would be ideal, but am having a problem
with it: Its not working.
The size is recomputed every time the sizeRecursive() method is called
on a CachedDirectory.
Also, please forgive the amount of code I'm posting, I think all of it
is pertinant though.
Note the comments in DirectoryCache.CachedDirectory.sizeRecursive()'s
for loop, In order to work on the Directorys contained in the
Directory[] as CachedDirectorys, I have to wrap them in a new instance
of CachedDirectory. I don't know how to get around the lastComputed
field of the new CachedDirectory being set to -1 (forcing the new
CachedDirectory to recompute its value instead of looking in the cache
for it).
Please help!
PS - Is there any where I can put a line break in the declaration of a
generic Collection? The lines where I declare them at the top of the
class get kinda long, and they look ugly.
-------------------Class DirectoryCache-------------------
import java.util.*;
import java.io.*;
/**
* Provides one place where CachedDirectories can be
* instantiated, that all use the same cache.
* To instantiate a CachedDirectory from this cache
* use this code:
* DirectoryCache cache = new DirectoryCache();
* CachedDirectory dir = cache.new CachedDirectory(...);
*/
public class DirectoryCache {
// The storage facility:
private static HashMap<Directory, Long> dirSizes = new
HashMap<Directory, Long>();
/**
* This class subclasses Directory to add caching of the result of
* calling sizeRecursive().
*/
public class CachedDirectory extends Directory {
/** The last time the recusrive size was computed. */
private long lastComputed = -1;
/** This is provided as an alternative to
* typing DirectoryCache.this */
private final DirectoryCache cache = DirectoryCache.this;
public CachedDirectory (final String pathName)
throws NotADirectoryException {
super(pathName);
}
public CachedDirectory(final Directory f) throws IOException {
super(f);
}
public long sizeRecursive() throws IOException {
/* If this CachedDirectory is NOT in the cache,
* or if it was modified: */
if (!cache.dirSizes.containsKey(this)
|| lastModified() > lastComputed) {
// Debug output:
if (lastModified() > lastComputed)
System.out.println(this.toString()
+ " was modified.");
if (!cache.dirSizes.containsKey(this))
System.out.println(this.toString()
+ " is not cached.");
// Recompute the recursive size of this directory:
lastComputed = lastModified();
// Get the size of all files in this directory:
long newSize = size();
// Get all directories in this CachedDirectory:
Directory[] dirs = listDirectories();
for (Directory dir : dirs) {
/* TODO: Check if there's a better way to do this
* that doesn't involve creating a new instance.
* since this "fools" the equals() method...
* or does it?
*
* After some thought, becasuse its a new instance
* of CachedDirectory, its lastComputed variable
* is -1, meaning it will always recompute its
* size "manually". This is a bad thing, and
* I dont know how to fix it.
*/
dir = new CachedDirectory(dir);
newSize += dir.sizeRecursive();
}
// Cache and return new size:
cache.dirSizes.put(this, newSize);
return newSize;
} else { // This is in the cache and hasn't been modified.
System.out.println(this.toString() + " is cached.");
return cache.dirSizes.get(this);
}
}
}
}
-------------------Class Directory-------------------
import java.io.*;
import java.util.*;
/**
* This class represents a directory, othewise known as a folder.
* It provides a method of differentiating between Files and
Directories
* contained within the folder. Breaking from the java.io.File
"standard",
* this class provides a method of getting files and folders contained
within
* a folder seperately.
*/
public class Directory {
/** Internal representation of this directory. */
private final File representation;
/** The last time that the files and dirs arrays were refreshed.
*/
private long lastRefreshed = -1;
/** Contains the files stored in this directory. */
private File[] files;
/** Contains the directories stored in this directory. */
private Directory[] dirs;
public Directory(String pathname) throws NotADirectoryException {
this(new File(pathname));
}
public Directory(File f) throws NotADirectoryException {
if (f.exists() && f.isFile())
throw new NotADirectoryException(f);
representation = f;
}
/**
* Constructs a new Directory object pointing to the same
* data on disk.
* @todo Make constructors call each other (via this()).
*/
public Directory(Directory dir) {
/*
* TODO: Try to make all constructors call each other.
* right now, I cant call this(dir.representation)
* since I need to catch the NotADirectoryException.
*/
representation = dir.representation;
}
/**
* If recursive is false, gets the sum of all the sizes
* of the files contained in this Directory.
*/
public long size() throws IOException {
//System.out.println("Computing size for " + toString());
long size = 0;
File[] files = listFiles();
//System.out.println("File[] files == " +
Arrays.toString(files));
for (File file : files) {
size += file.length();
}
return size;
}
/**
* This method gets the sum of all the sizes of
* the files contained within this Directory,
* including those contained within the directories
* within this directory.
*/
public long sizeRecursive() throws IOException {
long size = size();
Directory[] directories = listDirectories();
//System.out.println("Directory[] directories == " +
Arrays.toString(directories));
for (Directory dir : directories) {
//System.out.println("Directory currently scanning == " +
dir);
size += dir.sizeRecursive();
}
return size;
}
/**
* Repopulates the arrays containing the files and directories
* contained within this directory.
*/
private void refreshContentArrays() throws IOException {
lastRefreshed = representation.lastModified();
File[] contents = representation.listFiles();
// contents shouldnt be null unless this directory doesn't
exist.
if (contents == null) {
this.dirs = null;
this.files = null;
return;
}
// A directory, at most, has contents.length files/directories
in it.
List<File> newFiles = new
ArrayList<File>(contents.length);
List<Directory> newDirs = new
ArrayList<Directory>(contents.length);
for (File item : contents) {
// TODO: Why does this need to be here?
if (item.exists() && !isSymbolicLink(item)) {
if (item.isFile()) {
newFiles.add(item);
} else { // Item is directory
try {
newDirs.add(new Directory(item));
} catch (NotADirectoryException nade) {
nade.printStackTrace();
}
// NotADirectoryException shouldn't happen.
}
}
}
// Store new data in the fields:
this.dirs = new Directory[newDirs.size()];
this.files = new File[newFiles.size()];
newDirs.toArray(dirs);
newFiles.toArray(files);
}
/**
* Lists all the directories contained within this directory.
* Note: This doesn't recurse into the directories.
* The special .. (parent) and . (this) directories are excluded.
* Returns null if this directory does not exist.
* @todo Move content array validity check to setLastModified().
*/
public Directory[] listDirectories() throws IOException {
// Make sure contents arrays are up-to-date.
// TODO: Check if I can just override setLastModified() to do
this.
long lastModified = representation.lastModified();
if (lastModified > lastRefreshed) {
refreshContentArrays();
}
Directory[] ret = null;
if (dirs != null) {
// Cant pass a refrence to the field, since
// its not immutable (can I use an UnmodifiableSet?)
ret = new Directory[dirs.length];
System.arraycopy(dirs, 0, ret, 0, dirs.length);
}
return ret;
}
/**
* Returns all the files contained within this directory.
* Contrary to java.io.File's listFiles() method, this returns
* only the *files* in this directory, not the directories.
*/
public File[] listFiles() throws IOException {
// Make sure contents arrays are up-to-date.
// TODO: Check if I can just override setLastModified() to do
this.
long lastModified = representation.lastModified();
if (lastModified > lastRefreshed) {
refreshContentArrays();
}
File[] ret = null;
if (files != null) {
// Cant pass a refrence to the field, since
// its not immutable (can I use an UnmodifiableList?)
ret = new File[files.length];
System.arraycopy(files, 0, ret, 0, files.length);
}
return ret;
}
private static boolean isSymbolicLink(File f) throws IOException {
return !f.getAbsolutePath().equals(f.getCanonicalPath());
}
/**
* @see java.io.File#lastModified()
*/
public long lastModified() {
return representation.lastModified();
}
/**
* @see java.io.File#exists()
*/
public boolean exists() {
return representation.exists();
}
/**
* @see java.io.File#mkdir()
*/
public boolean mkdir() {
return representation.mkdir();
}
/**
* @see java.io.File#delete()
*/
public boolean delete() {
return representation.delete();
}
/**
* @see java.io.File#toString()
*/
public String toString() {
return representation.toString();
}
/**
* @see java.io.File#hashCode()
*/
public int hashCode() {
return representation.hashCode();
}
/**
* @see java.io.File#equals(Object)
*/
public boolean equals(Object o) {
return representation.equals(o);
}
public int compareTo(Directory pathname) {
return representation.compareTo(pathname.representation);
}
/**
* @see java.io.File#getCanonicalPath()
*/
public String getCanonicalPath() throws IOException {
return representation.getCanonicalPath();
}
/**
* @see java.io.File#getPath()
*/
public String getPath() {
return representation.getPath();
}
}
-------------------Class NotADirectoryException-------------------
import java.io.File;
/**
* This exception is thrown when a Directory is constructed
* via a File that already exists as a file on the storage media.
* @todo See if this should instead subclass RuntimeException.
*/
public class NotADirectoryException extends Exception {
private final File file;
public NotADirectoryException(File file) {
this.file = file;
}
public NotADirectoryException(String filePath) {
this(new File(filePath));
}
public String getMessage() {
return file.toString() + " is not a directory.";
}
}
recursive folder size computation, and need to speed it up immensely.
I thought the use of a cache to store the result of recursively
getting the size of a folder would be ideal, but am having a problem
with it: Its not working.
The size is recomputed every time the sizeRecursive() method is called
on a CachedDirectory.
Also, please forgive the amount of code I'm posting, I think all of it
is pertinant though.
Note the comments in DirectoryCache.CachedDirectory.sizeRecursive()'s
for loop, In order to work on the Directorys contained in the
Directory[] as CachedDirectorys, I have to wrap them in a new instance
of CachedDirectory. I don't know how to get around the lastComputed
field of the new CachedDirectory being set to -1 (forcing the new
CachedDirectory to recompute its value instead of looking in the cache
for it).
Please help!
PS - Is there any where I can put a line break in the declaration of a
generic Collection? The lines where I declare them at the top of the
class get kinda long, and they look ugly.
-------------------Class DirectoryCache-------------------
import java.util.*;
import java.io.*;
/**
* Provides one place where CachedDirectories can be
* instantiated, that all use the same cache.
* To instantiate a CachedDirectory from this cache
* use this code:
* DirectoryCache cache = new DirectoryCache();
* CachedDirectory dir = cache.new CachedDirectory(...);
*/
public class DirectoryCache {
// The storage facility:
private static HashMap<Directory, Long> dirSizes = new
HashMap<Directory, Long>();
/**
* This class subclasses Directory to add caching of the result of
* calling sizeRecursive().
*/
public class CachedDirectory extends Directory {
/** The last time the recusrive size was computed. */
private long lastComputed = -1;
/** This is provided as an alternative to
* typing DirectoryCache.this */
private final DirectoryCache cache = DirectoryCache.this;
public CachedDirectory (final String pathName)
throws NotADirectoryException {
super(pathName);
}
public CachedDirectory(final Directory f) throws IOException {
super(f);
}
public long sizeRecursive() throws IOException {
/* If this CachedDirectory is NOT in the cache,
* or if it was modified: */
if (!cache.dirSizes.containsKey(this)
|| lastModified() > lastComputed) {
// Debug output:
if (lastModified() > lastComputed)
System.out.println(this.toString()
+ " was modified.");
if (!cache.dirSizes.containsKey(this))
System.out.println(this.toString()
+ " is not cached.");
// Recompute the recursive size of this directory:
lastComputed = lastModified();
// Get the size of all files in this directory:
long newSize = size();
// Get all directories in this CachedDirectory:
Directory[] dirs = listDirectories();
for (Directory dir : dirs) {
/* TODO: Check if there's a better way to do this
* that doesn't involve creating a new instance.
* since this "fools" the equals() method...
* or does it?
*
* After some thought, becasuse its a new instance
* of CachedDirectory, its lastComputed variable
* is -1, meaning it will always recompute its
* size "manually". This is a bad thing, and
* I dont know how to fix it.
*/
dir = new CachedDirectory(dir);
newSize += dir.sizeRecursive();
}
// Cache and return new size:
cache.dirSizes.put(this, newSize);
return newSize;
} else { // This is in the cache and hasn't been modified.
System.out.println(this.toString() + " is cached.");
return cache.dirSizes.get(this);
}
}
}
}
-------------------Class Directory-------------------
import java.io.*;
import java.util.*;
/**
* This class represents a directory, othewise known as a folder.
* It provides a method of differentiating between Files and
Directories
* contained within the folder. Breaking from the java.io.File
"standard",
* this class provides a method of getting files and folders contained
within
* a folder seperately.
*/
public class Directory {
/** Internal representation of this directory. */
private final File representation;
/** The last time that the files and dirs arrays were refreshed.
*/
private long lastRefreshed = -1;
/** Contains the files stored in this directory. */
private File[] files;
/** Contains the directories stored in this directory. */
private Directory[] dirs;
public Directory(String pathname) throws NotADirectoryException {
this(new File(pathname));
}
public Directory(File f) throws NotADirectoryException {
if (f.exists() && f.isFile())
throw new NotADirectoryException(f);
representation = f;
}
/**
* Constructs a new Directory object pointing to the same
* data on disk.
* @todo Make constructors call each other (via this()).
*/
public Directory(Directory dir) {
/*
* TODO: Try to make all constructors call each other.
* right now, I cant call this(dir.representation)
* since I need to catch the NotADirectoryException.
*/
representation = dir.representation;
}
/**
* If recursive is false, gets the sum of all the sizes
* of the files contained in this Directory.
*/
public long size() throws IOException {
//System.out.println("Computing size for " + toString());
long size = 0;
File[] files = listFiles();
//System.out.println("File[] files == " +
Arrays.toString(files));
for (File file : files) {
size += file.length();
}
return size;
}
/**
* This method gets the sum of all the sizes of
* the files contained within this Directory,
* including those contained within the directories
* within this directory.
*/
public long sizeRecursive() throws IOException {
long size = size();
Directory[] directories = listDirectories();
//System.out.println("Directory[] directories == " +
Arrays.toString(directories));
for (Directory dir : directories) {
//System.out.println("Directory currently scanning == " +
dir);
size += dir.sizeRecursive();
}
return size;
}
/**
* Repopulates the arrays containing the files and directories
* contained within this directory.
*/
private void refreshContentArrays() throws IOException {
lastRefreshed = representation.lastModified();
File[] contents = representation.listFiles();
// contents shouldnt be null unless this directory doesn't
exist.
if (contents == null) {
this.dirs = null;
this.files = null;
return;
}
// A directory, at most, has contents.length files/directories
in it.
List<File> newFiles = new
ArrayList<File>(contents.length);
List<Directory> newDirs = new
ArrayList<Directory>(contents.length);
for (File item : contents) {
// TODO: Why does this need to be here?
if (item.exists() && !isSymbolicLink(item)) {
if (item.isFile()) {
newFiles.add(item);
} else { // Item is directory
try {
newDirs.add(new Directory(item));
} catch (NotADirectoryException nade) {
nade.printStackTrace();
}
// NotADirectoryException shouldn't happen.
}
}
}
// Store new data in the fields:
this.dirs = new Directory[newDirs.size()];
this.files = new File[newFiles.size()];
newDirs.toArray(dirs);
newFiles.toArray(files);
}
/**
* Lists all the directories contained within this directory.
* Note: This doesn't recurse into the directories.
* The special .. (parent) and . (this) directories are excluded.
* Returns null if this directory does not exist.
* @todo Move content array validity check to setLastModified().
*/
public Directory[] listDirectories() throws IOException {
// Make sure contents arrays are up-to-date.
// TODO: Check if I can just override setLastModified() to do
this.
long lastModified = representation.lastModified();
if (lastModified > lastRefreshed) {
refreshContentArrays();
}
Directory[] ret = null;
if (dirs != null) {
// Cant pass a refrence to the field, since
// its not immutable (can I use an UnmodifiableSet?)
ret = new Directory[dirs.length];
System.arraycopy(dirs, 0, ret, 0, dirs.length);
}
return ret;
}
/**
* Returns all the files contained within this directory.
* Contrary to java.io.File's listFiles() method, this returns
* only the *files* in this directory, not the directories.
*/
public File[] listFiles() throws IOException {
// Make sure contents arrays are up-to-date.
// TODO: Check if I can just override setLastModified() to do
this.
long lastModified = representation.lastModified();
if (lastModified > lastRefreshed) {
refreshContentArrays();
}
File[] ret = null;
if (files != null) {
// Cant pass a refrence to the field, since
// its not immutable (can I use an UnmodifiableList?)
ret = new File[files.length];
System.arraycopy(files, 0, ret, 0, files.length);
}
return ret;
}
private static boolean isSymbolicLink(File f) throws IOException {
return !f.getAbsolutePath().equals(f.getCanonicalPath());
}
/**
* @see java.io.File#lastModified()
*/
public long lastModified() {
return representation.lastModified();
}
/**
* @see java.io.File#exists()
*/
public boolean exists() {
return representation.exists();
}
/**
* @see java.io.File#mkdir()
*/
public boolean mkdir() {
return representation.mkdir();
}
/**
* @see java.io.File#delete()
*/
public boolean delete() {
return representation.delete();
}
/**
* @see java.io.File#toString()
*/
public String toString() {
return representation.toString();
}
/**
* @see java.io.File#hashCode()
*/
public int hashCode() {
return representation.hashCode();
}
/**
* @see java.io.File#equals(Object)
*/
public boolean equals(Object o) {
return representation.equals(o);
}
public int compareTo(Directory pathname) {
return representation.compareTo(pathname.representation);
}
/**
* @see java.io.File#getCanonicalPath()
*/
public String getCanonicalPath() throws IOException {
return representation.getCanonicalPath();
}
/**
* @see java.io.File#getPath()
*/
public String getPath() {
return representation.getPath();
}
}
-------------------Class NotADirectoryException-------------------
import java.io.File;
/**
* This exception is thrown when a Directory is constructed
* via a File that already exists as a file on the storage media.
* @todo See if this should instead subclass RuntimeException.
*/
public class NotADirectoryException extends Exception {
private final File file;
public NotADirectoryException(File file) {
this.file = file;
}
public NotADirectoryException(String filePath) {
this(new File(filePath));
}
public String getMessage() {
return file.toString() + " is not a directory.";
}
}