Qi's Blog

不打无准备之仗


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

字符编码

发表于 2016-10-24   |   分类于 web   |  

字符编码问题

java:

1.java使用jchardet检测文本文件(字节流)的编码方式 wings里面使用的就是这个。

2.深度剖析Java的字符编码

3.java自动探测文件的字符编码

4.借助JCharDet获取文件字符集

编码教程

1.

  • ASCII是最基本的,最早的,用的最广泛的,字符编码

    • 但是只支持普通的基本字符,即常见英文字母,数字,下划线等
  • 简体中文的编码

    • 发展路径以此是:GB2312 < GBK < GB18030
    • 对应的所包含的字符个数,也是以此增加
  • 繁体中文的编码

    • 最常见的是BIG5
  • 西欧字符编码

    • 最常用的就是ISO8895-1

      • ISO8859是从1到15,是一系列的编码
      • ISO8859-1是属于ISO8859系列编码其下的,但是用的最广泛的的
  • 统一了世界上所有字符的Unicode

    • Unicode,可以叫做大一统的编码,包含了世界上所有的字符

    • 但是Unicode只是字符编码集

      • 字符(编码)集,只表示包含了哪些字符
      • 字符编码,表示了用何种方式去表示此字符集
      • Unicode这个字符集,可以有多种字符编码表示出来,最常见的包括UTF-8,UTF-16,UTF-32等等
      • 最最常用的是UTF-8

网页中的编码,一般是通过charset指定的;很多中文网页,charset用的GB2312;但是部分网页,所声明的编码和实际编码不同:虽然charset指定的是GB2312,但是实际上用的是(包含了更多字符的)GBK;导致的情况是:如果你按照GB2312去解码,由于部分字符是属于GBK的,而非GB2312,则会出现解码失败。解决办法是:尝试用GBK,或包含更多字符的GB18030去解码,则一般都可以正常解码的。

参见字符编码简明教程

2.

当你通过浏览器,打开某个网站,即某个url地址的时候,你所能正常看到网页的内容,各种文字,都可以正常显示,且没有显示乱码。此过程,涉及到,浏览器帮你正确解析HTML源码。

流程如下所示:

1.浏览器访问对应的url地址,并获取对应的html(或者,以及,其他的css,javascript等)网页源码

2.浏览器识别解析HTML源码内容

其中包含了,解析html的头部(header),找到对应的charset=xxx这部分的内容,然后把根据xxx所指示的字符编码类型,去解码对应的html内容,显示对应的文字,以保证不是乱码,可以正确的显示文字信息;

所以爬虫在抓取了网页的源代码后,也要跟浏览器一样去对网页进行解码。

注:有个别的html网页本身做的不规范,导致本身自己声明的是某种编码类型,但是实际上,并不全是该类型的编码,导致你去按照其所声明的编码去解析,有时候仍会出现乱码。比如,其html中明明写的是charset=gb2312,但是实际上,其部分字符是属于GBK的,导致你按照gb2312去解码,有些字符仍会是乱码,而安装gbk去解码,就全都正常显示了,没有乱码了。即实际上应该是charset=gbk。

python编码问题

乱码问题是我原本用的某种编码方式,如果要进行解码,也应该要用对应的解码格式去解码,或者用兼容性更大的,比如gb2312,也可以用gbk或gb18030去解码,但不能用utf-8去解码,虽然他能表达的编码更多。

GB2312,GBK,GB18030,编码技术上都是兼容的。

参见写的很不错的这篇

python解析网页源代码返回乱码问题

抓取的网页是gb2312的,然后直接输出到console里面是乱码,原因是console采用的是utf-8编码的,解决方案

1.run Configuration,修改console的编码,从UTF-8改为GBK(或GB2312,或GB18030)。

2.修改代码获得GB2312的html后,去decode为Unicode,然后直接输出Unicode字符串到UTF-8的Eclipse+PyDev的console中,

则比如也是可以正常显示无乱码的。(内部会自动将输出的Unicode转换为当前console的UTF-8编码的字符串的)。

3.基于上面那种方式,(不改Eclipse+PyDev的console的字符编码,仍保持旧的UTF-8的配置),但是在转换为Unicode后,再故意转为输出目标(此处的Eclipse+PyDev的console)的编码,即UTF-8。

1
2
3
uniCodehtml = html.decode("GBK") //此处只是转化为了unicode,并不是utf-8
utf8html = uniCodehtml.encode("UTF-8")
print utf8html
关于beautifulsoup的总结

以后用python的Beautiful Soup去解析中文网页的话:

1.如果本身网页的编码自己标称的,和本身其中文字符所用编码完全符合的话,即没有部分字符超出了其所标称的编码,比如标称为GBK,网页所有的内容,都的确是GBK编码,没有超出的其他字符(比如属于GB18030的编码),那么,是可以通过:

1
2
3
page = urllib2.urlopen(url)
soup = BeautifulSoup(page) #此处不需要传递参数,BeautifulSoup也完全可以自己去解析网页内容所使用的编码
print soup.originalEncoding

而得到真实的网页的编码的。

2.讨论中文乱码问题之前,先解释关于中文字符编码:

时间上的发展先后是,GB2312,GBK,GB18030,编码技术上都是兼容的。

从所包含的中文字符个数来说,算是:GB2312 < GBK < GB18030,也因此,才可能会出现上面所说的,标称GB2312的,部分字符用到了GBK里面的,或者是标称GBK的,部分字符用到了GB18030里面的,所以被人家编码工具解析错误,退回认为编码是最基本的windows-2152了。

所以:

(1)如果是网页标称为GB2312,但是部分字符用到了GBK的了,那么解决办法就是在调用BeautifulSoup,传递参数fromEncoding=”GBK”

(2)如果是网页标称为GBK,但是部分字符用到了GB18030的了,那么解决办法就是在调用BeautifulSoup,传递参数fromEncoding=”GB18030”。

(3)实际上由于GB18030从字符数上都涵盖了GB2312和GBK,所以如果是上述两种任意情况,即只要是中文字符出现乱码,不管是标称GB2312中用到了GBK,还是标称GBK中用到了GB18030,那么都直接传递GB18030,也是可以的,即:

1
soup = BeautifulSoup(page, fromEncoding="GB18030")

参见python中文字符乱码(GB2312,GBK,GB18030相关的问题)

可能遇到的坑: beautifulsoup在输出文本时默认以UTF-8的方式编码,无论原文是否以它进行编码的.即即使你用了正确的编码格式,如gb18030,但是beautifulsoup默认是使用utf-8输出,所以依旧乱码。

参见requests和BeautifulSoup中文编码转换心得

chardet代码
1
2
3
4
5
>>> import urllib
>>> rawdata = urllib.urlopen('http://www.google.cn/').read()
>>> import chardet
>>> chardet.detect(rawdata)
{'confidence': 0.98999999999999999, 'encoding': 'GB2312'}
encode & decode

字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即先将其他编码的字符串解码(decode)成unicode,再从unicode编码(encode)成另一种编码。

decode的作用是将其他编码的字符串转换成unicode编码,如str1.decode(‘gb2312’),表示将gb2312编码的字符串str1转换成unicode编码。

encode的作用是将unicode编码转换成其他编码的字符串,如str2.encode(‘gb2312’),表示将unicode编码的字符串str2转换成gb2312编码。

因此,转码的时候一定要先搞明白,字符串str是什么编码,然后decode成unicode,然后再encode成其他编码。

代码中字符串的默认编码与代码文件本身的编码一致。

如:s=’中文’。如果是在utf8的文件中,该字符串就是utf8编码,如果是在gb2312的文件中,则其编码为gb2312。这种情况下,要进行编码转换,都需 要先用decode方法将其转换成unicode编码,再使用encode方法将其转换成其他编码。

通常,在没有指定特定的编码方式时,都是使用的系统默 认编码创建的代码文件。如果字符串是这样定义:s=u’中文’,则该字符串的编码就被指定为unicode了,即python的内部编码,而与代码文件本身的编码无关。因此,对于这种情况做编码转换,只需要直接使用encode方法将其转换成指定编码即可。

如果一个字符串已经是unicode了,再进行解码则将出错,因此通常要对其编码方式是否为unicode进行判断:

isinstance(s, unicode) #用来判断是否为unicode,用非unicode编码形式的str来encode会报错

如何获得系统的默认编码?

1
2
3
4
#!/usr/bin/env python
#coding=utf-8
import sys
print sys.getdefaultencoding()

该段程序在英文WindowsXP上输出为:ascii。

在某些IDE中,字符串的输出总是出现乱码,甚至错误,其实是由于IDE的结果输出控制台自身不能显示字符串的编码,而不是程序本身的问题。

如在UliPad中运行如下代码:

1
2
s=u"中文"
print s

会提示:UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position 0-1: ordinal not in range(128)。这是因为UliPad在英文WindowsXP上的控制台信息输出窗口是按照ascii编码输出的(英文系统的默认编码是 ascii),而上面代码中的字符串是Unicode编码的,所以输出时产生了错误。

将最后一句改为:print s.encode(‘gb2312’),则能正确输出“中文”两个字。

若最后一句改为:print s.encode(‘utf8’),则输出:\xe4\xb8\xad\xe6\x96\x87,这是控制台信息输出窗口按照ascii编码输出utf8编码的字符串的结果。

unicode(str,’gb2312’)与str.decode(‘gb2312’)是一样的,都是将gb2312编码的str转为unicode编码。

使用str.class可以查看str的编码形式。

特别推荐这篇:

python新手必碰到的问题—encode与decode,中文乱码

2.

处理流程,可以这么使用,把python看做一个水池,一个入口,一个出口

入口处,全部转成unicode, 池里全部使用unicode处理,出口处,再转成目标编码(当然,有例外,处理逻辑中要用到具体编码的情况)

1
2
3
4
5
6
7
8
9
读文件
外部输入编码,decode转成unicode
处理(内部编码,统一unicode)
encode转成需要的目标编码
写到目标输出(文件或控制台)

IDE和控制台报错,原因是print时,编码和IDE自身编码不一致导致

输出时将编码转换成一致的就可以正常输出

1
2
3
4
>>> print u'中文'.encode('gbk')
����
>>> print u'中文'.encode('utf-8')
中文

处理顺序

1
2
3
1. Decode early
2. Unicode everywhere
3. Encode later

引用自PYTHON-进阶-编码处理小结

3.

在python中,使用unicode类型作为编码的基础类型,编解码要以其为中间形式过渡,即进行str和unicode之间的转换。
解码然后再编码的过程,即str->unicode->str的过程。中间得到的叫做unicode对象。

这里需要强调的是unicode是一种字符编码方法,是 “与存储无关的表示”,而utf8是一种以unicode进行编码的计算机二进制表示,或者说传输规范。gbk,gb2312,gb18030, utf8等属于不同的字符集,转换编码就是在它们中的任意两者间进行。

具体的转换,比如直接将一个字符串encode成另一种字符集表示,注意此处是字符串,即type为str的,引号前没有加u前缀的

1
2
3
# coding: utf8
s='美丽'
s.encode('gbk')

则实际上会先以默认编码进行decode,即decode('ascii')(假设系统默认的是),开头声明了utf8,s的编码就是utf8,ascii解码不了utf8的字符会报错。那就改默认编码,

1
2
3
4
5
6
7
8
# coding: utf8
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
s='美丽'
s.encode('gbk')

这样把默认编码改成utf8,decode的时候就以默认编码utf8来进行,能够成功运行.

或者decode时指定类型,

1
2
3
4
5
# coding: utf8
import sys
s='美丽'
s.decode('utf8').encode('gbk')

对于type为unicode的,即加了u前缀的字符串,如上所说,直接encode即可

1
2
3
4
5
# coding: utf8
import sys
s = u'美丽'
s.encode('gbk')

java编码

1.

JVM里面的任何字符串资源都是Unicode,就是说,任何String类型的数据都是Unicode编码。没有例外。既然只有一种编码,那么,我们可以这么说,JVM里面的String是不带编码的。String相当于 char[]。
JVM里面的 byte[] 数据是带编码的。比如,Big5,GBK,GB2312,UTF-8之类的。
一个GBK编码的byte[] 转换成 String,其实就是从GBK编码向Unicode编码转换。
一个String转换成一个Big5编码的byte[],其实就是从Unicode编码向Big5编码转换。所以,Unicode是所有编码转换的中间介质。所有的编码都有一个转换器可以转换到Unicode,而Unicode也可以转换到其他所有的编码。这样构成了一个总线结构。

最容易出问题的地方是浏览器和服务器JVM之间.我们把浏览器编码叫做 Browser_Charset,把JVM编码叫做JVM_Charset(通常等于服务器系统编码)。
当浏览器的数据过来的时候,是一个带有Browser_Charset的byte[]。
如果用户处理程序需要一个String类型的数据,那么JVM会好心好意地把这个byte[]转换成String。使用的转换器是 JVM_Charset -> Unicode。
注意,如果这个时候,Browser_Charset 和 JVM_Charset并不相等。那么,这个自动转换是错误的。
为了弥补这个错误。我们需要做两步工作。
(1) Unicode -> JVM_Charset,把这个String 转换回到原来的 byte[]。
(2) Browser_Charset -> Unicode,把这个还原的byte[]转换成 String。

这个效果,和直接从HTTP Request取得byte[],然后执行 (2) Browser_Charset -> Unicode 的效果是一样的。如果在Request里面设置了CharacterEncoding,那么POST Data参数就不需要自己手工转换了,web server的自动转换就是正确的。URL的参数编码还涉及到URL编码,需要考虑的问题多一些,没有这么简单。

JVM把数据发到浏览器的时候。也需要考虑编码问题。可以在Response里面设置。另外,HTML Meta Header里面也可以设置编码,提醒Browser选择正确编码。

2.

JAVA中字符的表达

JAVA 中有char、byte、String这几个概念。char 指的是一个UNICODE字符,为16位的整数。byte 是字节,字符串在网络传输或存储前需要转换为byte数组。在从网络接收或从存储设备读取后需要将byte数组转换成String。String是字符串,可以看成是由char组成的数组。String 和 char 为内存形式,byte是网络传输或存储的序列化形式。

编码方式的简介

String序列化成byte数组或反序列化时需要选择正确的编码方式。如果编码方式不正确,就会得到一些0x3F的值。

J2SE中相关的函数

String是unicode,所以new String()得到的是unicode,str.getBytes()得到的是带编码格式的bytes。两种bytes互相转换需要通过unicode。然后,对于console里面的输出,他跟输出到文本是一样的,console和文件一样,且关联系统的编码。console里面的编码如果与之前编码的不一致的话,就会出现乱码现象。

String str =”英”;
//取得GB2312编码的字节
byte[] bytesGB2312 = str.getBytes(“GB2312”);
//取得平台缺省编码的字节(solaris为ISO8859_1,windows为GB2312)
byte[] bytesDefault = str.getBytes();
//用指定的编码将字节转换成字符串
String newStrGB = new String(bytesGB2312, “GB2312”);

//用平台缺省的编码将字节转换成字符串(solaris为ISO8859_1,windows为GB2312)
String newStrDefault = new String(bytesDefault);
//用指定的编码从字节流里面读取字符
InputStream in = xxx;
InputStreamReader reader = InputStreamReader( in, “GB2312”);
char aChar = reader.read();

JSP、数据库的编码

1.JSP中的编码
(1) 静态声明:
CHARSET有两个作用:
JSP文件的编码方式:在读取JSP文件、生成JAVA类时,源JSP文件中汉字的编码
JSP输出流的编码方式:在执行JSP时,往response流里面写入数据的编码方式
(2) 动态改变:在往response流里面写数据前可以调用response.setContentType(),设定正确的编码类型。
(3) 在TOMCAT中,由Request.getParameter() 得到的参数,编码方式都是ISO8859_1。所以如果在浏览器输入框内输入一个汉字“英”,在服务器端就得到一个ISO8859_1编码的(0x00,0xD3,0x00,0xA2)。所以通常在接收参数时转码:
String wrongStr = response.getParameter(“name”);
String correctStr = new String(wrongStr.getBytes(“ISO8859_1”),”GB2312”);
在最新的SERVLET规范里面,也可以在获取参数之前执行如下代码:
request.setCharacterEncoding(“GB2312”);

2.数据库的编码

(1) 数据库使用UTF-16
如果String中是UNICODE字符,写入读出时不需要转码
(2) 数据库使用ISO8859_1
如果String中是UNICODE字符,写入读出时需要转码
写入:String newStr = new String(oldStr.getByte(“GB2312”), “ISO8859_1”);
读出:String newStr = new String(oldStr.getByte(“ISO8859_1”),”GB2312”);

3.

String newStr = new String(oldStr.getBytes(), “UTF-8”);

java中的String类是按照unicode进行编码的,当使用String(byte[] bytes, String encoding)构造字符串时,encoding所指的是bytes中的数据是按照那种方式编码的,而不是最后产生的String是什么编码方式,换句话说,是让系统把bytes中的数据由encoding编码方式转换成unicode编码。如果不指明,bytes的编码方式将由jdk根据操作系统决定。

当我们从文件中读数据时,最好使用InputStream方式,然后采用String(byte[] bytes, String encoding)指明文件的编码方式。不要使用Reader方式,因为Reader方式会自动根据jdk指明的编码方式把文件内容转换成unicode 编码。

当我们从数据库中读文本数据时,采用ResultSet.getBytes()方法取得字节数组,同样采用带编码方式的字符串构造方法即可。

ResultSet rs;
bytep[] bytes = rs.getBytes();
String str = new String(bytes, “gb2312”);

不要采取下面的步骤。

ResultSet rs;
String str = rs.getString();
str = new String(str.getBytes(“iso8859-1”), “gb2312”);

这种编码转换方式效率底。之所以这么做的原因是,ResultSet在getString()方法执行时,默认数据库里的数据编码方式为 iso8859-1。系统会把数据依照iso8859-1的编码方式转换成unicode。使用str.getBytes(“iso8859-1”)把数据还原,然后利用new String(bytes, “gb2312”)把数据从gb2312转换成unicode,中间多了好多步骤。

从HttpRequest中读参数时,利用reqeust.setCharacterEncoding()方法设置编码方式,读出的内容就是正确的了。

以上参见JAVA 字符串编码总结

eclipse

发表于 2016-10-24   |   分类于 web   |  

部署

如果部署项目后发现访问不了,出现404,原因可能是:

1.路径错误

2.web.xml映射路径写错

3.服务器设置

1)eclipse自带的wtwebapp 2) tomcat webapp

4.jar包没有导进入或者没有随项目发布:

1
严重: Error configuring application listener of class org.springframework.web.context.ContextLoaderListener java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener at org.Apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1647) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1493) at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4115) at org.apache.catalina.core.StandardContext.start(StandardContext.java:4671) at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1053) at org.apache.catalina.core.StandardHost.start(StandardHost.java:785) at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1053) at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:463) at org.apache.catalina.core.StandardService.start(StandardService.java:525) at org.apache.catalina.core.StandardServer.start(StandardServer.java:701) at org.apache.catalina.startup.Catalina.start(Catalina.java:585) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289) at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414)

解决方法:设置deployment assembly。add——>java build path entries——>maven dependencies —>finish.

以后出现404,可以去发布的目录下查看,是否存在项目,然后,

1.检查项目的包是否完整,比如缺少javax.servlet.http之类的(对于这种问题,直接引入包,而不是依赖于server)。

1
2
3
4
5
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>

2.检查是否项目目录下把那些包拷过去了。

以后碰到这种问题,第一反应就是跑到部署的目录下面去,看看它是否缺jar包,缺文件等等。再者就是看deploy assembly下面是否设置正确了。

maven install出现必须要idk 1.7之上才支持什么表达式,是因为ArrayList list=ArrayList<>(); <>这个表达式得1.7之上支持。

解决方法:在build里面加入

1
2
3
4
5
6
7
8
9
10
11
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>

附:

maven配置web项目:

http://www.jianshu.com/p/a92831ab2f92

https://my.oschina.net/u/2430057/blog/549029

日志配置

发表于 2016-10-19   |   分类于 web   |  

日志配置

在spring中配置日志

1.让 spring-context 排除对 commons-logging 的依赖

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
<scope>runtime</scope>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>

2.添加 slf4j-api 和 jcl-over-slf4j 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j-version}</version>
<scope>runtime</scope>
</dependency>

3.如果是web项目,则配置web.xml文件的Log4jConfigLocation和Log4jConfigListener

1
2
3
4
5
6
7
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>WEB-INF/classes/log4j.properties</param-value>
</context-param>

4.在当前classpath中添加log4j.properties配置文件

1
2
3
4
log4j.rootCategory=info, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n

问题:

1.若打印出了SLF4J: Class path contains multiple SLF4J bindings,说明jar包冲突了。slf4j就是所谓的门面模式,提供了一个接口,自己不去实现,让其他日志jar包去实现这个接口。logback、log4j什么的,都有实现这个接口,但运行的时候,必须保证只能有一个接口实现类,如果有两个或以上,就抛上面那个异常了。做下依赖排除就行了,有的时候是其他包里面引进了日志包,不是你自己引进的。

参考博客:

Spring配置日志

mac上的抓包工具

发表于 2016-10-18   |   分类于 web   |  

mac上的抓包工具

网络抓包神器-Charles使用指南

问题:怎么抓java程序发出的网络请求

java-httpclient

发表于 2016-10-18   |   分类于 web   |  

HTTPCLIENT

httpclient又可分为

  • httpclient3.x
  • httpclient4.x到httpclient4.3以下
  • httpclient4.3以上

不同httpclient版本其请求发送的方式也不一样,参见

HttpClient 4.3与4.3版本以下版本比较

Util工具类

HttpClient 发送 HTTP、HTTPS 请求的简单封装

注:此代码有问题,stringEntity.setContentEncoding(“UTF-8”); 这句应该注调,里面传的参数应该是header类型。

elasticsearch权威指南

发表于 2016-10-17   |   分类于 big-data   |  

elasticsearch 权威指南

3.数据

3.5更新

文档在Elasticsearch中是不可变的——我们不能修改他们。如果需要更新已存在的文档,我们可以使用《索引文档》章节提到的index API 重建索引(reindex) 或者替换掉它。

1
2
3
4
5
6
PUT /website/blog/123
{
"title": "My first blog entry",
"text": "I am starting to get the hang of this...",
"date": "2014/01/02"
}

在响应中,我们可以看到Elasticsearch把_version增加了。

1

1
2
3
4
5
6
7
{
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 2,
"created": false <1>
}
  • <1> created标识为false因为同索引、同类型下已经存在同ID的文档。

在内部,Elasticsearch已经标记旧文档为删除并添加了一个完整的新文档。旧版本文档不会立即消失,但你也不能去访问它。Elasticsearch会在你继续索引更多数据时清理被删除的文档。

在本章的后面,我们将会在《局部更新》中探讨update API。这个API 似乎 允许你修改文档的局部,但事实上Elasticsearch遵循与之前所说完全相同的过程,这个过程如下:

  1. 从旧文档中检索JSON
  2. 修改它
  3. 删除旧文档
  4. 索引新文档
3.6创建
1
2
3
4
PUT /website/blog/123/_create
{
...
}
3.9局部更新
文档局部更新

在《更新文档》一章,我们说了一种通过检索,修改,然后重建整文档的索引方法来更新文档。这是对的。然而,使用update API,我们可以使用一个请求来实现局部更新,例如增加数量的操作。

我们也说过文档是不可变的——它们不能被更改,只能被替换。update API必须遵循相同的规则。表面看来,我们似乎是局部更新了文档的位置,内部却是像我们之前说的一样简单的使用update API处理相同的检索-修改-重建索引流程,我们也减少了其他进程可能导致冲突的修改。

最简单的update请求表单接受一个局部文档参数doc,它会合并到现有文档中——对象合并在一起,存在的标量字段被覆盖,新字段被添加。举个例子,我们可以使用以下请求为博客添加一个tags字段和一个views字段:

1
2
3
4
5
6
7
POST /website/blog/1/_update
{
"doc" : {
"tags" : [ "testing" ],
"views": 0
}
}
使用脚本更新

脚本能够使用update API改变_source字段的内容,它在脚本内部以ctx._source表示。例如,我们可以使用脚本增加博客的views数量:

1
2
3
4
POST /website/blog/1/_update
{
"script" : "ctx._source.views+=1"
}
1
2
3
4
5
6
7
POST /website/blog/1/_update
{
"script" : "ctx._source.tags+=new_tag",
"params" : {
"new_tag" : "search"
}
}
3.10 MGET

检索多个文档

mget API参数是一个docs数组,数组的每个节点定义一个文档的_index、_type、_id元数据。如果你只想检索一个或几个确定的字段,也可以定义一个_source参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /_mget
{
"docs" : [
{
"_index" : "website",
"_type" : "blog",
"_id" : 2
},
{
"_index" : "website",
"_type" : "pageviews",
"_id" : 1,
"_source": "views"
}
]
}

响应体也包含一个docs数组,每个文档还包含一个响应,它们按照请求定义的顺序排列。每个这样的响应与单独使用get request响应体相同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"docs" : [
{
"_index" : "website",
"_id" : "2",
"_type" : "blog",
"found" : true,
"_source" : {
"text" : "This is a piece of cake...",
"title" : "My first external blog entry"
},
"_version" : 10
},
{
"_index" : "website",
"_id" : "1",
"_type" : "pageviews",
"found" : true,
"_version" : 2,
"_source" : {
"views" : 2
}
}
]
}
3.11 批量

bulk请求体如下,它有一点不同寻常:

1
2
3
4
5
{ action: { metadata }}\n
{ request body }\n
{ action: { metadata }}\n
{ request body }\n
...

这种格式类似于用"\n"符号连接起来的一行一行的JSON文档流(stream)。两个重要的点需要注意:

  • 每行必须以"\n"符号结尾,包括最后一行。这些都是作为每行有效的分离而做的标记。
  • 每一行的数据不能包含未被转义的换行符,它们会干扰分析——这意味着JSON不能被美化打印。

删除操作不需要请求体(request body)。

每个子请求都被独立的执行,所以一个子请求的错误并不影响其它请求。如果任何一个请求失败,顶层的error标记将被设置为true,然后错误的细节将在相应的请求中被报告。

1
2
3
4
5
6
7
8
POST /_bulk
{ "delete": { "_index": "website", "_type": "blog", "_id": "123" }} <1>
{ "create": { "_index": "website", "_type": "blog", "_id": "123" }}
{ "title": "My first blog post" }
{ "index": { "_index": "website", "_type": "blog" }}
{ "title": "My second blog post" }
{ "update": { "_index": "website", "_type": "blog", "_id": "123", "_retry_on_conflict" : 3} }
{ "doc" : {"title" : "My updated blog post"} }

5.搜索

5.1空搜索

1
GET /_search

返回结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"hits" : {
"total" : 14,
"hits" : [
{
"_index": "us",
"_type": "tweet",
"_id": "7",
"_score": 1,
"_source": {
"date": "2014-09-17",
"name": "John Smith",
"tweet": "The Query DSL is really powerful and flexible",
"user_id": 2
}
},
... 9 RESULTS REMOVED ...
],
"max_score" : 1
},
"took" : 4,
"_shards" : {
"failed" : 0,
"successful" : 10,
"total" : 10
},
"timed_out" : false
}

5.2 多搜索和多索引

1
/gb,us/user,tweet/_search

5.3分页

Elasticsearch接受from和size参数:

size: 结果数,默认10

from: 跳过开始的结果数,默认0

5.4 查询字符串

search API有两种表单:一种是“简易版”的查询字符串(query string)将所有参数通过查询字符串定义,另一种版本使用JSON完整的表示请求体(request body),这种富搜索语言叫做结构化查询语句(DSL)

在tweet字段中包含elasticsearch字符。

1
GET /_all/tweet/_search?q=tweet:elasticsearch

6.映射和分析

6.4 分析

当我们索引(index)一个文档,全文字段会被分析为单独的词来创建倒排索引。不过,当我们在全文字段搜索(search)时,我们要让查询字符串经过同样的分析流程处理,以确保这些词在索引中存在。

  • 当你查询全文(full text)字段,查询将使用相同的分析器来分析查询字符串,以产生正确的词列表。
  • 当你查询一个确切值(exact value)字段,查询将不分析查询字符串,但是你可以自己指定。

6.5 映射

string类型的字段,默认的,考虑到包含全文本,它们的值在索引前要经过分析器分析,并且在全文搜索此字段前要把查询语句做分析处理。

string类型默认进行analyze,即全文搜索。而其他字段则是精确匹配。

index

index参数控制字符串以何种方式被索引。

值 解释
analyzed 首先分析这个字符串,然后索引。换言之,以全文形式索引此字段。
not_analyzed 索引这个字段,使之可以被搜索,但是索引内容和指定值一样。不分析此字段。
no 不索引这个字段。这个字段不能为搜索到。

string类型字段默认值是analyzed。如果我们想映射字段为确切值(即不分词),我们需要设置它为not_analyzed。

分析

对于analyzed类型的字符串字段,使用analyzer参数来指定哪一种分析器将在搜索和索引的时候使用。默认的,Elasticsearch使用standard分析器。

6.6 复合类型

多层对象

内部对象(inner objects)经常用于在另一个对象中嵌入一个实体或对象

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"tweet": "Elasticsearch is very flexible",
"user": {
"id": "@johnsmith",
"gender": "male",
"age": 26,
"name": {
"full": "John Smith",
"first": "John",
"last": "Smith"
}
}
}
内部对象的映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"gb": {
"tweet": { <1>
"properties": {
"tweet": { "type": "string" },
"user": { <2>
"type": "object",
"properties": {
"id": { "type": "string" },
"gender": { "type": "string" },
"age": { "type": "long" },
"name": { <3>
"type": "object",
"properties": {
"full": { "type": "string" },
"first": { "type": "string" },
"last": { "type": "string" }
}
}
}
}
}
}
}
}

<1> 根对象.

<2><3> 内部对象.

内部对象怎么被索引

Lucene 并不了解内部对象。 一个 Lucene 文件包含一个键-值对应的扁平表单。

1
2
3
4
5
6
7
8
9
{
"tweet": [elasticsearch, flexible, very],
"user.id": [@johnsmith],
"user.gender": [male],
"user.age": [26],
"user.name.full": [john, smith],
"user.name.first": [john],
"user.name.last": [smith]
}

7.结构化查询

7.2结构化查询

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"bool": {
"must": { "match": { "email": "business opportunity" }},
"should": [
{ "match": { "starred": true }},
{ "bool": {
"must": { "folder": "inbox" }},
"must_not": { "spam": true }}
}}
],
"minimum_should_match": 1
}
}

7.3查询与过滤

原则上来说,使用查询语句做全文本搜索或其他需要进行相关性评分的时候,剩下的全部用过滤语句。

一条过滤语句会询问每个文档的字段值是否包含着特定值:

  • created 的日期范围是否在 2013 到 2014 ?
  • status 字段中是否包含单词 “published” ?
  • lat_lon 字段中的地理位置与目标点相距是否不超过10km ?

一条查询语句与过滤语句相似,但问法不同:

查询语句会询问每个文档的字段值与特定值的匹配程度如何?

查询语句的典型用法是为了找到文档:

  • 查找与 full text search 这个词语最佳匹配的文档
  • 查找包含单词 run ,但是也包含runs, running, jog 或 sprint的文档
  • 同时包含着 quick, brown 和 fox — 单词间离得越近,该文档的相关性越高
  • 标识着 lucene, search 或 java — 标识词越多,该文档的相关性越高

7.4重要的查询过滤子句

term过滤

term主要用于精确匹配哪些值,比如数字,日期,布尔值或 not_analyzed的字符串(未经分析的文本数据类型)

terms过滤

terms 跟 term 有点类似,但 terms 允许指定多个匹配条件。 如果某个字段指定了多个值,那么文档需要一起去做匹配。

1
2
3
4
5
{
"terms": {
"tag": [ "search", "full_text", "nosql" ]
}
}

range过滤

1
2
3
4
5
6
7
8
{
"range": {
"age": {
"gte": 20,
"lt": 30
}
}
}

exists和missing过滤

exists 和 missing 过滤可以用于查找文档中是否包含指定字段或没有某个字段,类似于SQL语句中的IS_NULL条件。

这两个过滤只是针对已经查出一批数据来,但是想区分出某个字段是否存在的时候使用。

bool过滤

match_all查询

match查询

match查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。

如果你使用 match 查询一个全文本字段,它会在真正查询之前用分析器先分析match一下查询字符。

如果用match下指定了一个确切值,在遇到数字,日期,布尔值或者not_analyzed 的字符串时,它将为你搜索你给定的值。

做精确匹配搜索时,你最好用过滤语句,因为过滤语句可以缓存数据。

multi_match 查询

multi_match查询允许你做match查询的基础上同时搜索多个字段:

1
2
3
4
5
6
{
"multi_match": {
"query": "full text search",
"fields": [ "title", "body" ]
}
}

bool查询

bool 查询与 bool 过滤相似,用于合并多个查询子句。不同的是,bool 过滤可以直接给出是否匹配成功, 而bool 查询要计算每一个查询子句的 _score (相关性分值)。

7.5 过滤与查询

search API中只能包含 query 语句,所以我们需要用 filtered 来同时包含 “query” 和 “filter” 子句。

1
2
3
4
5
6
{
"filtered": {
"query": { "match": { "email": "business opportunity" }},
"filter": { "term": { "folder": "inbox" }}
}
}

我们在外层再加入 query 的上下文关系:

1
2
3
4
5
6
7
8
9
GET /_search
{
"query": {
"filtered": {
"query": { "match": { "email": "business opportunity" }},
"filter": { "term": { "folder": "inbox" }}
}
}
}

7.6验证查询

validate API 可以验证一条查询语句是否合法。

1
2
3
4
5
6
7
8
GET /gb/tweet/_validate/query
{
"query": {
"tweet" : {
"match" : "really powerful"
}
}
}

想知道语句非法的具体错误信息,需要加上 explain 参数:

1
2
3
4
5
6
7
8
GET /gb/tweet/_validate/query?explain <1>
{
"query": {
"tweet" : {
"match" : "really powerful"
}
}
}

explain参数可以帮助我们理解语句是否正确。

8.排序

8.2.字符串排序

为了使一个string字段可以进行排序,它必须只包含一个词:即完整的not_analyzed字符串(译者注:未经分析器分词并排序的原字符串)。 当然我们需要对字段进行全文本搜索的时候还必须使用被 analyzed 标记的字段。

在 _source 下相同的字符串上排序两次会造成不必要的资源浪费。 而我们想要的是同一个字段中同时包含这两种索引方式,我们只需要改变索引(index)的mapping即可。 方法是在所有核心字段类型上,使用通用参数 fields对mapping进行修改。 比如,我们原有mapping如下:

1
2
3
4
"tweet": {
"type": "string",
"analyzer": "english"
}

改变后的多值字段mapping如下:

1
2
3
4
5
6
7
8
9
10
"tweet": { <1>
"type": "string",
"analyzer": "english",
"fields": {
"raw": { <2>
"type": "string",
"index": "not_analyzed"
}
}
}

<1> tweet 字段用于全文本的 analyzed 索引方式不变。

<2> 新增的 tweet.raw 子字段索引方式是 not_analyzed。

现在,在给数据重建索引后,我们既可以使用 tweet 字段进行全文本搜索,也可以用tweet.raw字段进行排序:

1
2
3
4
5
6
7
8
9
GET /_search
{
"query": {
"match": {
"tweet": "elasticsearch"
}
},
"sort": "tweet.raw"
}

警告: 对 analyzed 字段进行强制排序会消耗大量内存。

8.3相关性

理解评分

当调试一条复杂的查询语句时,想要理解相关性评分 _score 是比较困难的。ElasticSearch 在 每个查询语句中都有一个explain参数,将 explain 设为 true 就可以得到更详细的信息。

1
2
3
4
GET /_search?explain <1>
{
"query" : { "match" : { "tweet" : "honeymoon" }}
}

<1> explain 参数可以让返回结果添加一个 _score 评分的得来依据。

提示: JSON形式的explain描述是难以阅读的 但是转成 YAML 会好很多,只需要在参数中加上format=yaml

Explain api

当explain选项加到某一文档上时,它会告诉你为何这个文档会被匹配,以及一个文档为何没有被匹配。如请求路径为 /index/type/id/_explain。

10.索引管理

10.1 创建删除
1
2
3
4
5
6
7
8
PUT /my_index
{
"settings": { ... any settings ... },
"mappings": {
"type_one": { ... any mappings ... },
"type_two": { ... any mappings ... },
...
}
10.3配置分析器
1
2
GET /spanish_docs/_analyze?analyzer=es_std
El veloz zorro marrón
10.7 元数据中的source字段

禁用_source字段

1
2
3
4
5
6
7
8
9
10
PUT /my_index
{
"mappings": {
"my_type": {
"_source": {
"enabled": false
}
}
}
}

限制返回的字段

1
2
3
4
5
GET /_search
{
"query": { "match_all": {}},
"_source": [ "title", "created" ]
}
10.8 元数据中的all字段
1
2
3
4
5
6
GET /_search
{
"match": {
"_all": "john smith marketing"
}
}

12.结构化搜索

通过结构化搜索,你的查询结果始终是 是或非;是否应该属于集合。结构化搜索不关心文档的相关性或分数,它只是简单的包含或排除文档。

过滤器filter有term(terms),bool,range等等。

12.1查询准确值

当term用于查询字符串的时候,记得把term里面field设为not analyzed.

1
2
3
4
5
6
7
8
9
10
11
12
GET /my_store/products/_search
{
"query" : {
"filtered" : {
"filter" : {
"term" : {
"productID" : "XHDK-A-1293-#fJ3"
}
}
}
}
}

term(terms)是包含操作,不是等于操作。

12.2 组合过滤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /my_store/products/_search
{
"query" : {
"filtered" : { <1>
"filter" : {
"bool" : {
"should" : [
{ "term" : {"price" : 20}}, <2>
{ "term" : {"productID" : "XHDK-A-1293-#fJ3"}} <2>
],
"must_not" : {
"term" : {"price" : 30} <3>
}
}
}
}
}
}
12.3查询多个准确值
1
2
3
4
5
6
7
8
9
10
11
12
GET /my_store/products/_search
{
"query" : {
"filtered" : {
"filter" : {
"terms" : { <1>
"price" : [20, 30]
}
}
}
}
}
12.4 包含而不是相等

假如你有一个 term 过滤器 { "term" : { "tags" : "search" } },它将匹配下面两个文档:

1
2
{ "tags" : ["search"] }
{ "tags" : ["search", "open_source"] } <1>

<1> 虽然这个文档除了 search 还有其他短语,它还是被返回了

term 和 terms 是 必须包含 操作,而不是 必须相等。

完全匹配

假如你真的需要完全匹配这种行为,最好是通过添加另一个字段来实现。在这个字段中,你索引原字段包含值的个数。引用上面的两个文档,我们现在包含一个字段来记录标签的个数:

1
2
{ "tags" : ["search"], "tag_count" : 1 }
{ "tags" : ["search", "open_source"], "tag_count" : 2 }

一旦你索引了标签个数,你可以构造一个 bool 过滤器来限制短语个数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /my_index/my_type/_search
{
"query": {
"filtered" : {
"filter" : {
"bool" : {
"must" : [
{ "term" : { "tags" : "search" } }, <1>
{ "term" : { "tag_count" : 1 } } <2>
]
}
}
}
}
}
12.5范围

Elasticsearch 有一个 range 过滤器,让你可以根据范围过滤:

1
2
3
4
5
6
"range" : {
"price" : {
"gt" : 20,
"lt" : 40
}
}

range 过滤器既能包含也能排除范围,通过下面的选项:

  • gt: > 大于
  • lt: < 小于
  • gte: >= 大于或等于
  • lte: <= 小于或等于

range 过滤器也可以用于日期字段:

1
2
3
4
5
6
"range" : {
"timestamp" : {
"gt" : "2014-01-01 00:00:00",
"lt" : "2014-01-07 00:00:00"
}
}

当用于日期字段时,range 过滤器支持日期数学操作。例如,我们想找到所有最近一个小时的文档:

1
2
3
4
5
"range" : {
"timestamp" : {
"gt" : "now-1h"
}
}

这个过滤器将始终能找出所有时间戳大于当前时间减 1 小时的文档,让这个过滤器像移窗一样通过你的文档。

日期计算也能用于实际的日期,而不是仅仅是一个像 now 一样的占位符。只要在日期后加上双竖线 ||,就能使用日期数学表达式了。

1
2
3
4
5
6
"range" : {
"timestamp" : {
"gt" : "2014-01-01 00:00:00",
"lt" : "2014-01-01 00:00:00||+1M" <1>
}
}

<1> 早于 2014 年 1 月 1 号加一个月

12.8 过滤顺序

在 bool 条件中过滤器的顺序对性能有很大的影响。更详细的过滤条件应该被放置在其他过滤器之前,以便在更早的排除更多的文档。

假如条件 A 匹配 1000 万个文档,而 B 只匹配 100 个文档,那么需要将 B 放在 A 前面。

缓存的过滤器非常快,所以它们需要被放在不能缓存的过滤器之前。

13.全文检索

全文检索:怎样对全文字段(full-text fields)进行检索以找到相关度最高的文档。

全文检索最重要的两个方面是:

  • 相关度(Relevance)

    根据文档与查询的相关程度对结果集进行排序的能力。相关度可以使用TF/IDF、地理位置相近程度、模糊相似度或其他算法计算。

  • 分析(Analysis)

    将一段文本转换为一组唯一的、标准化了的标记(token),用以(a)创建倒排索引,(b)查询倒排索引。

注意,一旦提到相关度和分析,指的都是查询(queries)而非过滤器(filters)。

准确值('not_analyzed') 全文('analyzed')

1.基于短语的查询

不会有分析阶段。这些查询在单一的短语上执行,term查询只在倒排查询里精确地查找特定短语(或者说精确包含),而不会匹配短语的其它变形。

2.全文检索

match和query_string这样的查询是高级查询,它们会对字段进行分析:

  • 如果检索一个'date'或'integer'字段,它们会把查询语句作为日期或者整数格式数据。

  • 如果检索一个准确值('not_analyzed')字符串字段,它们会把整个查询语句作为一个短语。

  • 如果检索一个全文('analyzed')字段,查询会先用适当的解析器解析查询语句,产生需要查询的短语列表。然后对列表中的每个短语执行低级查询,合并查询结果,得到最终的文档相关度。

    我们很少需要直接使用基于短语的查询。通常我们会想要检索全文,而不是单独的短语,使用高级的全文检索会更简单(全文检索内部最终还是使用基于短语的查询)。

    如果确实要查询一个准确值字段('not_analyzed'),需要考虑使用查询还是过滤器。

    [提示] 单一短语的查询通常相当于是/否问题,用过滤器可以更好的描述这类查询,并且过滤器缓存可以提升性能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    GET /_search
    {
    "query": {
    "filtered": {
    "filter": {
    "term": { "gender": "female" }
    }
    }
    }
    }
分析器

有时,在索引时和搜索时使用不同的分析器是合理的。比如,在索引时我们希望索引到同义词(在quick出现的地方,我们希望同时索引fast,rapid和speedy)。但在搜索时,我们不需要搜索所有的同义词。我们只需要关注用户输入的单词,无论是quick,fast,rapid,还是speedy,输入什么就是什么。

为了区分,ElasticSearch支持参数 index_analyzer 和 search_analyzer,以及 default_index 和 default_search的分析器。

被破坏的相关度(Relevance is Broken)

在我们讨论更复杂的多字段查询之前,这里先解释一下为什么我们将测试数据只创建在一个主分片上(primary shard)。

用户会时不时的抱怨遇到这样的问题:用户索引了一些文档,运行了一个简单的查询,然后明显发现一些低相关性的结果出现在高相关性结果之上。

为了理解为什么会这样,我们可以想象一下,我们在两个主分片上创建了索引,总共10个文档,其中6个文档有单词foo,可能是shard 1有其中3个文档,而shard 2有其中另外3个文档,换句话说,所有文档是均匀分布存储的。

在何为相关性中,我们描述了ElasticSearch默认的相似度算法,这个算法叫做 TF/IDF (term frequency/inverse document frequency)。详细参照之前提到的内容。

但是由于性能原因,ElasticSearch在计算IDF时,不会计算所有的文档。相反,每个分片会根据该分片的所有文档,计算一个本地的IDF。

因为我们的文档是均匀的分布存储的,每个shard的IDF是相同的。如果有5个文档存在于shard 1,而第6个文档存在于shard 2,在这种场景下,foo在一个shard里非常普通(所以不那么重要),但是在另个shard里非常少(所以会显得更重要)。这样局部IDF的差异会导致不正确的结果。

在实际中,这不是一个问题,本地和全局的IDF的差异随着索引里文档数的增多会渐渐消失,在真实世界的数据量下,局部的IDF会迅速被均化,所以这里的问题并不是相关性被破坏了,而是数据量太小了。

为了测试,我们可以通过两种方式解决这个问题。第一种是只在主分片上创建索引,就如我们例子里做的那样,如果只有一个shard,那么本地的IDF就是全局的IDF。

第二个方式就是在搜索请求后添加 ?search_type=dfs_query_then_fetch,dfs 是指 分布式频率搜索(Distributed Frequency Search),它告诉ElasticSearch,先分别获得每个shard本地的IDF,然后根据结果再计算全局的IDF。

注意:
不要在产品上使用第二种方式,我们完全无须如此。

14.多字段搜索

14.1多字符串查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
GET /_search
{
"query": {
"bool": {
"should": [
{ "match": { (1)
"title": {
"query": "War and Peace",
"boost": 2
}}},
{ "match": { (1)
"author": {
"query": "Leo Tolstoy",
"boost": 2
}}},
{ "bool": { (2)
"should": [
{ "match": { "translator": "Constance Garnett" }},
{ "match": { "translator": "Louise Maude" }}
]
}}
]
}
}
}

为什么将译者条件语句放入另一个独立的 bool 查询中呢?所有的4个 match 查询都是 should 语句,我们为什么不将translator语句与其他语句(如 title、author)放在同一层呢?

答案在于分数的计算方式。bool 查询执行每个 match 查询,然后把他们加在一起,然后将结果与所有匹配的语句数量相乘,再除以所有的语句数量。处于同一层的每条语句具有相同的权重。在上面这个例子中,包含translator语句的 bool 查询,只占总分数的三分之一,如果我们将translator语句与 title 和 author 两个语句放入同一层,那么 title 和 author 语句只贡献四分之一。

14.2单一查询字符串

近似度匹配

部分匹配

补充

elasticsearch 设置 mapping 时的 store 属性

elasticsearch的store属性跟_source字段

后续的翻译:

Elasticsearch权威指南翻译目录

ElasticSearch 2 (17) - 深入搜索系列之部分匹配

对权威指南的总结博客

ElasticSearch的搜索和过滤

elk

发表于 2016-10-15   |   分类于 big-data   |  

ELK

1.

spring mvc+ELK从头开始搭建日志平台

2.

Nlog、elasticsearch、Kibana以及logstash在项目中的应用(二)

3.

spring-data-elasticsearch

发表于 2016-10-15   |   分类于 big-data   |  

spring-data-elasticsearch

SpringBoot整合ElasticSearch

gitbook :

Spring-Data-Elasticsearch

如何在Spring中注入ElasticSearch实例

入门整合案例(SpringBoot+Spring-data-elasticsearch)

elasticsearch spring 集成

Spring Data ElasticSearch

spring data 对elasticsearch的支持

集成Spring、Elasticsearch、paoding,将ES服务嵌入到Web程序

使用 Spring、Elasticsearch 及 Logstash 构建企业级数据搜索和分析平台

elasticsearch初探

项目实例:

elasticsearch spring 集成

前人造的轮子

发表于 2016-10-12   |   分类于 other   |  

前人造的轮子

Maven Repository: Top Projects at Maven Repository

https://github.com/jiangxincode/cnblogs

1.java篇

推荐!国外程序员整理的Java资源大全

Java 世界有哪些优秀的第三方开源 jar 包值得推荐使用?

  1. log4j/slf4j + logback

    向system.out.println()说永别,刚开始学java的时候总是喜欢依靠system.out.println()的输出来查看异常和调试。后来工作后就果断log4j了,这样项目开发和发布的时候,可以根据自己的需求开关日志级别,把日志打印到远程服务等多种功能。现在这个基本成为标配了。

  2. guava

    google出品的第三方工具库。当java.util 提供的数据结构不能满足的时候从这里你可以快速找到大量已经写好的数据结构了,这使得你不用花费心思在一些常用的数据结构上了。比如LRU缓存之类的。只是好几个版本的跨度比较大,兼容也不怎么好。

  3. apache commons 包含的组件

    提供各种用途的函数,比如配置、验证、集合、文件上传或XML处理等。

    Java工具类之Apache的Commons Lang和BeanUtils

    1)commons lang

    主要使用StringUtils和ArrayUtils类。

    ​

  4. netty

    一个网络通信框架,当需要实现自定义协议的时候我就用这个,netty的新版本自带了很多协议的实现版本,这是搞网络快速开发不二的选择。

  5. mina

  6. httpclient

    基于http协议网络工具

  7. jetty

    httpclient 的同一个项目下有一个简易的http server ,但是没有实现servlet。

  8. maven

    现在的java已经离不开这个玩意了。你可以自己搭建一个nexus 来做maven私服。当你存在RPC的需求的时候。完全可以把自己的接口部分和client打包上传到maven私服,调用的服务只需要include这个包就可以远程调用你的服务了。在国内配合上dubbo这类 SOA框架。那个效果酸爽的很。完成了实际意义上的接口于实现在网络层级的分离。让java 的package 形成一个网络上的package。需要某个服务的时候,include 直接调用。其他的一律不用管。

  9. Disruptor

    高性能的并发框架,一般用来在涉及到 生产者–消费者模型的时候会用到。抛开性能不谈(实际上性能相当棒)它的抽象方式和接口都设计得很好。

  10. quartz

    一个调度器,当涉及到多任务定时调用的时候这个框架能帮上非常多。特别在网络游戏服务器中,如果需要定时或者短时定时来做某些事情的时候(用户的长时间buff状态,刷新时间等),quart是一个非常不错的选择。如果时间比较短的话,利用java内置的DelayQueue 也可以。

  11. jOOQ

    用来替代hibernate.

  12. joda

    最好用的日期时间库,抛弃蹩脚的java.util.Calendar、java.util.Date 吧。

  13. Gson

    谷歌提供的解析json的强大jar包,无论你的json多复杂,一个方法搞定,比之前哪些json 的jar包要好用很多,

  14. lombok

    http://www.blogjava.net/fancydeepin/archive/2012/07/12/382933.html

  15. FastJSON

    Taobao的JSON类库,我专门和GSON做过性能测试,好像没什么差别,不过FastJSON大部分是静态方法,直接调用就行,使用更顺手吧,然后是——国产

  16. jfinal

  17. spring&spring data

2.Android篇

maven

发表于 2016-10-12   |   分类于 web   |  

maven

1.archetype

最常用的骨架是maven-archetype-quickstart和maven-archetype-webapp骨架。maven-archetype-quickstart骨架是用来创建一个Java Project,而maven-archetype-webapp骨架则是用来创建一个JavaWeb Project。

Maven的Archetype简介

2.依赖冲突

Maven解决类包依赖冲突

1…345…7
Qi Liu

Qi Liu

Less is More

69 日志
11 分类
63 标签
© 2018 Qi Liu
由 Hexo 强力驱动
主题 - NexT.Pisces