Huffman编解码实现文本压缩
...{
char ch;
unsigned long weight;
struct HT *pre;
struct HT *next;
struct HT *parent;
struct HT *LChild;
struct HT *RChild;
}HuffmanTree,RateTabNode;//树结点,同时也是双链表结点
...{
char temp;
RateTabNode *head,*p,*q;
q=(RateTabNode *)malloc(sizeof(RateTabNode));
head=p=q;
q->next=NULL;
head->pre=NULL;
head->LChild=head->RChild=NULL;
q->ch=temp=fgetc(ifp);
q->weight=0;
while(!feof(ifp))
...{
if(!IsExisted(head,temp))//如果存在temp字符,则权加(IsExisted函数中);不存在,创建新结点
...{
q=(RateTabNode *)malloc(sizeof(RateTabNode));
p->next=q;
q->pre=p;
p=q;
p->next=NULL;
p->LChild=p->RChild=NULL;
p->ch=temp;
p->weight=1;
}
temp=fgetc(ifp);
}
return head;
}
int IsExisted(RateTabNode *head,char ch)//判断当前双链表中是否有ch字符,若有则数量加并返回真,否则返回假
...{
RateTabNode *temp=head;
while(temp!=NULL)
...{
if(ch==temp->ch)
...{
++temp->weight;
return 1;
}
temp=temp->next;
}
return 0;
}
HuffmanTree *HTCreate(RateTabNode *head)//双链表形式创建Huffman树
{
HuffmanTree *p1,*p2,*root;
RateTabNode *temp,*p;
root=(HuffmanTree *)malloc(sizeof(HuffmanTree));
//printf("%d/n",SizeAssure(head));
if(SizeAssure(head)==2)//当只生剩下两个元素,直接将这两棵树作为子树,返回总根结点地址
{
root->ch='/0';
root->LChild=head;
head->parent=root;
root->RChild=head->next;
root->weight=head->weight+head->next->weight;
head->next->parent=root;
head=root;
head->parent=NULL;
return head;
}
else
{
for(p=temp=head;p!=NULL;p=p->next)//找权最小的结点
{
if(temp->weight>p->weight)
temp=p;
}
p1=temp;
/****记下最小结点的地址,将它从链表中移除(分三种情况,头、尾和中间)***/
if(p1->pre==NULL)//表头
{
head=head->next;
head->pre=NULL;
}
else if(p1->next==NULL)//表尾
{
p1->pre->next=NULL;
}
else
{
p1->pre->next=p1->next;
p1->next->pre=p1->pre;
}//中间
p1->next=p1->pre=NULL;
/*********寻找次小权结点,过程同上面完全相同,找去掉最小后的最小,即次小**********/
for(temp=p=head;p!=NULL;p=p->next)
{
if(temp->weight>p->weight)
temp=p;
}
p2=temp;
if(p2->pre==NULL)
{
head=head->next;
head->pre=NULL;
}
else if(p2->next==NULL)
{
p2->pre->next=NULL;
}
else
{
p2->pre->next=p2->next;
p2->next->pre=p2->pre;
}
//取次小值
p2->next=p2->pre=NULL;
//printf("%d/n",SizeAssure(head));getch();
/*********将两个最小值建立一棵子树,并将它们的根结点插入双链表**********/
root->LChild=p1;
root->RChild=p2;
p1->parent=root;
p2->parent=root;
root->ch='/0';
root->weight=p1->weight+p2->weight;
root->next=head;
head->pre=root;
head=root;
head->pre=NULL;
head=HTCreate(head);//递归,直到剩两个结点在双链表中
}
}
void FindLeaf(RateTabNode *head,LeafNode *leafhead,int size)//递归查找叶子,记下地址到数组
{
static int num=0;
if(head->LChild==NULL&&head->RChild==NULL&&num<size)
{
leafhead[num].ch=head->ch;
leafhead[num].leaf=head;
++num;
return;
}
if(head==NULL)return;
FindLeaf(head->LChild,leafhead,size);
FindLeaf(head->RChild,leafhead,size);
}
void HFCoding(LeafNode *leafhead,CodeNode *codehead,int size)//自下向上编码,左0右1
{
int i=0,j=CodeMaxLen;
LeafNode *leaftmp=leafhead;
while(i<size)
{
j=CodeMaxLen;
codehead[i].ch=leaftmp[i].ch;
codehead[i].code[--j]='/0';
while(leaftmp[i].leaf->parent!=NULL&&j>=0)
{
if(leaftmp[i].leaf->parent->LChild==leaftmp[i].leaf)
codehead[i].code[--j]='0';
if(leaftmp[i].leaf->parent->RChild==leaftmp[i].leaf)
codehead[i].code[--j]='1';
leaftmp[i].leaf=leaftmp[i].leaf->parent;//向上
}
++i;
}
}
/********************************************/
/* Huffman编码的无损压缩V2.0 */
/* 2008.3.28 */
/* 作者:付闯 */
/********************************************/
#include<stdio.h>
#include<stdlib.h>
#include<conio.h>
#include<malloc.h>
#include<string.h>
#define MaxSize 65536
//定义缓冲区最大高度
#define CodeMaxLen 32
//定义Huffman编码最大长度
typedef struct HT
{
char ch;
unsigned long weight;
struct HT *pre;
struct HT *next;
struct HT *parent;
struct HT *LChild;
struct HT *RChild;
}HuffmanTree,RateTabNode;//树结点,同时也是双链表结点
typedef struct
{
char ch;//字符
char code[CodeMaxLen];//字符的编码
}CodeNode;//存储字符的Huffman编码
typedef struct
{
char ch;
HuffmanTree *leaf;
}LeafNode;//临时存储叶子结点的地址
typedef struct
{
char ch;
unsigned long weight;
}OFinal;//最后写入文件的单元
void compress(FILE *,FILE*);//压缩
void decompress(FILE *,FILE*);//解压缩
/*********************统计字符权并编码************************/
RateTabNode *Probability(FILE *);//建立双链表
int IsExisted(RateTabNode *,char);//统计文本中字符个数
int SizeAssure(RateTabNode *);//返回双链表大小,即字符的总个数
HuffmanTree* HTCreate(RateTabNode *);//创建Huffman树
void FindLeaf(RateTabNode *,LeafNode *,int);//找出所有叶子并记录地址
void HFCoding(LeafNode *,CodeNode *,int);//从叶子到根,从下向上的进行编码左0 右1
void buf_init(char (*)[8],int);//缓冲初始化
void wbuffer(FILE *,CodeNode *,char (*)[8],int,int);//从文件读取字节
//void bufprintf(char (*)[8],int);//打印缓冲,测试用
void zip(FILE *,char (*)[8],int);//最终将压缩编码写入输出文件
RateTabNode *RebuildLink(OFinal *,int);//双链表重建,从结构体数组
/********************以下是解压缩相关函数**********************/
void deWbuffer(FILE *,char (*)[8],int);//读文件数据到缓冲
char CodeMatch(CodeNode *,int,char *);//编码字符串的匹配
void deRbuffer(FILE *,CodeNode *,int,char (*)[8],int);//从缓冲计数据,解码写入文件
void chstract(char *,char);//将字符加入字符串末尾
int main()
{
FILE *ifp,*ofp,*ffp;
char choic;
char *ifilename,*ofilename;
ifilename=(char *)malloc(255);
ofilename=(char *)malloc(255);
printf("a.压 缩/nb.解压缩/n");
choic=getch();
if(choic=='a')
{
printf("请输入(compress)源文件名:");
scanf("%s",ifilename);
printf("请输入(compress)输出文件名:");
scanf("%s",ofilename);
if((ifp=fopen(ifilename,"rb"))==NULL)
{
printf("无法打开文件!");
getch();
return 1;
}
if((ofp=fopen(ofilename,"wb"))==NULL)
{
printf("无法创建文件!");
getch();
return 1;
}
compress(ifp,ofp);
printf("文件%s成功压缩为文件%s",ifilename,ofilename);
fclose(ifp);
fclose(ofp);
}
else
{
printf("请输入(decompress)源文件名:");
scanf("%s",ifilename);
printf("请输入(decompress)输出文件名:");
scanf("%s",ofilename);
if((ofp=fopen(ifilename,"rb"))==NULL)
{
printf("无法打开文件!");
getch();
return 1;
}
if((ffp=fopen(ofilename,"wb"))==NULL)
{
printf("无法创建文件!");
getch();
return 1;
}
decompress(ofp,ffp);
printf("文件%s成功解压缩为文件%s!",ifilename,ofilename);
fclose(ofp);
fclose(ffp);
}
getch();
return 0;
}
/*************************以下是压缩算法************************/
void compress(FILE *ifp,FILE *ofp)
{
RateTabNode *head,*temp;
HuffmanTree *root;
unsigned ArraySize;
int i=0;
LeafNode *leafhead;//存储叶子结点的地址,利于编码
CodeNode *codehead;//存储编码的数组首地址]
OFinal *ofinalhead;//把原始的字符和权信息输出文件
char buffer[MaxSize][8];//缓冲区,最大处理K数据
buf_init(buffer,MaxSize);//初始化缓冲区
head=Probability(ifp);//统计概率,建立双链表
ArraySize=SizeAssure(head);//叶子个数
ofinalhead=(OFinal *)malloc(ArraySize*sizeof(OFinal));
for(i=0,temp=head;temp!=NULL;temp=temp->next,++i)
{
ofinalhead[i].ch=temp->ch;
ofinalhead[i].weight=temp->weight;
}
root=HTCreate(head);//创建Huffman树,返回根结点
leafhead=(LeafNode *)malloc(ArraySize*sizeof(LeafNode));
codehead=(CodeNode *)malloc(ArraySize*sizeof(CodeNode));
for(i=0;i<ArraySize;++i)
{
int j=0;
for(j=0;j<CodeMaxLen;++j)
codehead[i].code[j]='#';
}
//初始化编码表全部为'#'
FindLeaf(root,leafhead,ArraySize);//找出所有叶子结点,记录地址
HFCoding(leafhead,codehead,ArraySize);//从叶子到根,从下向上进行编码
/***********以上两个函数完成编码***********/
for(i=0;i<ArraySize;++i)
printf("/n%c/t%s",codehead[i].ch,codehead[i].code);
fwrite(&ArraySize,sizeof(unsigned),1,ofp);//写入共有多少种字符
fwrite(ofinalhead,sizeof(OFinal),ArraySize,ofp);//写入编码表,以便解压
rewind(ifp);
while(!feof(ifp))
{
wbuffer(ifp,codehead,buffer,MaxSize,ArraySize);//读文件二进制,转换为或写入缓冲
zip(ofp,buffer,MaxSize);//转换成编码输出
if(feof(ifp))break;
}
}
RateTabNode *Probability(FILE *ifp)//对字符作分类并统计数量(权)
{
char temp;
RateTabNode *head,*p,*q;
q=(RateTabNode *)malloc(sizeof(RateTabNode));
head=p=q;
q->next=NULL;
head->pre=NULL;
head->LChild=head->RChild=NULL;
q->ch=temp=fgetc(ifp);
q->weight=0;
while(!feof(ifp))
{
if(!IsExisted(head,temp))//如果存在temp字符,则权加(IsExisted函数中);不存在,创建新结点
{
q=(RateTabNode *)malloc(sizeof(RateTabNode));
p->next=q;
q->pre=p;
p=q;
p->next=NULL;
p->LChild=p->RChild=NULL;
p->ch=temp;
p->weight=1;
}
temp=fgetc(ifp);
}
return head;
}
int IsExisted(RateTabNode *head,char ch)//判断当前双链表中是否有ch字符,若有则数量加并返回真,否则返回假
{
RateTabNode *temp=head;
while(temp!=NULL)
{
if(ch==temp->ch)
{
++temp->weight;
return 1;
}
temp=temp->next;
}
return 0;
}
int SizeAssure(RateTabNode *head)//测链表长度,即字符的种类数
{
RateTabNode *temp;
int num=1;
temp=head;
while(temp->next!=NULL)
{
++num;
if(temp->next==NULL)break;
temp=temp->next;
}
return num;
}
void wbuffer(FILE *ifp,CodeNode *codehead,char (*buffer)[8],int size,int ArraySize)//将字符用编码替换,写入缓冲
{
char temp;
int i=0;
unsigned long offset=0;
buf_init(buffer,MaxSize);
while(!feof(ifp)&&offset<8*size)
{
if(feof(ifp))break;
temp=fgetc(ifp);
for(i=0;i<ArraySize;++i)
{
int k=0;
if(temp==codehead[i].ch)
{
for(k=0;k<CodeMaxLen;++k)
if(codehead[i].code[k]!='#'&&codehead[i].code[k]!='/0')//不要忘记还有个'/0'
{
if(offset==8*size)break;
*(*buffer+offset)=codehead[i].code[k];
++offset;
}
}
}
}
}
/*void bufprintf(char (*buffer)[8],int size)
{
int i,j;
for(i=0;i<size;++i)
{
for(j=0;j<8;++j)
printf("%d",buffer[i][j]);
printf("/n");
}
}*/
void zip(FILE *ofp,char (*buffer)[8],int size)//写缓冲到文件
{
int i=0,j=0;
while(i<size)
{
char temp=0;
for(j=0;j<8&&buffer[i][j]!=-1;++j)
{
if(buffer[i][j]==-1)break;
temp+=(buffer[i][j]-48)<<(7-j);
}
fputc(temp,ofp);
if(buffer[i][j]==-1)break;
++i;
}
}
/**********************以下是解压缩算法************************/
void decompress(FILE *ifp,FILE *ofp)
{
int i=0;
unsigned ArraySize=0;
RateTabNode *head,*temp;
CodeNode *codehead,*codetmp;//存储编码的数组首地址]
OFinal *ofinalhead;
HuffmanTree *root;//树根
LeafNode *leafhead;//存储叶子结点的地址,利于编码
char buffer[MaxSize][8];
buf_init(buffer,MaxSize);
fread(&ArraySize,sizeof(unsigned),1,ifp);//读字符种类数
codehead=(CodeNode *)malloc(ArraySize*sizeof(CodeNode));
codetmp=(CodeNode *)malloc(ArraySize*sizeof(CodeNode));
ofinalhead=(OFinal *)malloc(ArraySize*sizeof(OFinal));
fread(ofinalhead,sizeof(OFinal),ArraySize,ifp);//读字符和权
head=RebuildLink(ofinalhead,ArraySize);
root=HTCreate(head);//创建Huffman树,返回根结点
leafhead=(LeafNode *)malloc(ArraySize*sizeof(LeafNode));
codehead=(CodeNode *)malloc(ArraySize*sizeof(CodeNode));
for(i=0;i<ArraySize;++i)
{
int j=0;
for(j=0;j<CodeMaxLen;++j)
codetmp[i].code[j]='#';
}
//初始化编码表全部为'#'
FindLeaf(root,leafhead,ArraySize);//找出所有叶子结点,记录地址
HFCoding(leafhead,codetmp,ArraySize);//从叶子到根,从下向上进行编码
/*******以下循环去除#在编码前,以便字符串的匹配********/
for(i=0;i<ArraySize;++i)
{
int j=0,k=0;
codehead[i].ch=codetmp[i].ch;
for(j=0,k=0;j<CodeMaxLen;++j)
{
if(codetmp[i].code[j]!='#')
{
codehead[i].code[k]=codetmp[i].code[j];
++k;
}
}
}
free(codetmp);
while(!feof(ifp))
{
deWbuffer(ifp,buffer,MaxSize);
deRbuffer(ofp,codehead,ArraySize,buffer,MaxSize);
}
}
RateTabNode *RebuildLink(OFinal *ofinalhead,int ArraySize)//恢复双链表结构
{
RateTabNode *head,*p,*q;
int i=0;
q=(RateTabNode *)malloc(sizeof(RateTabNode));
head=p=q;
q->next=NULL;
head->pre=NULL;
head->LChild=head->RChild=NULL;
for(i=0;i<ArraySize;++i)
{
p->ch=ofinalhead[i].ch;
p->weight=ofinalhead[i].weight;
if(i==ArraySize-1)break;
q=(RateTabNode *)malloc(sizeof(RateTabNode));
p->next=q;
q->pre=p;
p=q;
p->next=NULL;
p->LChild=p->RChild=NULL;
}
return head;
}
void deWbuffer(FILE *ifp,char (*buffer)[8],int size)
{
char temp;
int i=0;
buf_init(buffer,MaxSize);
while(i<size-1&&!feof(ifp))
{
int j=0;
temp=fgetc(ifp);
if(feof(ifp))return;
for(j=0;j<8;++j)
buffer[i][j]=((temp>>(7-j))&1)+48;//写入的是数字,注意转换为字符'1'
++i;
}
}
char CodeMatch(CodeNode *codehead,int ArraySize,char *ch)//在编码中找当前字符串是否是编码,是则返回该编码对应字符,否则返回'/0'
{
int i;
for(i=0;i<ArraySize;++i)
if(strcmp(codehead[i].code,ch)==0)
return codehead[i].ch;
return '/0';
}
void deRbuffer(FILE *ofp,CodeNode *codehead,int ArraySize,char (*buffer)[8],int size)//解码,还原文件
{
int i=0,j;
char temp[CodeMaxLen];
char ch;
temp[0]='/0';
while(*(*buffer+i)!=-1)//读一个字符
{
chstract(temp,*(*buffer+i));//将字符加到字符串temp后
if((ch=CodeMatch(codehead,ArraySize,temp))!='/0')//匹配则解码,否则字符串继续变长
{
fputc(ch,ofp);
temp[0]='/0';
}
++i;
}
}
void chstract(char *temp,char ch)//把一个字符加到当前字符串末尾
{
int i;
for(i=0;i<CodeMaxLen;++i)
{
if(temp[i]=='/0')
{
temp[i]=ch;
temp[i+1]='/0';
return;
}
}
}
/***************共用算法****************/
HuffmanTree *HTCreate(RateTabNode *head)//双链表形式创建Huffman树
{
HuffmanTree *p1,*p2,*root;
RateTabNode *temp,*p;
root=(HuffmanTree *)malloc(sizeof(HuffmanTree));
//printf("%d/n",SizeAssure(head));
if(SizeAssure(head)==2)//当只生剩下两个元素,直接将这两棵树作为子树,返回总根结点地址
{
root->ch='/0';
root->LChild=head;
head->parent=root;
root->RChild=head->next;
root->weight=head->weight+head->next->weight;
head->next->parent=root;
head=root;
head->parent=NULL;
return head;
}
else
{
for(p=temp=head;p!=NULL;p=p->next)//找权最小的结点
{
if(temp->weight>p->weight)
temp=p;
}
p1=temp;
/****记下最小结点的地址,将它从链表中移除(分三种情况,头、尾和中间)***/
if(p1->pre==NULL)//表头
{
head=head->next;
head->pre=NULL;
}
else if(p1->next==NULL)//表尾
{
p1->pre->next=NULL;
}
else
{
p1->pre->next=p1->next;
p1->next->pre=p1->pre;
}//中间
p1->next=p1->pre=NULL;
/*********寻找次小权结点,过程同上面完全相同,找去掉最小后的最小,即次小**********/
for(temp=p=head;p!=NULL;p=p->next)
{
if(temp->weight>p->weight)
temp=p;
}
p2=temp;
if(p2->pre==NULL)
{
head=head->next;
head->pre=NULL;
}
else if(p2->next==NULL)
{
p2->pre->next=NULL;
}
else
{
p2->pre->next=p2->next;
p2->next->pre=p2->pre;
}
//取次小值
p2->next=p2->pre=NULL;
//printf("%d/n",SizeAssure(head));getch();
/*********将两个最小值建立一棵子树,并将它们的根结点插入双链表**********/
root->LChild=p1;
root->RChild=p2;
p1->parent=root;
p2->parent=root;
root->ch='/0';
root->weight=p1->weight+p2->weight;
root->next=head;
head->pre=root;
head=root;
head->pre=NULL;
head=HTCreate(head);//递归,直到剩两个结点在双链表中
}
}
void FindLeaf(RateTabNode *head,LeafNode *leafhead,int size)//递归查找叶子,记下地址到数组
{
static int num=0;
if(head->LChild==NULL&&head->RChild==NULL&&num<size)
{
leafhead[num].ch=head->ch;
leafhead[num].leaf=head;
++num;
return;
}
if(head==NULL)return;
FindLeaf(head->LChild,leafhead,size);
FindLeaf(head->RChild,leafhead,size);
}
void HFCoding(LeafNode *leafhead,CodeNode *codehead,int size)//自下向上编码,左右
{
int i=0,j=CodeMaxLen;
LeafNode *leaftmp=leafhead;
while(i<size)
{
j=CodeMaxLen;
codehead[i].ch=leaftmp[i].ch;
codehead[i].code[--j]='/0';
while(leaftmp[i].leaf->parent!=NULL&&j>=0)
{
if(leaftmp[i].leaf->parent->LChild==leaftmp[i].leaf)
codehead[i].code[--j]='0';
if(leaftmp[i].leaf->parent->RChild==leaftmp[i].leaf)
codehead[i].code[--j]='1';
leaftmp[i].leaf=leaftmp[i].leaf->parent;//向上
}
++i;
}
}
void buf_init(char (*buffer)[8],int size)//缓冲初始化
{
int i,j;
for(i=0;i<size;++i)
for(j=0;j<8;++j)
buffer[i][j]=-1;
}
此程序还有一点小问题,会造成解压后文本与原文本有一两个字符的出入。
这是因为程序中压缩算法里,当缓冲区中二进制位个数不足下8个的时候(比如11),算法还是
会写入8位(1字节),相当于写入(11000000),所以当解压缩时,会多读入6位(6个0),如果这6个0序列中,存在编码,则程序会错误译码,最坏情况会出入6个字节(当0是某字符的编码时,错译6个;当00时,则3个字符;依次类推),最好情况就是这6个0不含有任何编码。
时间关系,这个问题没来得及修正,修正也较简单,只要压缩时把原文件中字节数一并写入即可,解压时,如果超过这个数以后的字符全都无效。
Huffman编解码实现文本压缩相关推荐
- 实验三 Huffman编解码算法实现与压缩效率分析
一.Huffman编解码原理 1. Huffman编码 对原始文件进行Huffman编码,首先需要解决以下几点问题: 文件符号的概率分布情况是怎样的? Huffman树是如何建立的? 建立起Huffm ...
- 数据压缩 实验三 Huffman编解码算法实现与压缩效率分析
实验目的 掌握Huffman编解码实现的数据结构和实现框架, 进一步熟练使用C编程语言, 并完成压缩效率的分析. 实验原理 1.本实验中Huffman编码算法 (1)将文件以ASCII字符流的形式读入 ...
- Huffman编解码
Huffman编解码算法实现与压缩效率分析 一.背景知识及相关公式 1.信源熵 信源熵是信息的度量单位,一般用H表示,单位是比特,对于任意一个随机变量,它的熵定义为,变量的不确定性越大,熵也就越大. ...
- 多媒体技术与应用之图像Huffman编解码
多媒体技术与应用之图像Huffman编解码 一.实验内容 1.了解BMP图像的格式,实现BMP图片格式的数据域及文件头的分离 2.熟悉Huffman编码原理 3.使用Huffman编码算法对给定图像文 ...
- Huffman编解码完全注释
Huffman编解码完全注释 /** huffman - Encode/Decode files using Huffman encoding.* Copyright (C) 2003 Douglas ...
- huffman编解码算法实验与压缩效率分析
一.基本原理 1.huffman编码原理 huffman编码是一种无失真编码方式,是可变长(VLC)编码的一种. huffman编码基于信源的概率统计模型,基本思路是出现概率大的信源符号编长码,出现概 ...
- 实验三—Huffman编解码
一.实验原理 1.Huffman编码的步骤: (1)首先将所有字符发生的概率从小到大进行排序: (2)将最小的两个概率进行两两一合并,之后继续找最小的两个概率进行合并包括前面已经合并的和数: (3)一 ...
- 数据压缩原理 实验三 Huffman编解码算法实现与压缩效率分析
实验原理 Huffman编码是一种无失真编码方式,是一种可变长编码,它将出现概率大的信源符号短编码,出现概率小的信源符号长编码. 编码步骤: ①将文件以ASCII字符流的形式读入,统计每个符号的发生概 ...
- Huffman 编解码算法实现与压缩效率分析
一.实验原理 1 熵,又称为"信息熵" (Entropy) 1.1 在信息论中,熵是信息的度量单位.信息论的创始人 Shannon 在其著作<通信的 数学理论>中提出了 ...
最新文章
- 关于空指针(指针指向为NULL)和void类型的指针的理解
- python生成器使用场景桌面_Python – 如何更简洁地使用生成器?
- PMCAFF微课堂「已结束」 | 产品汪如何做好“时间管理”让效率提高100倍
- 小余学调度:学习记录2021.8月
- thinkphp5.0学习(九):TP5.0视图和模板
- CSDN博客图片水印|自定义水印|去除水印
- apache php 重写url无效,apache用rewrite重写url时出现问题
- Hexo+NexT搭建博客笔记
- oracle日期虚数0去掉,第 14 章 使用复数运算库
- 解决idea创建ssm项目找不到mybatis的mapper的xml文件问题
- struts2中,在使用 convention 插件的情况下,如何使用 “chain” 这个resu
- 实用多媒体技术 课程习题及解答
- excel小写转大写公式_EXCEL人民币金额小写转大写
- CADD之分子对接二:简单autodockvina对接流程——redocking
- 宁夏移民文化的内涵及特点
- 你想要的宏基因组-微生物组知识全在这(1909)
- CVPR 2021 论文大盘点-去阴影、去反光、去高光、去伪影篇
- python递归必须要有_Python的递归
- centos7.4安装
- 基本数据类型和内置方法 08