-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHuffmanEncode.java
More file actions
311 lines (289 loc) · 9.23 KB
/
HuffmanEncode.java
File metadata and controls
311 lines (289 loc) · 9.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
import java.util.*;
import java.io.*;
import javax.swing.*;
/**
* Class which represents a Huffman encoding system, which can compress and decompress a text file.
* @author Jasper Bingham
*/
public class HuffmanEncode
{
private BinaryTree<CharAndFreq> codeTree;
private TreeMap<Character, String> codeTable = new TreeMap<Character, String>();
private String pathName;
private String compressedPathName;
private String decompressedPathName;
private BufferedReader regInput;
private BufferedWriter regOutput;
private BufferedBitReader bitInput;
private BufferedBitWriter bitOutput;
private static final int TXT_LENGTH = 4;
private static final int COMPRESSED_AND_TXT_LENGTH = 15;
private static final int LARGE_NUMBER = 100000000;
/**
* Constructor.
* Prompts user for file to compress / decompress, then generates a character tree,
* as well as target file names, readers, and writers.
* @throws IOException
*/
public HuffmanEncode() throws IOException
{
//get original file from user
pathName = HuffmanEncode.getFilePath();
//if the user presses cancel, exit program
if(pathName.equals(""))
{
System.out.println("You have cancelled the program.");
System.exit(0);
}
//make compressed/decompressed file names
compressedPathName = pathName.substring(0, pathName.length() - TXT_LENGTH) + "_compressed" + ".txt";
decompressedPathName = compressedPathName.substring(0, compressedPathName.length() - COMPRESSED_AND_TXT_LENGTH) + "_decompressed" + ".txt";
//set up compression readers and writers
regInput = new BufferedReader(new FileReader(pathName));
regInput.mark(LARGE_NUMBER); //mark the reader for later reset in compress method
bitOutput = new BufferedBitWriter(compressedPathName);
//generate frequency table, tree, and code table
Map<Character, Integer> freqTable = generateFreqTable(pathName);
codeTree = makeHuffmanTree(freqTable);
codeTable = generateCodeTable("", makeHuffmanTree(freqTable));
}
/**
* Generates a frequency table for an input text file.
* @param pathName the path name for the input text file
* @return the frequency table
*/
public Map<Character, Integer> generateFreqTable(String pathName) throws IOException
{
Map<Character, Integer> myFreqTable = new TreeMap<Character, Integer>();
try
{
int c = regInput.read();
if(c == -1) //if file is empty, quit program
{
System.out.println("File is empty -- compression will not be useful.");
System.exit(0);
}
while(c != -1) //check that reader has more characters to read
{
char thisChar = (char) c; //cast to character
if(myFreqTable.containsKey(thisChar))
{
myFreqTable.put(thisChar, myFreqTable.get(thisChar) + 1); //increment frequency
}
else
{
myFreqTable.put(thisChar, 1); //frequency starts at 1
}
c = regInput.read(); //move to next character
}
}
catch(IOException i)
{
System.out.println(i + " exception");
}
return myFreqTable;
}
/**
* Generates a Huffman tree from a frequency table.
* @param freqTable the frequency table
* @return the Huffman tree
*/
public BinaryTree<CharAndFreq> makeHuffmanTree(Map<Character, Integer> freqTable)
{
PriorityQueue<BinaryTree<CharAndFreq>> myQueue = new PriorityQueue<BinaryTree<CharAndFreq>>(freqTable.size(), new TreeComparator());
Set<Character> keys = freqTable.keySet();
//BOUNDARY CASE: if there is only one type of character in the file,
//we have to make a tree where there is a dummy root and then two identical children,
//both of which represent the one character and that character's frequency
if(keys.size() == 1)
{
for(Character c : keys)
{
int freq = freqTable.get(c);
CharAndFreq loneRoot = new CharAndFreq('\0', freq + freq);
CharAndFreq loneChar = new CharAndFreq(c, freqTable.get(c));
BinaryTree<CharAndFreq> loneTree = new BinaryTree<CharAndFreq>(loneChar);
BinaryTree<CharAndFreq> loneCombo = new BinaryTree<CharAndFreq>(loneRoot, loneTree, loneTree);
myQueue.add(loneCombo);
return myQueue.poll();
}
}
//otherwise go through the regular process -- create singleton trees
for(Character c : keys)
{
CharAndFreq cf = new CharAndFreq(c, freqTable.get(c));
BinaryTree<CharAndFreq> singleton = new BinaryTree<CharAndFreq>(cf);
myQueue.add(singleton);
}
//tree creation
while(myQueue.size() > 1)
{
//get two characters with lowest frequencies, then make a tree with those two and a dummy header
//and add that tree to the priority queue
int freq1 = myQueue.peek().getValue().getFreq();
BinaryTree<CharAndFreq> tree1 = myQueue.poll();
int freq2 = myQueue.peek().getValue().getFreq();
BinaryTree<CharAndFreq> tree2 = myQueue.poll();
CharAndFreq newParent = new CharAndFreq('\0', freq1 + freq2);
BinaryTree<CharAndFreq> combo = new BinaryTree<CharAndFreq>(newParent, tree1, tree2);
myQueue.add(combo);
}
//once the process is over, there is one element in the priority queue: the complete tree
return myQueue.poll();
}
/**
* Generates a code table from a Huffman tree.
* @param pathSoFar the path taken through the tree so far - 0s mark left turns, 1s mark right turns
* @param focusNode the current node in the traversal - changes during recursion
* @return
*/
public TreeMap<Character, String> generateCodeTable(String pathSoFar, BinaryTree<CharAndFreq> focusNode)
{
//go left & right down the root subtrees recursively until leaf reached
if(focusNode.hasLeft())
{
generateCodeTable(pathSoFar + "0", focusNode.getLeft());
}
if(focusNode.hasRight())
{
generateCodeTable(pathSoFar + "1", focusNode.getRight());
}
//pair leaf character with the path so far in code table
if(focusNode.isLeaf())
{
codeTable.put(focusNode.getValue().getChar(), pathSoFar);
}
return codeTable;
}
/**
* Compresses the file.
* @throws IOException
*/
public void compress() throws IOException
{
regInput.reset(); //reset the file reader after making the frequency table
try
{
int c = regInput.read(); //get first character
while(c != -1) //run until reader is done
{
char thisChar = (char) c;
String s = codeTable.get(thisChar); //find correct code for char
for(int i = 0; i < s.length(); i++) //run through code, coding corresponding bits for each 0 or 1
{
if(s.charAt(i) == '0')
{
bitOutput.writeBit(0);
}
else bitOutput.writeBit(1);
}
c = regInput.read(); //advance reader
}
}
catch(IOException i)
{
System.out.println(i + " exception");
}
finally
{
regInput.close();
bitOutput.close();
}
}
/**
* Decompresses the file.
* @throws IOException
*/
public void decompress() throws IOException {
//Make appropriate reader/writer (only possible here because compressed file has to be made first)
bitInput = new BufferedBitReader(compressedPathName);
regOutput = new BufferedWriter(new FileWriter(decompressedPathName));
try{
//read through bits
int bit = bitInput.readBit();
while (bit != -1) {
decompressCharacter(bit, codeTree); //use helper method to recognize and write character
bit = bitInput.readBit(); //advance bit after character is written
}
}
catch(IOException i)
{
System.out.println(i + " exception");
}
finally
{
bitInput.close();
regOutput.close();
}
}
/**
* Recognizes and writes a character, given a starting bit in the compressed file and the Huffman tree.
* @param bit the starting bit in the compressed file
* @param tree the Huffman tree
* @throws IOException
*/
public void decompressCharacter(int bit, BinaryTree<CharAndFreq> tree) throws IOException
{
//base case: we get to a leaf
if(tree.isLeaf())
{
regOutput.write(tree.getValue().getChar()); //writes the character to the decompressed file
return;
}
try
{
//if bit is 0 go to left subtree
if(bit == 0)
{
//if next left node is not a leaf, advance bit
if(tree.getLeft().isInner())
{
decompressCharacter(bitInput.readBit(), tree.getLeft());
}
//otherwise we do not advance the bit
else decompressCharacter(bit, tree.getLeft());
}
//if bit is 1 go to right subtree
if(bit == 1)
{
//if next right node is not a leaf, advance bit
if(tree.getRight().isInner())
{
decompressCharacter(bitInput.readBit(), tree.getRight());
}
//otherwise we do not advance the bit
else decompressCharacter(bit, tree.getRight());
}
}
catch(IOException i)
{
System.out.println(i + " exception");
}
}
/**
* Puts up a fileChooser and gets path name for file to be opened.
* Returns an empty string if the user clicks "cancel".
* @return path name of the file chosen
*/
public static String getFilePath() {
//Create a file chooser
JFileChooser fc = new JFileChooser();
int returnVal = fc.showOpenDialog(null);
if(returnVal == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
String pathName = file.getAbsolutePath();
return pathName;
}
else
return "";
}
/**
* Tester. Compresses and decompresses an input file.
*/
public static void main (String [] args) throws IOException
{
HuffmanEncode h = new HuffmanEncode();
h.compress();
h.decompress();
}
}