`
cq520
  • 浏览: 167778 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

哈弗曼压缩与解压的原理及对象化实现

阅读更多
上一篇博客当中提到了哈弗曼树的构建与编码,详情请参见:http://cq520.iteye.com/blog/1870454

       这一次主要是跟大家探讨一下哈弗曼压缩的原理及实现,由于过程化的实现更加容易理解也更加直观,所以这里首先会分步骤跟大家讲解一下哈弗曼压缩的具体实现方法,然后再与大家分享一下对象化的实现。

       首先,我们要知道文件为什么能压缩

       文件是由字节所组成的,一个字节的长度为8位,所以最多只存在256种字节,而一个文件中一般存在许多相同的字节,我们把相同的字节以一种更加精简的方式表示,就完成了我们所说的压缩。

       哈弗曼压缩的原理是什么?

       上次博客中提到了哈弗曼编码,但是只是粗略的带过了,这一次举一个具体的例子来更加直观的说明哈弗曼压缩的原理。

       假设一个文件中是这样的一串字节ABBCCCDDDD,那么这个文件的大小就是10byte。那么接下来就是我们的哈弗曼压缩的第一步:

一.读取文件,统计每一种字节出现的次数,将出现的字节种类与对应的次数保存起来(可采用数组或者是HashMap,或者是其他的数据结构)

保存完了之后干什么呢??当然是构建哈弗曼树呀。第二步:

. 利用得到的字节与对应的频次构建哈弗曼树,需要注意的是,构建树的时候是以字节出现的频次作为我们的评判标准,出现次数越多的放在越上层。

比如我们上面所说的这个文件,它所构成的树应该是这样的:



 

 

我们现在得到的树还处于未编码的状态,那么第三步毫无疑问就是我们所说的编码了:

. 将得到的哈弗曼树进行编码

编码之后的树就变成这个样子了(采取向左编1的方式):



 

 

到了这一步其实我们的压缩就已经完成一半了,听到这里,你可能纳闷了,不对呀,不是还没开始么??

       下面我们就来看一下哈弗曼压缩的精华所在:

       编码之后,A所对应的的编码就是111B就是110C10D0,那么我们的文件就变成了11111011,01010100000是不是有一种亲切感?下面只要把这些10串每8个作为一组编码成一个新的字节(2进制转10进制),我们的压缩工作就大功告成了,所以这里每8位我也特别用逗号表示出来了,怎么样一个10byte的文件瞬间变成了3byte,是不是很神奇呢?

       等一下,做到这一步其实是远远不够的,有几个问题:

1.       怎么样把这一串的01变成我们所说的byte数?

需要注意的是,我们把文件中的字节变成这个样子只是把它们变成了一个01的字符串,那么这个问题就要用编程方法来解决了,具体方法有很多种,下面会给大家介绍一两种。

2如果最后几位不满8个怎么办?

       我们可以定义这样一个规则,在最后的位置补0,在文件的末尾再加一位表示最后一个数补0的个数,这样的话这个问题就变得很简单了。

3. 压缩之后我们怎么知道压缩前每种字节对应的编码是什么样子的

       如果你理解了压缩的前三步,你一定会想到这个问题,确实,我们如果按照这种方式压缩,我们所得到的文件将会无法复原。那么要完成压缩,最关键的一步,还要将压缩时所得到的每个字节对应的码表写入文件,这样我们才能保证,我们所做的工作是可逆的。

       好的,说完了这些,压缩剩下来的步骤相信你也已经明了了,压缩第四步:

四.根据每种字节对应的哈弗曼编码,将文件转换成01字符串

五.将得到的01串重新编码成新的byte数组写入文件

       当然,第四步与第五步可以同时完成,而且,每生成8个以上的字符串就将其前8位转换成byte数组的效率要比一次性转换的效率要高,这是因为,当文件转换成编码之后,长度增大,JVM中需要开辟一个很大的内存空间去存放这个字符串,这显然是很耗时的。

不过在过程化实现的代码中,楼主并没有将这一层优化,具体的优化工作需要读者们自己去做。

下面就是过程化实现的代码了:

 

package 哈弗曼压缩;

/**
 * 哈弗曼压缩
 * @author 陈强
 *
 */
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Set;

public class HFMCondense {
	public static void main(String args[]){
		String file="D://b.txt";
		HFMCondense condense=new HFMCondense();
		//System.out.println("元素与对应的频次表:");
		//System.out.println(condense.readFiletoMap(file));
		//System.out.println("生成的哈弗曼树");
		HFMNode hfmTree=condense.HashMapToHFMTree(condense.readFiletoMap(file));
		//condense.PreOrderTraverse(hfmTree);
		//System.out.println();
		//System.out.println("产生的哈弗曼编码表:");
		//condense.HuffmanCode(hfmTree,"");
		condense.HuffmanCoding(hfmTree, "");
		//转译后的字符串
		//String codeString=condense.FileToString(file);
		//System.out.println(codeString);
		//压缩后的字符串
		//String newString=new String(condense.createByteArray(codeString));
		//System.out.println(newString);
		System.out.println("开始压缩...");
		long start=System.currentTimeMillis();
		//System.out.println(condense.FileToString(file));
		//System.out.println(condense.CodeToString(file));
		//condense.CompressFile(condense.createByteArray(condense.CodeToString(file)),"D://c");
		condense.CompressFile(condense.createByteArray(condense.FileToString(file)),"D://c");
		System.out.println("压缩结束...用时:"+(System.currentTimeMillis()-start));
		//打印数组最后一个补0的个数
		//byte content[]=condense.createByteArray(condense.FileToString(file));
		//System.out.println(content[content.length-2]);
	}
	/**
	 * 读取将要被压缩的文件,统计每一个字符出现的频率,并将得到的内容存入HashMap中
	 * @param fileName 将要被压缩的文件
	 * @return 每一个字节数出现的频率所对应的HashMap
	 */
	public HashMap<Byte,Integer> readFiletoMap(String fileName){
		HashMap<Byte,Integer> hashMap=new HashMap<Byte,Integer>();
		File file=new File(fileName);
		if(file.exists()){
			try{
				InputStream in=new FileInputStream(file);
				//创建与文件大小相同的字节数组
				byte[] content=new byte[in.available()];
				//读取文件
				in.read(content);
				//存入HashMap中
				for(int i=0;i<content.length;i++){
					//如果表中存在某一个键
					if(hashMap.containsKey(content[i])){
						//获取该字节当前的键值
						int rate=hashMap.get(content[i]);
						//键值增大
						rate++;
						hashMap.put(content[i], rate);
					}
					//如果不存在某一个字节对象,则将它存入HashMap当中
					else{
						hashMap.put(content[i],1);
					}
				}
				in.close();
			}catch(Exception e){
				e.printStackTrace();
			}	
		}
		else{
			System.out.println("文件不存在");
		}
		return hashMap;
	}
	/**
	 * 将HashMap中的元素取出来封装到哈弗曼树中,树中叶子结点保存的是HashMap中的每一个键值与频率
	 * @param map 读取的Map
	 * @return	哈夫曼树的根结点
	 */
	public HFMNode HashMapToHFMTree(HashMap<Byte,Integer> map){
		//得到存储键值的系
		Set<Byte> keys=map.keySet();
		//得到迭代器对象
		Iterator<Byte> iter=keys.iterator();
		//如果还有值
		while(iter.hasNext()){
			byte key=iter.next();//获取系中的键
			int value=map.get(key);//得到该键出现的频率
			//创建结点并将结点对象加入到队列当中
			HFMNode node=new HFMNode(key,value);
			nodeQueue.add(node);
			nodeList.add(node);
		}
		//当所剩的结点数还大于两个
		while(nodeQueue.size()>=2){
			//得到键值频率最小的两个结点
			HFMNode left=nodeQueue.poll();
			HFMNode right=nodeQueue.poll();
			//将这两个结点组合起来生成新的结点
			HFMNode node=new HFMNode(left.data,left.value+right.value,left,right);
			nodeQueue.add(node);
		}
		//获取队列中的最后一个结点作为根结点
		HFMNode hfm=nodeQueue.poll();
		return hfm;
	}
	/**
	 * 为生成的哈弗曼树进行编码,产生对应的哈弗曼编码表
	 * @param hfm  对应的哈弗曼树
	 * @param code 对应生成的哈弗曼编码
	 * @return 哈弗曼编码表
	 */
	//创建一个新的哈弗曼编码表
	HashMap<Byte,String> codeMap=new HashMap<Byte,String>();
	public HashMap<Byte,String> HuffmanCoding(HFMNode hfm,String code){
		//如果左子树不为空,则左子树编码加1
		if(hfm.lchild!=null){
			HuffmanCoding(hfm.lchild,code+"1");
		}
		//如果右子树不为空,则右子树编码加0
		if(hfm.rchild!=null){
			HuffmanCoding(hfm.rchild,code+"0");
		}
		//如果到达叶子结点,则将元素放入HashMap中生成哈弗曼编码表
		if(hfm.lchild==null&&hfm.rchild==null){
			codeMap.put(hfm.data,code);
			hfm.code=code;
		}
		return codeMap;
	}
	/**
	 * 将哈弗曼编码转换成字符串
	 * @param fileName 读取的文件名
	 * @return 编码之后的哈弗曼字符串
	 */
	public String CodeToString(String fileName){
		File file=new File(fileName);
		String codeString="";
		//如果文件存在
		if(file.exists()){
			try{
				InputStream in=new FileInputStream(file);
				byte content[]=new byte[in.available()];
				in.read(content);
				int i=0;
				int len=content.length;//得到文件的字节长度
				int size=nodeList.size();//得到队列的长度
				while(i<len){
					for(int j=0;j<size;j++){
						if(content[i]==nodeList.get(j).data){
							codeString+=nodeList.get(j).code;
							break;
						}
					}
					i++;
				}
				in.close();
			}catch(Exception e){
				e.printStackTrace();
			}
		}else {
			System.out.println("文件不存在");
		}
		return codeString;
	}
	/**
	 * 将文件按照对应的哈弗曼编码表转成01字符串
	 * @param fileName 读入的文件名
	 * @return 转译后的字符串
	 */
	public String FileToString(String fileName){
		File file=new File(fileName);
		String StringContent="";
		//如果文件存在
		if(file.exists()){
			try{
				InputStream in=new FileInputStream(file);
				byte content[]=new byte[in.available()];
				in.read(content);
				//循环转译
				int len=content.length;
				for(int i=0;i<len;i++){
					StringContent+=codeMap.get(content[i]);
				}
				in.close();
			}catch(Exception e){
				e.printStackTrace();
			}
		}else{
			System.out.println("文件不存在");
		}
		return StringContent;
	}
	/**
	 * 将转译后的01字符串重新转换后放入新的字节数组当中
	 * @param code 转译后的01字符串
	 * @return 新的字节数组,里面包含了压缩后的字节内容
	 */
	public byte[] createByteArray(String code) {
		//将每8位字符分隔开来得到字节数组的长度
        int size=code.length()/8;
        //截取得到字符串8整数后的最后几个字符串
        String destString=code.substring(size*8);   
        byte dest[]=destString.getBytes();  
        //s用来记录字节数组的单字节内容
        int s = 0;
        int i=0;      
        int temp = 0;   
        // 将字符数组转换成字节数组,得到字符的字节内容,方便将二进制转为十进制
        byte content[] = code.getBytes();   
        for (int k = 0; k < content.length; k++) {   
            content[k] = (byte) (content[k] - 48);   
        }
        //转译后的字节内容数组
        byte byteContent[];   
        if (content.length % 8 == 0) {// 如果该字符串正好是8的倍数   
            byteContent = new byte[content.length / 8 + 1];   
            byteContent[byteContent.length - 1] = 0;// 那么返回的字节内容数组的最后一个数就补0   
        } else {   
        	//否则该数组的最后一个数就是补0的个数
            byteContent = new byte[content.length / 8 + 2];   
            byteContent[byteContent.length - 1] = (byte) (8 - content.length % 8);   
        }   
        int bytelen=byteContent.length;
        int contentlen=content.length;
        // byteContent数组中最后一个是补0的个数,实际操作到次后个元素
        //Math.pow返回第一个参数的第二个参数次幂的值
        while (i < bytelen - 2) {   
            for (int j = 0; j < contentlen; j++) {   
                if (content[j] == 1) {// 如果数组content的值为1   
                    s =(int)(s + Math.pow(2, (7 - (j - 8 * i))));// 累积求和   
                }// if   
                if ((j+1)%8==0) {// 当有8个元素时   
                    byteContent[i] = (byte) s;// 就将求出的和放入到byteContent数组中去   
                    i++;   
                    s = 0;// 并重新使s的值赋为0   
                }// if   
            }// for   
        }// while     
        int destlen=dest.length;
        for(int n=0;n<destlen;n++){   
            temp+=(dest[n]-48)*Math.pow(2, 7-n);//求倒数第2个字节的大小   
        }  
        byteContent[byteContent.length - 2] = (byte) temp; 
        return byteContent;   
    }  
	/**
	 * 压缩并输出新文件
	 * @param content 压缩后产生的新的字节数组
	 * @param fileName 输出文件名
	 */
	public void CompressFile(byte[] content,String fileName){
		File file=null;
		//统一后缀名
		if(!fileName.endsWith("hfm")){
			file=new File(fileName+".hfm");
		}else if(fileName.endsWith("hfm")){
			file=new File(fileName);	
		}
		int len=content.length;
		if(len>0){
			try{
				OutputStream out=new FileOutputStream(file);
				//将字节内容写入文件
				out.write(content);
				out.close();
			}catch(Exception e){
				e.printStackTrace();
			}
		}else{
			System.out.println("压缩出错");
		}
	}
	/**
	 * 测试一下哈弗曼树建立是否正确
	 * @param hfm
	 */
	public void PreOrderTraverse(HFMNode hfm){
		if(hfm!=null){
			System.out.print(hfm.value+" ");
			PreOrderTraverse(hfm.lchild);
			PreOrderTraverse(hfm.rchild);
		}
	}
	/**
	 * 存储哈弗曼树结点的优先队列
	 */
	ArrayList<HFMNode> nodeList=new ArrayList<HFMNode>();
	PriorityQueue<HFMNode> nodeQueue=new PriorityQueue<HFMNode>(11,new MyComparator());
	/**
	 * 实例化的一个比较器类
	 */
	class MyComparator implements Comparator<HFMNode>{
		public int compare(HFMNode o1, HFMNode o2) {
			return o1.value-o2.value;
		}	
	}
}

 结点类:

 

package 哈弗曼压缩;

public class HFMNode {
	byte data;  //存储字节的数据域
	int value;  //字节出现的频率
	String code;//叶子结点的哈弗曼编码
	HFMNode lchild,rchild;//左右孩子的引用
	//只指定数据的构造体
	public HFMNode(byte data,int rate){
		this(data,rate,null,null);
	}
	//同时指定左右孩子的构造体
	public HFMNode(byte data,int value,HFMNode lchild,HFMNode rchild){
		this.data=data;
		this.value=value;
		this.lchild=lchild;
		this.rchild=rchild;
	}
}

 

大家也看到了里面有两个方法做了同样一件事情,而经过测试之后发现两个方法的效率差不多,我们注意到的是,上面所做的每一步都是下一步的前提,所以在做过程化的实现时,一定要先理清楚前后的逻辑关系,此外,上文也提到了,这样做完之后还有几个问题没有解决。

1.       码表还未写入文件。(写入码表的方法有很多种,可以使用DataOutputStream类或者ObjectOutputStream类里面所提供的方法,大家自己查看一下API吧)

2.       压缩的效率很低(上面这一种实现的方法效率几乎是最低了,优化有几个方向,例如引入缓冲,生成编码的同时进行转译,采用更加快速的存储方式(采用数组存储的效率要高于HashMap.

压缩完成之后就要进行解压了,相信掌握了压缩技术的你,解压已经不成难题了,需要注意的是,

1.       码表最好放在压缩文件的前半部分,(这是因为,你可以在码表的最后用两个0表示码表的结束,而在文件的尾部,你可能也可以碰到两个0,这样你就无法判断哪里是码表的开始了

2.       读出来码表之后记得保存起来,解压时需要一个一个字节的去对照码表

 

说到这里,我们的过程化实现就已经基本结束了,但是大家可能想起来了,不是要讲对象化的实现么??怎么讲了这么久还没开始呀?别急别急,上面只是为了让大家更好的理解哈弗曼压缩的原理。下面就是对象化实现的具体代码,由于对象化的实现难于理解,所以很多地方都给予了注释,甚至一些不必要的地方都写上了,(当然注释中可能有些不到位的地方)不过我想写好注释或许才是我们真正所需要的,湖大的陆亮学长就曾说过,我可能写不出一行有技术含量的代码,但我能写出一行规范的代码。

对象化的实现方法中,提供了按位输入与输出的类,这些类都是自定义的,因为在编程中我们所能操作的计算机的最小单元是byte那么在这个类中是怎么做的呢?将一个字节进行8次移位按位与运算,我们就得到了这个字节的8bit的表示方式

里面还有很多比较实用的方法,希望大家能够耐心的看一下,好的,话不多说了,方法如下:

基本数据接口:

package 哈弗曼完全压缩;

public interface BitUtils {
	 public static final int BITS_PER_BYTES=8;//位与byte之间的转换单位
	 public static final int DIFF_BYTES=256;//0x100
	 public static final int EOF=256;//EndOfFile 资料源无更多的资料可读取
}

 位输入类:

package 哈弗曼完全压缩;
/**
 * InputStream的包装类,提供按位输入
 */
import java.io.IOException;
import java.io.InputStream;

public class BitInputStream {
	private InputStream in;//基本输入流
	private int buffer;//byte缓冲区
	private int bufferPos;//表示缓冲区中有多少未被使用的空间
	/**
	 * 封装InputStream的构造方法,初始化byte缓冲区的大小
	 * @param is InputStream对象
	 */
	public BitInputStream(InputStream is){
		in=is;
		bufferPos=BitUtils.BITS_PER_BYTES;//初始化缓冲区的剩余空间
	}
	/**
	 * 读取一位的方法,每8次对其进行调用就会从基本输入流中读出一个byte
	 * @return 1位数据,1或者0
	 * @throws IOException 
	 */
	public int readBit() throws IOException{
		//如果缓冲区还未被使用
		if(bufferPos==BitUtils.BITS_PER_BYTES){
			//输入流读取一位
			buffer=in.read();
			//读到文件的末尾了
			if(buffer==-1)
				return -1;
			//清空缓冲区
			bufferPos=0;
		}
		//扩张缓冲区
		return getBit(buffer,bufferPos++);
	}
	/**
	 * 关闭输入流
	 * @throws IOException 
	 */
	public void close() throws IOException{
		in.close();
	}
	/**
	 * 获取一个byte中每一位的方法
	 * @param pack 
	 * @param pos 
	 * @return 
	 */
	private static int getBit(int pack,int pos){
		//将一个字节进行8次按位与运算,得到这个字节的8位
		return (pack&(1<<pos))!=0?1:0;
	}
}

 位输出类:

 

package 哈弗曼完全压缩;
/**
 * OutputStream的包装类,提供按位输出的方法
 */
import java.io.IOException;
import java.io.OutputStream;

public class BitOutputStream {
	private OutputStream out; //基本输出流
	private int buffer;//输出的缓冲区
	private int bufferPos;//缓冲区中剩余的位数
	/**
	 * 封装OutputStream的构造方法,初始化缓冲区大小
	 * @param os
	 */
	public BitOutputStream(OutputStream os){
		bufferPos=0;
		buffer=0;
		out=os;
	}
	/**
	 * 写入一串的位
	 * @param val 包含有位数据的数组
	 * @throws IOException
	 */
	public void writeBits(int []val) throws IOException{
		int len=val.length;
		for(int i=0;i<len;i++){
			writeBit(val[i]);
		}
	}
	/**
	 * 写入位的方法(0或1),每8次对其进行调用就从基本流中写入一个byte
	 * @param val 当前写入的位数据
	 * @throws IOException
	 */
	public void writeBit(int val) throws IOException{
		buffer=setBit(buffer,bufferPos++,val);//将缓冲数据转换成位数据
		//每读到一个byte就刷新一次
		if(bufferPos==BitUtils.BITS_PER_BYTES)//缓冲区已满则刷新缓冲区
			flush();
	}
	/**
	 * 刷新此缓冲的输出流
	 * @throws IOException
	 */
	public void flush() throws IOException{
		if(bufferPos==0)//如果缓冲中没有数据则不执行
			return;
		//将缓冲区中的数据写入
		out.write(buffer);
		//重置缓冲区
		bufferPos=0;
		buffer=0;
	}
	/**
	 * 关闭流的方法
	 * @throws IOException
	 */
	public void close() throws IOException{
		flush();
		out.close();
	}
	/**
	 * 进行位数据转换的方法
	 * @param pack
	 * @param pos
	 * @param val 当前位
	 * @return
	 */
	private int setBit(int pack,int pos,int val){
		if(val==1)
			//按位或运算
			pack|=(val<<pos);
		return pack;
	}
}

 结点类:

package 哈弗曼完全压缩;
/**
 * 哈弗曼结点类
 */
public class HuffNode implements Comparable<HuffNode>{
	public int value;//结点数据
	public int weight;//权重
	HuffNode left,right;//左右孩子结点
	HuffNode parent;//父亲结点
	/**
	 * 初始化结点的数据,权重,左右孩子结点与父亲结点
	 * @param v 数据
	 * @param w	权重
	 * @param lchild 左孩子结点
	 * @param rchild 右孩子结点
	 * @param pt	父亲结点
	 */
	HuffNode(int v,int w,HuffNode lchild,HuffNode rchild,HuffNode pt){
		value=v;
		weight=w;
		left=lchild;
		right=rchild;
		parent=pt;
	}
	/**
	 * 比较两个结点的权重
	 */
	public int compareTo(HuffNode rhs) {
		return weight-rhs.weight;
	}
}

 字符统计类:

package 哈弗曼完全压缩;
/**
 * 字符统计类,获取输入流(通常是文件)中所含的字符数
 * 8位字节认为是ASCII字符
 */
import java.io.IOException;
import java.io.InputStream;

public class CharCounter {
	//字节的下标表示字节的种类,对应的值表示出现的次数
	private int theCounts[]=new int[BitUtils.DIFF_BYTES];//字节的种类总共有256种
	/**
	 * 默认的无参的构造方法
	 */
	public CharCounter(){
		
	}
	/**
	 * 封装了基本的InputStream,读取数据并进行字符的频次统计
	 * @param input InputStream对象
	 * @throws IOException
	 */
	public CharCounter(InputStream input) throws IOException{
		int ch;//读到的字节
		//一直读到文件的末尾,每一种byte出现了多少次
		while((ch=input.read())!=-1){
			theCounts[ch]++;
		}
	}
	/**
	 * 获取该字符统计数组的某一个字符出现的次数
	 * @param ch 数组下标
	 * @return 该下标位置字符出现的次数
	 */
	public int getCount(int ch){
		return theCounts[ch&0xff];
	}
	/**
	 * 设置某一个字符出现的次数
	 * @param ch 数组下标
	 * @param count 字符出现次数
	 */
	public void setCount(int ch,int count){
		theCounts[ch&0xff]=count;
	}
}

 哈弗曼树类:

package 哈弗曼完全压缩;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.PriorityQueue;

public class HuffmanTree {
	private CharCounter theCounts;//字符统计类对象
	private HuffNode root;//根结点
	private HuffNode[] theNodes=new HuffNode[BitUtils.DIFF_BYTES+1];//存储HuffNode的数组
	public static final int ERROR=-3;//错误
	public static final int INCOMPLETE_CODE=-2;//不完全的结点编码
	public static final int END=BitUtils.DIFF_BYTES;//字节的溢出位
	/**
	 * 实例化一个字符统计类对象
	 */
	public HuffmanTree(){
		theCounts=new CharCounter();
		root=null;
	}
	/**
	 * 可以通过CharCounter对象来创建一个huffmanTree对象
	 * @param cc CharCounter对象
	 */
	public HuffmanTree(CharCounter cc){
		theCounts=cc;
		root=null;
		createTree();//创建HuffmanTree
	}
	/**
	 * 得到要寻找的字符编码所在的树结点,如果该字符不在树上,则返回null表示出错,否则,通过父亲链逆向查找,直到到达根结点
	 * @param ch 当前结点的下标
	 * @return 结点相对的字符编码数组
	 */
	public int[] getCode(int ch){
		HuffNode current=theNodes[ch];
		
		if(current==null)
			return null;
		String v="";//结点的编码
		HuffNode parent=current.parent;
		
		while(parent!=null){
			if(parent.left==current)
				v="0"+v;//左结点编码
			else 
				v="1"+v;//右结点编码
			//向下遍历
			current=current.parent;
			parent=current.parent;
		}
		int len=v.length();
		int [] result=new int[len];//创建一个与编码相同大小数组
		for(int i=0;i<len;i++)
			result[i]=v.charAt(i)=='0'?0:1;
		return result;
	}
	/**
	 * 获取编码对应的字符
	 * @param code 哈弗曼编码
	 * @return 存储在结点中的值(如果结点不是叶子结点,则返回符号INCOMPLETE)
	 */
	public int getChar(String code){
		HuffNode leaf=root;//获取根结点
		int len=code.length();
		//按照编码向左或向右遍历到叶子结点
		for(int i=0;leaf!=null&&i<len;i++)
			if(code.charAt(i)=='0')
				leaf=leaf.left;
			else
				leaf=leaf.right;
		//根结点为空
		if(leaf==null)
			return ERROR;
		return leaf.value;
	}
	/**
	 * 写入编码表的方法
	 * @param out 写入的数据流
	 * @throws IOException
	 */
	public void writeEncodingTable(DataOutputStream out) throws IOException{
		for(int i=0;i<BitUtils.DIFF_BYTES;i++){
			if(theCounts.getCount(i)>0){
				out.writeByte(i);//将字节写入(通常是文件)
				out.writeInt(theCounts.getCount(i));//将字节出现的次数写入(通常是文件)
			}
		}
		//最后写入0表示编码的结束
		out.writeByte(0);
		out.writeInt(0);
	}
	/**
	 * 读取编码表的方法
	 * @param in 数据输入流对象
	 * @throws IOException
	 */
	public void readEncodingTable(DataInputStream in) throws IOException{
		for(int i=0;i<BitUtils.DIFF_BYTES;i++)
			theCounts.setCount(i, 0);
		byte ch;
		int num;
		for(;;){
			ch=in.readByte();//读到的字节
			num=in.readInt();//字符出现的次数
			if(num==0)//如果读到0表示编码表的结束
				break;
			theCounts.setCount(ch, num);
		}
		createTree();//创建HuffmanTree
	}
	/**
	 * 构造哈弗曼编码树的方法
	 */
	public void createTree(){
		//创建一个优先队列来保存HuffNode
		PriorityQueue<HuffNode> pq=new PriorityQueue<HuffNode>();
		
		for(int i=0;i<BitUtils.DIFF_BYTES;i++){
			if(theCounts.getCount(i)>0){//如果某一个字节出现过
				HuffNode newNode=new HuffNode(i,theCounts.getCount(i),null,null,null);
				theNodes[i]=newNode;
				pq.add(newNode);//将新结点添加到队列中
			}
		}
		
		theNodes[END] =new HuffNode(END,1,null,null,null);
		pq.add(theNodes[END]);
		//当剩余的结点多于一个时
		while(pq.size()>1){
			//每次取出当前最小的两个结点
			HuffNode n1=pq.remove();//remove方法与poll方法的唯一不同之处在于:此队列为空时将抛出一个异常
			HuffNode n2=pq.remove();
			//将最小的两个结点链接形成新结点
			HuffNode result=new HuffNode(INCOMPLETE_CODE,n1.weight+n2.weight,n1,n2,null);
			n1.parent=n2.parent=result;
			//将新结点添加到队列当中
			pq.add(result);
		}
		root=pq.element();//根结点就是队列中的最后一个结点
	}
}

 解压缩类:

package 哈弗曼完全压缩;
/**
 * 包含解压缩的包装类
 */
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;

public class HZIPInputStream extends InputStream{
	private BitInputStream bin;//位输入流
	private HuffmanTree codeTree;//编码的HuffmanTree对象
	/**
	 * 封装InputStream对象,实例化HuffmanTree对象与BitInputStream对象,并读入哈弗曼编码
	 * @param in
	 * @throws IOException
	 */
	public HZIPInputStream(InputStream in) throws IOException{
		//数据输入流
		DataInputStream din=new DataInputStream(in);
		
		codeTree=new HuffmanTree();
		codeTree.readEncodingTable(din);
		
		bin=new BitInputStream(in);
	}
	/**
	 * 读取文件的方法
	 */
	public int read() throws IOException{
		String bits="";//哈弗曼编码的字符串
		int bit;//位
		int decode;//解码后的字符
		while(true){
			bit=bin.readBit();
			if(bit == -1)
				throw new IOException("Unexpected EOF");//意外的资源结束
			
			bits+=bit;
			decode=codeTree.getChar(bits);//获取编码对应的字符
			if(decode==HuffmanTree.INCOMPLETE_CODE)//向下搜索到叶子结点
				continue;
			else if(decode==HuffmanTree.ERROR)//编码出错
				throw new IOException("Unexpected error");
			else if(decode==HuffmanTree.END)//编码溢出
				return -1;
			else 
				return decode;
		}
	}
	/**
	 * 关闭输入流
	 */
	public void close() throws IOException{
		bin.close();
	}
}

 压缩类:

package 哈弗曼完全压缩;
/**
 * 包含压缩的包装类
 */
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class HZIPOutputStream extends OutputStream{
	private ByteArrayOutputStream byteOut=new ByteArrayOutputStream();//实例化的一个字节数组输出流对象
	private DataOutputStream dout;//数据输出流对象
	/**
	 * 实例化一个DataOutputStream对象的构造方法
	 * @param out 输出流对象
	 * @throws IOException
	 */
	public HZIPOutputStream(OutputStream out) throws IOException{
		dout=new DataOutputStream(out);
	}
	/**
	 * 写入编码频率的方法
	 */
	public void write(int ch) throws IOException{
		byteOut.write(ch);
	}
	/**
	 * 关闭流的方法
	 */
	public void close() throws IOException{
		byte[] theInput=byteOut.toByteArray();//将字节数组输出流转换数据转换成字节数组进行输入
		ByteArrayInputStream byteIn=new ByteArrayInputStream(theInput);//将字节数组封装到字节输入流中
		
		CharCounter countObj=new CharCounter(byteIn);//实例化字符统计对象并统计字节数组的字符出现的次数
		byteIn.close();//关闭字节输入流
		
		HuffmanTree codeTree=new HuffmanTree(countObj);//通过CharCounter对象实例化一个HuffmanTree对象
		codeTree.writeEncodingTable(dout);//将编码写入数据输出流中
		
		BitOutputStream bout=new BitOutputStream(dout);//创建位输出流
		
		//将按编码转换后的位写入
		int len=theInput.length;
		for(int i=0;i<len;i++)
			bout.writeBits(codeTree.getCode(theInput[i]&0xff));
		bout.writeBits(codeTree.getCode(BitUtils.EOF));//文件结束的标示符
		
		//关闭流
		bout.close();
		byteOut.close();
	}
}

 压缩与解压方法及测试:

package 哈弗曼完全压缩;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;


public class HZIP {
	/**
	 * 压缩文件的方法,此方法需要传入正确的绝对路径名
	 * @param inFile 需要被压缩的文件
	 * @param outFile 压缩之后的输出文件
	 * @throws IOException IO异常
	 */
	public static void compress(String inFile,String outFile) throws IOException{
		String compressFile=null;//创建压缩文件
		String extension=inFile.substring(inFile.length()-4);//获取源文件的后缀名
		File file=new File(outFile);
		//如果文件已经存在
		if(file.exists()){
			System.out.println("文件已经存在");
		}else{
			//自动补齐后缀名
			if(!outFile.endsWith(".hfm")){
				compressFile=outFile+extension+".hfm";	
			}
			else{
				compressFile=outFile+extension;
			}
			//创建文件输入的缓冲流
			InputStream in=new BufferedInputStream(new FileInputStream(inFile));
			//创建文件输出的缓冲流
			OutputStream out=new BufferedOutputStream(new FileOutputStream(compressFile));
			int ch;
			//创建哈弗曼压缩的输入流
			HZIPOutputStream hzout=new HZIPOutputStream(out);
			while((ch=in.read())!=-1){
				hzout.write(ch);
			}
			//关闭流
			in.close();
			hzout.close();
		}
	}
	/**
	 * 解压文件的方法,此方法需要填入正确的绝对路径名
	 * @param compressedFile  需要被解压的文件
	 * @param outFile 解压之后的输出文件
	 * @throws IOException IO异常
	 */
	public static void uncompress(String compressedFile,String outFile) throws IOException{
		String extension;//文件的后缀名
		extension =compressedFile.substring(compressedFile.length()-4);
		//得到压缩前的文件的后缀名
		String suffix=compressedFile.substring(compressedFile.length()-8,compressedFile.length()-4);
		//如果文件不合法则不执行解压操作
		if(!extension.equals(".hfm")){
			System.out.println("文件格式错误或者不是有效的压缩文件");
			return;
		}
		File file=new File(outFile);
		//如果已经存在同名文件
		if(file.exists()){
			System.out.println("该文件已经存在,请重新命名解压后的文件");
		}
		else{
			outFile+=(suffix+".uc");//输出文件的格式统一为uc格式
			//创建文件输入的缓冲流
			InputStream fin=new BufferedInputStream(new FileInputStream(compressedFile));
			//创建数据读入流
			DataInputStream in=new DataInputStream(fin);
			//创建哈弗曼压缩输入流
			HZIPInputStream hzin=new HZIPInputStream(in);
			//创建文件输出的缓冲流
			OutputStream fout=new BufferedOutputStream(new FileOutputStream(outFile));
			int ch;
			//解压并输出文件
			while((ch=hzin.read())!=-1){
				fout.write(ch);
			}
			//关闭流
			hzin.close();
			fout.close();	
		}
	
	}
	public static void main(String args[]) throws IOException{
                System.out.println("开始压缩"); 
		long start=System.currentTimeMillis();
		compress("d://a.txt","d://cd");
		System.out.println("压缩结束,用时:"+(System.currentTimeMillis()-start));
	}
}

这个压缩还有很多值得改进的地方,比如当文件较小或过大时,压缩之后的文件比源文件还大,为什么呢?这是因为:

文件较小时,由于要写入编码表,所以造成了较大的空间占用。

而文件较大时,由于各种字节出现的频率已经趋于了相近的地步,那么我们再来回顾一下哈弗曼的压缩过程时会发现,极端情况下,当所有字节都出现过,且出现的次数相同时,每一种字节的编码长度都达到了8位(哈弗曼树的第9层刚好有256个叶子结点),达不到压缩的效果。

不过既然达不到压缩,做一个文件加密的工作也是不错的,怎么样,自己动手试试吧。

  • 大小: 19.3 KB
  • 大小: 21.1 KB
4
2
分享到:
评论

相关推荐

    课程实习时做的哈弗曼压缩程序

    在这个“课程实习时做的哈弗曼压缩程序”中,我们可以推测作者尝试实现了哈弗曼编码的基本流程,包括以下几个步骤: 1. **频率统计**:首先,需要对输入的文本或数据流中的字符进行频率统计,了解每个字符出现的...

    文件压缩与解压实验报告.pdf

    【文件压缩与解压实验报告】的实验主要探讨了如何使用哈弗曼编码技术实现文本的压缩和解压缩。哈弗曼编码是一种基于频率的前缀编码方法,它通过为出现频率较高的字符分配较短的编码,从而在不改变信息含义的前提下...

    文件压缩与解压实验报告 (2).docx

    【文件压缩与解压实验报告】探讨的是使用哈弗曼编码技术进行文本压缩与解压的实践过程。哈弗曼编码是一种高效的前缀编码方法,主要用于数据压缩,它通过构建最优二叉树来为每个字符分配独一无二的二进制编码,使得...

    haffman 编码

    在MATLAB中,这可以通过序列化对象或存储编码表来实现。在解码时,根据编码表和编码后的文本,可以反向解码回原始文本。 6. **压缩与解压**:最后,将编码后的文本进行二进制转换,然后可以写入文件进行存储。解压...

    基于Python的天气预测与可视化(完整源码+说明文档+数据)

    基于Python的天气预测与可视化(完整源码+说明文档+数据),个人经导师指导并认可通过的高分设计项目,评审分99分,代码完整确保可以运行,小白也可以亲自搞定,主要针对计算机相关专业的正在做大作业的学生和需要项目实战练习的学习者,可作为毕业设计、课程设计、期末大作业,代码资料完整,下载可用。 基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基于Python的天气预测与可视化(完整源码+说明文档+数据)基

    超表面设计中MIM结构的FDTD仿真:基于磁偶极子共振的高效光束偏折实现

    内容概要:本文详细介绍了利用MIM(金属-介质-金属)结构进行梯度相位超表面的设计与仿真的全过程。首先,通过Au-MgF2-Au三明治结构,利用磁偶极子共振实现高效的相位控制。接着,通过FDTD仿真工具,编写参数扫描脚本来优化纳米柱尺寸,从而实现广泛的相位覆盖。然后,通过近远场变换计算异常反射效率,验证了高达85%以上的反射效率。此外,还探讨了宽带性能验证的方法以及梯度相位阵列的设计思路。最后,提供了实用的代码片段和注意事项,帮助读者理解和复现实验结果。 适合人群:从事超表面研究、光束控制、电磁仿真领域的科研人员和技术开发者。 使用场景及目标:适用于希望深入了解MIM结构在超表面设计中的应用,掌握FDTD仿真技巧,以及探索高效光束偏折机制的研究人员。目标是通过详细的步骤指导,使读者能够成功复现并优化类似实验。 其他说明:文章不仅提供了理论背景,还包括大量具体的代码实现和实践经验分享,有助于读者更好地理解和应用所学知识。

    基于主从博弈的MATLAB实现:共享储能与综合能源微网优化运行

    内容概要:本文探讨了利用主从博弈理论解决共享储能与综合能源微网之间的利益冲突。通过MATLAB和YALMIP+Cplex工具,构建了微网运营商、用户聚合商和共享储能服务商三者之间的博弈模型。主要内容包括系统架构介绍、核心代码解析、求解策略以及仿真结果分析。文中详细展示了如何通过Stackelberg模型实现三方利益的最大化,并提供了完整的代码实现和详细的注释。 适合人群:从事能源互联网项目的研发人员、对博弈论及其应用感兴趣的学者和技术爱好者。 使用场景及目标:适用于希望深入了解能源系统优化、主从博弈理论及其MATLAB实现的研究人员和工程师。目标是掌握如何通过编程手段解决复杂系统中的多主体利益协调问题。 其他说明:文章不仅介绍了理论背景,还提供了具体的代码实现细节,如参数初始化、目标函数构建、约束条件处理等。此外,还包括了仿真结果的可视化展示,帮助读者更好地理解模型的实际效果。

    FPGA图像处理领域的直方图统计与均衡化技术及其Matlab验证

    内容概要:本文深入探讨了基于FPGA平台实现直方图统计与均衡化的全过程,涵盖直方图统计、累积直方图计算和均衡化处理三大核心步骤。文中不仅提供了详细的Verilog代码实现,还介绍了关键的设计思路和技术难点,如双端口BRAM的应用、流水线控制、除法器资源优化等。此外,通过Matlab代码进行了结果验证,确保FPGA实现的准确性。 适合人群:从事FPGA开发、图像处理、计算机视觉等相关领域的工程师和技术爱好者。 使用场景及目标:适用于需要高性能、低延迟图像处理的应用场景,如实时视频处理、医学图像处理、卫星图像增强等。目标是掌握FPGA实现直方图均衡化的技术细节,提高图像对比度和清晰度。 其他说明:文章强调了FPGA相较于CPU和GPU在并行处理和硬件加速方面的优势,并提供了丰富的代码实例和测试结果,帮助读者更好地理解和应用这一技术。

    基于LSTM的高速公路车辆换道轨迹预测:数据处理、模型设计与性能评估

    内容概要:本文详细介绍了利用LSTM模型进行高速公路车辆换道轨迹预测的研究过程。首先,作者使用来自I-80和US-101高速公路的实际换道轨迹数据,这些数据包括横向和纵向的速度、加速度以及轨迹坐标等特征。通过对数据进行预处理,如标准化、划分训练集和测试集等步骤,确保了数据的质量。然后,设计并实现了包含两层LSTM和一层全连接层的神经网络模型,采用Adam优化器进行训练,并通过交叉熵损失函数评估模型性能。实验结果显示,模型在测试集上的准确率达到85%,表明LSTM模型能够有效捕捉车辆换道的行为模式。 适合人群:从事自动驾驶技术研发的专业人士,尤其是对深度学习应用于交通预测感兴趣的工程师和技术研究人员。 使用场景及目标:本研究旨在提高自动驾驶系统的安全性与效率,具体应用场景包括但不限于城市快速路、高速公路等复杂路况下车辆换道行为的提前预测,从而辅助驾驶员或自动驾驶系统做出更好的决策。 其他说明:尽管目前模型已经取得了较好的成绩,但仍存在改进空间,例如可以通过引入更多类型的传感器数据(如摄像头图像)、优化现有模型结构等方式进一步提升预测精度。此外,考虑到实际应用中的实时性和鲁棒性要求,后续还需针对硬件平台进行针对性优化。

    个人资料-1111相关内容

    个人资料-111相关内容

    汽车碰撞仿真CAE:基于HyperWorks与LS-DYNA的全流程解析及实战技巧

    内容概要:本文详细介绍了使用HyperWorks和LS-DYNA进行汽车碰撞仿真的方法和技术要点。从网格划分、材料属性设置、连接装配到最后的分析计算和结果处理,每个环节都配有具体的代码示例和注意事项。文中不仅涵盖了正碰、侧碰、偏置碰等多种类型的碰撞分析,还包括了座椅安全带约束等特殊部件的建模技巧。此外,作者分享了许多实践经验,如网格尺寸的选择、材料参数的设定以及求解器设置的最佳实践,帮助读者避免常见的陷阱并提高仿真效率。 适合人群:从事汽车工程领域的工程师、研究人员以及对汽车碰撞仿真感兴趣的初学者。 使用场景及目标:适用于需要掌握汽车碰撞仿真完整流程的专业人士,旨在提升其在实际项目中的应用能力,确保仿真结果的准确性和可靠性。 其他说明:附赠的源代码进一步增强了学习效果,使读者能够快速上手并在实践中不断优化自己的技能。

    MATLAB/Simulink中四分之一车被动悬架双质量模型的构建与分析

    内容概要:本文详细介绍了如何在MATLAB/Simulink环境中搭建四分之一车被动悬架双质量(二自由度)模型。该模型主要用于研究车辆悬架系统在垂直方向上的动态特性,特别是面对路面不平度时的表现。文中不仅提供了具体的建模步骤,包括输入模块、模型主体搭建和输出模块的设计,还给出了详细的参数配置方法和仿真分析技巧。此外,文章还探讨了如何通过调整悬架系统的参数(如阻尼系数)来优化车辆的乘坐舒适性和行驶安全性。 适合人群:从事汽车动力学研究的专业人士、高校相关专业的学生以及对车辆悬架系统感兴趣的工程师。 使用场景及目标:①用于教学目的,帮助学生理解车辆悬架系统的理论知识;②用于科研实验,验证不同的悬架设计方案;③为企业产品研发提供技术支持,改进现有产品的性能。 其他说明:文中提供的代码片段和建模思路有助于读者快速上手并掌握Simulink建模技能。同时,强调了实际应用中的注意事项,如选择合适的求解器、处理代数环等问题。

    MATLAB实现语音数据特征提取与分类全流程解析

    内容概要:本文详细介绍了使用MATLAB进行语音数据处理的完整流程,涵盖从音频文件读取、特征提取(特别是梅尔倒谱系数MFCC)、分类器构建(支持向量机SVM)到最后的性能评估(混淆矩阵)。作者分享了许多实用技巧,如避免常见错误、优化特征提取参数以及提高分类准确性的方法。文中提供了大量具体代码示例,帮助读者快速理解和应用相关技术。 适合人群:对语音信号处理感兴趣的初学者或有一定经验的研究人员和技术爱好者。 使用场景及目标:适用于希望深入了解语音识别系统内部机制的人群,尤其是希望通过MATLAB平台实现简单而有效的语音分类任务的学习者。主要目的是掌握如何利用MATLAB工具箱完成从原始音频到分类结果可视化的全过程。 其他说明:除了介绍基本概念外,还强调了一些实践经验,例如预处理步骤的重要性、选择合适的滤波器数目、尝试不同的分类器配置等。此外,作者鼓励读者根据实际情况调整参数设置,以获得更好的实验效果。

    基于python+yolov5和deepsort实现的行人或车辆跟踪计数系统+源码+项目文档+演示视频(毕业设计&课程设计&项目开发)

    基于python+yolov5和deepsort实现的行人或车辆跟踪计数系统+源码+项目文档+演示视频,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 项目运行环境:win10,pycharm,python3.6+ 主要需要的包:pytorch >= 1.7.0,opencv 运行main.py即可开始追踪检测,可以在控制台运行 基于python+yolov5和deepsort实现的行人或车辆跟踪计数系统+源码+项目文档+演示视频,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 项目运行环境:win10,pycharm,python3.6+ 主要需要的包:pytorch >= 1.7.0,opencv 运行main.py即可开始追踪检测,可以在控制台运行~

    超表面全息技术中MIM结构的高效几何相位与FDTD仿真解析

    内容概要:本文详细介绍了金-氟化镁-金(MIM)结构在超表面全息领域的应用及其高效性能。首先探讨了MIM结构中磁偶极子模式的优势,特别是其低辐射损耗的特点。接着讨论了几何相位的应用,展示了纳米柱旋转角度与相位延迟之间的线性关系,并解决了相位误差的问题。随后介绍了改进的GS算法,提高了迭代收敛速度。最后,通过FDTD仿真验证了MIM结构的高效率,提供了详细的仿真参数设置和优化技巧。 适合人群:从事超表面研究、光学工程、纳米技术和FDTD仿真的研究人员和技术人员。 使用场景及目标:适用于希望深入了解MIM结构在超表面全息中的应用,以及希望通过FDTD仿真进行相关研究的专业人士。目标是提高超表面全息的转换效率,探索新的应用场景如涡旋光生成和偏振加密全息。 其他说明:文中提供了大量具体的代码片段和参数设置,帮助读者更好地理解和复现实验结果。此外,还提到了一些常见的仿真陷阱和解决方案,有助于避免常见错误并提升仿真准确性。

    【金融科技领域】信用飞利用大数据与AI实现用户信用成长及资产增值:个性化金融解决方案设计

    内容概要:文章介绍了金融科技公司信用飞如何通过关注用户信用成长,利用先进技术和专业服务为用户量身定制金融解决方案,从而实现用户资产的稳健增值。首先,信用飞通过多维度数据分析,全面了解用户的信用状况和需求,为不同信用水平的用户提供个性化服务。其次,建立了动态信用评估体系,实时监测并调整用户信用服务策略,帮助用户持续提升信用。再者,根据不同用户的需求,提供包括信用消费、理财投资、融资借贷等在内的多样化金融服务。最后,借助大数据、人工智能、区块链等技术手段,确保金融服务的安全可靠和高效便捷,持续陪伴用户实现信用与财富的双重增长。 适合人群:对个人信用管理有一定需求,希望通过科学金融规划实现资产稳健增值的个人及小微企业主。 使用场景及目标:①希望提升个人或企业信用评级的用户;②寻求合适金融产品和服务以优化财务管理的人群;③需要安全可靠的融资渠道支持业务发展的创业者和中小企业。 阅读建议:本文详细阐述了信用飞如何通过技术创新和个性化服务助力用户信用成长及资产增值,建议读者重点关注文中提到的技术应用和服务特色,结合自身情况思考如何更好地利用此类金融科技服务来优化个人或企业的财务状况。

    少儿编程scratch项目源代码文件案例素材-AI战争.zip

    少儿编程scratch项目源代码文件案例素材-AI战争.zip

    工业自动化中出口设备1200线体程序的PLC通讯与V90-FB284协同控制开源指南

    内容概要:本文详细介绍了出口设备1200线体程序的配置与优化方法,涵盖PLC通讯控制、V90模块配置以及工艺对象与FB284的协同控制。文章强调了开源特性的优势,使得用户可以自由扩展和优化控制系统。主要内容包括:1) 出口设备1200线体程序的核心地位及其复杂控制逻辑;2) 多个PLC设备的通讯协作,确保数据可靠传输;3) V90模块的具体配置步骤,确保各模块稳定运行;4) 工艺对象与FB284的协同控制,避免逻辑冲突;5) 开源带来的便利性,便于用户进行功能扩展和学习;6) 实际应用中的优化措施,提高系统的运行效率。 适合人群:从事工业自动化领域的工程师和技术人员,尤其是那些希望深入了解PLC通讯控制和V90伺服配置的人。 使用场景及目标:适用于需要配置和优化出口设备1200线体程序的实际工程项目,帮助用户掌握PLC通讯、V90配置及工艺对象与FB284协同控制的方法,从而提升生产线的效率和稳定性。 其他说明:文章提供了大量实用的代码片段和调试技巧,有助于读者更好地理解和实施相关配置。同时,文中提到的一些具体案例和经验分享也为实际操作提供了宝贵的参考。

    前端面试与vue源码讲解

    前端面试与vue源码讲解

    少儿编程scratch项目源代码文件案例素材-green vs blue.zip

    少儿编程scratch项目源代码文件案例素材-green vs blue.zip

Global site tag (gtag.js) - Google Analytics