自己动手写压缩软件

        想吃项记的烩面了……这小地方的可难吃。

        看完了《裸婚时代》,我觉得冬瓜说得对,刘易阳不敢面对自己的真实感受。
        女生都是感性的,工作永远不如生活重要。

        不是坐在一起就叫团队,不是不吵架就叫态度。

        昨晚一哥们说求k小直接可以进行k次冒泡,我都想不起来,我想到的是区间快拍,说明基础很重要。

        对原版本的算法有很大修改,个人认为原版代码的命名不太规范,读起来比较累,本程序主要是界面参考了参考资料,读写文件完全是自己搞的(原文字节流,我的字符流),不过原文不可压缩汉字(原版压缩效率高一些),我的可以,压缩比40%左右(文件大小做除)。

一.概念引入

        优先级队列(Priority Queue)又叫最小堆。Huffman( 哈夫曼 ) 算法在上世纪五十年代初提出来了,它是一种无损压缩方法,在压缩过程中不会丢失信息熵,而且可以证明Huffman 算法在无损压缩算法中是最优的。Hufmann coding 是最古老,以及最优雅的数据压缩方法之一。它是以最小冗余编码为基础的,即如果我们知道数据中的不同符号在数据中的出现频率,我们就可以对它用一种占用空间最少的编码方式进行编码,这种方法是,对于最频繁出现的符号制定最短长度的编码,而对于较少出现的符号给较长长度的编码。哈夫曼编码可以对各种类型的数据进行压缩,但在本文中我们仅仅针对字符进行编码。

        哈夫曼编码是一种前缀码,即任一个字符的编码都不是其他字符编码的前缀。从我们的编码过程中可以很容易看到这一点,因为所有字符都是哈夫曼树中的叶子节点,这个特征能够保证解码的唯一性,不会产生歧义。笔者认为这样说或许更好理解:任意字符的编码的所有前缀都不是其它字符的编码,而且字符和编码是满单射,为后面value反查key打基础。可以看出,出现频率最高的字符,使用最短的编码,字符出现频率越低,编码逐渐增长。这样不同字符在文档中出现的频率差异越大,则压缩效果将会越好。因此字符出现频率越大,我们希望给它的编码越短(在哈夫曼树中的深度越浅),即我们希望更晚的将它所在的树进行合并。反之,字符频率越低,我们希望给他的编码最长(在哈夫曼树中的深度越深),因此我们希望越早的将它所在的树进行合并。因此,哈夫曼编码的贪心策略就体现在合并树的过程中,我们每一次总是选择根节点频率最小的两个树先合并,这样就能达到我们所希望的编码结果。

        例:假设一个文本文件中只包含7个字符{A,B,C,D,E,F,G},这7个字符在文本中出现的次数为{5,24,7,17,34,5,13},求这些字符的哈夫曼编码。

                               

         仔细看上图,发现树的深度不一定是n(节点数),内节点个数是(n-1)。右子树的频率为17的内节点并没有和字符节点的D(频率17)合并,先和G(频率13)合并了(用的是内节点频率为17的,不是字符节点,数据结构课本上也是这么搞的),然后左子树的D、B合并。

        为什么能压缩?压缩的时候当我们遇到了文本中的1 、 2 、 3 、 4 几个字符的时候,我们不用原来的存储,而是转化为用它们的 01 串来存储不久是能减小了空间占用了吗。(什么 01 串不是比原来的字符还多了吗?怎么减少?)大家应该知道的,计算机中我们存储一个 int 型数据的时候一般式占用了32 个 01 位,因为计算机中所有的数据都是最后转化为二进制位去存储的。所以,想想我们的编码不就是只含有 0 和 1 嘛,因此我们就直接将编码按照计算机的存储规则用位的方法写入进去就能实现压缩了。比如:1这个数字,用整数写进计算机硬盘去存储,占用了32个二进制位, 而如果用它的哈弗曼编码去存储,只有几个二进制位,效果显而易见。不过笔者认为这是采用字节流,那为什么我用字符流也能实现压缩呢?因为写入的时候都是一个字节,原来可能1、2、4字节。

二.Java实现


import java.awt.Container;

import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;

public class HuffmanMain extends javax.swing.JFrame{

	static javax.swing.JTextArea textarea=null;

	public static void main(String[] args) {
		new HuffmanMain().display();
	}

	public void display(){
		this.setSize(400,400);
		this.setTitle("火星十一郎解压压缩软件");
		this.setLayout(new java.awt.FlowLayout());
		//添加菜单栏
		this.setJMenuBar(getMenu());
		this.setVisible(true);
	}

	public JMenuBar getMenu(){
		//菜单栏、菜单项、菜单条目
		JMenuBar mb = new JMenuBar();

		//菜单项
		JMenu file = new JMenu("File");
		JMenu contact = new JMenu("Contact");

		//菜单条目
		JMenuItem Code = new JMenuItem("Code");
		JMenuItem Decode = new JMenuItem("Decode");
		JMenuItem exit = new JMenuItem ("Exit");
		JMenuItem qq = new JMenuItem ("About");

		//加入文件菜单项中
		file.add(Code);
		file.addSeparator();
		file.add(Decode);
		//在这加了个分割线
		file.addSeparator();
		file.add(exit);
		contact.add(qq);

		//添加菜单事件
		Action action = new Action();
		Code.addActionListener(action);
		Decode.addActionListener(action);
		exit.addActionListener(action);
		qq.addActionListener(action);

		//放上去
		mb.add(file);
		mb.add(contact);
		return mb;
	}
}

class Node implements Comparable<Node>{

   public char data;
   public int times;
   public Node lchild;
   public Node rchild;

   public Node(char data,int times){
	   this.data=data;
	   this.times=times;
   }

	public int getData() {
		return data;
	}

	public void setData(char data) {
		this.data = data;
	}

	public int getTimes() {
		return times;
	}

	public void setTimes(int times) {
		this.times = times;
	}

	public Node getLchild() {
		return lchild;
	}

	public void setLchild(Node lchild) {
		this.lchild = lchild;
	}

	public Node getRchild() {
		return rchild;
	}

	public void setRchild(Node rchild) {
		this.rchild = rchild;
	}

	public int compareTo(Node o) {
		Node other = o;
		int temp = times-other.times;
		if(temp>0){
			return 1;
		}else {
			return 0;
		}
	}
}

---------------------------------------------------------------------------------------------------------


import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileNameExtensionFilter;

public class Action implements ActionListener {

	public void actionPerformed(ActionEvent e) {
		String command = e.getActionCommand();
		//3个if是并列的,因为可能多次压缩、解压
		if ("Code".equals(command)) {

			System.out.println("进行压缩");
			// 文件选择
			JFileChooser Chooser = new JFileChooser();
			int t = Chooser.showOpenDialog(null);// 弹出文件选择框

			if (t == Chooser.APPROVE_OPTION) {// 如果点击的是确定
				// 得到文件的绝对路径
				String path = Chooser.getSelectedFile().getAbsolutePath();
				HuffmanCode code = new HuffmanCode(path);
				code.readFile();// 读取文件
				code.writeFile();// 写出压缩文件
			}
		}
		if ("Decode".equals(command)) {

			System.out.println("解压缩");
			// 显示打开的窗口
			JFileChooser chooser = new JFileChooser();
			//参数:文件描述(显示的)和文件类型
			FileNameExtensionFilter filter = new FileNameExtensionFilter(
					"hxsyl压缩文件", "hxsyl");
			chooser.setFileFilter(filter);
			int returnVal = chooser.showOpenDialog(null);
			if (returnVal == chooser.APPROVE_OPTION) {
				String path = chooser.getSelectedFile().getAbsolutePath();// 得到压缩文件的路径
				HuffmanDecode decode = new HuffmanDecode(path);
				decode.decode();// 解压缩文件
			}
		}
		if ("Exit".equals(command)) {

			System.exit(0);
		}
		if ("About".equals(command)) {

			JOptionPane.showConfirmDialog(null, "By 791909235@qq.com", "作者",
					JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE);
		}
	}
}

---------------------------------------------------------------------------------------


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Scanner;

public class HuffmanCode {

	private String path;// 文件的输入绝对路径
	Node root = null;// 定义的根节点
	private Map<Character,Integer> mm = new Hashtable<Character, Integer>();

	//大小:大小写字母、数字、汉字,就开100吧
	private Map<Character,String> mapCode = new Hashtable<Character, String>();

	public HuffmanCode() {

	}

	public HuffmanCode(String path) {

		//频率清零 初始化
		this.path = path;
		mapCode.clear();
	}

	//我想起了单例模式,才想起这种方法来在另一个java文件里因为一个java文件里的成员变量
	//注意不是方法,否则直接new 类调用就好
	public Map<Character,String> returnMap() {
		return this.mapCode;
	}
	public void readFile() {
		try {
			System.out.println("---------------读入文件--------------");
			//把“\”换成“/”,实际上没必要
			path.replaceAll("\\\\", "/");
			FileReader fr = new FileReader(path);
			BufferedReader br = new BufferedReader(fr);//有readline
			String str = "";
			mm.clear();
			while ((str=br.readLine())!=null) {
				System.out.println("读入了:"+str);
				char[] ch = str.toCharArray();
				for(int i=0; i<ch.length; i++) {
					//加上if判断就可以防止NullPointer异常了
					if(mm.get(ch[i])!=null) {
						mm.put(ch[i], mm.get(ch[i])+1);
					}else {
						mm.put(ch[i], 1);
					}
				}
			}
			System.out.println("---------文件读入结束-------------");
			// 构建哈弗曼树
			createHfmTree();
			// 递归生成编码,root是成员函数
			genenateHFMCode(root, "");
			br.close();
			fr.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void writeFile() {
		try {
			System.out.println("------------写入文件---------------");
			path.replaceAll("\\\\", "/");
			FileReader fr = new FileReader(path);
			BufferedReader br = new BufferedReader(fr);//有readline
			//第二个参数true表示追加
			BufferedWriter bw = new BufferedWriter(new FileWriter(path + ".hxsyl",true));
			String str = "";

			while ((str=br.readLine())!=null) {
				char[] ch = str.toCharArray();
				for(int i=0; i<ch.length; i++) {
					bw.write(mapCode.get(ch[i]));
				}
			}
			//刷新该流的缓冲,br没有该方法
			bw.flush();
			System.out.println("------------文件写入完毕---------------");
		} catch (Exception ef) {
			ef.printStackTrace();
		}
	}

	//广搜建树
	public void createHfmTree() {

		//里面是Node
		PriorityQueue<Node> nodeQueue = new PriorityQueue<Node>();
		// 把所有的节点都加入到 队列里面去
		Iterator<Character> iter = mm.keySet().iterator();
		while(iter.hasNext()) {
			char key = iter.next();
			if (mm.get(key)!= 0) {//实际上这个判断没必要,因为Map里没的都是null
				Node node = new Node(key, mm.get(key));
				nodeQueue.add(node);// 加入节点
			}
		}
		//不是不为空,最后是一个节点
		while (nodeQueue.size() > 1) {
			Node min1 = nodeQueue.poll();
			Node min2 = nodeQueue.poll();
			/*
			 * data搞为‘$’,说明是内节点
			 * 权值和就是data值为$的节点的times值之和
			 * 使用优先队列算权值就是基于此
			 */
			Node result = new Node('$', min1.times + min2.times);
			result.lchild = min1;
			result.rchild = min2;
			nodeQueue.add(result);
		}
		root = nodeQueue.peek(); // 得到根节点
	}

	//递归生成Hfm编码
	public void genenateHFMCode(Node root, String s) {

		if (null == root) {
			return ;
		}
		//hfm节点全部是叶子节点
		if ((root.lchild == null) && (root.rchild == null)) {
			//root是Node
			System.out.println("节点" + root.data + "编码" + s);
			mapCode.put(root.data,s);
		}
		if (root.lchild != null) {// 左0 右 1
			genenateHFMCode(root.lchild, s + '0');
		}
		if (root.rchild != null) {
			genenateHFMCode(root.rchild, s + '1');
		}
	}
}

----------------------------------------------------------------------------------------------


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/*
 * 为什么输出汉子提示呢?
 * 因为实际上需要保存在日志里的.
 *
 * 解码需要全部读入文件,不能一行一行,因为
 */
public class HuffmanDecode {

	private String path;
	Map<Character,String> mapCode = new Hashtable<Character, String>();

	public HuffmanDecode(String path) {
		this.path = path;
		//想了好久想到的方法,哈哈
		this.mapCode = new HuffmanCode().returnMap();
	}

	//通过
	private static char valueGetKey(Map<Character,String> map,StringBuffer value) {
	    Set set = map.entrySet();
	    Iterator it = set.iterator();
	    char ch='$';//表示没有找到,实际没必要,因为是哈夫曼编码是单射
	    while(it.hasNext()) {
	      Map.Entry entry = (Map.Entry)it.next();
	      if(entry.getValue().equals(value)) {
	    	  //转换为char报错
	        ch = (Character)entry.getKey();
	        return ch;
	      }
	    }
	    //加这个是为了不让编译器报错,原因如上
	    return ch;
	  }

	//解压缩
	public void decode() {
		try {
			path.replaceAll("\\\\", "/");
			FileReader fr = new FileReader(path);
			BufferedReader br = new BufferedReader(fr);//有readline
			//第二个参数true表示追加
			int index = path.lastIndexOf(".hxsyl");
			path = path.substring(0,index);
			BufferedWriter bw = new BufferedWriter(new FileWriter(path,true));
			StringBuffer sb = new StringBuffer("");
			String str = "";

			while ((str=br.readLine())!=null) {
				sb.append(str);
			}
			//刷新该流的缓冲,br没有该方法
			bw.flush();
			System.out.println(sb);
			System.out.println("------------解码文件---------------");
			StringBuffer waitSB = new StringBuffer("");
			for(int i=0; i<sb.length(); i++) {
				if(mapCode.containsValue(waitSB)) {
					char ch = valueGetKey(mapCode, waitSB);
					bw.write(ch);
					waitSB.delete(0, waitSB.length());
					if(null==waitSB) {
						break;
					}

				}else {
					waitSB.append(sb.charAt(i));
				}
			}
			System.out.println("------------解码完毕---------------");

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

        运行界面如下:

三.结束语

         处理IO过程中我发现以Reader结尾的都是read单个字符,以stream结尾的都是read字节。如何从现有字符读入呢?可以用StringReader或者System.in(str)。

        压缩也能实现信息隐藏,把压缩文件命名为常见格式比如txt,只有有该解压软件的才能打开。自己写的,你懂得,bug在所难免,若您发现,还请告知,咱们共同进步。

       参考文献:http://www.cppblog.com/biao/archive/2010/12/04/135457.html

时间: 2024-10-24 19:19:58

自己动手写压缩软件的相关文章

用C#写一个压缩软件

问题描述 本人西电大三,工程设计想写一个压缩软件,因为刚学过信源编码,对哈弗曼编码,LZ编码,算术编码都有了解.觉得现在主流软件压缩率太低了,想稍作改进,至少对于一些特定的文件可以更高的压缩率,比如用游程编码模式替换压缩代码等,想问问大家有什么好的建议没,因为又没有头绪了...还有觉得C#做图形界面比较方便才用的,而且C#自己好像也有压缩方法可以直接调用 解决方案 解决方案二:压缩率低点不算问题,流行才是关键,如果楼主的压缩只支持自己的格式肯定是不行的.解决方案三:可以参考WinRAR.这个产品

动手写个小组件(组件入门)(1)

动手写个小组件(组件入门) 这篇文章主要是为想将自己的ASP水平提高的人写的! 把ASP代码变成组件,开发者不仅是加快了ASP的速度,而且也能保护自己的代码.这篇文章写出来,也是为了给想开发组件网友上一堂入门课! 下面,我们会来编写一个非常简单的组件,重点是知道怎样开发DLL组件,而不是其复杂的代码!这些都要靠你们自己以后的努力了. 服务器端组件 首先,服务器端的组件要有别于客户端的组件.客户端的组件是通过网络传输,依靠HTML来起作用.而且只能在IE上有用.但是服务器端的组件是运行在服务器端,

分享一个自己动手写的jQuery分页插件_jquery

工作需要一个JS分页插件,心想自己动手写一个吧,一来上网找一个不清楚代码结构的,出了问题难以解决,而且网上的插件所包含的功能太多,有些根本用不到,就没必要加载那段JS,二来想起没写过jQuery插件,就当练一下手了,好了,先看结果: http://demo.jb51.net/js/2014/EasyPage/  简单说一下这个插件所要实现的功能  后台将查询出来的内容全部显示到页面上,这个插件要控制这些内容,使其一页一页显示.有上一页,下一页,首页,尾页的功能.在第一页时,上一页,首页要隐藏.在

压缩软件进化论:从诞生说起

中介交易 SEO诊断 淘宝客 云主机 技术大厅 回顾到DOS年代的时候,1984年的个人计算机标配是容量360kB 的 5.25 寸软盘.计算机存储介质容量之微小价格之昂贵和今时今日完全不能同日而语.当时,数据如果能进行压缩之后储存,在空间上和金钱上都是极大的节省. PKZIP .LHA和ARJ作为当时在DOS系统下的压缩软件基本三分天下,特别是第一个实现分卷压缩的ARJ压缩软件,对以软盘为主要存储方式的彼时意义不可小觑.然而1995年Win95诞生,PC真正开始普及,压缩软件市场也开始有了微妙

自己动手写ASP.NET ORM框架(二):AdoHelper支持多数据库操作的封装(2)

在上一篇文章中已经分析了AdoHelper的部分代码,接下来将继续分析剩余的部分代码,这里分析ExecuteNonQuery方法的实现,代码块1-1: // <summary>//通过提供的参数,执行无结果集的数据库操作命令// 并返回执行数据库操作所影响的行数.// </summary>// <param name="connectionString">数据库连接字符串</param>// <param name="co

好压压缩软件需要注意的使用细节

熟悉好压的朋友应该都对这款软件不陌生吧,它是一款小巧实用的国产免费压缩软件,不管从用户界面.软件功能或者是实用工具方便,都不输给任何一款国外压缩软件. 您可别小看了解压缩软件哦,它已经成了我们的装机必备.如何才能更好更炫的玩转这样一款装机必备的压缩软件呢?下面,我就大家介绍几条好压使用的小技巧. 一:如何设置所有的压缩包都解压到指定目录 电脑上的压缩包多如牛毛,每个压缩包解压后都会产生一个文件夹,何不让将这些压缩包都统一解压到一个目录下呢?其实这很容易,只要你打开好压主程序--工具--设置--"

性能-一款手写笔记软件的实现方式及问题

问题描述 一款手写笔记软件的实现方式及问题 要实现安卓设备上的手写功能,一种实现方式是:利用安卓的手势识别,主要是GestureOverlayView这个类里的功能和方法,记录用户触摸和移动的轨迹,形成路径path.路径path实际上就是一些点的集合.将这些路径组合成的笔画形成一个bitmap.写入到一个重写过的edittext中,即实现了手写笔记软件的主要功能. 上面实现的手写内容当文字数量比较多时,比如达到1000字左右,就会出现一些性能的问题.比如,想在笔记的中间插入一个手写字,实际执行的

win8系统电脑如何使用自带的压缩软件功能

  win8系统电脑如何使用自带的压缩软件功能 1.首先是压缩文件,我们经常会把一类有关联的文件同意整理到一起,这时再打个包就能便于管理而且体积也会变小.选中我们想要打包的文件,也可以是多个文件,然后点击鼠标右键; 2.这时候将鼠标移动到"发送到"上,在延伸菜单中我们就可以看到"压缩(zipped)文件夹"了,点击它之后,刚刚选中的文件就都被打包成一个新的压缩包了; 3.创建压缩包 如果我们双击这个压缩包,系统就会使用文件管理器来打开,我们就可以看到压缩包内包含的这

压缩软件日常操作的妙用技巧

提到好压压缩软件想必大家都不会陌生,除了压缩.解压缩之外,好压还提供了众多的实用工具和实用功能,下面介绍三则好压压缩软件在日常电脑操作中的妙用. 1.快速提取07版Word/Powerpoint/Excel中的图片 在2007版的Office中,用户不再能够像2003版一样直接把文档存储为Web格式提取图片.以07版的Word文档为例,07版的Word文档比03版的体积更小,实质上是一个压缩包,右键重命名,可以直接把扩展名改为zip,回车确定. 改名之后的Zip文件是可以用好压打开的,在文档名/