一个线上全文索引BUG的排查:关于类阿拉件数字的分词与检索

说到全文检索的分词,多半讲到的是中(日韩)文分词,少有英文等拉丁文系语言,因为英语单词天然就是分词的。
但更少讲到阿拉伯数字。比如金额,手机号码,座机号码等等。

以下不是传统的从0开始针对mysql全文索引前世今生讲起。
我更喜欢从一个小问题入手,见缝插针的将相关的知识点,以非时间线性顺序零散穿插起来。

从一个线上的BUG说起

我们有一张人口表,里面的数据有多种数据源合并而来,因此每个用户的手机号可能有多个。
这也很好理解,有的人就是有多个手机号,有的人就是经常换手机号,对吧。
现在有个功能需要通过手机号去关联用户。

因为手机号有多个,所以要么使用like进行模糊匹配。用户表有上千万条记录,这样的效率肯定是不能接受的。

select * from t_user where phone like '%13112345678%'

要么使用另一个折中的方案,将手机号单独成表,用户表对手机号表一对多关联。
这种方式效率上能接受,但需要改变现有数据结构,故放弃。

select u.id,u.username,u.phone from t_user u LEFT JOIN t_user_phone p on u.id = p.user_id where p.phone = '13112345678'

最终选用全文索引。(mysql 5.7.6+)

先在用户表针对手机号创建一个全文索引。
使用内置分词引擎ngram

CREATE FULLTEXT INDEX idx_full_text_phone ON t_user (phone) WITH PARSER ngram;

当使用手机模糊查询关联用户时可使用以下语句。

  1. 布尔模式模糊检索
select * from t_user where match(phone) AGAINST('13996459860' in boolean mode)
  1. 自然语言模式。mysql默认为此模式,所以第2条sql没有显式指定时,仍然为自然语言模式。
select * from t_user where match(phone) AGAINST('13996459860' in NATURAL LANGUAGE mode)
或
select * from t_user where match(phone) AGAINST('13996459860')

根据我们的需求,查询手机号需要全匹配才算命中。所以选择布尔模式。
自然语言模式做不到。
关于布尔模式和自然语言模式的区别,后面做介绍。


以上算是简单的背景介绍。

但是
万恶的但是,虽迟但到

有一天产品过来告诉我,某个手机号关联出来上百个人。
他问,这种情况是正常的吗?

他如果直接说你这里有个bug,我可能直接就怼回去了(bushi ?
但是他说得这么委婉,我反而没底了。 ?


不要对一个程序员说:你的代码有Bug。他的第一反应是:①你的环境有问题吧;②S13你会用吗?
如果你委婉地说:你这个程序和预期的有点不一致,你看看是不是我的使用方法有问题?
他本能地会想:woco!是不是出Bug了!

直觉告诉我这不正常,不然这个人是搞电诈或者海王吗?

我拿手机号去数据库里查询。使用布尔模式全文检索,确实关联出来多个人。
但也确实是个BUG.

我们来完整地模拟一下。
先创建一张测试用户表。
phone字段加上全文索引,使用ngram分词器。

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL,
  `username` varchar(10) COLLATE utf8_bin DEFAULT NULL,
  `phone` varchar(50) COLLATE utf8_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  FULLTEXT KEY `idx_full_text_phone` (`phone`) /*!50100 WITH PARSER `ngram` */ 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

插入几条测试数据

-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES ('1', '张三', '13996459860,15987569874,0797-12345');
INSERT INTO `t_user` VALUES ('2', '李四', '0797-6789');
INSERT INTO `t_user` VALUES ('3', '王五', '0797-94649');

正常情况下

select * from t_user where match(phone) AGAINST('13996459860' in boolean mode)
select * from t_user where match(phone) AGAINST('13996459860' in NATURAL LANGUAGE mode)
select * from t_user where match(phone) AGAINST('13996459860')

都能得到

异常情况

select * from t_user where match(phone) AGAINST('0797-12345' in boolean mode)

得到结果

可以看到后面两条记录不是预期的结果。
也是产品经理反映的问题。

大家应该都猜到了,就是座机号的原因。嗯,用户有个座机,这很河狸嘛。

都是广义上的联系方式嘛。

看起来,这条SQL是将包含0797的数据行都返回了,但我使用的是布尔模式,要求全部匹配上0797-12345才返回。

我猜可能是'-'导致分词的问题,将其分成了两部份。


分词器


分词就是对需要进行搜索的关键词进行拆分。MySQL最初支持全文索引时,使用的是parser (拉丁语法分词器,通过空格来分词),
如英文I am programmer ,天然可以通过空格拆分成I am programmer3个单词,这也就是前文说的英语天然没有分词的问题。

但对于像中文这类不以空格拆分词语的语言来说无法适用。
因此MYSQL5.7.6后提供了n_gram parser字符长度分词器) ,对中文的全文索引支持更友好,分词器的使用也很简单,创建索引时添加 WITH PARSER ngram即为使用n_gram parser(字符长度分词器),不加则默认使用传统parser(拉丁语法空格分词器)。

注意字符长度分词器这几个字,故名思义,它就是按字符的长度来分词的,之所以单独提出来,是区别于基于NLP自然语义的分词,如复旦分词等。

比如我是程序员这个短句,如果按照自然语义分析来进行分词的话,它可能会分成 程序 程序员等。
断不可能分出来序员。除非分词器有问题。

n_gram parser分词器就有可能。 mysql默认分词长度为2,可在my.cnf里进行配置,ngram_token_size = 2指定分词长度。

针对不同的分词长度,我是程序员这个短句可以有以下多种分词效果。


ngram_token_size=1: '我', '是', '程', '序', '员'
ngram_token_size=2: '我是', '是程', '程序' , '序员' 
ngram_token_size=3: '我是程', '是程序' , '程序员'
...
ngram_token_size=5: '我是程序员'
...
最大ngram_token_size=10

我的测试库ngram_token_size为2,加个字段简单测试一下。

单个字搜不到,因为最小分词单位为2。

搜索程序序员都能得到正确的结果。




以上是汉字的分词,回到今天的正题,对于阿拉伯数字呢?
如金额23.45元,手机号13912345678,座机号0797-12345678,日期2023-01-01等等。

针对上面说到的BUG,座机号0797-12345678关联出来了多个带0797但-后面不相同的号码,
我一开始以为是-的问题。它将0797-12345678分成了079712345678两部份。

但通过这一小节的n_gram parser的介绍,我们知道它是基于长度的分词器,那么原因肯定就不是这样的。

通过以下两句SQL可以证明它是两两拆分的。

select * from t_user where match(phone) AGAINST('7-' in NATURAL LANGUAGE mode)
select * from t_user where match(phone) AGAINST('07' in boolean mode)

7-07都能将3条记录全部匹配出来。

但是在布尔模式下,7-搜索不出来。

为什么呢?

这里mysql把7-中的-当成逻辑运算符了,而不是整体当作一个搜索关键词。


stopword


内置的MySQL全文解析器将单词与stopword 列表中的条目进行比较。如果一个单词在stopword列表当中,则该单词将从索引中排除。

对于ngram解析器,stopword处理的执行方式不同。ngram解析器不排除与stopword中的条目相等的令牌,而是排除包含stopword的令牌。

例如,假设ngram_token_size=2,包含a,b的文档将被解析为a,,b
如果逗号被定义为stopword,则a,,b都将从索引中排除,因为它们包含逗号。


同理,如果stopword当中包含-,同时ngram_token_size=4,那么座机号0797-1789就被拆分成两个大的部份,07971789
其中 797-1 97-17 7-178 等都将被排除。

如此以上猜想成立的话,就有可能导致开头的BUG。 前提是wordstop当中包含-

在innodb当中,stopword可以通过INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD表来查看。
可以通过此表来自定义删除或添加stopword,从而改变分词规则。

通过查看,可以发现'-'并不在stopword当中,所以上面的猜想是错误的,并不是这个原因导致的BUG。

mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD;
+-------+
| value |
+-------+
| a     |
| about |
| an    |
| are   |
| as    |
| at    |
| be    |
| by    |
| com   |
| de    |
| en    |
| for   |
| from  |
| how   |
| i     |
| in    |
| is    |
| it    |
| la    |
| of    |
| on    |
| or    |
| that  |
| the   |
| this  |
| to    |
| was   |
| what  |
| when  |
| where |
| who   |
| will  |
| with  |
| und   |
| the   |
| www   |
+-------+
36 rows in set (0.00 sec)

布尔模式的逻辑运算符


mysql全文检索有两种最常用的方式。自然语言模式和布尔模式。


自然语言模式


对于自然语言模式搜索,搜索项被转换为ngram项的并集。例如,字符串abc(假设ngram_token_size=2)被转换为ab bc。给定两个文档,一个包含ab,另一个包含abc,搜索词ab bc匹配这两个文档。

可以简单的理解为,将搜索关键词再拆分,与文档进行模式匹配。

上图所示,文档中包含12和'0997'都被命中了。


布尔模式


对于布尔模式搜索,搜索项被转换为ngram短语搜索。例如,字符串abc(假设ngram_token_size=2)被转换为ab bc。给定两个文档,一个包含ab,另一个包含abc,搜索短语ab bc只匹配包含abc的文档。

可以理解为不会对关键词进行再拆分,相当于对搜索关键词进行全匹配。

使用相同的测试数据和相当的搜索关键词,使用布尔模式搜索。

结果为空。 没有数据被命中。


但是

在布尔模式下搜索0797-12345命中了0797-946490797-1789
但不会命中'07','09','12'等。

我只能解释为,布尔模式下,搜索关键词0797-12345中的'-'被当成语法了,导致无形中被拆分成了079712345两部份。

但是,我从mysql官网没有找到证据。 所以此点存疑。各位看官要有自己的思考,不要被我误导!

跟上一小节当中'7-'没有命中任何记录一样,也是布尔模式下语法的原因。

现在我们来讨论一下布尔模式下的逻辑运算符问题。

布尔模式的逻辑运算符

  1. +
    select * from t_user where match(phone) AGAINST('a +b' in boolean mode)
    其中 + 会被识别成逻辑运算符,而不是将a +b作为一个整体,以下同理。
    'a +b' 指'a'和'b'必须同时出现才满足搜索条件。
  2. -
    select * from t_user where match(phone) AGAINST('0797 -12345' in boolean mode)
    0797 -123450797必须包含,但不包含12345才能满足搜索条件。
    以下查询排除了包含0797-12345的记录。

    注意-前后空格 0797 -12345才表示包含0797 同时不包含12345.
    0797-12345等于0797 - 12345,它并不等于0797 -12345
    有图为证:

  3. > <
    提高/降低该条匹配数据的权重值。不管使用>还是 <,其权重值均大于没使用其中任何一个的。
    select * from t_user where match(phone) AGAINST('0797(>94649 <12345)' in boolean mode)
    表示匹配0797,同时包含94649的列往前排,包含12345的往后排
    select * from t_user where match(phone) AGAINST('a > b' in NATURAL LANGUAGE mode)
  4. ()
    相当于表达式分组,参考上一个例子。
  5. *
    通配符,只能在字符串后面使用
  6. "
    完全匹配,被双引号包起来的单词必须整个被匹配。
    select * from t_user where match(phone) AGAINST('"0797-1789"' in boolean mode)
    "0797-1789"中不可再分。其它包含0797-1234等记录就不再匹配。

解决方案


现在,让我们回到最初的美好。
我们遇到了一个问题,一个座机号0979-1789全文检索返回了不完全匹配的记录。

那么,想要完全匹配,需要怎么做呢。
经过上面的旅程,我们有了两种方案。

  1. 使用 ""
    将座机号包起来,"0979-1789",表示此搜索关键词不可再分。自然就能全匹配。
  2. 主动拆分,再使用+
    我们知道,之所以座机号能将不完全匹配的记录查询出来,是因为将座机号当中的"-"当成了逻辑运算符,从而导致了座机号被拆分成了两部份。
    那我们先主动将座机号拆分两部份,再使用逻辑运算符"+",表示两部份都必须包含才能返回。

建议使用第一种方法。

其它的电话号码表示方法,比如区号+电话号码,023+12345678,国际长途0086-10-1234567或+86-573-82651630,610-643-4567等。
这里面涉及到+-等逻辑运算符,用第一种方法最安全。

倒排索引

全文索引即是倒排索引。
好像这种说法,在lucene或者elasticsearch更流行。

文末还是简单说一下它的原理。


传统数据库索引的方式是,【表->字段】。而倒排索引的方式是先将字段进行分词,然后将单词跟文档进行关联,变为【文档 -> 单词】,并将记录其它更为强大的信息(文档编号、词项频率、词项的位置、词项开始和结束的字符位置可以被存储)。

 有两篇文章:

1 我是程序员

2 我热爱写程序

先分词(这里假设以自然语义分词)

1 【我】【是】【程序】【程序员】

2 【我】【热爱】【写】【程序】

 

前面文章对关键字,经倒排后变成关键字对文章

 

关键字 文章号
1,2
1
程序 1,2
程序员 1
热爱 2
2

 

为了快速定位和节省存储大小,还需要加上关键字出现频率和位置。  

关键字 文章号(频率) 位置
1(1) 1
  2(1) 1
1(1) 2
程序 1(1) 3
  2(1) 4
程序员 1(1) 4
热爱 1(1) 1
1(1) 3

如果我要对“程序”进行搜索,能就能快速定位到文档1,2,并且能直接知道它在文档当中出现了多少次,分别出现在哪里。

小结

关于分词,mysql有两种引擎,一种是基于空格的拉丁语系模式,默认就是这种。如'i love you'拆分为i love you三部份。
在5.7.6以后,针对中日韩文字内置了一种基于长度的分词器,n_gram parser。
此分词器并不区分中文和阿拉伯数字,两种文本分词的标准是一样的。
但一些特殊的文本里面带有布尔模式下的逻辑运算符(+-><*())的时候需要特别注意。


同时,mysql全文索引本身有很多限制,该用elasticsearch的时候也该大胆上:

1:只支持char、varchar、text类型。
2:MySQL的全文索引只有全部在内存中的时候,性能才非常好。如果内存无法装载全部索引,那么性能可能会非常慢(可以为全文索引设置单独的键缓存(key cache),保证不会被其他的索引缓存挤出内存)
3:相比其它的索引类型,当insert、update和delete操作进行时,全文索引的操作代价非常大。而且全文索引会有更多的碎片,可能需要做更多的optimize table操作。
4:全文索引优先级在索引中最高,即便这时有更合适的索引可用,MySQL也会放弃性能比较,优先使用全文索引。
5:全文索引不存储索引列的实际值,也就不可能用作索引覆盖扫描。
6:除了相关性排序,全文索引不能用作其他的排序。如果查询需要做相关性以外的排序操作,都需要使用文件排。



参考:
http://dev.mysql.com/doc/refman/8.0/en/fulltext-search-ngram.html
http://dev.mysql.com/doc/refman/8.0/en/fulltext-stopwords.html

苍茫之天涯,乃吾辈之所爱也;浩瀚之程序,亦吾之所爱也,然则何时而爱耶?必曰:先天下之忧而忧,后天下之爱而爱也!
本文转载于网络 如有侵权请联系删除

相关文章

  • Django中allauth的安装与基本使用

    安装django-allauth与基本使用pipinstalldjango-allauth复制安装完成后在settings.py中将allauth相关的app注册到INSTALLED_APP里面去,值得注意的是allauth对于站点设置django.contrib.sites有依赖,所以也需要将它注册进去,同时设置SITE_ID。INSTALLED_APPS=[ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', #all_auth对site有依赖,需要导入 'django.contrib.sites', #导入allauth 'allauth', '

  • 自旋锁的概念,栗子和应用条件

    自旋锁(spinlock)概念:是指尝试获取锁的线程不会立即阻塞,:是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环.获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU.举个栗子 此时相当于一把锁spinLock代码控制台思考自旋锁与普通的锁以及信号量不同,使用普通的锁和信号量在访问资源必须等待的时候操作系统会先把等待的线程加入相应的锁的链表里

  • HTTP协议简介

    HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。HTTP是不保存状态的协议,既无状态协议,协议本身对于请求或响应之间的通信状态不进行保存,因此连接双方不能知晓对方当前的身份和状态。这也是Cookie技术产生的重要原因之一:客户端的状态管理。浏览器会根据从服务器端发送的响应报文内Set-Cookie首部字段信息自动保持Cookie。而每次客户端发送HTTP请求,都会在请求报文中携带Cookie,作为服务端识别客户端身份状态的标识。TCP/IP协议族为了更好的了解HTTP协议,我们必须先了解一下TCP/IP协议族。TCP/IP协议族是Internet最基本的协议,HTTP协议是它的一个子集。TCP/IP协议族按层次分为以下四层(网络基础,最好记住):应用层应用层规定了向用户提供应用服务时通信的协议,如:TCP

  • 定时任务应该这么玩

    1.场景在电商系统中会经常遇到这样一种场景,就是商品的定时上下架功能,总不能每次都手动执行吧,这个时候我们首先想到的就是利用定时任务来实现这个功能。目前实现定时任务主要有以下几种方式:JDK自带:JDK自带的Timer以及JDK1.5+新增的ScheduledExecutorService; 第三方框架:使用Quartz、elastic-job、xxl-job等开源第三方定时任务框架,适合分布式项目应用。该方式的缺点是配置复杂。 Spring:使用Spring提供的一个注解@Schedule,开发简单,使用比较方便。 本文博主主要向大家介绍Quartz框架和Spring定时任务的使用。2.什么是QuartzQuartz是一个完全由Java编写的开源作业调度框架,为在Java应用程序中进行作业调度提供了简单却强大的机制。 Quartz可以与J2EE与J2SE应用程序相结合也可以单独使用。 Quartz允许程序开发人员根据时间的间隔来调度作业。 Quartz实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。 3.Quartz几个核心概念在正式学习使用Quartz之前,我们

  • 玩转C语言链表-链表各类操作详解

     链表概述  链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。它可以根据需要开辟内存单元。链表有一个“头指针”变量,以head表示,它存放一个地址。该地址指向一个元素。链表中每一个元素称为“结点”,每个结点都应包括两个部分:一为用户需要用的实际数据,二为下一个结点的地址。因此,head指向第一个元素:第一个元素又指向第二个元素;……,直到最后一个元素,该元素不再指向其它元素,它称为“表尾”,它的地址部分放一个“NULL”(表示“空地址”),链表到此结束。  链表的各类操作包括:学习单向链表的创建、删除、插入(无序、有序)、输出、排序(选择、插入、冒泡)、反序等等。  单向链表的图示:  ---->[NULL]  head  图1:空链表  ---->[p1]---->[p2]...---->[pn]---->[NULL]  headp1->nextp2->nextpn->next  图2:有N个节点的链表  创建n个节点的链表的函数为:  #include"stdlib.h"  #include&qu

  • C++核心准则C.86:保证==语义遵守操作数规则并不会抛出异常

    C.86:Make==symmetricwithrespecttooperandtypesandnoexceptC.86:保证==语义遵守操作数规则并不会抛出异常Reason(原因)Asymmetrictreatmentofoperandsissurprisingandasourceoferrorswhereconversionsarepossible.==isafundamentaloperationsandprogrammersshouldbeabletouseitwithoutfearoffailure.操作数的非对称处理会令人诧异而且成为错误的源头当可能发生类型转换时。==是一个基础的操作而且程序员应该可以使用它而不必担心失败。Example(示例)structX{ stringname; intnumber; }; booloperator==(constX&a,constX&b)noexcept{ returna.name==b.name&&a.number==b.number; }复制Example,bad(反面示例)classB{ st

  • 1+1>2,Paddle Lite与EdgeBoard无缝连接,快速实现部署应用

    导读:PaddleLite高性能推理引擎支持FPGA作为其底层加速硬件,其支持的模型可以很简单的部署到FPGA计算卡上,利用PaddleLite上层框架的优化能力,加上FPGA底层超强的计算能力,在精度损失很小的情况下,模型运行速度可以得到很大的提升。本文通过PaddleLite高性能推理引擎在百度EdgeBoard计算卡上部署蔬菜识别模型,达到了实时高效识别蔬菜品类的效果。1.基于飞桨开源深度学习平台的AI解决方案作为飞桨开源深度学习平台的重要组成部分,PaddleLite和EasyEdge通过有机组合,可以快速实现基于FPGA的嵌入式AI解决方案,具有高性能、高通用、低成本、易开发等四大优点,适用于开发验证、产品集成、科研教学、项目落地等应用方向,以及安防监控、工业质检、医疗诊断、农作物生长监控、无人驾驶、无人零售等应用场景。2.真实案例:蔬菜种类识别针对真实的业务需求:蔬菜种类识别,本文进行了完整的案例实现。简便起见,我们采用了百度AIStudio的公开蔬菜识别模型,为了进一步提高识别速度和效率,采用的是int8量化训练的模型,量化的优点包括低内存带宽、低功耗、低计算资源占用以及

  • 新的技术资料来了

    我分享资料有一个特点,从来不要求大家转发,大家按需下载学习,如果资源有失效的,请第一时间告知【web前端全套】链接:https://pan.baidu.com/s/16K9bdHmXf4EEWBtsSH9r5A密码:mb4q1.ps链接:https://pan.baidu.com/s/1mjYbcJA密码:pqdt2.HTML5+CSS3从入门到精通.iso链接:https://pan.baidu.com/s/1cUHz3o密码:gdyw3.Javascript视频教程链接:https://pan.baidu.com/s/1i6eC5YP密码:ku5k4.Jquery视频教程链接:https://pan.baidu.com/s/17DbNSnR3q5OGSjdw9tvCEA密码:twcp5.Bootstrap视频教程链接:https://pan.baidu.com/s/1jKb2UDS密码:wr8c6.Vue.js链接:https://pan.baidu.com/s/1slYqBa1密码:75xd7.Smarty模板引擎视频教程链接:https://pan.baidu.com/s/1s

  • 热水器选择笔记

    现在热水器的种类很多,包括燃气热水器,即热式电热水器,储水式电热水器,空气能热水器,太阳能热水器。空气能热水器空气能热水器太贵,价格一般在5k以上,占地也较大。不是我等屌丝考虑的范围内。当然它有环保,使用时费用低等优点。太阳能热水器优点:安全、节能环保;可多路供水。缺点:安装复杂,如安装不当,会影响住房的外观、质量及城市的市容市貌;维护较麻烦,因太阳能热水器安装在室外,多数在楼顶、房顶,因此相对于电热水器和燃气热水器比较难维护;阴天使用水温不够热,有排气孔,散热快,带辅助电加热的非常耗电,一般晚上用热水比较多,水温下降电加热启动,天亮了水也热了,太阳的作用也不大了;易受雷击影响。家里没地方装,不考虑。燃气热水器燃气热水器的优点是,即热的,使用费用低等。目前我家用的就是燃气热水器,感觉水温不太稳定,水压小就点不着火;窗开着会闻到煤气燃烧后的尾气的味道。维修工人的解释是,我家用的代充的煤气的问题,煤气不稳定,呵呵。即热式热水器优点:安全小巧美观,干净环保;即开即热,3-5秒出热水无须等候,热水使用时间不受限制,想用多久就用多久;用多少烧多少,省电省水,没有损耗;寿命较长:内置温控仪保证温度

  • Java程序员你跟架构师的差别在哪里?

    一、如何定义架构师Java架构师,首先要是一个Java程序员,熟练使用各种框架,并知道它们实现的原理。jvm虚拟机原理、调优,懂得jvm能让你写出性能更好的代码;池技术,什么对象池,怎么解决并发量、连接池,线程池。Java反射技术,写框架必备的技术,但是有严重的性能问题,替代方案Java字节码技术;nio,没什么好说的,值得注意的是”直接内存”的特点,使用场景;java多线程同步异步;java各种集合对象的实现原理,了解这些可以让你在解决问题时选择合适的数据结构,高效的解决问题,比如hashmap的实现原理,好多五年以上经验的人都弄不清楚,还有为什扩容时有性能问题?不弄清楚这些原理,就写不出高效的代码,还会认为自己做的很对;总之一句话越基础的东西越重要,很多人认为自己会用它们写代码了,其实仅仅是知道如何调用api而已,离会用还差的远。熟练使用各种数据结构和算法,数组、哈希、链表、排序树…,一句话要么是时间换空间要么是空间换时间,这里展开可以说一大堆,需要有一定的应用经验,用于解决各种性能或业务上的问题。二、技术归纳熟练使用linux操作系统,必备,没什么好说的。熟悉tcp协议,创建连接

  • Bwapp漏洞平台答案全解-A1(第三篇)

    0x01A1-Injection(第三次)1.21-SQLInjection-Stored(Blog)输入:test','test')# candy','candy')# candy',(selectpasswordfrommysql.userwhereuser='root'limit0,1))# candy',(selectversion()))# candy',(selectuser()))#复制1.22-SQLInjection-Stored(SQLite)用SQLmap的图形界面+burp抓包存成文件可以跑出来-1.23-SQLInjection-Stored(User-Agent)在User-Agent上有注入使用sqlmap的--risk--level可以检测出-1.24-SQLInjection-Stored(XML)burp扫描出结果 -1.25-SQLInjection-Blind-Boolean-BasedSQL盲注-基于布尔*1.26-SQLInjecti

  • 27-登录注册页面基本实现

    登录注册页面基本实现前端注册页面<%-- CreatedbyIntelliJIDEA. User:renboyu010214 Date:2021/2/1 Time:19:54 TochangethistemplateuseFile|Settings|FileTemplates. --%> <%@pagecontentType="text/html;charset=UTF-8"language="java"%> <html> <head> <title>书城注册页面</title> <style> #div1{ border:4px; border-style:ridge; border-radius:5px; width:400px; } #div2{ background:gray; opacity:0.6; z-index:10; width:350px; height:35px; margin-bottom:20px; } </style> &

  • java 16.数组

    数组 数组元素 数组是一种最简单的复合数据类型,它是有序数据的集合,数组中的每个元素具有相同的数据类型,可以用一个统一的数组名和不同的下标来唯一确定数组中的元素。根据数组的维度,可以将其分为一维数组、二维数组和多维数组等。总的来说,数组具有以下特点: 数组可以是一维数组、二维数组或多维数组。 数值数组元素的默认值为0,而引用元素的默认值为null。 交错数组是数组的数组,因此,它的元素是引用类型,初始化为null。交错数组元素的维度和大小可以不同。 数组的索引从0开始,如果数组有n个元素,那么数组的索引是从0到(n-1)。 数组元素可以是任何类型,包括数组类型。 数组类型是从抽象基类Array派生的引用类型。 声明和使用数组 在Java中,数组是对象。要创建数组,必须声明数组引用变量。然后,可以用new运算符实例化数组,为数组分配保存值的内存空间。 int[]height=newint[11];复制 变量height被声明为整形数组,其类型为int[]。保存在一个数组中的所有值,就有相同类型(或者至少是可兼容的类型)。复制 publicstaticvoidmain(

  • Cache应用/任务Mutex,用于高并发任务处理经过多个项目使用

    <?php /** *ClassCacheredis用于报表的缓存基本存储和读写2.0 *<pre> *Cache::read("diamond.account",$nick); *Cache::readSync("diamond.account",$nick); *$finder=Cache::createFinder("diamond.account",$nick); *$finder->read(); *$finder->readSync(); * *Cache::save("diamond.account",$nick,$data); *$storage=Cache::createStorage("diamond.account",$nick); *$storage->save($data); *$storage->save($data,7200); *</pre> *@categorycache *@packagecache *@authoroShine<oyjqdlp@126.com> *@version2.

  • 批量杀掉多个pid文件中记录的pid进程, 并集成到shell脚本中

        1head_files=`find./fmsConf/-name"*.pid"` 2forfilein$head_files 3do 4cat$file|awk'{print$1}'|xargskill-9 5rm-f$file 6done复制 代码如上 解释: 1.查找到./fmsConf目录下的所有的pid文件 2.对查找到的文件进行遍历 3.开始遍历 4.分开解释:   1.cat$file:打印出file文件的内容   2.awk'{print$1}':打印第一个单元字符   3.xargs:将上一个的输出作为这个的输入   4.  kill-9:调用linux底层,强行杀死进程 5.删除文件

  • JS自带字符串函数

    concat将两个或多个字符的文本组合起来,返回一个新的字符串。vara="hello";varb=",world";varc=a.concat(b);alert(c);//c="hello,world"indexOf返回字符串中一个子串第一处出现的索引(从左到右搜索)。如果没有匹配项,返回-1。varindex1=a.indexOf("l");//index1=2varindex2=a.indexOf("l",3);//index2=3charAt返回指定位置的字符。varget_char=a.charAt(0);//get_char="h"lastIndexOf返回字符串中一个子串最后一处出现的索引(从右到左搜索),如果没有匹配项,返回-1。varindex1=lastIndexOf('l');//index1=3varindex2=lastIndexOf('l',2)//index2=2match检查一个字符串匹配一个正则表达式内容,如果么有匹配返回null。varre=newRegExp(/^\w+$/);varis_alpha1=a.match(re);//is_alpha1

  • 原生JS和jQuery的概念?关系?区别?

  • (转)Xcode6中自动布局autolayout和sizeclass的使用

    Xcode6中自动布局autolayout和sizeclass的使用   一、关于自动布局(Autolayout) 在Xcode中,自动布局看似是一个很复杂的系统,在真正使用它之前,我也是这么认为的,不过事实并非如此。   我们知道,一款iOS应用,其主要UI组件是由一个个相对独立的可视单元构成,这些可视单元有的主要负责向用户输出有用的信息,有些则负责信息的输入(交互),交互的过程中往往还伴随有动画的效果,已达到整个信息传递的连贯性以及用户体验的细腻感。可视单元,在实际开发中主要是view、button等,那么这些可视单元的关系由两个基本的关系构成:兄弟关系和父子关系,整个视图单元就是一个树形结构: 对于任何一个UI组件,确定了它的(相对于父view)位置、大小也就确定了它在整个UI视图中的展示效果。   Autolayout(以及iOS8中新增的sizeclass)是为了解决这些UI可视单元或者元素是怎样布局、排列的问题。在过去只有iPhone4的时候,我们可以在代码里将没一个可视单元的位置写死,这样是没问题的,但随着iPhone5、6的发布,屏

  • Александр Хейфец - Дождь

    СтихотворениенаписаноАлександромподвпечатлениемоднойисториилюбви.Историилюбви междуКонстантиномСимоновымиВалентинойСеровой,которойонпосвятил'Ждименя'. Онирасстались,ноонещедолгописалей,объяснял,чторазлюбил,сообщал,чтоесливстретитчеловека,которогополюбит,то,неколеблясь,свяжетснимсвоюжизнь,советовалиейвыйтизамуж,желалейсчастьяитого,чтобыона«неразрушилаещеоднужизньтак,какужеразрушилаодинраз».Но,судяповсему,забытьееоннесумел,хотяделалвсевозможноеиневозможное,чтобыстеретьизпамятитустрасть,ч

  • VS重置命令:devenv.exe/resetuserdata

    VS命令行下执行下面的命令: devenv.exe/resetuserdata

  • 跳台阶python实现

    题目描述 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。时间限制:1秒 空间限制:32768K Python实现 #-*-coding:utf-8-*-classSolution:  defjumpFloor(self,number):    ifnumber<=2:      returnnumber    else:      methods=[]      foriinrange(number):        ifi<=1:          methods.append(i+1)        else:          methods.append(methods[i-1]+methods[i-2])      returnmethods[number-1]  

相关推荐

推荐阅读