现在有一个需求:用户表里面有将近200万条数据,查询时需要按用户名字的汉语拼音按从a-z排序。有两种解决方案:1.查询时使用数据库自带的CONVERT()函数进行转化,按拼音首字母排序;2.新加一个拼音字段(spell_name),用户注册时,将用户名字的汉语拼音也一并插入数据库。权衡之后我采用了第二种,因为用户量还会持续增长,使用数据库自带的函数会拖慢查询速度,索引也会失效,如果采用第二种加一个拼音字段,就涉及到对原来老数据拼音字段的一个初始化,数据量比较多,使用多线程,在这里记录一下。

1.使用jpinyin和emoji-java将汉字转化成拼音

引入相关jar

      <!--汉字转拼音jar--><dependency><groupId>com.github.stuxuhai</groupId><artifactId>jpinyin</artifactId><version>1.0</version></dependency><!--java操作emoji的jar--><dependency><groupId>com.vdurmont</groupId><artifactId>emoji-java</artifactId><version>4.0.0</version></dependency>
复制代码

emoji表情处理工具类

public class EmojiDealUtil extends EmojiParser {/*** 获取非表情字符串* @param input* @return*/public static String getNonEmojiString(String input) {int prev = 0;StringBuilder sb = new StringBuilder();List<UnicodeCandidate> replacements = getUnicodeCandidates(input);for (UnicodeCandidate candidate : replacements) {sb.append(input.substring(prev, candidate.getEmojiStartIndex()));prev = candidate.getFitzpatrickEndIndex();}return sb.append(input.substring(prev)).toString();}/*** 获取表情字符串* @param input* @return*/public static String getEmojiUnicodeString(String input){EmojiTransformer  transformer = new EmojiTransformer() {public String transform(UnicodeCandidate unicodeCandidate) {return unicodeCandidate.getEmoji().getHtmlHexadecimal();}};StringBuilder sb = new StringBuilder();List<UnicodeCandidate> replacements = getUnicodeCandidates(input);for (UnicodeCandidate candidate : replacements) {sb.append(transformer.transform(candidate));}return  parseToUnicode(sb.toString());}public static String getUnicode(String source){String returnUniCode=null;String uniCodeTemp=null;for(int i=0;i<source.length();i++){uniCodeTemp = "\\u"+Integer.toHexString((int)source.charAt(i));returnUniCode=returnUniCode==null?uniCodeTemp:returnUniCode+uniCodeTemp;}return returnUniCode;}
}
复制代码

汉字转换成拼音的工具类

public class ChineseToPinYinUtil {/*** 转换为不带音调的拼音字符串* @param pinYinStr 需转换的汉字* @return 拼音字符串*/public static String changeToTonePinYin(String pinYinStr) {String tempStr = null;try {tempStr = PinyinHelper.convertToPinyinString(pinYinStr, " ", PinyinFormat.WITHOUT_TONE);} catch (Exception e) {e.printStackTrace();}return tempStr;}
}
复制代码

拼音转换不做为本文点,网上搜下有很多库和代码解决,主要说下面的多线程处理。

2.使用多线程查询并更新数据库

创建初始容量为5的线程池,每次每个线程查询500条记录并处理,加个同步锁,分配给每个线程它查询的起始记录,查询出记录之后调用上面的汉字转换成拼音方法处理,完毕之后更新到数据库。

2.1接收请求方法

    //每个线程每次查询的条数private static final Integer LIMIT = 500;//起的线程数private static final Integer THREAD_NUM = 5;ThreadPoolExecutor pool = new ThreadPoolExecutor(THREAD_NUM, THREAD_NUM*2,0,TimeUnit.SECONDS,new LinkedBlockingQueue<>(100));@GetMapping("/chineseToSpellName")public void execute(){//计数器,一次转换只能一个请求调,不然会出错int count = 0;logger.info("trans start");//查询总记录数int total = userService.getTotalCount2();logger.info("total num:{}",total);int num = total/(LIMIT*THREAD_NUM) + 1;logger.info("要经过的轮数:{}",num);for(int j=0;j<num;j++){//起 THREAD_NUM 个线程并行查询更新库,加锁for(int i=0;i<THREAD_NUM;i++){synchronized(ChineseToPinYinController.class){int start = count*LIMIT;count++;pool.submit(new TransTask(start,LIMIT));}}}}
复制代码

2.2多线程处理业务方法

    class TransTask implements Runnable{int start;int limit;public TransTask(int start, int limit) {this.start = start;this.limit = limit;}@Overridepublic void run() {//查询记录并更新数据库List<User> userList =  userService.getList2(start,limit);logger.info("更新记录起始位置:{}--{}",start,limit);if(!CollectionUtils.isEmpty(userList)){userList.stream().forEach(u -> {u.setSpellName(ChineseToPinYinUtil.changeToTonePinYin(EmojiDealUtil.getNonEmojiString(u.getName())).trim());userService.updateUser2(u);});}}}
复制代码

3.不使用传统的limit分页查询数据

userService.getList2(start,num)是根据起始位置和查询条数查询记录,以前我们写的分页查询一般是这样写的:select * from table limit start,num(如:select * from user limit 0,20)。这种查询在数据量小时没有问题,但是数据量大的时候查询会非常慢,因为它走的不是索引,而是全表扫描,数据量越大,越到后面速度越慢。对于id是自增长的查询可以采用另一种查询方式,select * from table where id>start limit num(如:select * from user where id>1000 limit 20),从指定id查询num条记录。这种查询即使到百万级数据量,查询速度也不会明显变慢,因为走的是主键索引,而不是全表扫描。

4.优化后记

代码写完之后在实际使用中,数据初始化到70多万条的时候,数据库连接数开的太多,将数据库里面的全部占满了,考虑再次优化,采用分段的方式,传入两个参数,初始化记录和初始化条数。比如第一次初始化0-10万条记录,第二次初始化10到20万条记录,依次类推,这样的好处是可以人工干预,即使出错,也可以只运行出错的这部分区间数据,代码如下:

   //每个线程每次查询的条数private static final Integer LIMIT = 500;//起的线程数private static final Integer THREAD_NUM = 5;ThreadPoolExecutor pool = new ThreadPoolExecutor(THREAD_NUM,Integer.MAX_VALUE,0,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));@GetMapping("/chineseToSpellName")public void execute(@RequestParam("startId") Integer startId,@RequestParam("total") Integer total){logger.info("trans start");int num = total/(LIMIT*THREAD_NUM) + 1;logger.info("要经过的轮数:{}",num);for(int j=0;j<num;j++){//起 THREAD_NUM 个线程并行查询更新库,加锁for(int i=0;i<THREAD_NUM;i++){synchronized(ChineseToPinYinController.class){pool.submit(new TransTask(startId,LIMIT));startId+=LIMIT;}}}}class TransTask implements Runnable{int start;int limit;public TransTask(int start, int limit) {this.start = start;this.limit = limit;}@Overridepublic void run() {//查询记录并更新数据库List<User> userList =  userService.getList2(start,limit);logger.info("更新记录起始位置:{}--{}",start,limit);if(!CollectionUtils.isEmpty(userList)){userList.stream().forEach(u -> {u.setSpellName(ChineseToPinYinUtil.changeToTonePinYin(EmojiDealUtil.getNonEmojiString(u.getName())).trim());userService.updateUser2(u);});}}}
复制代码

使用多线程查询百万条用户数据将汉字转化成拼音相关推荐

  1. 昨天,A站受黑客攻击千万条用户数据外泄,量子加密能救得了吗?

    昨天,弹幕视频鼻祖网站AcFun(A站)发公告称其遭受黑客攻击导致近千万条用户数据外泄,建议用户及时更改密码.一周前,A站才被快手全资收购. 2017年11月,Uber称其曾在2016年遭黑客攻击,全 ...

  2. A 站彻底要凉?近千万条用户数据外泄!

    点击上方"CSDN",选择"置顶公众号" 关键时刻,第一时间送达! 用户出走.内容整改.高层撕逼.资本动荡......弹幕视频鼻祖 AcFun (简称 A 站) ...

  3. 6秒内找出一百万条IP数据的地理位置

    大家好,我是小小明,今天见过有群友从库里跑出了几百万条ip数据,现在想将它们转成具体的ip地址. 对于这类问题,我们常规的做法就是去百度搜索ip获取对应的地址,所有方法之一就是使用爬虫: 但是几百万个 ...

  4. 超30亿条用户数据被窃取 BAT无一幸免

    微博莫名其妙关注了乱七八糟的营销号,QQ 号忽然加了一些不认识的好友及群聊,淘宝账号不知何时多了几个好友--如果你遇到过这些问题,说明你的账号密码不幸中招,已经被他人非法窃取. 近日,绍兴警方破获了一 ...

  5. 移动运营商AIS泄漏了83亿条用户数据 容量约4.7 TB

    "#榜样的力量#数据猿公益策划活动--寻找新冠战"疫",中国数据智能产业先锋力量:申报项目.提交文章(或深度采访),即可参与此次活动最终推出的榜单.勋章.思想者合集以及人 ...

  6. 每天近百亿条用户数据,携程大数据高并发应用架构涅槃

    互联网二次革命的移动互联网时代,如何吸引用户.留住用户并深入挖掘用户价值,在激烈的竞争中脱颖而出,是各大电商的重要课题.通过各类大数据对用户进行研究,以数据驱动产品是解决这个课题的主要手段,携程的大数 ...

  7. oracle 设置查询条数,SQL、MySQL、Oracle、 Sqlite、Informix数据库查询指定条数数据的方法...

    SQL查询前10条的方法为: select top X * from table_name --查询前X条记录,可以改成需要的数字,比如前10条. select top X * from table_ ...

  8. 绿洲因涉嫌抄袭下架;Facebook 泄露 4.19 亿条用户数据;Go 1.13 发布 | 极客头条...

    快来收听极客头条音频版吧,智能播报由标贝科技提供技术支持. 「CSDN 极客头条」,是从 CSDN 网站延伸至官方微信公众号的特别栏目,专注于一天业界事报道.风里雨里,我们将每天为朋友们,播报最新鲜有 ...

  9. python从数据库读取几百万条的数据结果内存直接爆掉

    转载自:http://blog.csdn.net/luckyzhou_/article/details/69061621 Python导数据的时候,需要在一个大表上读取很大的结果集. 如果用传统的方法 ...

最新文章

  1. 嵌入式VxWorks系统开发与应用
  2. 进程和线程的区别?什么时候用进程?什么时候用线程?
  3. Ubuntu9.10 server 安装配置 vsftpd2.2.0 ftp服务器 并且 解决 putty 登陆 sshd 显示中文乱码
  4. java 发送邮件昵称_javaMail发送邮件设置发件人中文昵称
  5. could not find any translatable text for key 'Search' in bundle '/cus.crm.notes/
  6. 程序的内存分配模式(堆栈以及静态存储区,文字常量区,代码区)
  7. gohu恒温花洒使用教程_使用家庭助理构建更好的恒温器
  8. vue中怎么点击修改文字_杭州展馆设计中说明牌和说明文字怎么样才能使用最大化?...
  9. 创业公司的技术总监,去上市公司面试,结果凉了。
  10. poj3694 Network 求桥边个数[tarjan + LCA]
  11. android自定义软键盘-中文与英文大小切换
  12. mybatis plus(包米豆)json存储Mysql数据库
  13. html页面访pdf样式,html网页布局模板.pdf
  14. golden ticket和sliver ticket的区别是什么?
  15. 硬盘换个计算机怎么启动,更换硬盘后,从U盘启动安装win7的13步方法
  16. Android中MVP模式
  17. 麒麟信安操作系统:挖掘场景,与云俱进 ——携手openEuler赋能关键行业应用
  18. 博客搭建(基于hexo)
  19. RouterOS的基本设置
  20. 洛谷P2832 行路难

热门文章

  1. 富途【Web端系统测试岗】 一面
  2. 嵌入式linux触摸屏校正命令,嵌入式系统触摸屏的校准 tslib的交叉编译与测试
  3. locust分布式压测
  4. Button、ImageButton、EditText、CheckBox、ToggleButton、RadioButton和Radio
  5. 西门子S7-300控制PVC配料注塑机程序 称重仪表通讯,模拟量控制
  6. Kali-linux:atk6-thcping6命令
  7. STM32的RTC晶振不起振的原因及解决方法
  8. 11.Xcode中实时监测网络状态
  9. CentOS7设置定时任务 每隔30秒执行一次命令详解
  10. python list.count用法