实验四、基于 Lempel-Ziv 算法的英文文本文件压缩
实验目的
一、通过设计计算机程序,实现基于 Lempel-Ziv 算法的英文文本文件压缩,进
一步理解 Lempel-Ziv 算法的意义。
二、熟练运用二进制数据文件的读写操作。
三、学习程序调试技术,进一步提高程序调试技能。
实验内容
教材里的 Lempel-Ziv 算法是针对二进制比特序列的编码而设计的,但算法的思想完全可以用于非二进制序列的编码。本次实验就是要利用 Lempel-Ziv 算法的思想,将英文文本文件看作是英文字符序列,实现英文文本文件的压缩。
一、编码算法思想:
1)建立一个只包含“空前缀串”的字典;将文本文件读入缓冲区,然后将当前编码指针指向第一个字符。
2)从当前编码指针所指的字符开始,用它及后面所有字符作为一个字符串,在字典中查找能与该字符串的前缀匹配的最长前缀串;
若没有匹配,则将“空前缀串”的编号0作为当前码字中的编号部分,再将当前编码指针所指的输入字符“A”作为当前码字中的符号部分。输出当前码字,然后编码指针指向下一个字符;
若有匹配的前缀串,则寻找最长匹配的前缀串,然后将该最长匹配前缀串在字典中的编号作为当前码字中的编号部分,再将当前输入序列中,与该前缀串匹配结束后的第一个字符“A”作为当前码字的符号部分。输出当前码字,再将编码指针指向字符“A”之后的第一个字符。
3)将上一步中,最长匹配前缀串加上字符“A”作为一个新的前缀串添加到字典中;若上一步中没有找到匹配的前缀串,则将“空前缀串”加上字符“A”作为新前缀串,实际就是将字符“A”本身添加到字典中。该前缀串编号为现有前缀串的最大编号加一。
4)4)若输入字符串没有处理完,则回到步骤 2),否则结束编码。
二、解码算法思想:
1)建立一个只包含“空前缀串”的字典;将压缩文件读入输入缓冲区,取第一个码字
2)在字典中查找当前码字中编号对应的前缀串,加上当前码字中的字符“A”后,将得到的字符串保存到输出缓冲区中。
3)再将这个字符串作为一个新的前缀串添加到字典中,该前缀串编号为现有前缀串的最大编号加一。
4)若码字处理完则结束,否则取下一个码字然后回到步骤 2)。
要求:
字典尺寸:65536;前缀串编号二进制比特数:16;字符“A”占一个字节。因此编码输出的每个码字为 3 个字节。
提示:
字典的每一个条目不一定要实际保存每一个前缀串本身,只需保存对应前缀串在输入缓冲区(编码方)或输出缓冲区(解码方)中的起始位置和长度就行。
实验任务:
- 设计编码程序。
- 在不用解码程序的情况下,以调试的手段说明编码程序是基本正确的。
- 设计解码程序。
- 在不比较重建序列和原始序列的情况下,以调试的手段说明解码程序是基本正确的。
package com.atguigu.java;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Lempel-Ziv 算法的英文文本文件压缩
*/
public class LZ {
public static List<byte[]> compress(char[] text, String filePath) throws IOException {
//定义属性 dictionary 用来存放字符串编码表
Map<String, Integer> dictionary = new HashMap<>();
List<byte[]> compressList = new ArrayList<>();
// 将需要压缩的文本文件
//定义一个空字符串
//todo 这里定义的String 字符串产生了大量大垃圾其实可以优化
//String str = "";
StringBuilder str = new StringBuilder();
for (int i = 0; i<text.length-1; i++) {
str.append(text[i]);
//控制 dictionary 的大小, dictionary大小没有超过 65535时,就一直往里面put
if(dictionary.size() < 65535) {
// 如果字典不包含 没有匹配到
if (!dictionary.containsKey(str.toString())) {
//把当前码子输出到缓冲区
compressList.add(IntegerStr2Byte(0,str.toString()));
// 把没有匹配到的字符串添加到字典中
dictionary.put(str.toString(), dictionary.size()+1);
// 字符串str清空,指针指向一下一个字符
str.delete(0,str.length());
//3 . 第一次进来需要put A 字符 入 字典
// if(!dictionary.containsKey("A"))
// dictionary.put("A",dictionary.size());
} else {
// 2. 若匹配到最长字符串
while(dictionary.containsKey(str.toString())){
i++;
str.append(text[i]);
}
String s = str.substring(0,str.length()-1);
String s1 = str.substring(str.length()-1,str.length());
// 把对应的编码添加的输出缓存区
compressList.add(IntegerStr2Byte(dictionary.get(s), s1));
// 字典中添加上 最大匹配串+下一字符
dictionary.put(str.toString(),dictionary.size()+1);
str.delete(0, str.length());
}
}else {
// 如果大于 65535时, 字典不能再添加文件了,此刻只能从字典中找
while(dictionary.containsKey(str.toString())){
if (i < text.length-1){
i++;
str.append(text[i]);
}else {
break;
}
}
String s = str.substring(0,str.length()-1);
String s1 = str.substring(str.length()-1,str.length());
// 把对应的编码添加的输出缓存区
compressList.add(IntegerStr2Byte(dictionary.get(s), s1));
str.delete(0, str.length());
}
}
// 调用写二进制文件方法生成压缩文件
outByFileDataOutputStream(filePath, compressList);
return compressList;
}
/**
* 工具方法
* @param integer
* @param string
* @return
*/
private static byte[] IntegerStr2Byte(Integer integer,String string){
byte[] bytes = new byte[3];
bytes[0] = (byte) (integer / 256);
bytes[1] = (byte) (integer % 256);
bytes[2] = string.getBytes()[0];
return bytes;
}
/**
* 保存压缩文件
* @param filePath
*/
private static void outByFileDataOutputStream(String filePath, List<byte[]> compressList ) {
File target = new File(filePath);
if (target.exists() && target.isFile()){
boolean flag = target.delete();
}
try {
if (target.createNewFile()){
for (byte[] bytes : compressList) {
DataOutputStream out = new DataOutputStream(new FileOutputStream(filePath, true));
out.write(bytes);
out.close();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 解压压缩文件
* @param compressText
* @throws IOException
*/
public static void unCompress(List<byte []> compressText,String filePath) throws IOException {
//定义属性 dictionary 用来存放字符串编码表
Map<Integer, String> dictionary = new HashMap<>();
// 用来存放解码后的字符串
StringBuilder Stringbuilder = new StringBuilder();
for (byte [] bytes: compressText) {
Integer integer = (bytes[0]&0x0FF) *256+ (bytes[1]&0x0FF);
char c = (char)(int)bytes[2];
//字典中没有把这个代码放进字典
if (!dictionary.containsKey(integer)){
//添加到输入缓存区
Stringbuilder.append(c);
//添加到字典中
if (dictionary.size()< 65535)
dictionary.put( dictionary.size()+1, String.valueOf(c));
}else{
if (dictionary.size() == 65535)
//添加到输入缓存区
Stringbuilder.append(dictionary.get(integer)+String.valueOf(c));
//添加到字典中
if (dictionary.size()< 65535)
dictionary.put(dictionary.size()+1, dictionary.get(integer)+String.valueOf(c));
}
}
FileWriter fw = new FileWriter(new File(filePath));
fw.write(Stringbuilder.toString());
// 在这里不关闭文件的话,会有一部分字符串没有写到文件中,可以调用 fw.flush(); 获取 close() 方法都能够把内容中的内容真正写到文件中
fw.close();
// // 把编码表 dictionary key value 互换 方便取数据
// Map<Integer, String> dictionary1 = new HashMap<>();
// for(Map.Entry<String, Integer> entry : dictionary.entrySet()){
// dictionary1.put(entry.getValue(),entry.getKey());
// }
// for (byte []: compressText) {
// Stringbuilder.append(dictionary1.get(ii));
// }
}
/**
* 读取text文件
* @param fileName
* @return
*/
public static String readFileContent(String fileName) {
File file = new File(fileName);
BufferedReader reader = null;
StringBuffer sbf = new StringBuffer();
try {
reader = new BufferedReader(new FileReader(file));
String tempStr;
while ((tempStr = reader.readLine()) != null) {
sbf.append(tempStr);
}
reader.close();
return sbf.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
return sbf.toString();
}
/**
* 主函数
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
// 读取text文件
String s = readFileContent("E:\\test.txt");
List<byte[]> compress = compress(s.toCharArray(), "E:\\compress.LZ");
unCompress(compress,"E:\\generate.txt");
//读取待压缩的文本文件加载到缓冲区
//把缓冲区内容传入compress方法中进行压缩
//把压缩过的内容传入解压缩方法中进行解压缩还原文本文件
}
}