- 浏览: 141677 次
- 性别:
- 来自: 南京
-
文章分类
最新评论
-
tonytony3:
客户端弃用nio了?
NIO学习笔记3(UDP) -
love_chengjiang:
都是这句话惹的祸:“如果已经引入jquery-ui包则jque ...
jquery.fileupload插件的简易使用日志 -
yixiandave:
rjw 写道楼主,你这么写,能跑起吗?只能选择文件,不能提交啊 ...
jquery.fileupload插件的简易使用日志 -
rjw:
楼主,你这么写,能跑起吗?只能选择文件,不能提交啊。 求教了。 ...
jquery.fileupload插件的简易使用日志 -
scyllor:
http://www.oschina.net/code/sni ...
利用Spring HandlerInterceptor和RequestMapping注解进行权限访问控制
认识哈夫曼树之前首先我们简单的来了解一下二叉树,不难理解,二叉树就是每个节点都只有2个子节点的树状结构,也就分为父节点(parent node)、左子树(left child)和右子树(right child)。每个节点最多只能有2个节点,当然也可以更少,比如只有左节点没有右节点或者相反,没有子节点的节点我们称之为叶节点,有子树的称之为根节点。
二叉树相关就先介绍到这里,我们还是回到主题。首先,哈夫曼树就是一种特殊的二叉树,也是一种加权二叉树。首先,我们对我们需要储存到二叉树的数据赋予权重,权比较高的放在浅层,权重低的放在深层,并且所有的数据都储存在叶节点。接下来的问题就是如何构造这个哈夫曼树了。
举个例子:我们要储存的数据及其权重分别为:a:1|b:4|c:4|d:2|e:7|f:3,下面将通过例子来讲解哈夫曼树的构造方法
首先,将数据按权重排序
a | d | f | b | c | e
1 | 2 | 3 | 4 | 4 | 7
我们先将权最低的2个节点组成一个二叉树,根节点为A,没有数据,但权重为2个子节点的和,并将他们的根节点放入列表重新排序:(注意例中的大写字母没有实际意义,只是代号而已,最后一步我们会把打些字母擦除的)
重复上一步骤,取新的根节点为B
这个哈夫曼树我们就算完成了,最后将无关的东西去掉:
大功告成!
哈夫曼树的意义就在于可以对其中的数据重新编码,编码方式很简单,左树为0右树为1,每下一层加一个数字,以上图中的例子来说,abcdef对应编码就是:
|a|1000
|b|1001
|c|111
|d|110
|e|0
|f|101
大家不难发现,这几个编码没有任何一个是另一个编码的前置。这是由哈夫曼树的特性决定的,哈夫曼树所有的数据都在页节点,如果出现一个数据是另一个数据编码的前置,这个数据必然在另一个数据的路径前,也就是在根节点,然而这是不可能的,这也确保了解码的唯一性,避免了歧义的出现,就拿刚才的例子来说,11111001011001的解码只可能是cdefb而不会是别的。
对于程序来说,取得这个编码就牵扯到一个二叉树遍历的问题了,通常来说,遍历一个二叉树可以用递归方式解决,这里举个简单的例子:
当然也有其他方法,这里就不赘述了。
哈夫曼树大家应该基本了解了,现在我们就需要应用它做点事了,就是今天的另一个主题:压缩算法。哈夫曼树压缩算法的原理就是对所有的byte进行重新编码,由于哈夫曼树生成的编码是长短不一的。我们可以把出现频率高的byte放在哈夫曼树的上层,出现频率低的放在下层,缩短出现频率高的byte的编码,延长出现频率低的byte的编码,替换掉固定的8位二进制编码,达到压缩的目的。所以一个所有byte完全平均的文件经过哈夫曼树压缩算法后大小不会有变化,然而这个可能性很低,通常一篇文章中总有一些字词出现频率很高,在文件中也是一样,有些byte出现频率也比较高,这种情况越不平均,哈夫曼树压缩算法的压缩效率越高。
通过以上原理,我们就需要在对文件压缩前对文件中的所有byte做一个统计,把该byte出现的次数作为它的权,然后生成哈夫曼树,然后遍历生成编码。现在又出现一个新的问题了,我们在之前的例子中生成的编码是用字符串保存的,如何将其转换为二进制位就成了一个新的问题,因为java中最小的单位是byte,怎样把一个byte分解为单二进制位就是一个瓶颈了。当然,方法很多~~如下就是一个方法,由于java没有保存bit的单位,我们暂且用byte数组表示
然而这还不够,因为哈夫曼树组生成的编码是不等长的,而一个byte必须是8个bit组成,所以需要拼接,将不足8位的用下一个byte的编码补充完整并截断,如若a的编码是01101,b的编码是0011,那么ab组成的byte就应该是01101001 1.......,后面的则由下一位补齐。将二进制位转成二进制不算太难,如二进制位运算、二进制四则运算都可以完成。
举个例子,我们假设byte[] changeByte(byte oBt)方法就是将原byte转换为用byte[]表示的二进制位,注意:此方法不完善!
相信有些人又发现问题了,最后一个byte编完的时候很可能出现没有补满的情况,那么最后一位怎么填满就成了第二个比较重要的问题了,弄不好就会出现解压时候多了个byte的情况,当然解决的方法也有很多,可以在最前面添加一个参数表示最后一位的空位数然后填满,也可以填入一个编码表中不存在的编码,我采用的是后者,可以节省1个byte的说明。
这样下来,一个文件算是压缩完了,然而不能只压缩不解压,要解压的话,我们需要把编码表存入文件。由于文件长度未知,一般可以保存在文件头部。当然,编码表和正文部分需要用标记来区分开的,我自己是计算出编码表长度在开头用个int值注明,然后读取int个字节作为编码表,后面的就是正文了。当然还有别的方法,就看各位看官自己探索了~~~
下面就是解压了,由于哈夫曼树生成的编码长度的不同,压缩过程中我们需要把8个bit转换为一个byte,解压过程就会需要把一个byte拆分为8个bit,方法同样有很多,我这里提供一种方法:
特别注意的是,将bit[]合并为byte和将byte拆分为bit[]的过程千万不要将byte的高低位弄颠倒!我就犯过这个错误。。找了半天才找到问题。有了这个方法,我们就可以做出解压动作了,将文件中每个byte依次读出(前面的编码表不要再读进来了),将byte拆分为bit[],然后一位一位的对照,就是先取bit[]第一位,查看编码表,没有再取前2位对照,如此反复,找到对应的就储存下来,一个byte读完了读下一个,一直到结束,这个文件就被解压完成了。
后面的扩展就靠各位看官了,比如如何提高压缩/解压效率、压缩多个文件如何实现等,另外,如果修改下这个算法,各byte加权不是由出现次数来定而是事先约定,压缩的时候不将编码表存入文件,那么我们做出来的就是一个加密程序~~
***************************我是华丽的分割线**************************
大家好~~今天刚刚实现了多线程双缓冲完成压缩文件的程序,不过还是只能压缩单个文件,只是速度较以前有了较大提升。这里把我的一点心得发出来吧。
首先谢谢之前朋友的支持,也欢迎更多的朋友来交流和指正。
闲话不谈了,开始正题。前面的内容我们已经比较完善的讲述了用哈夫曼完成一个压缩程序,但我们的操作基本是读取一个字节→转换→写入→读取下一个字节这个流程,由于读取和写入操作耗时教多,效率有限,现在我们将流程修改一下:
读取一个字节→转换→存入队列1→读取下一个字节
↓(当队列1足够满了就将数据转到队列2)
将队列2的数据取出→写入文件→取出队列2中下一组数据
分2个线程同时运行,效率就可以提高很多。这就是双缓冲的方法。
下面,我们需要更深入理解何为双缓冲。这就牵扯到生成-销售模型了。我们可以理解为两个线程,一端不断的在产生数据(也就是生产方生产出产品),另一端不断在消耗这些数据(也就是消费掉这些生产的产品),首先明确的一点是我们不能保证生产出的数据能立刻被消费端消耗掉,也就是两端不是同步的。因此,我们需要在中间建立一个仓库来储存被生产出来而没被消费掉的数据,这里用的就是队列了。生产-消费模型本质上还是一个数据传递方法,因此,顺序是被严格要求的,也就是先生产的数据必须先被消费掉。而队列正好胜任这个职责。
这里先举一个例子:
线程1:
线程2:
以上只是个简单的例子。值得注意的是使用了synchronized()关键字,该方法就是将一个对象锁定不让其他线程读取,主要作用就是防止多线程同时修改一个对象的时候导致错误结果。就如同两个线程之间的仓库有2个门,一个线程进去后放东西放了一半另一个线程进来把东西又取走了,如果第一个线程还需要用到刚刚放进来的东西就会出错。synchronized可以有效避免这种情况的发生。需要注意:synchronized必须和notify()或notifyAll()同时使用
以上的例子就是单缓冲队列的例子,生产方生产数据,存入仓库,然后消费方再进去把需要取出的数据取出来。就解决了两端不同步的问题,当然,如果只是这样的话,当仓库没有数据时消费端是一个没有任何操作的while(true){}动作,将会造成极大的CPU消耗,我们可以在if后加入else{try{Thread.sleep(10);}catch(Exception e){}}来降低系统消耗。
单缓冲模型中,生产端和消费端之间只有一个仓库,还是那种只有一个门的,每次都是一个人进去反锁,处理完了再出来,另一个人只有等没锁的时候才能进去,很多时间就浪费在等待上面了。为了改善这一现象,又出现了双缓冲模型,原理就是在生产端和消费端建立两座仓库,生产端只管将生产的数据放入生产端仓库,消费端只管从消费端仓库里取出数据,互不影响了,自然快很多。我们只需要在生产端仓库满的时候或者消费端仓库空掉的时候将生产端仓库的数据一起运到消费端仓库就可以了,两种方法视情况而定二选一,虽然还是需要锁门,但锁门的时间大大减少了,时间就节省出来了。举例代码如下,假定list1和list2是2个仓库:
线程一:
线程2:
一次取出一批的消费端方法
想查看结果的话可以将Object修改,然后读出数据后输出就可以了。这个例子中由于生产端是无限生产的,不需要考虑剩余数据的问题,如果你的生产端生产的数据是有限的,一定记住最后将没有满的仓库中数据全部转入消费端仓库!
双缓冲就先说到这里了,看到这里,用双缓冲完成压缩/解压的意义大家应该明白了吧~~就是加快我们压缩/解压的速度。
前面已经大致介绍过了流程,比起前面的压缩文件,我们需要增加一个压缩线程,一个解压线程和一个写入文件线程,将原来ActionListener里的代码移出来,然后添加2个仓库队列用于储存需要写入的byte。就是List<Byte>啦~~~再把压缩/解压的输出(原来是直接写入文件)改成添加到list1,最后加个双缓冲的转移就可以了。中间出了个小问题,由于前4个byte是用来保存编码表长度的,将一个int转换为4个byte确实伤了一番脑筋,这里给出代码吧,最后还是在网上找的,另求解位运算符>>和>>>有啥区别:
当然可以有别的方法,看各位大虾各显神通啦。此外还增加了进度条和百分比显示,不过总体来说还是比较简陋的。刚想到一种3线程的方法,一个线程读取数据,一个线程处理,一个线程写入,中间需要用到2个双缓冲,不知道效率如何,有兴趣的朋友也可以尝试下~。后面准备尝试添加多文件压缩了,不过目前还比较纠结,没有太好的方法~~
整个项目代码在下面已经给出来了,compressorII.rar那个,可能还有点小漏洞什么的,发现的朋友提醒下哈~~
最后,本人是湖南师大大二学生,QQ327667882,欢迎志同道合的朋友多多交流学习,加好友请注明:javaeye网友~~谢谢大家捧场
哦。。。知道了。。无符号移位。。。那我有一些代码又可以改了啊~~
这说明算法说明有问题
大写字母不是真正的单词........是合并后的指代名词
恩,多谢指正,你的意思就是我的意思,大写字母为指代名词,最后一步有处理的,这个例子真正储存的只有abcdef
这说明算法说明有问题
大写字母不是真正的单词........是合并后的指代名词
好文章!!!
这是文件压缩算法的其中之一,还有一种字典压缩算法,2者可以一起用,字典压缩算法我还没摸熟~~呵呵
这2种压缩算法同时使用后理论上文件就无法再次被压缩了
二叉树相关就先介绍到这里,我们还是回到主题。首先,哈夫曼树就是一种特殊的二叉树,也是一种加权二叉树。首先,我们对我们需要储存到二叉树的数据赋予权重,权比较高的放在浅层,权重低的放在深层,并且所有的数据都储存在叶节点。接下来的问题就是如何构造这个哈夫曼树了。
举个例子:我们要储存的数据及其权重分别为:a:1|b:4|c:4|d:2|e:7|f:3,下面将通过例子来讲解哈夫曼树的构造方法
首先,将数据按权重排序
a | d | f | b | c | e
1 | 2 | 3 | 4 | 4 | 7
我们先将权最低的2个节点组成一个二叉树,根节点为A,没有数据,但权重为2个子节点的和,并将他们的根节点放入列表重新排序:(注意例中的大写字母没有实际意义,只是代号而已,最后一步我们会把打些字母擦除的)
引用
A(3) f(3) d(4) c(4) e(7)
/ \
a(1) b(2)
/ \
a(1) b(2)
重复上一步骤,取新的根节点为B
引用
b(4) c(4) B(6) e(7)
/ \
A(3) f(3)
/ \
a(1) b(2)
/ \
A(3) f(3)
/ \
a(1) b(2)
引用
B(6) C(8) e(7)
/ \ / \
A(3) f(3) d(4) c(4)
/ \
a(1) b(2)
/ \ / \
A(3) f(3) d(4) c(4)
/ \
a(1) b(2)
引用
e(7) D(14)
/ \
B(6) C(8)
/ \ / \
A(3) f(3) d(4) c(4)
/ \
a(1) b(2)
/ \
B(6) C(8)
/ \ / \
A(3) f(3) d(4) c(4)
/ \
a(1) b(2)
引用
E(21)
/ \
e(7) D(14)
/ \
B(6) C(8)
/ \ / \
A(3) f(3) d(4) c(4)
/ \
a(1) b(2)
/ \
e(7) D(14)
/ \
B(6) C(8)
/ \ / \
A(3) f(3) d(4) c(4)
/ \
a(1) b(2)
这个哈夫曼树我们就算完成了,最后将无关的东西去掉:
引用
○
/ \
e ○
/ \
○ ○
/ \ / \
○ f d c
/ \
a b
/ \
e ○
/ \
○ ○
/ \ / \
○ f d c
/ \
a b
大功告成!
哈夫曼树的意义就在于可以对其中的数据重新编码,编码方式很简单,左树为0右树为1,每下一层加一个数字,以上图中的例子来说,abcdef对应编码就是:
|a|1000
|b|1001
|c|111
|d|110
|e|0
|f|101
大家不难发现,这几个编码没有任何一个是另一个编码的前置。这是由哈夫曼树的特性决定的,哈夫曼树所有的数据都在页节点,如果出现一个数据是另一个数据编码的前置,这个数据必然在另一个数据的路径前,也就是在根节点,然而这是不可能的,这也确保了解码的唯一性,避免了歧义的出现,就拿刚才的例子来说,11111001011001的解码只可能是cdefb而不会是别的。
对于程序来说,取得这个编码就牵扯到一个二叉树遍历的问题了,通常来说,遍历一个二叉树可以用递归方式解决,这里举个简单的例子:
//首先,我们假设节点类为Node,包含getLChild()和getRChild()两个方法 //分别取得左子节点和右子节点,然后一个char data属性储存值 //code为储存编码的字符串,Code类为编码类,包含data和code两个个属性 public void viewBTree(Node nd,String code,List<Code> cL){ Node leftNode = nd.getLChild();//取得左子树 Node rightNode = nd.getRChild();//取得右子树 if(leftNode!=null){ viewBTree(leftNode,code+”0”,cL);//递归,编码字符串加0 } if(rightNode!=null){ viewBTree(rightNode,code+”1”,cL);//递归遍历右子树 } if(leftNode==null && rightNode==null){//左右子树都为空,页节点 Code nCode = new Code(nd.data,code);//新建编码对象 cL.add(nCode);//存入队列 } }
当然也有其他方法,这里就不赘述了。
哈夫曼树大家应该基本了解了,现在我们就需要应用它做点事了,就是今天的另一个主题:压缩算法。哈夫曼树压缩算法的原理就是对所有的byte进行重新编码,由于哈夫曼树生成的编码是长短不一的。我们可以把出现频率高的byte放在哈夫曼树的上层,出现频率低的放在下层,缩短出现频率高的byte的编码,延长出现频率低的byte的编码,替换掉固定的8位二进制编码,达到压缩的目的。所以一个所有byte完全平均的文件经过哈夫曼树压缩算法后大小不会有变化,然而这个可能性很低,通常一篇文章中总有一些字词出现频率很高,在文件中也是一样,有些byte出现频率也比较高,这种情况越不平均,哈夫曼树压缩算法的压缩效率越高。
通过以上原理,我们就需要在对文件压缩前对文件中的所有byte做一个统计,把该byte出现的次数作为它的权,然后生成哈夫曼树,然后遍历生成编码。现在又出现一个新的问题了,我们在之前的例子中生成的编码是用字符串保存的,如何将其转换为二进制位就成了一个新的问题,因为java中最小的单位是byte,怎样把一个byte分解为单二进制位就是一个瓶颈了。当然,方法很多~~如下就是一个方法,由于java没有保存bit的单位,我们暂且用byte数组表示
/** *将String编码转换为二进制位,用byte[]表示 *@param:code: String 编码 *@return:用byte[]表示的二进制位代码 */ public byte[] getBit(String code){ byte[] bi = new byte[code.length]; for(int i=0;i<code.length;i++){//取出String中的每个字符 char ch = code.charAt(i); if(ch=='0'){ bi[i]=0; } if(ch=='1'){ bi[i]=1; } } return bi; }
然而这还不够,因为哈夫曼树组生成的编码是不等长的,而一个byte必须是8个bit组成,所以需要拼接,将不足8位的用下一个byte的编码补充完整并截断,如若a的编码是01101,b的编码是0011,那么ab组成的byte就应该是01101001 1.......,后面的则由下一位补齐。将二进制位转成二进制不算太难,如二进制位运算、二进制四则运算都可以完成。
举个例子,我们假设byte[] changeByte(byte oBt)方法就是将原byte转换为用byte[]表示的二进制位,注意:此方法不完善!
/** *将二进制位转换为byte方法 *@param:oBytes 组成源文件的byte[] *@return:转换后的Byte队列 */ public byte[] compressor(byte[] oBytes){ byte[] bits = new byte[8];//储存8位二进制位 List<Byte> bL = new ArrayList<Byte>();//转换后长度未知,先用list储存 int bitsLeft = 8;//储存当前bits中剩余位数 for(byte b:oBytes){ byte[] nBits = changeByte(b);//转码,得到二进制位 for(byte oneBit:nBits){ bits[8-bitsLeft]=oneBit;//将新二进制位加入到当前byte的二进制位中 bitsLeft--;//当前byte剩余位数-1 if(bitsLeft==0){//当当前byte填满的时候 try{ byte nByte = changeToByte(); bL.add(nByte); bitsLeft=8; }catch(Exception ef){} } } } } /** * 将8位二进制位转换为byte方法,此处用的是位运算 * @param:bits : 储存8个二进制位的byte[] * @return : 转换成的byte * throws:bits不是8个二进制位格式:不是8位或不止0和1 */ public byte changeToByte throws Exception(byte[] bits){ if(bits.length!=8){//排除非8位数组的异常 throw new Exception("不是8位!"); } byte b=0; for(int i = 0;i<8;i++){ if(bits[i]!=0 && bits[i]!=1){//排除值不为0或1的异常 throw new Exception("值不是0或1") } b=(byte)(b<<1);//左移1位 b+=bits[i]; } return b; }
相信有些人又发现问题了,最后一个byte编完的时候很可能出现没有补满的情况,那么最后一位怎么填满就成了第二个比较重要的问题了,弄不好就会出现解压时候多了个byte的情况,当然解决的方法也有很多,可以在最前面添加一个参数表示最后一位的空位数然后填满,也可以填入一个编码表中不存在的编码,我采用的是后者,可以节省1个byte的说明。
这样下来,一个文件算是压缩完了,然而不能只压缩不解压,要解压的话,我们需要把编码表存入文件。由于文件长度未知,一般可以保存在文件头部。当然,编码表和正文部分需要用标记来区分开的,我自己是计算出编码表长度在开头用个int值注明,然后读取int个字节作为编码表,后面的就是正文了。当然还有别的方法,就看各位看官自己探索了~~~
下面就是解压了,由于哈夫曼树生成的编码长度的不同,压缩过程中我们需要把8个bit转换为一个byte,解压过程就会需要把一个byte拆分为8个bit,方法同样有很多,我这里提供一种方法:
/** * 将一个byte的bit拆分方法 * @param b: 需要拆分的byte * @return:一个长度为8的byte[],每个元素储存一位bit */ public byte[] getBits(byte b){ byte[] bits = new byte[8];//定义用于储存bit的byte[] for(int i=0;i<8;i++){ byte t = (byte)(b<<i);//左移i位 t = (byte)(t>>7)//再右移7位,这样最低位就是从高位开始第一位了 t = (byte)(Math.abs(t));//避免负数的影响,主要是当该位为1时 bits[i] = t;//赋值 } return bits; }
特别注意的是,将bit[]合并为byte和将byte拆分为bit[]的过程千万不要将byte的高低位弄颠倒!我就犯过这个错误。。找了半天才找到问题。有了这个方法,我们就可以做出解压动作了,将文件中每个byte依次读出(前面的编码表不要再读进来了),将byte拆分为bit[],然后一位一位的对照,就是先取bit[]第一位,查看编码表,没有再取前2位对照,如此反复,找到对应的就储存下来,一个byte读完了读下一个,一直到结束,这个文件就被解压完成了。
后面的扩展就靠各位看官了,比如如何提高压缩/解压效率、压缩多个文件如何实现等,另外,如果修改下这个算法,各byte加权不是由出现次数来定而是事先约定,压缩的时候不将编码表存入文件,那么我们做出来的就是一个加密程序~~
***************************我是华丽的分割线**************************
大家好~~今天刚刚实现了多线程双缓冲完成压缩文件的程序,不过还是只能压缩单个文件,只是速度较以前有了较大提升。这里把我的一点心得发出来吧。
首先谢谢之前朋友的支持,也欢迎更多的朋友来交流和指正。
闲话不谈了,开始正题。前面的内容我们已经比较完善的讲述了用哈夫曼完成一个压缩程序,但我们的操作基本是读取一个字节→转换→写入→读取下一个字节这个流程,由于读取和写入操作耗时教多,效率有限,现在我们将流程修改一下:
读取一个字节→转换→存入队列1→读取下一个字节
↓(当队列1足够满了就将数据转到队列2)
将队列2的数据取出→写入文件→取出队列2中下一组数据
分2个线程同时运行,效率就可以提高很多。这就是双缓冲的方法。
下面,我们需要更深入理解何为双缓冲。这就牵扯到生成-销售模型了。我们可以理解为两个线程,一端不断的在产生数据(也就是生产方生产出产品),另一端不断在消耗这些数据(也就是消费掉这些生产的产品),首先明确的一点是我们不能保证生产出的数据能立刻被消费端消耗掉,也就是两端不是同步的。因此,我们需要在中间建立一个仓库来储存被生产出来而没被消费掉的数据,这里用的就是队列了。生产-消费模型本质上还是一个数据传递方法,因此,顺序是被严格要求的,也就是先生产的数据必须先被消费掉。而队列正好胜任这个职责。
这里先举一个例子:
线程1:
/** *生产端,将一个数据(暂定为Object)存入仓库 */ public void run(){ while(true){//不断重复这个动作 Object o = new Object(); synchronized(list){//synchronized关键字,将list对象锁定不让其他线程读取 list.add(o);//队列list为仓库队列 list.notifyAll();//唤醒所有线程,解除锁定 } } }
线程2:
/** *消费端,将一个数据从仓库取出 */ public void run(){ while(true){ if(list.size()>0){ synchronized(list){//将list对象锁定不让其他线程读取 Object o = list.remove(0);//读取队列中第一个元素并移除 list.notifyAll();//解除锁定状态 } } } }
以上只是个简单的例子。值得注意的是使用了synchronized()关键字,该方法就是将一个对象锁定不让其他线程读取,主要作用就是防止多线程同时修改一个对象的时候导致错误结果。就如同两个线程之间的仓库有2个门,一个线程进去后放东西放了一半另一个线程进来把东西又取走了,如果第一个线程还需要用到刚刚放进来的东西就会出错。synchronized可以有效避免这种情况的发生。需要注意:synchronized必须和notify()或notifyAll()同时使用
以上的例子就是单缓冲队列的例子,生产方生产数据,存入仓库,然后消费方再进去把需要取出的数据取出来。就解决了两端不同步的问题,当然,如果只是这样的话,当仓库没有数据时消费端是一个没有任何操作的while(true){}动作,将会造成极大的CPU消耗,我们可以在if后加入else{try{Thread.sleep(10);}catch(Exception e){}}来降低系统消耗。
单缓冲模型中,生产端和消费端之间只有一个仓库,还是那种只有一个门的,每次都是一个人进去反锁,处理完了再出来,另一个人只有等没锁的时候才能进去,很多时间就浪费在等待上面了。为了改善这一现象,又出现了双缓冲模型,原理就是在生产端和消费端建立两座仓库,生产端只管将生产的数据放入生产端仓库,消费端只管从消费端仓库里取出数据,互不影响了,自然快很多。我们只需要在生产端仓库满的时候或者消费端仓库空掉的时候将生产端仓库的数据一起运到消费端仓库就可以了,两种方法视情况而定二选一,虽然还是需要锁门,但锁门的时间大大减少了,时间就节省出来了。举例代码如下,假定list1和list2是2个仓库:
线程一:
public void run(){ while(true){ Object o = new Object(); list1.add(o);//先存入1号仓库 if(list1.size()>100){//1号仓库满了 synchronized(list1){//锁1号仓库门 synchronized(list2){//锁2号仓库门 list2.addAll(list1);//将1号仓库的数据全部运到2号仓库 list2.notifyAll();//2号仓库解锁 } list1.clear();//清空1号仓库 list1.notifyAll();//1号仓库解锁 } } try{ Thread.sleep(10);//不想机器太卡的话不妨加上这句 }catch(Exception e){} } }
线程2:
public void run(){ while(true){ if(list2.size()<1){ Thread.sleep(100);//仓库是空的就先睡一会吧 } synchronized(list2){ //取出仓库2中的数据,当然,也可以一次取出一批,这样效率更高 Object o = list2.remove(0); list2.notifyAll(); } } }
一次取出一批的消费端方法
public void run(){ while(true){ if(list2.size()<1){ Thread.sleep(100);//仓库是空的就先睡一会吧 } int size = list2.size()<100 ? list2.size() : 100; Object[] os = new Object[size]; synchronized(list2){ //批量取出仓库2中的数据 for(int i=0;i<size;i++){ os[i] = list2.remove(0); } list2.notifyAll(); } } }
想查看结果的话可以将Object修改,然后读出数据后输出就可以了。这个例子中由于生产端是无限生产的,不需要考虑剩余数据的问题,如果你的生产端生产的数据是有限的,一定记住最后将没有满的仓库中数据全部转入消费端仓库!
双缓冲就先说到这里了,看到这里,用双缓冲完成压缩/解压的意义大家应该明白了吧~~就是加快我们压缩/解压的速度。
前面已经大致介绍过了流程,比起前面的压缩文件,我们需要增加一个压缩线程,一个解压线程和一个写入文件线程,将原来ActionListener里的代码移出来,然后添加2个仓库队列用于储存需要写入的byte。就是List<Byte>啦~~~再把压缩/解压的输出(原来是直接写入文件)改成添加到list1,最后加个双缓冲的转移就可以了。中间出了个小问题,由于前4个byte是用来保存编码表长度的,将一个int转换为4个byte确实伤了一番脑筋,这里给出代码吧,最后还是在网上找的,另求解位运算符>>和>>>有啥区别:
/** * 将int值转换为长度为4的byte数组 * * @param num * 需要转换的int值 * @return 长度为4的byte[]数组 */ public byte[] getIntByte(int num) { byte[] b = new byte[4]; for (int i = 0; i < 4; i++) { b[i] = (byte) (num >> (24 - i * 8)); } return b; }
当然可以有别的方法,看各位大虾各显神通啦。此外还增加了进度条和百分比显示,不过总体来说还是比较简陋的。刚想到一种3线程的方法,一个线程读取数据,一个线程处理,一个线程写入,中间需要用到2个双缓冲,不知道效率如何,有兴趣的朋友也可以尝试下~。后面准备尝试添加多文件压缩了,不过目前还比较纠结,没有太好的方法~~
整个项目代码在下面已经给出来了,compressorII.rar那个,可能还有点小漏洞什么的,发现的朋友提醒下哈~~
最后,本人是湖南师大大二学生,QQ327667882,欢迎志同道合的朋友多多交流学习,加好友请注明:javaeye网友~~谢谢大家捧场
- compressor.rar (9.2 KB)
- 描述: 初步完成的压缩程序代码,仅供参考~~正在开发多线程和多个文件压缩,运行还没成功,就先不传上来了
- 下载次数: 233
- compressorII.rar (12.3 KB)
- 描述: 添加了多线程双缓冲,大家多多捧场哈
- 下载次数: 225
评论
19 楼
feipigzi
2010-11-21
将byte拆分为bit[],然后一位一位的对照,就是先取bit[]第一位,查看编码表,没有再取前2位对照,如此反复,找到对应的就储存下来,一个byte读完了读下一个,一直到结束,这个文件就被解压完成了。
这个方法我试过了,实际效率不如下面方法
按照编码表,构造一个二叉树(每个节点为0或1),根据读入的二进流,寻找一条从根(根不含0或1)到叶子节点的路径
这个方法我试过了,实际效率不如下面方法
按照编码表,构造一个二叉树(每个节点为0或1),根据读入的二进流,寻找一条从根(根不含0或1)到叶子节点的路径
18 楼
feipigzi
2010-11-21
我记得io包有个字节缓冲流
17 楼
bluepeer
2010-02-28
真的不错,这个很实用,不只在加密和文件处理。实际应用也应用可以用到。
16 楼
yixiandave
2010-02-26
Yaw.Eno 写道
http://blog.csdn.net/dingxy/archive/2009/04/30/4140149.aspx
给你参考哈,>>与>>>的区别……
师大大三……NetJava。
哈哈……
给你参考哈,>>与>>>的区别……
师大大三……NetJava。
哈哈……
哦。。。知道了。。无符号移位。。。那我有一些代码又可以改了啊~~
15 楼
Yaw.Eno
2010-02-26
http://blog.csdn.net/dingxy/archive/2009/04/30/4140149.aspx
给你参考哈,>>与>>>的区别……
师大大三……NetJava。
哈哈……
给你参考哈,>>与>>>的区别……
师大大三……NetJava。
哈哈……
14 楼
yixiandave
2010-02-26
恩恩,刚刚更新了下,把双缓冲也加进来了~~大家多多捧场啊,有错误请多多指正
同时欢迎交流学习QQ:327667882,加好友的话注明一下javaeye哈~~~否则不知道是谁
嘿嘿
同时欢迎交流学习QQ:327667882,加好友的话注明一下javaeye哈~~~否则不知道是谁
嘿嘿
13 楼
sxjkk
2010-02-26
好东西啊,不错
现在论坛中讲的都是高端应用,很少有讲数据结构和底层的算法了
现在论坛中讲的都是高端应用,很少有讲数据结构和底层的算法了
12 楼
yixiandave
2010-02-26
抛出异常的爱 写道
yixiandave 写道
|b:4|c:4|d:2|e:7|f:3,下面将通过例子来讲解哈夫曼树的构造方法
B(6)和C(8)之前已经成为啦D(14)的左右子树了,排序只排最顶端的,所以队列里应该只剩下e(7)和D(14)了
superheizai 写道
e(7) D(14)
/ \
B(6) C(8)
/ \ / \
A(3) f(3) d(4) c(4)
/ \
a(1) b(2)
这里是不是应该B(6)和e(7)?
/ \
B(6) C(8)
/ \ / \
A(3) f(3) d(4) c(4)
/ \
a(1) b(2)
这里是不是应该B(6)和e(7)?
B(6)和C(8)之前已经成为啦D(14)的左右子树了,排序只排最顶端的,所以队列里应该只剩下e(7)和D(14)了
这说明算法说明有问题
大写字母不是真正的单词........是合并后的指代名词
e(7) abfdc(14) / \ abf(6) dc(8) / \ / \ ab(3) f(3) d(4) c(4) / \ a(1) b(2)
恩,多谢指正,你的意思就是我的意思,大写字母为指代名词,最后一步有处理的,这个例子真正储存的只有abcdef
11 楼
抛出异常的爱
2010-02-26
yixiandave 写道
|b:4|c:4|d:2|e:7|f:3,下面将通过例子来讲解哈夫曼树的构造方法
B(6)和C(8)之前已经成为啦D(14)的左右子树了,排序只排最顶端的,所以队列里应该只剩下e(7)和D(14)了
superheizai 写道
e(7) D(14)
/ \
B(6) C(8)
/ \ / \
A(3) f(3) d(4) c(4)
/ \
a(1) b(2)
这里是不是应该B(6)和e(7)?
/ \
B(6) C(8)
/ \ / \
A(3) f(3) d(4) c(4)
/ \
a(1) b(2)
这里是不是应该B(6)和e(7)?
B(6)和C(8)之前已经成为啦D(14)的左右子树了,排序只排最顶端的,所以队列里应该只剩下e(7)和D(14)了
这说明算法说明有问题
大写字母不是真正的单词........是合并后的指代名词
e(7) abfdc(14) / \ abf(6) dc(8) / \ / \ ab(3) f(3) d(4) c(4) / \ a(1) b(2)
10 楼
yixiandave
2010-02-26
|b:4|c:4|d:2|e:7|f:3,下面将通过例子来讲解哈夫曼树的构造方法
B(6)和C(8)之前已经成为啦D(14)的左右子树了,排序只排最顶端的,所以队列里应该只剩下e(7)和D(14)了
superheizai 写道
e(7) D(14)
/ \
B(6) C(8)
/ \ / \
A(3) f(3) d(4) c(4)
/ \
a(1) b(2)
这里是不是应该B(6)和e(7)?
/ \
B(6) C(8)
/ \ / \
A(3) f(3) d(4) c(4)
/ \
a(1) b(2)
这里是不是应该B(6)和e(7)?
B(6)和C(8)之前已经成为啦D(14)的左右子树了,排序只排最顶端的,所以队列里应该只剩下e(7)和D(14)了
9 楼
likeblood
2010-02-25
请lz把哈弗曼树的简介里的笔误修改一下
支持原创
支持原创
8 楼
lioncin
2010-02-24
数字影像编码不就是基于这个东西的吗(原谅我大学的时候,上数据结构和多媒体的时候没用心)
7 楼
superheizai
2010-02-24
e(7) D(14)
/ \
B(6) C(8)
/ \ / \
A(3) f(3) d(4) c(4)
/ \
a(1) b(2)
这里是不是应该B(6)和e(7)?
/ \
B(6) C(8)
/ \ / \
A(3) f(3) d(4) c(4)
/ \
a(1) b(2)
这里是不是应该B(6)和e(7)?
6 楼
hommy8
2010-02-24
zli.ray 写道
lz讲的很好呀,理论与实践相结合,受教。



好文章!!!
5 楼
zli.ray
2010-02-24
lz讲的很好呀,理论与实践相结合,受教。
4 楼
achenbj
2010-02-24
很强大,学习!
3 楼
fengzl
2010-02-24
受教了,收藏
2 楼
yixiandave
2010-02-23
lyy3323 写道
文件压缩算法?
这是文件压缩算法的其中之一,还有一种字典压缩算法,2者可以一起用,字典压缩算法我还没摸熟~~呵呵
这2种压缩算法同时使用后理论上文件就无法再次被压缩了
1 楼
lyy3323
2010-02-23
文件压缩算法?
发表评论
-
java生成zip压缩文件
2013-10-21 11:40 3993jdk自身有zip相关的代码,不过直到1.6的版本没有提供设置 ... -
POI学习笔记,Excel解析实现
2013-09-03 11:07 5320最近要完成一个根据模板将Excel数据批量导入数据库的业务 本 ... -
Spring使用session,request,global session几种scope的方法
2013-08-14 10:04 1437要在web.xml中加入一个新的listener ... -
java调用su权限执行命令方式
2013-09-03 10:29 2593String[] cmd = {"/bash/bin ... -
使用Maven打包Runnable jar文件
2013-06-17 19:23 1707网上资料很乱,找到一个亲测可用的demo: ... -
使用Spring自带工具对uri进行通配符匹配
2013-06-07 14:22 5525自己做项目的时候碰到一个要对请求的uri进行过滤的需求,而过滤 ... -
Spring3 MVC和Velocity整合配置笔记
2013-05-31 02:17 12117刚刚尝试用Spring MVC框架来替换Struts2框架,遇 ... -
Spring在初始化后执行某项操作及动态注册bean到Spring容器
2013-05-17 17:12 14046本文为技术备忘,觉得很重要所以记一下。大部分资料来源于网络搜索 ... -
java进行jpeg压缩和解析(不使用com.sun.image包)
2012-10-23 20:55 4039前段时间准备写一个桌面监控的软件,BufferedImage直 ...
相关推荐
内容概要:本文详细介绍了基于SpringBoot和Vue开发的养老院管理系统的具体实现细节。该系统采用前后端不分离的架构,旨在快速迭代并满足中小项目的开发需求。文中涵盖了多个关键技术点,如数据库设计(组合唯一约束、触发器)、定时任务(@Scheduled、@Async)、前端数据绑定(Vue的条件渲染和动态class绑定)、权限控制(RBAC模型、自定义注解)以及报表导出(SXSSFWorkbook流式导出)。此外,还讨论了开发过程中遇到的一些常见问题及其解决方案,如CSRF防护、静态资源配置、表单提交冲突等。 适合人群:具备一定Java和前端开发经验的研发人员,尤其是对SpringBoot和Vue有一定了解的开发者。 使用场景及目标:适用于需要快速开发中小型管理系统的团队,帮助他们理解如何利用SpringBoot和Vue进行全栈开发,掌握前后端不分离架构的优势和注意事项。 其他说明:文章不仅提供了详细的代码示例和技术要点,还分享了许多实用的小技巧和避坑指南,有助于提高开发效率和系统稳定性。
家族企业如何应对人才流失问题?
员工关怀制度.doc
内容概要:本文详细探讨了对传统蚁群算法进行改进的方法,特别是在路径规划领域的应用。主要改进措施包括:采用排序搜索机制,即在每轮迭代后对所有路径按长度排序并只强化前20%的优质路径;调整信息素更新规则,如引入动态蒸发系数和分级强化策略;优化路径选择策略,增加排序权重因子;以及实现动态地图调整,使算法能够快速适应环境变化。实验结果显示,改进后的算法在收敛速度上有显著提升,在复杂地形中的表现更加稳健。 适合人群:从事路径规划研究的技术人员、算法工程师、科研工作者。 使用场景及目标:适用于需要高效路径规划的应用场景,如物流配送、机器人导航、自动驾驶等领域。目标是提高路径规划的效率和准确性,减少不必要的迂回路径,确保在动态环境中快速响应变化。 其他说明:改进后的蚁群算法不仅提高了收敛速度,还增强了对复杂环境的适应能力。建议在实际应用中结合可视化工具进行调参,以便更好地观察和优化蚂蚁的探索轨迹。此外,还需注意避免过度依赖排序机制而导致的过拟合问题。
内容概要:本文详细介绍了利用粒子群优化(PSO)算法解决配电网中分布式光伏系统的选址与定容问题的方法。首先阐述了问题背景,即在复杂的配电网环境中选择合适的光伏安装位置和确定合理的装机容量,以降低网损、减小电压偏差并提高光伏消纳效率。接着展示了具体的PSO算法实现流程,包括粒子初始化、适应度函数构建、粒子位置更新规则以及越界处理机制等关键技术细节。文中还讨论了目标函数的设计思路,将多个相互制约的目标如网损、电压偏差和光伏消纳通过加权方式整合为单一评价标准。此外,作者分享了一些实践经验,例如采用前推回代法进行快速潮流计算,针对特定应用场景调整权重系数,以及引入随机波动模型模拟光伏出力特性。最终实验结果显示,经过优化后的方案能够显著提升系统的整体性能。 适用人群:从事电力系统规划与设计的专业人士,尤其是那些需要处理分布式能源集成问题的研究人员和技术人员。 使用场景及目标:适用于希望深入了解如何运用智能优化算法解决实际工程难题的人士;旨在帮助读者掌握PSO算法的具体应用方法,从而更好地应对配电网中分布式光伏系统的选址定容挑战。 其他说明:文中提供了完整的Matlab源代码片段,便于读者理解和复现研究结果;同时也提到了一些潜在改进方向,鼓励进一步探索和创新。
内容概要:本文详细介绍了丰田Prius2004永磁同步电机的设计流程,涵盖从初始参数计算到最终温升仿真的各个环节。首先利用Excel进行基本参数计算,如铁芯叠厚、定子外径等,确保设计符合预期性能。接着使用Maxwell进行参数化仿真,通过Python脚本自动化调整磁钢尺寸和其他关键参数,优化电机性能并减少齿槽转矩。随后借助橡树岭实验室提供的实测数据验证仿真结果,确保模型准确性。最后采用MotorCAD进行温升仿真,优化冷却系统设计,确保电机运行安全可靠。文中还分享了许多实用技巧,如如何正确设置材料参数、避免常见的仿真错误等。 适合人群:从事电机设计的专业工程师和技术人员,尤其是对永磁同步电机设计感兴趣的读者。 使用场景及目标:适用于希望深入了解永磁同步电机设计全过程的技术人员,帮助他们在实际工作中提高设计效率和精度,解决常见问题,优化设计方案。 其他说明:文章提供了丰富的实战经验和具体的操作步骤,强调了理论与实践相结合的重要性。同时提醒读者注意一些容易忽视的细节,如材料参数的选择和仿真模型的准确性。
内容概要:本文详细介绍了基于DSP28335的单相逆变器的设计与实现,涵盖了多个关键技术模块。首先,ADC采样模块用于获取输入电压和电流的数据,确保后续控制的准确性。接着,PWM控制模块负责生成精确的脉宽调制信号,控制逆变器的工作状态。液晶显示模块则用于实时展示电压、电流等重要参数。单相锁相环电路实现了电网电压的频率和相位同步,确保逆变器输出的稳定性。最后,电路保护程序提供了过流保护等功能,保障系统的安全性。每个模块都有详细的代码示例和技术要点解析。 适合人群:具备一定嵌入式系统和电力电子基础知识的研发人员,尤其是对DSP28335感兴趣的工程师。 使用场景及目标:适用于单相逆变器项目的开发,帮助开发者理解和掌握各个模块的具体实现方法,提高系统的可靠性和性能。 其他说明:文中不仅提供了具体的代码实现,还分享了许多调试经验和常见问题的解决方案,有助于读者更好地理解和应用相关技术。
SecureCRT安装包
内容概要:本文详细介绍了如何利用C#、WPF和MVVM模式构建一个大屏看板3D可视化系统。主要内容涵盖WPF编程设计、自定义工业控件、数据库设计、MVVM架构应用以及典型的三层架构设计。文中不仅提供了具体的代码实例,还讨论了数据库连接配置、3D模型绑定、依赖属性注册等关键技术细节。此外,文章强调了项目开发过程中需要注意的问题,如3D坐标系换算、MVVM中命令传递、数据库连接字符串加密等。 适合人群:具备一定C#编程基础,对WPF和MVVM模式有一定了解的研发人员。 使用场景及目标:适用于希望深入了解WPF和MVVM模式在实际项目中应用的开发者,特别是那些从事工业控制系统、数据可视化平台开发的专业人士。通过学习本文,读者可以掌握如何构建高效、稳定的大屏看板3D可视化系统。 其他说明:本文提供的设计方案和技术实现方式,可以帮助开发者更好地理解和应用WPF和MVVM模式,同时也能为相关领域的项目开发提供有价值的参考。
基于ssm的系统设计,包含sql文件(Spring+SpringMVC+MyBatis)
内容概要:本文详细介绍了利用COMSOL进行非厄米超表面双参数传感器的设计与实现。首先,通过构建超表面单元并引入虚部折射率,实现了PT对称系统的增益-损耗交替分布。接着,通过频域扫描和参数化扫描,捕捉到了复频率空间中的能级劈裂现象,并找到了奇异点(Exceptional Point),从而显著提高了传感器对微小扰动的敏感度。此外,文章探讨了双参数检测的独特优势,如解耦温度和折射率变化的能力,并展示了其在病毒检测、工业流程监控等领域的潜在应用。 适合人群:从事光学传感器研究的专业人士,尤其是对非厄米系统和COMSOL仿真感兴趣的科研人员。 使用场景及目标:适用于需要高精度、多参数检测的应用场合,如生物医学检测、环境监测等。目标是提高传感器的灵敏度和分辨率,解决传统传感器中存在的参数交叉敏感问题。 其他说明:文中提供了详细的建模步骤和代码片段,帮助读者理解和重现实验结果。同时,强调了在建模过程中需要注意的关键技术和常见问题,如网格划分、参数设置等。
怎样健全员工福利体系.docx
离职证明范本.doc
6538b79724855900a9c930904a302920.part6
员工离职单.doc
内容概要:本文详细介绍了在COMSOL中进行超材料异常折射仿真的关键技术。首先解释了异常折射现象及其产生的原因,接着通过具体代码展示了如何利用相位梯度和结构色散精确计算折射角。文中还讨论了边界条件的设置、网格划分的优化以及参数化扫描的应用。此外,提供了多个实用脚本和技巧,帮助提高仿真的精度和效率。最后强调了验证结果的重要性和一些常见的注意事项。 适合人群:从事电磁仿真研究的专业人士,尤其是对超材料和异常折射感兴趣的科研人员和技术开发者。 使用场景及目标:适用于需要深入理解和解决超材料中异常折射问题的研究项目。主要目标是掌握COMSOL中异常折射仿真的完整流程,确保仿真结果的准确性并优化计算性能。 其他说明:文章不仅提供了详细的代码示例和技术细节,还分享了许多实践经验,有助于读者更好地应对实际仿真过程中可能出现的问题。
招聘工作数据分析表.xls
platform-tools-latest-windows.zip
个人资料临时存储QT资源
内容概要:本文详细介绍了微电网中三相交流下垂控制的工作原理和技术细节。首先,通过Matlab/Simulink搭建模型,展示了传统阻感型线路下垂特性的实现方法,特别是有功-频率和无功-电压下垂曲线的解析。文中强调了关键参数Kp和Kq的选择及其对系统稳定性的影响,并通过具体的仿真案例展示了不同参数设置下的动态响应。此外,文章讨论了波形分析中的注意事项,如谐波成分、滤波器设计以及虚拟阻抗的应用。最后,通过Python和C语言实现了下垂控制器的代码示例,进一步解释了实际工程中的实现细节。 适合人群:从事微电网研究和开发的技术人员,尤其是对下垂控制感兴趣的电气工程师和研究人员。 使用场景及目标:适用于希望深入了解微电网下垂控制原理及其实际应用的研究人员和技术人员。目标是帮助读者掌握下垂控制的核心概念和技术实现,提高在实际工程项目中的调试和优化能力。 其他说明:文章不仅提供了理论分析,还包括了大量的仿真代码和波形图,使读者能够更好地理解和验证所学内容。同时,文中提到的实际调试经验和常见错误也为初学者提供了宝贵的指导。