-
Notifications
You must be signed in to change notification settings - Fork 107
Expand file tree
/
Copy pathArithmeticCompress.java
More file actions
97 lines (81 loc) · 3.2 KB
/
Copy pathArithmeticCompress.java
File metadata and controls
97 lines (81 loc) · 3.2 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
/*
* Reference arithmetic coding
*
* Copyright (c) Project Nayuki
* MIT License. See readme file.
* https://www.nayuki.io/page/reference-arithmetic-coding
*/
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Compression application using static arithmetic coding.
* <p>Usage: java ArithmeticCompress InputFile OutputFile</p>
* <p>Then use the corresponding "ArithmeticDecompress" application to recreate the original input file.</p>
* <p>Note that the application uses an alphabet of 257 symbols - 256 symbols for the byte
* values and 1 symbol for the EOF marker. The compressed file format starts with a list
* of 256 symbol frequencies, and then followed by the arithmetic-coded data.</p>
*/
public class ArithmeticCompress {
public static void main(String[] args) throws IOException {
// Handle command line arguments
if (args.length != 2) {
System.err.println("Usage: java ArithmeticCompress InputFile OutputFile");
System.exit(1);
return;
}
File inputFile = new File(args[0]);
File outputFile = new File(args[1]);
// Read input file once to compute symbol frequencies
FrequencyTable freqs = getFrequencies(inputFile);
freqs.increment(256); // EOF symbol gets a frequency of 1
// Read input file again, compress with arithmetic coding, and write output file
try (InputStream in = new BufferedInputStream(new FileInputStream(inputFile));
BitOutputStream out = new BitOutputStream(new BufferedOutputStream(new FileOutputStream(outputFile)))) {
writeFrequencies(out, freqs);
compress(freqs, in, out);
}
}
// Returns a frequency table based on the bytes in the given file.
// Also contains an extra entry for symbol 256, whose frequency is set to 0.
private static FrequencyTable getFrequencies(File file) throws IOException {
FrequencyTable freqs = new SimpleFrequencyTable(new int[257]);
try (InputStream input = new BufferedInputStream(new FileInputStream(file))) {
while (true) {
int b = input.read();
if (b == -1)
break;
freqs.increment(b);
}
}
return freqs;
}
// To allow unit testing, this method is package-private instead of private.
static void writeFrequencies(BitOutputStream out, FrequencyTable freqs) throws IOException {
for (int i = 0; i < 256; i++)
writeInt(out, 32, freqs.get(i));
}
// To allow unit testing, this method is package-private instead of private.
static void compress(FrequencyTable freqs, InputStream in, BitOutputStream out) throws IOException {
ArithmeticEncoder enc = new ArithmeticEncoder(32, out);
while (true) {
int symbol = in.read();
if (symbol == -1)
break;
enc.write(freqs, symbol);
}
enc.write(freqs, 256); // EOF
enc.finish(); // Flush remaining code bits
}
// Writes an unsigned integer of the given bit width to the given stream.
private static void writeInt(BitOutputStream out, int numBits, int value) throws IOException {
if (numBits < 0 || numBits > 32)
throw new IllegalArgumentException();
for (int i = numBits - 1; i >= 0; i--)
out.write((value >>> i) & 1); // Big endian
}
}