StringBuilder
str = "" + ch;
If you find yourself doing a lot of string addition, consider using a StringBuilder
(or StringBuffer
if you need thread support).
builder.setLength(0);
builder.append(ch);
This saves creating a new string object with each new character.
if (dictionary.containsKey(str + ch)) {
str = str + ch;
} else {
becomes
builder.append(ch);
if (!dictionary.containsKey(builder.toString())) {
which simplifies things.
Use bitwise operations
String s12 = to12bit(dictionary.get(str));
// Store the 12 bits into an array and then write it to the
// output file
if (onleft) {
buffer[0] = (byte) Integer.parseInt(
s12.substring(0, 8), 2);
buffer[1] = (byte) Integer.parseInt(
s12.substring(8, 12) + "0000", 2);
} else {
buffer[1] += (byte) Integer.parseInt(
s12.substring(0, 4), 2);
buffer[2] = (byte) Integer.parseInt(
s12.substring(4, 12), 2);
for (int b = 0; b < buffer.length; b++) {
out.writeByte(buffer[b]);
buffer[b] = 0;
}
}
This is clever but more complicated than is necessary.
int compressed = dictionary.get(str));
// Store the 12 bits into an array and then write it to the
// output file
if (onleft) {
buffer[0] = (byte) (compressed & 0xff);
This only includes the last eight bits of the compressed valued. Note that 0xff is the same as binary 11111111. So you AND compressed
and 0xff which leaves all of the last eight bits that were set still set. But it zeroes out bits outside that area and leaves cleared those bits that were already cleared.
buffer[1] = (byte) ((compressed >> 8) << 4);
We right shift eight bits, which clears those bits (what we put in buffer[0]
). Then we left shift four bits, which has the same effect as appending "0000"
does in the original code. This relies on compressed
never being greater than or equal to 4096. Otherwise the conversion to byte
will drop some of the information.
} else {
buffer[1] += (byte) (compressed & 0xf);
This masks out everything but the last four bits.
buffer[2] = (byte) (compressed >> 4);
Remove the four bits that we put in buffer[1]
and put the rest in buffer[2]
. Again, this relies on compressed
never being greater than or equal to 4096. Otherwise the conversion to byte
will drop some of the information.
for (int b = 0; b < buffer.length; b++) {
out.writeByte(buffer[b]);
buffer[b] = 0;
}
}
By using the bitwise operators, we save the entire to12bit
method. We also avoid creating a String
just so that we could use substring
.
You also may want to put this into its own method. Then when you do it again in the catch
block, you could just call the method.
HashMap
tracks its own size
dictionary.put(str + ch, dictSize++);
You don't need a dictSize
variable. You could just say
dictionary.put(str + ch, dictionary.size());
This saves managing the dictSize
variable.
catch
EOFException
Your code runs forever until it encounters an IOException
. Then it assumes that the IOException
is an EOFException
and writes out whatever is waiting to be written. Instead, consider catching just the EOFException
. Then an IOException
will crash the program. Which is what an IOException
thrown in the catch
block would do anyway.
Use buffered I/O
You are using RandomAccessFile
, which works but is unnecessary. You only do sequential operations. You don't use the random access capability at all. You could just use a buffered I/O method.
You could even write your own wrapper for it. Then you could say something like
out.write12bits(compressed);
and let your wrapper handle the details.
Use descriptive variable names if you can
Some variables don't have descriptive names that make sense, so we just call them string
or something like that. But some do.
String str = input.nextLine();
In this case, the String
represents a file name. So call it that.
String filename = input.nextLine();
Use try
with resources
try {
LZWCompression lzw = new LZWCompression();
Scanner input = new Scanner(System.in);
System.out.println("Enter the name of your (input.txt) file.");
String str = input.nextLine();
File file = new File(str);
Scanner fileScanner = new Scanner(file);
String line = "";
while (fileScanner.hasNext()) {
line = fileScanner.nextLine();
System.out.println("Contents of your file being compressed: \n"
+ line);
}
lzw.compress(str);
System.out.println("\nCompression of your file is complete!");
System.out.println("Your new file is named: " + str.concat(".lzw"));
But the early part of this can't throw a FileNotFoundException
, so it doesn't need to be in the try
block.
LZWCompression lzw = new LZWCompression();
Scanner input = new Scanner(System.in);
System.out.println("Enter the name of your (input.txt) file.");
String filename = input.nextLine();
try (File file = new File(filename)) {
Scanner fileScanner = new Scanner(file);
while (fileScanner.hasNext()) {
String line = fileScanner.nextLine();
System.out.println("Contents of your file being compressed: \n"
+ line);
}
lzw.compress(filename);
System.out.println("\nCompression of your file is complete!");
System.out.println("Your new file is named: " + filename.concat(".lzw"));
Now the file
will be managed by the try
statement.
Confirm what we should know
} catch (FileNotFoundException e) {
System.out.println("File was not found!");
}
Which file was not found?
} catch (FileNotFoundException e) {
System.out.println("File '" + filename + "' was not found!");
}
Now we know what the program thought it wanted to find.