您的位置:首页 > 其它

gbk编码的简介以及针对gbk文本飘红截断原理以及实现

2014-02-11 10:46 429 查看
一个检索系统,在归并拉链并获得摘要数据之后,必不可少的环节是飘红截断。

对于整个架构来说,检索索引以及rank等后端一般使用c/c++来实现,真正的展现ui可以使用php/python等脚本语言来实现。对于飘红而言,可以放在ui端使用php截断飘红,也可以放在后端通过c/c++来飘红截断。文本编码可以用gbk也可以用utf-8。对于存储而言,如果使用gbk编码可以比utf-8

使用php的优点:操作简单,有大量现成的库,不需要关注gbk等具体实现,通过mb_str库可以搞定所有事情,缺点在文本较长的时候性能及其低下。显而易见,使用C/C++的有点是性能极高,但是确定是需要自己去关注字符编码。

因为排期较紧,我起初使用了php来进行飘红截断,一个文本(字数大概在5k左右)飘红大概花了200+ms,这个性能是不能忍受的,之后我只好改成了c/c++来进行飘红截断,性能得到显著提升,耗时只有0.01ms。性能提升达到万倍。

现在描述下怎么来使用c/c++来飘红截断文本。

1、 gbk简介

首先先简要介绍一下gbk(gb2312编码)。

GBK是在国家标准GB2312基础上扩容后兼容GB2312的标准,包含所有的中文字符。一个中文需要3个字节,最高位为1,所以第一个字节大于0x80. 此外字符编码还有utf-8。gbk和utf-8之间可以通过Unicode编码进行转换。gbk,utf-8,Unicode之间的关系如果有不了解的请自己Google或者百度.

2、飘红需求

2.1、输入:

需要截断文本content
需要飘红的词组hi_words,用"|"进行进行分割
需要截断的字数 len

2.2、飘红截断规则

优先截断字节数目一定;优先截取飘红词右边的整句;如果整句不到截断字数,在拿飘红词左边的句子进行填充。

3、实现

3.1 把输入的飘红词语翻入vector.

这个使用strtok_r轻轻松松搞定(注意不要使用非线程安全的strtok).

int getHighWord(char* high_words, vector<string>& words)
{
char keyword[1024];
char *ptok = NULL;
snprintf(keyword, sizeof(keyword), "%s", high_words);
char *part = strtok_r(keyword, "|", &ptok);
while( part != NULL)
{
string tmp(part);
words.push_back(tmp);
part = strtok_r(NULL, "|", &ptok);
}
return 0;
}


3.2、把句子分隔符放入到vector中。

sep_china是中文句子分隔符,sep_uni是英文分割符,他们都是一个整句。

vector<string> sep_china;
vector<string> sep_uni;
sep_china.push_back(",");
sep_china.push_back("。");
sep_china.push_back(";");
sep_china.push_back(":");
sep_china.push_back("!");

sep_uni.push_back(",");
sep_uni.push_back(".");
sep_uni.push_back("?");
sep_uni.push_back(";");
sep_uni.push_back(":");


3.3、具体处理流程



3.4 判断有没有这些words,返回pos_word

string str_cnt(content);
//首先判断有没有这个word
int pos_word = -1;
string min_word;
int i;
for(i=0; i<words.size(); i++)
{
pos_word = str_cnt.find(words[i]);
if(pos_word > 0)
{
min_word = words[i];
break;
}
}

//如果没有找到,直接截断返回
if( pos_word < 0)
{
if( num <= getExtraWord(content, hi_word, 0, num) )
{
strcat(hi_word,"...");
return 0;
}
}


3.5 计算word前面字符数before,后面字符串after,还需要截断字符数left,以及word前面第一个标点的偏移位置quoto(需要考虑中文和英文)。

//还需要多少字节
int left = num - min_word.size();
//前面有多少字节
int before = pos_word;
//后面还有多少字节
int after = str_cnt.size() - min_word.size() - pos_word;
//获得前一个标点的位置
int pos_quoto_china = -1;
int pos_quoto_uni = -1;
string quoto_str = str_cnt.substr(0, pos_word);
pos_quoto_china = findMaxPos(quoto_str, sep_china) + 2; //一个标点2个字符
pos_quoto_uni = findMaxPos(quoto_str,sep_uni) +1 ; //一个标点一个字符
int quoto_pos = pos_quoto_uni > pos_quoto_china ? pos_quoto_uni : pos_quoto_china;
//获得前一个标点有多少字节
int quoto = quoto_pos > 0 ? pos_word - quoto_pos : 0;


3.6 根据before, after, quoto, left计算目前status

int getStatus(int before, int after, int quoto, int left)
{
int status = 0;
if(quoto >= left)
{
//直接截断quoto
status = 0;
}
else if( quoto + after > left)
{
//返回截断quoto+left
status =1 ;
}
else
{
//返回截断after和left-after的前面
status =2;
}
return status;
}


3.7 根据返回的status进行处理

char before_word[1024];
char after_word[1024];
int left_cnt;
int right_cnt;
before_word[0] = after_word[0] = '0';

switch(status)
{
case 0:
getExtraWord(content, hi_word, pos_word, left);
strcat(hi_word, "...");
break;
case 1:
left_cnt = getExtraWord(content, before_word, pos_word -1, -1 * quoto);
right_cnt = getExtraWord(content, after_word, pos_word + min_word.size(), left- quoto);
if( left_cnt < pos_word)
{
strcpy(hi_word, "...");
}
strcat(hi_word, before_word);
strcat(hi_word, min_word.c_str() );
strcat(hi_word, after_word);
strcat(hi_word, "...");
break;
case 2:
right_cnt = getExtraWord(content, before_word, pos_word + min_word.size() , after);
left_cnt = getExtraWord(content, after_word, pos_word-1 , after- left);
if( left_cnt < pos_word)
{
strcpy(hi_word, "...");
}
strcat(hi_word, before_word);
strcat(hi_word, min_word.c_str() );
strcat(hi_word, after_word);
strcat(hi_word, "...");
break;
default:
getExtraWord(content, hi_word, 0, num);
break;
}


3.8 截断函数

其中最为核心的为截断函数,如下所示,从word的begin位置截取length个字符(length小于0表示从左截断,0x80是判断标准。

int getExtraWord(const char* word, char* res, int begin, int length)
{
if(length == 0 )
{
res[0] = '\0';
return 0;
}
int i;
int flag;
const char *str = word + begin;
const char *chech_vaild = (length >0 )? str : str+1;

if( false == checkVaild(chech_vaild) )
{
cout << "not vaild\n";
//尽力修复下吧
begin ++;
str++;
}
//如果从后截断 word没有 length,直接返回
if( (int)strlen(str)  <= length  )
{
strcpy(res, str);
return strlen(str);
}

//如果从前截断 word没有lenght长
if( begin <= -1 * length)
{
strncpy(res, word, begin);
res[begin ] =  '\0';
return begin;
}

flag = length > 0 ? 1 : -1;

int num = 0;

while( *str )
{
//如果是中文
if((unsigned char)*str > 0x80)
{
num += 2;
str += flag * 2;
}
else
{
num ++;
str += flag;
}

if( num >= flag * length)
{
break;
}
}
res[0] = '\0';
i = 0;
str = (length  > 0) ? word + begin : word + begin - num + 1;
while(*str)
{
res[i++] =  *(str++);
if(i==num)
{
break;
}
}
res[i] = '\0';
return i;
}


4、结果

4.1 测试case:

int main()
{
const char* content = "啊。鸡 从到信阳,然后在火车站有直达鸡公山的汽车,只收10元。约一个小时就到鸡公山脚下,以前票价是63,自从港中旅进驻鸡公山后,现在好像改成123了,当然了,港中旅把鸡公山搞得更加漂亮和特色了。 在鸡公山门口有去山顶的旅游大巴,单程15元,往返的20元,不过建议大家做单程上去,然后步行从登山古道或者长生谷下山,会更加有趣味。和朋友在登山古道的入口处景区处于全监控状态,所以相当安全。美龄舞厅外面的招牌很显眼啊。颐庐是鸡公山上最有名的建筑,尽管世界各国都曾有在山上建房子,但惟独中国人靳";

vector<string> words;
char* hi = "信阳";
getHighWord(hi, words);
for(int i=0; i<words.size(); i++)
{
cout << words[i] << endl;
}
char hi_word[1024];
hi_word[0] = '\0';
getHilight(content, hi_word, words, 200);
cout << "hi_word is " << hi_word << endl;
}


4.2 结果:



5、全部源代码

#include <stdio.h>
#include <iostream>
#include <string>
#include <vector>
using namespace std;

/** 查看一句话是否是完整的gbk语句 */
bool checkVaild(const char* word)
{
bool vaild = true;
while(*word)
{
if( (unsigned char)*word++ > 0x80 )
{
vaild = !vaild;
}
}
return vaild;
}

int getExtraWord(const char* word, char* res, int begin, int length) { if(length == 0 ) { res[0] = '\0'; return 0; } int i; int flag; const char *str = word + begin; const char *chech_vaild = (length >0 )? str : str+1; if( false == checkVaild(chech_vaild) ) { cout << "not vaild\n"; //尽力修复下吧 begin ++; str++; } //如果从后截断 word没有 length,直接返回 if( (int)strlen(str) <= length ) { strcpy(res, str); return strlen(str); } //如果从前截断 word没有lenght长 if( begin <= -1 * length) { strncpy(res, word, begin); res[begin ] = '\0'; return begin; } flag = length > 0 ? 1 : -1; int num = 0; while( *str ) { //如果是中文 if((unsigned char)*str > 0x80) { num += 2; str += flag * 2; } else { num ++; str += flag; } if( num >= flag * length) { break; } } res[0] = '\0'; i = 0; str = (length > 0) ? word + begin : word + begin - num + 1; while(*str) { res[i++] = *(str++); if(i==num) { break; } } res[i] = '\0'; return i; }

int getStatus(int before, int after, int quoto, int left) { int status = 0; if(quoto >= left) { //直接截断quoto status = 0; } else if( quoto + after > left) { //返回截断quoto+left status =1 ; } else { //返回截断after和left-after的前面 status =2; } return status; }

int findMaxPos(string content, vector<string>quotos)
{
int i;
int pos = string::npos;
int tmp_pos;
for(i=0; i<quotos.size(); i++)
{
tmp_pos = content.rfind(quotos[i]);
if( tmp_pos > pos)
{
pos = tmp_pos;
}
}
return pos;
}

int getHilight(const char* content, char* hi_word, vector<string> words, int num)
{

//如果不够长
if( strlen(content) <= num )
{
strcpy(hi_word, content);
return 0;
}

//首先分词
vector<string> sep_china;
vector<string> sep_uni;
sep_china.push_back(",");
sep_china.push_back("。");
sep_china.push_back("?");
sep_china.push_back(";");
sep_china.push_back(":");
sep_china.push_back("!");

sep_uni.push_back(",");
sep_uni.push_back(".");
sep_uni.push_back("?");
sep_uni.push_back(";");
sep_uni.push_back(":");
sep_uni.push_back(";");
//sep_uni.push_back(" ");

//内容
string str_cnt(content); //首先判断有没有这个word int pos_word = -1; string min_word; int i; for(i=0; i<words.size(); i++) { pos_word = str_cnt.find(words[i]); if(pos_word > 0) { min_word = words[i]; break; } } //如果没有找到,直接截断返回 if( pos_word < 0) { if( num <= getExtraWord(content, hi_word, 0, num) ) { strcat(hi_word,"..."); return 0; } }

//还需要多少字节 int left = num - min_word.size(); //前面有多少字节 int before = pos_word; //后面还有多少字节 int after = str_cnt.size() - min_word.size() - pos_word; //获得前一个标点的位置 int pos_quoto_china = -1; int pos_quoto_uni = -1; string quoto_str = str_cnt.substr(0, pos_word); pos_quoto_china = findMaxPos(quoto_str, sep_china) + 2; //一个标点2个字符 pos_quoto_uni = findMaxPos(quoto_str,sep_uni) +1 ; //一个标点一个字符 int quoto_pos = pos_quoto_uni > pos_quoto_china ? pos_quoto_uni : pos_quoto_china; //获得前一个标点有多少字节 int quoto = quoto_pos > 0 ? pos_word - quoto_pos : 0;
int status = getStatus(before, after, quoto, left);

char before_word[1024]; char after_word[1024]; int left_cnt; int right_cnt; before_word[0] = after_word[0] = '0'; switch(status) { case 0: getExtraWord(content, hi_word, pos_word, left); strcat(hi_word, "..."); break; case 1: left_cnt = getExtraWord(content, before_word, pos_word -1, -1 * quoto); right_cnt = getExtraWord(content, after_word, pos_word + min_word.size(), left- quoto); if( left_cnt < pos_word) { strcpy(hi_word, "..."); } strcat(hi_word, before_word); strcat(hi_word, min_word.c_str() ); strcat(hi_word, after_word); strcat(hi_word, "..."); break; case 2: right_cnt = getExtraWord(content, before_word, pos_word + min_word.size() , after); left_cnt = getExtraWord(content, after_word, pos_word-1 , after- left); if( left_cnt < pos_word) { strcpy(hi_word, "..."); } strcat(hi_word, before_word); strcat(hi_word, min_word.c_str() ); strcat(hi_word, after_word); strcat(hi_word, "..."); break; default: getExtraWord(content, hi_word, 0, num); break; }
return 0;
}

int getHighWord(char* high_words, vector<string>& words) { char keyword[1024]; char *ptok = NULL; snprintf(keyword, sizeof(keyword), "%s", high_words); char *part = strtok_r(keyword, "|", &ptok); while( part != NULL) { string tmp(part); words.push_back(tmp); part = strtok_r(NULL, "|", &ptok); } return 0; }

int main()
{
const char* content = "啊。鸡 从到信阳,然后在火车站有直达鸡公山的汽车,只收10元。约一个小时就到鸡公山脚下,以前票价是63,自从港中旅进驻鸡公山后,现在好像改成123了,当然了,港中旅把鸡公山搞得更加漂亮和特色了。 在鸡公山门口有去山顶的旅游大巴,单程15元,往返的20元,不过建议大家做单程上去,然后步行从登山古道或者长生谷下山,会更加有趣味。和朋友在登山古道的入口处景区处于全监控状态,所以相当安全。美龄舞厅外面的招牌很显眼啊。颐庐是鸡公山上最有名的建筑,尽管世界各国都曾有在山上建房子,但惟独中国人靳";

vector<string> words;
char* hi = "信阳";
getHighWord(hi, words);
for(int i=0; i<words.size(); i++)
{
cout << words[i] << endl;
}
char hi_word[1024];
hi_word[0] = '\0';
getHilight(content, hi_word, words, 100);
cout << "hi_word is " << hi_word << endl;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: