package com.javahuffman;
import java.io.Closeable;
import java.io.File;
import
java.io.FileInputStream;
import
java.io.FileOutputStream;
import
java.io.IOException;
public class JavaHuffman implements
Closeable {
public static int
fromByteArray(byte[] bytes) {
return bytes != null && bytes.length
>= 4 ? fromBytes(bytes[0],
bytes[1], bytes[2], bytes[3]) : 0;
}
public static int
fromBytes(byte b1, byte b2, byte b3, byte b4) {
return b1 << 24 | (b2
& 0xFF) << 16 | (b3 & 0xFF) << 8 | (b4 &
0xFF);
}
public static byte[]
toByteArray(int value) {
return new byte[] { (byte) (value >> 24), (byte) (value >> 16),
(byte) (value >> 8),
(byte) value };
}
public final static int NodesCount = 2 * 256 -
1;
public final static boolean DEBUG = false;
public class Node {
public Integer Parent = -1;
public Integer Count = 0;
public Integer LeftChild = -1;
public Integer RightChild = -1;
public StringBuilder
Code = new
StringBuilder();
}
protected String
escapeChar(Character character) {
StringBuilder result = new
StringBuilder();
if (character == '\"') {
result.append("\\\"");
} else if (character == '\\') {
result.append("\\\\");
} else if (character == '/') {
result.append("\\/");
} else if (character == '\b') {
result.append("\\b");
} else if (character == '\f') {
result.append("\\f");
} else if (character == '\n') {
result.append("\\n");
} else if (character == '\r') {
result.append("\\r");
} else if (character == '\t') {
result.append("\\t");
} else if (character >= 0 && character <
' ' || character >= 127) {
result.append("0x" +
(Integer.toHexString(character)));
} else {
result.append(character);
}
return result.toString();
}
protected int buildtree(Node[] nodes, int
total) {
int root = -1;
if (nodes != null) {
for (int p = (NodesCount + 1) / 2; p
<= NodesCount; p++) {
// 1. find s1 and s2
int s1 = -1, s2 = -1;
int min =
Integer.MAX_VALUE;
for (int i = 0; i < p; i++) {
if (nodes[i].Count > 0 && nodes[i].Count < min
&& nodes[i].Parent ==
-1) {
min = nodes[i].Count;
s1 = i;
}
}
min = Integer.MAX_VALUE;
for (int i = 0; i < p; i++) {
if (i != s1 &&
nodes[i].Count > 0 &&
nodes[i].Count < min
&& nodes[i].Parent ==
-1) {
min = nodes[i].Count;
s2 = i;
}
}
if (s1 >= 0 && s2
>= 0) {
nodes[s1].Parent = p;
nodes[s2].Parent = p;
nodes[p].LeftChild = s1;
nodes[p].RightChild = s2;
if ((nodes[p].Count = nodes[s1].Count +
nodes[s2].Count) == total) {
root = p;
}
}
}
if (DEBUG) {
for (int i = 0; i < NodesCount;
i++) {
if (nodes[i].Count > 0)
System.out.println("char = " + i +
",\tcount="
+ nodes[i].Count +
",\tparent="
+ nodes[i].Parent +
",\tleft="
+ nodes[i].LeftChild +
",\tright="
+ nodes[i].RightChild);
}
}
for (int t = 0; t < (NodesCount +
1) / 2; t++) {
int k = t;
while (nodes[k].Parent >= 0) {
if (k ==
nodes[nodes[k].Parent].LeftChild) {
nodes[t].Code.append('0');
} else {
nodes[t].Code.append('1');
}
k = nodes[k].Parent;
}
// make reverse!
nodes[t].Code.reverse();
}
if (DEBUG) {
System.out.println("result:");
for (int i = 0; i < (NodesCount +
1) / 2; i++) {
if (nodes[i].Count > 0)
System.out.println("char = ["
+ this.escapeChar(((char) i)) +
"] Count = ["
+ nodes[i].Count +
"] Code = ["
+ nodes[i].Code.toString() +
"]");
}
}
}
return root;
}
public void round(String plainfilepath, String encodefilepath)
{
this.encode(plainfilepath,
encodefilepath);
this.decode(plainfilepath +
".ret", encodefilepath);
}
public void decode(String plainfilepath, String
encodefilepath) {
File encodefile = new
File(encodefilepath);
if (encodefile.exists())
{
File plainfile = new
File(plainfilepath);
boolean outputReady =
false;
if (!plainfile.exists())
{
try {
outputReady = plainfile.createNewFile();
} catch (IOException e) {
}
} else {
outputReady = true;
}
if (outputReady) {
try (FileInputStream
inputStream = new FileInputStream(
encodefile);
FileOutputStream outputStream = new FileOutputStream(
plainfile)) {
if (inputStream.available()
>= 4) {
byte[] values = new byte[4];
inputStream.read(values);
String signatureString = new
String(values);
if
(signatureString.equals("HUFF")) {
int total = 0;
if (inputStream.available()
>= 4)
inputStream.read(values);
total = fromByteArray(values);
Node[] nodes = new
Node[NodesCount];
for (int i = 0; i < nodes.length;
i++) {
nodes[i] = new Node();
if (i < (NodesCount+1)/2) {
if (inputStream.available()
>= 4) {
inputStream.read(values);
nodes[i].Count =
fromByteArray(values);
}
}
}
int root = this.buildtree(nodes, total);
int done = 0;
int val =
inputStream.read();
int c = 0;
while (done < total
&& val != -1) {
int idx = root;
while (idx >=
(NodesCount + 1) / 2) {
if ((val & 0x80) ==
0)
idx = nodes[idx].LeftChild;
else {
idx = nodes[idx].RightChild;
}
val <<= 1;
val &= 0xff;
c++;
if (c == 8) {
c = 0;
val = inputStream.read();
}
if (idx >= 0 &&
idx < (NodesCount + 1) / 2) {
// this is the valid
char!
outputStream.write(idx);
done++;
break;
}
}
}
}
}
} catch (IOException e) {
}
}
}
}
public void encode(String plainfilepath, String
encodefilepath) {
File plainfile = new
File(plainfilepath);
if (plainfile.exists()) {
File encodefile = new
File(encodefilepath);
boolean outputReady =
false;
if (!encodefile.exists())
{
try {
outputReady = encodefile.createNewFile();
} catch (IOException e) {
}
} else {
outputReady = true;
}
if (outputReady) {
try (FileInputStream
inputStream = new FileInputStream(
plainfile);
FileOutputStream outputStream = new FileOutputStream(
encodefile)) {
Node[] nodes = new
Node[NodesCount];
for (int i = 0; i < nodes.length;
i++) {
nodes[i] = new Node();
}
@SuppressWarnings("unused")
int total = 0, finished =
0;
int val = 0;
while ((val =
inputStream.read()) != -1) {
int b = 0xff & val;
nodes[b].Count++;
total++;
}
inputStream.getChannel().position(0);
// save magic
outputStream
.write("HUFF".getBytes());
finished += 4;
// save total length
outputStream.write(toByteArray(total));
finished += 4;
// 1k space
for (int i = 0; i < (NodesCount +
1) / 2; i++) {
outputStream.write(toByteArray(nodes[i].Count));
}
finished += (NodesCount + 1)
/ 2 * 4;
this.buildtree(nodes,
total);
// write others
int v = 0;
int c = 0;
while ((val =
inputStream.read()) != -1) {
int b = 0xff & val;
StringBuilder builder = nodes[b].Code;
for (int z = 0; z< builder.length();z++) {
v <<= 1;
if (builder.charAt(z) ==
'1') {
v |= 1;
} else {
v |= 0;
}
c++;
if (c == 8) {
outputStream.write(v);
finished++;
v = 0;
c = 0;
}
}
}
if (c > 0) {
v <<= (8 - c);
outputStream.write(v);
finished++;
}
if (DEBUG) {
System.out.println("Compress " +
plainfilepath
+ " with " + total +
" bytes" + " into
"
+ encodefilepath + " with " +
finished
+ " bytes; compression ratio
= "
+ (float) finished /
(float) total * 100.0f
+ "%.");
}
} catch (IOException e) {
}
}
}
}
@Override
public void close() throws IOException
{
}
public static void main(String[] args)
{
if (args != null && args.length
<= 1) {
System.out.println("javahuffman
[-e/-d/-r] inputfile encodedfile");
} else {
try (JavaHuffman javaHuffman
= new JavaHuffman()) {
switch (args[0]) {
case "-e":
if (args.length >= 3) {
javaHuffman.encode(args[1], args[2]);
}
break;
case "-d":
if (args.length >= 3) {
javaHuffman.decode(args[1], args[2]);
}
break;
case "-r":
if (args.length >= 3) {
javaHuffman.round(args[1], args[2]);
}
break;
}
} catch (IOException e) {
}
}
}
}