为sqlite增加汉字拼音排序功能

勿忘初心2018-10-26 10:59

此文已由作者严跃杰授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。


Sqlite3是一款C语言实现的小型SQL数据库引擎,它体积小巧但功能强大, 性能表现也非常不错, 因此在客户端及嵌入式应用开发中受到广泛的欢迎。在网易云音乐的开发中,我们就用到了sqlite3,但是不幸的是在开发过程中发现它不支持汉字拼音排序,但这又是必须的功能。今天我们就来看下如何为sqlite3添加汉字拼音排序的功能。

首先我们想到的是既然原生不支持拼音排序,那它是否提供了扩展接口允许我们添加自定义排序功能呢?通过网上查找和文档阅读,果不其然,它为提供了安装比较函数的接口:

int sqlite3_create_collation16(
  sqlite3* db,  const char *zName,  int enc,  void* pCtx,  int(*xCompare)(void*,int,const void*,int,const void*)
)int sqlite3_create_collation(
  sqlite3* db,  const char *zName,  int enc,  void* pCtx,  int(*xCompare)(void*,int,const void*,int,const void*)
)

前者用来安装UTF-16的比较函数,后者用来安装UTF-8的比较函数。

除了扩展接口,还有一项资源是必须要有的,当然就是汉字拼音库。网上一通猛找,发现没有合适的。后来发现是云音乐资源包里已经有一个现成的汉字拼音库,省却了不少麻烦(共享在附件工程解决方案目录下)。

必要的资源有了,就可以着手实现了。

以下是获取汉字拼音实现


std::wstring LetterHelp::ConvertLetterToPinyin(const std::wstring& chinese)
{
	std::wstring str_pinyin;	for(unsigned int i = 0; i < chinese.length(); i++)
	{		wchar_t tch = chinese[i];		const char *pinyin_char = GetLetter((unsigned short)tch);		
	if(pinyin_char) 
		{			int len = strlen(pinyin_char);			
		wchar_t* wch = new wchar_t[len];
			FUTF82WConvert(pinyin_char,wch, len);
			str_pinyin.append(wch);
		}		else
		{
			str_pinyin.push_back(tch);
		}

	}	return str_pinyin;
}const char* LetterHelp::GetLetter(unsigned short ch)
{	if(ch >= HANZI_MIN && ch <= HANZI_MAX)		return m_pinyin + m_code[ch - HANZI_MIN];	else
		return nullptr;
}


以下比较函数实现:

// 两个都是汉字时比较函数static int pinyin_strcmp(const wchar_t *key1, const wchar_t *key2) {
	std::wstring str1 = LetterHelp::getInstance()->ConvertLetterToPinyin(key1);
	std::wstring str2 = LetterHelp::getInstance()->ConvertLetterToPinyin(key2);	return wcscmp(str1.c_str(), str2.c_str());
}// 升序排序时将数字,字母排到汉字前static int pinyin_cmplmpl(int nKey1, const void *pKey1,						int nKey2, const void *pKey2) {	int size = min(nKey1, nKey2);	int i = 0, flag = 0, ret = 0;;	wchar_t s[2]={0}, d[2]={0};	for (i = 0; i < size; i++) {
		flag = 0;
		ret = 0;
		s[0] = *((wchar_t*)pKey1+i);
		d[0] = *((wchar_t*)pKey2+i);		
		if ((int)s[0] > HANZI_MAX || (int)s[0] < HANZI_MIN) {
			flag += 1;
		} else {
			flag += 2;
		}		
		if ((int)d[0] > HANZI_MAX || (int)d[0] < HANZI_MIN) {
			flag += 4;
		} else {
			flag += 8;
		}		switch(flag) {		
		case  5:
			ret = wcscmp(s, d);		
				if (ret != 0) {				
				return ret;
			}			
			break;		
			case 9:			
			return -1;		
			case 6:		
				return 1;		
				case 10:
			ret = pinyin_strcmp(s, d);		
			if (ret != 0) {				
			return ret;
			}			
			break;
		}
	}	if (i >= size && nKey1 == nKey2) {	
		return 0;
	} else if (i >= size && nKey1 < nKey2) {	
		return -1;
	} else if(i >= size && nKey1 > nKey2) {		
	return 1;
	}	return -1;
}// 升序比较函数static int pinyin_cmp_asc(	
void *NotUsed,	int nKey1, const void *pKey1,	
int nKey2, const void *pKey2)
{	return pinyin_cmplmpl(nKey1,pKey1,nKey2,pKey2);
}// 降序比较函数static int pinyin_cmp_desc(	
void *NotUsed,	int nKey1, const void *pKey1,	
int nKey2, const void *pKey2)
{	int ret = pinyin_cmplmpl(nKey1,pKey1,nKey2,pKey2);	
if (ret == 0) {		
return ret;
	} else {		
	return -ret;
	}
}


在打开数据库后安装汉字拼音比较函数

// 打开数据库文件并安装汉字拼音比较函数bool Db::open(const string path) {	
if (db != nullptr) {	
	cout << "db file: " << path << " has been opened, cannot reopen." << endl;	
		return false;
	}	int ret = sqlite3_open(path.c_str(), &db);	
	if(ret != SQLITE_OK){		
	cout << "Cannot open Db file: "<< path << ", errmsg: "<< sqlite3_errmsg(db);	
		return false;
	}	unsigned short asc[] = {'p', 'i', 'n', 'y', 'i', 'n', '_', 'a', 's', 'c', 0};
	sqlite3_create_collation16(db, asc, SQLITE_UTF16, 0, pinyin_cmp_asc);	
	unsigned short desc[] = {'p', 'i', 'n', 'y', 'i', 'n', '_', 'd', 'e', 's', 'c', 0};
	sqlite3_create_collation16(db, desc, SQLITE_UTF16, 0, pinyin_cmp_desc);	return true;
}

最后如下sql进行测试

select * from person order by name collate pinyin_asc
select * from person order by name collate pinyin_desc

测试结果如下




网易云免费体验馆,0成本体验20+款云产品! 

更多网易技术、产品、运营经验分享请点击


相关文章:
【推荐】 代码混淆防止APP被反编译指南
【推荐】 pdfjs viewer 开发小结