
中国科学院大学硕士,《从Lucene到Elasticsearch:全文检索实战》一书作者
能力说明:
精通JVM运行机制,包括类生命、内存模型、垃圾回收及JVM常见参数;能够熟练使用Runnable接口创建线程和使用ExecutorService并发执行任务、识别潜在的死锁线程问题;能够使用Synchronized关键字和atomic包控制线程的执行顺序,使用并行Fork/Join框架;能过开发使用原始版本函数式接口的代码。
阿里云技能认证
详细说明Elasticsearch是一个基于Lucene的分布式搜索引擎,具有分布式、全文检索、近实时搜索和分析、高可用、模式自由、RESTFul API等诸多优点,在实时搜索、日志处理(ELK)、大数据分析等领域有着广泛的应用。Hadoop是一个由Apache基金会所开发的分布式系统基础架构,核心组件有HDFS和MapReduce,分别提供海量数据存储和海量数据计算。 图1 ES-Hadoop简介Elasticsearch for Apache Hadoop是一个用于Elasticsearch和Hadoop进行交互的开源独立库,简称ES-Hadoop,在Hadoop和Elasticsearch之间起到桥梁的作用,完美地把Hadoop的批处理优势和Elasticsearch强大的全文检索引擎结合起来。 ES-Hadoop开辟了更加广阔的应用空间,通过ES-Hadoop可以索引Hadoop中的数据到Elasticsearch,充分利用其查询和聚合分析功能,也可以在Kibana中做进一步的可视化分析,同时也可以把Elasticsearch中的数据放到Hadoop生态系统中做运算,ES-Hadoop支持Spark、Spark、 Streaming、SparkSQL,除此之外,不论是使用Hive、 Pig、Storm、Cascading还是运行单独的Map/Reduce,通过ES-Hadoop提供的接口都支持从Elasticsearch中进行索引和查询操作。 本文基于阿里云E-MapReduce和阿里云Elasticsearch,演示如何通过ES-Hadoop连通Hadoop生态系统和Elasticsearch。 一、云服务配置 图2 阿里云产品地图1.1 开通专有网络VPC 因为在公网访问推送数据安全性较差,为保证阿里云Elasticsearch访问环境安全,购买阿里云ES产品,对应区域下必须要有 VPC 和 虚拟交换机,因此首先开通VPC专有网络。按路径:阿里云首页-->产品-->网络->专有网络VPC,然后选择立即开通,进入到管理控制台界面,新建专有网络。 图3 创建专有网络创建完成之后在控制台中可以进行管理: 图4 专有网络管理更多关于专有网络VPC的文档参考这里:专有网络 VPC 1.2 开通阿里云Elasticsearch 按路径:阿里云首页-->产品-->数据库-->Elasticsearch或阿里云首页-->产品-->大数据基础服务-->Elasticsearch进入到阿里云Elasticsearch产品界面,新用户可以免费试用30天。 进入到购买入口,阿里云Elasticsearch提供了按月和按量两种付费模式,选择已经创建的专有网络并设置登录密码。 图5 阿里云Elasticsearch选购配置购买成功后,按路径:控制台-->大数据(数加)-->Elasticsearch,可以看到新创建的Elasticsearch集群实例。 图6 阿里云Elasticsearch实例列表页点击"管理"菜单,进入集群管理界面: 图7 阿里云Elasticsearch集群管理点击"Kibana控制台"按钮即可进入到Kibana操作界面: 图8 阿里云Elasticsearch集群Kibana操作管理界面点击"集群监控"按钮进入到监控界面: 图9 阿里云Elasticsearch集群监控界面1.3 开通阿里云E-MapReduce 按路径:阿里云首页-->产品-->大数据基础服务-->E-MapReduce,之后进入到购买界面: 图10 阿里云E-MapReduce软件配置下一步进行付费配置、网络配置和节点硬件配置: 图11 阿里云E-MapReduce硬件配置最后设置集群名称、日志路径和集群登录密码: 图12 阿里云E-MapReduce基础配置其中日志路径存储在OSS之上,如果没有创建bucket,需要到OSS管理控制台创建新的bucket。bucket的区域要和EMR集群一直,EMR集群为华东1区,这里的bucket区域也选择华东1区: 图13 阿里云OSS创建bucketbucket创建完成以后就可以新建目录、上传文件: 图14 阿里云OSS文件管理最后确定,完成EMR集群的创建: 图15 阿里云E-MapReduce确认页集群创建成功后在集群列表中查看:图16 阿里云E-MapReduce集群列表点击管理,可以查看master节点和data节点的详细信息: 图17 阿里云E-MapReduce集群详细信息公网IP可以直接访问,远程登录:ssh root@你的公网IP 使用jps命令查看后台进程: [root@emr-header-1 ~]# jps 16640 Bootstrap 17988 RunJar 19140 HistoryServer 18981 WebAppProxyServer 14023 Jps 15949 gateway.jar 16621 ZeppelinServer 1133 EmrAgent 15119 RunJar 17519 ResourceManager 1871 Application 19316 JobHistoryServer 1077 WatchDog 17237 SecondaryNameNode 16502 NameNode 16988 ApacheDsTanukiWrapper 18429 ApplicationHistoryServer 二、编写EMR写数据到ES的MR作业 推荐使用maven来进行项目管理,首先创建一个maven工程。操作步骤如下: 1.安装 Maven。首先确保计算机已经正确安装安装maven 2.生成工程框架。在工程根目录处执行如下命令: mvn archetype:generate -DgroupId=com.aliyun.emrtoes -DartifactId=emrtoes -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false mvn 会自动生成一个空的 Sample 工程,工程名为emrtoes(和指定的artifactId一致),里面包含一个简单的 pom.xml 和 App 类(类的包路径和指定的 groupId 一致) 3.加入 Hadoop 和ES-Hadoop依赖。使用任意 IDE 打开这个工程,编辑 pom.xml 文件。在 dependencies 内添加如下内容: <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-mapreduce-client-common</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch-hadoop-mr</artifactId> <version>5.5.3</version> </dependency> 4.添加打包插件。由于使用了第三方库,需要把第三方库打包到jar文件中,在pom.xml中添加maven-assembly-plugin插件的坐标: <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <archive> <manifest> <mainClass>com.aliyun.emrtoes.EmrToES</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ApacheLicenseResourceTransformer"> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> 5.编写代码。在com.aliyun.emrtoes包下和 App 类平行的位置添加新类 EmrToES.java。内容如下: package com.aliyun.emrtoes; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.input.TextInputFormat; import org.apache.hadoop.util.GenericOptionsParser; import org.elasticsearch.hadoop.mr.EsOutputFormat; import java.io.IOException; public class EmrToES { public static class MyMapper extends Mapper<Object, Text, NullWritable, Text> { private Text line = new Text(); @Override protected void map(Object key, Text value, Context context) throws IOException, InterruptedException { if (value.getLength() > 0) { line.set(value); context.write(NullWritable.get(), line); } } } public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { Configuration conf = new Configuration(); String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs(); //阿里云 Elasticsearch X-PACK用户名和密码 conf.set("es.net.http.auth.user", "你的X-PACK用户名"); conf.set("es.net.http.auth.pass", "你的X-PACK密码"); conf.setBoolean("mapred.map.tasks.speculative.execution", false); conf.setBoolean("mapred.reduce.tasks.speculative.execution", false); conf.set("es.nodes", "你的Elasticsearch内网地址"); conf.set("es.port", "9200"); conf.set("es.nodes.wan.only", "true"); conf.set("es.resource", "blog/yunqi"); conf.set("es.mapping.id", "id"); conf.set("es.input.json", "yes"); Job job = Job.getInstance(conf, "EmrToES"); job.setJarByClass(EmrToES.class); job.setMapperClass(MyMapper.class); job.setInputFormatClass(TextInputFormat.class); job.setOutputFormatClass(EsOutputFormat.class); job.setMapOutputKeyClass(NullWritable.class); job.setMapOutputValueClass(Text.class); FileInputFormat.setInputPaths(job, new Path(otherArgs[0])); System.exit(job.waitForCompletion(true) ? 0 : 1); } } 6.编译并打包。在工程的目录下,执行如下命令: mvn clean package 执行完毕以后,可在工程目录的 target 目录下看到一个emrtoes-1.0-SNAPSHOT-jar-with-dependencies.jar,这个就是作业 jar 包。 图18 IDEA中EMR写ES工程目录截图三、EMR中完成作业 3.1 测试数据 把下面的数据写入到blog.json中: {"id":"1","title":"git简介","posttime":"2016-06-11","content":"svn与git的最主要区别..."} {"id":"2","title":"ava中泛型的介绍与简单使用","posttime":"2016-06-12","content":"基本操作:CRUD ..."} {"id":"3","title":"SQL基本操作","posttime":"2016-06-13","content":"svn与git的最主要区别..."} {"id":"4","title":"Hibernate框架基础","posttime":"2016-06-14","content":"Hibernate框架基础..."} {"id":"5","title":"Shell基本知识","posttime":"2016-06-15","content":"Shell是什么..."} 上传到阿里云E-MapReduce集群,使用scp远程拷贝命令上传文件: scp blog.json root@你的弹性公网IP:/root 把blog.json上传至HDFS: hadoop fs -mkdir /work hadoop fs -put blog.json /work 3.2 上传JAR包 把maven工程target目录下的jar包上传至阿里云E-MapReduce集群: scp target/emrtoes-1.0-SNAPSHOT-jar-with-dependencies.jar root@YourIP:/root 3.3 执行MR作业 hadoop jar emrtoes-1.0-SNAPSHOT-jar-with-dependencies.jar /work/blog.json 运行成功的话,控制台会输出如下图所示信息: 图19 阿里云E-MapReduce运行MR作业截图命令查询Elasticsearch中的数据:curl -u elastic -XGET es-cn-v0h0jdp990001rta9.elasticsearch.aliyuncs.com:9200/blog/_search?pretty 图20 命令查看阿里云Elasticsearch中的数据或者在Kibana中查看: 图21 Kibana中查看阿里云Elasticsearch中的数据四、API分析 Map过程,按行读入,input kye的类型为Object,input value的类型为Text。输出的key为NullWritable类型,NullWritable是Writable的一个特殊类,实现方法为空实现,不从数据流中读数据,也不写入数据,只充当占位符。MapReduce中如果不需要使用键或值,就可以将键或值声明为NullWritable,这里把输出的key设置NullWritable类型。输出为BytesWritable类型,把json字符串序列化。 因为只需要写入,没有Reduce过程。配置参数说明如下: conf.set("es.net.http.auth.user", "你的X-PACK用户名"); 设置X-PACK的用户名 conf.set("es.net.http.auth.pass", "你的X-PACK密码"); 设置X-PACK的密码 conf.setBoolean("mapred.map.tasks.speculative.execution", false); 关闭mapper阶段的执行推测 conf.setBoolean("mapred.reduce.tasks.speculative.execution", false); 关闭reducer阶段的执行推测 conf.set("es.nodes", "你的Elasticsearch内网地址"); 配置Elasticsearch的IP和端口 conf.set("es.resource", "blog/yunqi"); 设置索引到Elasticsearch的索引名和类型名。 conf.set("es.mapping.id", "id"); 设置文档id,这个参数”id”是文档中的id字段 conf.set("es.input.json", "yes"); 指定输入的文件类型为json。 job.setInputFormatClass(TextInputFormat.class); 设置输入流为文本类型 job.setOutputFormatClass(EsOutputFormat.class); 设置输出为EsOutputFormat类型。 job.setMapOutputKeyClass(NullWritable.class); 设置Map的输出key类型为NullWritable类型 job.setMapOutputValueClass(BytesWritable.class); 设置Map的输出value类型为BytesWritable类型 FileInputFormat.setInputPaths(job, new Path(otherArgs[0])); 传入HDFS上的文件路径 五、总结 首先,总结一下实践过程中遇到的问题: 1.ClassNotFoundException异常遇到找不到EsOutputFormat所在类,导致的ClassNotFoundException异常: java.lang.NoClassDefFoundError: org/elasticsearch/hadoop/mr/EsOutputFormat 解决办法,使用maven-assembly-plugin插件,把第三方库打到jar包中。 2. 连接不到Elasticsearch集群 连接不到Elasticsearch集群的第一个原因是没有配置X-PACK 的用户名和密码,加上以下两行配置: conf.set("es.net.http.auth.user", "你的X-PACK用户名"); conf.set("es.net.http.auth.pass", "你的X-PACK密码"); 第二个原因就是EMR集群和Elasticsearch集群网络不通,在创建集群的时尽量选择同一区域,比如EMR集群在华东1区,Elasticsearch集群也在华东1区,事先用Ping命令测试。 第三个原因是端口,一般TCP端口(比如使用Java客户端)是9300,ES-Hadoop中使用的仍然是9200端口. 3.Reduce过程中格式错误注意测试文件中每一行都是一个JSON,在设置中加上: conf.set("es.input.json", "yes"); 否则会出现解析文件格式异常。 最后,ES-Hadoop连通了Hadoop和Elasticsearch两个大数据生态圈,本博客做了写数据的实践案例,更多资料请参考阿里云E-MapReduce 阿里云Elasticsearch
ELK 是 Elastic 公司出品的开源实时日志处理与分析解决方案,ELK 分别代表分布式搜索引擎 Elasticsearch、日志采集与解析工具 Logstash、日志可视化分析工具Kibana,具有配置方式灵活、集群可线性扩展、日志实时导入、检索性能高效、可视化分析方便等优点,已经成为业界日志处理方案的不二选择。 本场 Chat 我将分享 ELK 日志处理开发指南系列内容: 日志处理架构演进 ELK 架构解读 日志采集模块分析 日志解析模块分析 日志存储模块分析 日志可视化模块分析 项目实战 通过本场 Chat,读者应该可以掌握 ELK 日志处理的核心技术,避免采坑,对于很多 ELK 开发者关心的 Elasticsearch 集群优化、多行日志采集、复杂日志解析、索引模板的使用、Kibana 实现复杂图表等问题都会给出详细的解答。 chat链接:ELK 日志处理开发指南
一、HDFS体系结构 HDFS作为分布式文件系统,使用的是master/slave体系结构,角色有三种: NameNode:为HDFS提供元数据服务,NameNode可以控制所有文件的操作,它会把所有的文件元数据存储在文件系统树中,文件信息在硬盘上保存成两个文件:命名空间镜像文件(fsimage)和修改日志文件(edit log)。此外,NameNode还保存一个文件,用来存储数据块在数据节点的分布情况。系统启动之时,这些信息会加载到内存中。 DateNode:为HDFS提供存储,为系统提供存储服务,用于保存数据。 客户端Client:HDFS客户端节点。 还有一个Secondary NameNode,它并不是NameNode的备份,其职责是合并NameNode中的edit log和fsimage,协助NameNode工作,可以称为是检查节点。具体参考(Secondary NameNode:它究竟有什么作用?) HDFS中的文件块:HDFS基本存储单位是64M的数据块,每个文件被分成64M大小的数据块来存储。小于数据块大小的文件,不会占用整个数据块存储空间。 二、客户端文件读取流程 HDFS客户端文件读取过程如下: 应用程序通过HDFS客户端向NameNode发生远程调用请求。 NameNode收到请求之后,返回文件的块列表信息。块列表信息中包含每个block拷贝的datanode地址。 HDFS 客户端会选择离自己最近的那个拷贝所在的datanode来读取数据。 数据读取完成以后,HDFS客户端关闭与当前的datanode的链接。 如果文件没有读完,HDFS客户端会继续从NameNode获取后续的block信息,每读完一个块都需要进行校验和验证,如果读取出错,HDFS客户端会通知NameNode,重新选择一个该block拷贝的datanode读数据。 三、客户端文件写入流程 1.应用程序通过HDFS客户端向NameNode发起远程过程调用请求。 2.NameNode检查要创建的文件是否存在以及是否有足够的权限。 3.如果检测成功,NameNode会返回一个该文件的记录,否则让客户端抛出异常。 4.HDFS客户端把文件切分为若干个packets,然后向NameNode申请新的blocks存储新增数据。 5.NameNode返回用来存储副本的数据节点列表。 6.HDFS客户端把packets中的数据写入所有的副本中。 7.最后一个节点数据写入完成以后,客户端关闭。
一、前言 决定在CSDN写博客的原因是想把自己解决过的问题、踩过的坑、总结出来的经验记录下来,作为编程之路的“笔记本”,同时也能给遇到同样问题的人提供参考、节省时间,写书的初衷也一样。 二、缘起 说一下写书的前因后果。中国科学院大学雁栖湖校区是很重要的一年,师资团队无可挑剔,每次上课去的稍微晚一点,300人的大教室都没有座位。对于计算机学院而言,大数据、机器学习、信息检索(搜索引擎)、算法、人工智能是最火的几个领域,信息检索课是最受欢迎的几门课之一。最后的工程大作业是实现一个新闻搜索,图1是我最早完成的效果。 图1 基于Lucene的新闻搜索项目 后来,利用放暑假的时间,去找实习。也许是跟Elasticsearch有缘分,投的java实习生的岗位,做的是搜索的业务,也许我的简历上有Lucene这个小项目,Elasticsearch又是基于Lucene的,老大(中科大毕业的)给了我一份文档去学习Elasticsearch。这份文档是清华的一个学长做的Elasticsearch技术调研,其中很多基础性的东西都写在文档里了,有信息检索的理论、有Lucene基础,又有前辈总结的文档,上手Elasticsearch很快,业务中的大部分问题也都在短时间内得到了解决。当然,这期间也有遇到过很多问题,比如一开始对Elasticsearch中的一些概念不熟悉,理解起来比较困难,还有很多API不知道如何用,国内的相关文档也不太多,这期间我一直在Stack Overflow上和外国的大牛交流,不断的遇到问题、提问、解决问题、总结思考,如此反复,同时也把我解决过的问题贴到博客上面。就这样,博客积累的越来越多,对Elasticsearch、对搜索的体会也不断深入,同时公司里的老大、同事都给了我很多帮助和指导,进步也很快。 再后来,清华大学出版社的编辑通过博客联系到我,问我想不想写一本Elasticsearch的书,把技术分享出来。 三、创作 写书的过程跟我想的完全不一样,一开始以为博客上有那么多内容,整理整理很快书就完成了,事实上写书和写代码完全是两回事。写代码,明白需求,编写代码,解决问题,任务就完成了,而写书是写怎么写代码,要让读者看明白,同时整个书是一个体系,要有组织有路线,写作的过程要兼顾整体与部分。经过反复的思考,反复的修改,从理论到基础,从基础到项目实战,前前后后将近一年的时间完成了初稿。就在准备付梓之时,Elasticsearch 5.X发布了,而且变化比较大,又花了2个月的时间基于Elasticsearch 5.4把所有的内容更新了一遍。直到17年10月份,终于完稿! 四、特色 信息检索的基本术语、核心算法。 Lucene基础。 文件搜索项目。 Elasticsearch基础。 新闻搜索项目。 Elasticsearch和Hadoop的结合。 五、阅读指南 关于本书的阅读指南说明如下: 第1章 第一章主要介绍信息检索的基本定义、术语、分词算法、倒排索引、布尔检索模型、向量空间模型、BM25概率检索模型。这一章的目的是为了让读者对信息检索有一个基本的认识,明白搜索是这么回事。关于检索模型,不论Lucene还是ELasticsearch,相关性评分是搜索要解决的问题,理解检索模型非常重要,我的建议是自己动手推导数学公式,从数学上理解算法,到后面再用Lucene或Elasticsearch就能游刃有余。 第2章 第二章主要介绍怎么用Lucene,学习内容主要包括分词的实现、索引的构建、查询的用法以及关键字高亮。 第3章 用Lucene仿照百度文库实现一个文件检索项目。如图2所示。 第4章 Lucene和Elasticsearch的关系,Elasticsearch的诞生过程、流行度分析、架构、核心概念、和其它数据库的对比、安装与启动、中文分词器配置、常用插件配置。 第5章 第5章主要有三块内容:索引管理、文档管理和映射详解。索引管理包括索引的增、删、改、查、开、关、复制、收缩、别名,文档管理包括文档的增、删、更新、获取、批量操作、版本机制和路由机制,映射详解主要包括映射的分类、动态映射和静态映射、字段类型、元字段、映射参数和映射模板。其中映射配置和使用是重点,遇到过多次因为日期类型的mapping设置有误,上亿的数据导到Elasticsearch之后需要翻工修改。每一个需要使用Elasticsearch的开发者,在动手之前,把mapping中的所有字段、原字段、参数等配置信息过一遍是最基本的要求。 第6章 第6章主要介绍搜索机制、全文级别的查询、词项级别的查询、复合查询、嵌套查询、位置查询、特殊查询、搜索高亮的实现以及性能分析、搜索排序和评分的相关分析。 第7章 第7章主要介绍指标聚合和桶聚合。指标聚合可以实现关系型数据库中的统计功能,比如常用的求和、最大/小值、平均数等,桶聚合可以实现关系型数据库中的分组功能,每个的功能都通过具体例子进行讲解。 第8章 第8章介绍如何在Java项目中通过Java客户端做二次开发。Elasticsearch提供了多种客户端,包括Java、Javascript、Python、.NET、PHP、Ruby等常用语言,考虑到Lucene是纯java编写、基于java的项目比较多,这一章重点介绍如何使用java客户端基于Elasticsearch做二次开发。 第9章 第9章介绍集群的规划、分片的规划、分布式集群的配置、集群健康的监控与查看以及常用的API。 第10章 第10章讲解基于Elasticsearch整合Mysql实现新闻搜索项目,包括数据导入、 前端搜索框和搜索结果页、关键字高亮、搜索分页等关键步骤的实现。如图3和图4所示。 第11章 第11章介绍ES-Hadoop,ES-Hadoop是连通分布式搜索引擎Elasticsearch和Hadoop大数据生态系统的桥梁。 图2 基于Lucene的文件检索项目 图3 基于Elasticsearch的新闻搜索项目(搜索框) 图4 基于Elasticsearch的新闻搜索项目(搜索结果页) 六、购书 天猫 京东 七、勘误 囿于个人知识、能力、经验有限,欢迎读者对本书中的任何不足和疏漏之处进行批评指正,任何意见、建议都可以在本帖下回复。 八、总结 我希望阅读本书的读者: 对于数学理论,最好是能静下心,拿出纸笔,从数学公式上去推导; 对于Lucene基础,所有的代码都应该写一遍,例子都是环环相扣逐步深入的; 对于文件搜索项目,能够找一些格式不一样的文档,从0开始实现一个小的百度文库; 对于Elasticsearch,基本的安装、插件正确配置,所有的基础API都应亲自测一把; 对于新闻搜索项目,从0开始一步一步去实现一个小的百度新闻搜索; 对于ES-Hadoop,把Hadoop的安装配置好,例子运行起来。 这也是这本书所能给你的。 最后,图书的出版是技术路上的一个总结,希望本书能够帮助有需要的朋友。“三人行,必有我师”,我还会一如既往的努力,一如既往的和大家交流学习。
1.query是怎么分发到每个节点的? 2.Elasticsearch使用上的优化有哪些? 3.怎么避免脑裂? 4.query和filter的区别? 5.Elasticsearch的缺点有哪些?你觉得可以在哪些地方进行改进? 6.Lucene加快查询的机制有哪些? 7.如何使用Lucene构建分布式索引? 8.说一下master节点选举算法 9.Elasticsearch出现OOM的场景遇到过吗? 10.说一下副本的作用。 11.每天2个TB的数据怎么规划集群? 12.比较一下向量空间模型和概率检索模型的优缺点。 13.DocValues的作用。 14.一个索引下多个type可能会导致什么样的问题?
一、Spark环境搭建 1.1 下载Spark 下载地址:http://spark.apache.org/downloads.html 下载完成后解压即可。 把spark的运行目录加到环境变量: #Spark Home export SPARK_HOME=/usr/local/Cellar/spark-2.1.0-bin-hadoop2.7 export PATH=$PATH:$SPARK_HOME/bin 我这里用的是简单的本地单机版,运行计算PI的例子进行测试: run-example org.apache.spark.examples.SparkPi 如果一切顺利,可以看到以下结果: ....... 17/10/11 10:59:06 INFO DAGScheduler: Job 0 finished: reduce at SparkPi.scala:38, took 0.895042 s Pi is roughly 3.1441357206786034 ....... 二、下载安装Scala 下载地址:http://www.scala-lang.org/download/ 解压缩、添加scala目录到环境变量: #Scala Home export SCALA_HOME=/usr/local/Cellar/scala-2.12.0 export PATH=$PATH:$SCALA_HOME/bin 查看Scala版本信息: scala -version Scala code runner version 2.12.0 -- Copyright 2002-2016, LAMP/EPFL and Lightbend, Inc. 三、Idea中安装Scala插件 打开Idea,config中找到Plugins: 搜索scala: 四、Idea中创建Sbt工程 新建工程,选择SCALA->SBT: 配置工程名称和路径: 新建Scala Class: Kind选择Object,注意,这里不要选class. 写个Hello World: 运行( 如果上面文件选择class,这里没有运行scala文件到命令): 结果: 五、 Spark Maven工程 在maven工程中编写Spark程序,加入Spark的坐标: <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-core_2.10</artifactId> <version>2.1.0</version> </dependency> aven工程中要想支持Scala,需要配置Scala SDK。在IDEA中依次选择File-> project structure->Global Libraries,添加Scala SDK: 这里一定要注意Scala的版本。 六、使用Spark分析用户购物记录 下面的数据是用户购买商品的记录,数据列之间用逗号分割,依次为用户名、商品名、价格,把下面的数据保存到文件UserPurchaseHistory.csv中: John,iPhone Cover,9.99 John,HeadPhones,5.49 Jack,iPhone Cover,9.99 Jill,Samsung Galaxy Cover,8.95 Bob,iPad Cover,5.49 Jack,iPad Cover,5.49 新建一个scala文件:UserPurchaseApp.scala,编写代码: package com.sprakmllib import org.apache.spark.{SparkConf, SparkContext} /** * Created by bee on 17/10/10. */ object UserPurchaseApp { def main(args: Array[String]): Unit = { val sc = new SparkContext("local[1]", "first") val user_data = sc.textFile("/Users/bee/Documents/spark/sparkmllib/UserPurchaseHistory.csv") .map(line => line.split(",")) .map(purchaseRecord => (purchaseRecord(0), purchaseRecord(1), purchaseRecord(2))) //购买次数 val numPurchase = user_data.count(); println("购买次数: "+numPurchase) //购买商品的不同客户 val uniqueUsers = user_data.map { case (user, product, price) => user }.distinct().count() println("购买商品的不同客户: "+uniqueUsers) //总收入 val totalRevenue = user_data.map { case (user, product, price) => price.toDouble }.sum() println("总收入: "+totalRevenue) //统计最畅销的产品 val productsByPopularity=user_data.map{case(user,product,price)=>(product,1)} .reduceByKey(_+_) .collect() .sortBy(-_._2) val mostPopular=productsByPopularity(0) println("统计最畅销的产品: "+mostPopular) } } 运行结果: 购买次数: 6 购买商品的不同客户: 4 总收入: 45.400000000000006 统计最畅销的产品: (iPad Cover,2)
一、有限状态机 有限状态机是一个特殊的有向图,包含节点和连接这些节点的弧。每个有限状态机都有开始、结束和若干个中间状态,每个弧上带有从一个状态进入下一个状态的条件。 以一个简化的购物流程为例,开始和结束之间有待下单、待支付、待发货、待收货四个状态,从一个状态转向另外一个状态中间需要发送事件。 有限状态机可以用于中文地址分析,识别地址的有限状态机如下。 给出一个地址,如果当前状态是“省”,后面一个词组是二级市,就进入状态“市”;如果下一个词组是某某区或者某某县,就进入“区县”状态。 如果一个地址能从状态机的开始状态经过若干中间状态到达终止状态,地址有效,否则地址无效。 例如:北京市海淀区清华东路17号,有效。河北省大连市友好路,无效。 二、Spring Statemachine Spring Statemachine是spring的有限状态机开源框架,诞生不久。 Spring Statemachine框架的目标: 1.简单易用 2.采用层次化状态机结构简化复杂状态配置 3.State machine regions提供复杂的状态机配置 4.使用triggers, transitions, guards and actions. 5.类型安全的适配器配置 6.用于Spring应用程序上下文之外的简单实例化的生成器模式 7.基于ZooKeeper实现的分布式状态机 8.有事件监听器 9.在持久层存储状态配置 10.Spring IOC集成,bean可以和状态机交互 三、实例 3.1 创建spring boot工程 访问:http://start.spring.io/ ,新建一个spring boot工程: pom.xml: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.statemachine</groupId> <artifactId>statemachinedemo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>statemachinedemo</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-core</artifactId> <version>1.2.6.RELEASE</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 创建状态: public enum States { START, //开始 PROVINCE, //省 CITY, //市 DISTRIC, //区县 STREET, //街道 STREET_NUM, //街道号 } 定义事件: public enum Events { HAS_PROVINCE, //地址中有一级省份 HAS_CITY, //地址中有二级市 HAS_DISTRIC, //地址中有三级县或者区 HAS_STREET, //地址中有四级的街道 HAS_STREET_NUM //地址中有五级的街道号 } 设置状态之间的转换关系: import com.sun.org.apache.regexp.internal.RE; import org.apache.log4j.Logger; import org.springframework.context.annotation.Configuration; import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.listener.StateMachineListener; import org.springframework.statemachine.listener.StateMachineListenerAdapter; import org.springframework.statemachine.transition.Transition; import java.util.EnumSet; @Configuration @EnableStateMachine public class Config extends EnumStateMachineConfigurerAdapter<States, Events> { public static final Logger logger = Logger.getLogger(Config.class); @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.START) .states(EnumSet.allOf(States.class)); } @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withExternal().source(States.START).target(States.PROVINCE).event(Events.HAS_PROVINCE) .and() .withExternal().source(States.START).target(States.CITY).event(Events.HAS_CITY) .and() .withExternal().source(States.PROVINCE).target(States.CITY).event(Events.HAS_CITY) .and() .withExternal().source(States.PROVINCE).target(States.DISTRIC).event(Events.HAS_DISTRIC) .and() .withExternal().source(States.CITY).target(States.STREET).event(Events.HAS_STREET) .and() .withExternal().source(States.STREET).target(States.STREET_NUM).event(Events.HAS_STREET_NUM); } public StateMachineListener<States, Events> listener() { return new StateMachineListenerAdapter<States, Events>() { @Override public void transition(Transition<States, Events> transition) { if (transition.getTarget().getId() == States.START) { logger.info("开始:"); return; } if (transition.getSource().getId() == States.START && transition.getTarget().getId() == States.PROVINCE) { logger.info("省份:"); return; } if (transition.getSource().getId() == States.START && transition.getTarget().getId() == States.CITY) { logger.info("市:"); return; } if (transition.getSource().getId() == States.PROVINCE && transition.getTarget().getId() == States.CITY) { logger.info("市:"); return; } if (transition.getSource().getId() == States.PROVINCE && transition.getTarget().getId() == States.DISTRIC) { logger.info("县:"); return; } if (transition.getSource().getId() == States.CITY && transition.getTarget().getId() == States.STREET) { logger.info("街道:"); return; } if (transition.getSource().getId() == States.DISTRIC && transition.getTarget().getId() == States.STREET) { logger.info("街道:"); return; } if (transition.getSource().getId() == States.STREET && transition.getTarget().getId() == States.STREET_NUM) { logger.info("街道号:"); return; } } }; } @Override public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception { config.withConfiguration().listener(listener()); } } 运行: import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.statemachine.StateMachine; @SpringBootApplication public class Application implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Autowired private StateMachine<States, Events> stateMachine; @Override public void run(String... args) throws Exception { stateMachine.start(); stateMachine.sendEvent(Events.HAS_PROVINCE); stateMachine.sendEvent(Events.HAS_CITY); stateMachine.sendEvent(Events.HAS_STREET); stateMachine.sendEvent(Events.HAS_STREET_NUM); } } 结果: . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.5.6.RELEASE) 2017-09-23 17:55:27.787 INFO 9344 --- [ main] xtipc.statemachine.addr.Application : Starting Application on elk-PC with PID 9344 (E:\CODE\statemachinedemo\target\classes started by elk in E:\CODE\statemachinedemo) 2017-09-23 17:55:27.790 INFO 9344 --- [ main] xtipc.statemachine.addr.Application : No active profile set, falling back to default profiles: default 2017-09-23 17:55:27.864 INFO 9344 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5fdba6f9: startup date [Sat Sep 23 17:55:27 CST 2017]; root of context hierarchy 2017-09-23 17:55:28.565 INFO 9344 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.statemachine.config.configuration.StateMachineAnnotationPostProcessorConfiguration' of type [org.springframework.statemachine.config.configuration.StateMachineAnnotationPostProcessorConfiguration$$EnhancerBySpringCGLIB$$3d777845] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2017-09-23 17:55:29.075 INFO 9344 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2017-09-23 17:55:29.079 INFO 9344 --- [ main] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 0 2017-09-23 17:55:29.089 INFO 9344 --- [ main] xtipc.statemachine.addr.Config : 开始: 2017-09-23 17:55:29.092 INFO 9344 --- [ main] o.s.s.support.LifecycleObjectSupport : started org.springframework.statemachine.support.DefaultStateMachineExecutor@77102b91 2017-09-23 17:55:29.092 INFO 9344 --- [ main] o.s.s.support.LifecycleObjectSupport : started CITY PROVINCE END DISTRIC STREET_NUM STREET START / START / uuid=868923f4-239a-4730-ae7b-f71753876f3a / id=null 2017-09-23 17:55:29.097 INFO 9344 --- [ main] xtipc.statemachine.addr.Config : 省份: 2017-09-23 17:55:29.098 INFO 9344 --- [ main] xtipc.statemachine.addr.Config : 市: 2017-09-23 17:55:29.098 INFO 9344 --- [ main] xtipc.statemachine.addr.Config : 街道: 2017-09-23 17:55:29.098 INFO 9344 --- [ main] xtipc.statemachine.addr.Config : 街道号: 2017-09-23 17:55:29.099 INFO 9344 --- [ main] xtipc.statemachine.addr.Application : Started Application in 1.655 seconds (JVM running for 2.133) 2017-09-23 17:55:29.100 INFO 9344 --- [ Thread-2] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@5fdba6f9: startup date [Sat Sep 23 17:55:27 CST 2017]; root of context hierarchy 2017-09-23 17:55:29.101 INFO 9344 --- [ Thread-2] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 0 2017-09-23 17:55:29.102 INFO 9344 --- [ Thread-2] o.s.s.support.LifecycleObjectSupport : stopped org.springframework.statemachine.support.DefaultStateMachineExecutor@77102b91 2017-09-23 17:55:29.102 INFO 9344 --- [ Thread-2] o.s.s.support.LifecycleObjectSupport : stopped CITY PROVINCE END DISTRIC STREET_NUM STREET START / / uuid=868923f4-239a-4730-ae7b-f71753876f3a / id=null 2017-09-23 17:55:29.103 INFO 9344 --- [ Thread-2] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown 2017-09-23 17:55:29.112 INFO 9344 --- [ Thread-2] o.s.s.support.LifecycleObjectSupport : destroy called
一、硬件层面优化配置 (1) 合理选择服务器。 Elasticsearch的运行对JDK版本、Linux内核、最小内存等都有一定的要求,在安装部署集群之前需要选择和Elasticsearch版本匹配的的服务器配置,同时也要根据业务量做集群规划。 (2)提高Linux系统应用程序最大打开文件数。 在启动Elasticsearch集群以前,增大机器的最大文件数,可以避免数据导入高峰时期打开文件过多异常的发生。 (3)增大虚拟映射内存。 Elasticsearch写入文档时最终要使用Lucene构建倒排索引,增大虚拟映射内存可以加快索引速度。 二、数据导入性能优化 (4)关闭_all字段。 _all字段是把一个文档的所有字段合并在一起的超级字段,在搜索字段不明确的情况下可以对_all进行搜索,默认是开启的。如果没有模糊搜索的需要,关闭_all字段可以缩小索引大小,提高导入性能。 (5)设置副本为0。 如果副本不为0,Master节点需要把文档复制到副本分片上,复制过程需要消耗系统资源,因此可以在数据导入阶段设置副本为0,等数据导入完成以后再提高副本数。 (6)对不必要的字段不索引。 Elasticsearch可以通过参数设置字段是否建索引,建索引需要分词、解析、建倒排记录表等一系列过程,可以根据业务需求对不必要的字段(比如需要精确匹配的地名)不索引。 (7)使用批量导入。 文档一条一条的导入会导致频繁的磁盘读写,使用批量导入可以降低I/O消耗。 (8)适当增大刷新间隔。 索引数据过程中为了使数据尽快可搜索,Elasticsearch会不断刷新创建新的段并打开它,默认1s刷新1次,在数据导入期间可适当增大刷新间隔,待数据导入完成以后再恢复至默认设置。 三、查询优化 (9)使用路由。 路由机制即是通过哈希算法,将具有相同哈希值的文档放置到同一个主分片中,合理使用路由机制可以提高查询性能。 (10)正确使用Query和Filter。 Query和Filter都有查询的功能,但是Query使用的是搜索机制,需要通过评分模型计算相关度,而Filter是过滤机制,只需要根据条件进行过滤,无需计算相关度,因此Filter响应时间更快。 (11)使用Optimize API合并段。 Elasticsearch中每个分片有多个段,每个段都是一个完整的倒排索引,在查询时Elasticsearch会把所有段的查询结果汇总作为最终的查询结果。当索引的文档不断增多时段也会增多,查询性能就会下降。Optimize API可以强制分片进行段合并,把多个小的段合并成大的段(通常合并成一个段),通过减少段的数量达到提高搜索性能的目的。 在日志处理场景中,日志的存储一般按天、周、月存入索引,而且日志一旦索引就不会再修改,索引只是可读的,这种情况下把索引段设为1是非常有效的。 欢迎补充
一、学习网站 Redis官网 http://redis.io/ Redis中文网 http://www.redis.net.cn/ Redis教程 http://www.redis.net.cn/tutorial/3501.html http://www.runoob.com/redis/redis-tutorial.html 二、安装 $ brew install redis ==> Downloading https://homebrew.bintray.com/bottles/redis-3.2.1.el_capitan.bottle.tar.gz ######################################################################## 100.0% ==> Pouring redis-3.2.1.el_capitan.bottle.tar.gz ==> Caveats To have launchd start redis now and restart at login: brew services start redis Or, if you don't want/need a background service you can just run: redis-server /usr/local/etc/redis.conf ==> Summary /usr/local/Cellar/redis/3.2.1: 10 files, 1.7M 执行完命令以后Redis安装在/usr/local/Cellar/redis/3.2.1目录下. 打开terminal,启动redis服务: redis-server 启动redis服务之后就可以启动redis客户端,执行命令: redis-cli redis默认端口是6379. 配置文件在安装目录下的:redis.conf 后台启动: 修改redis.conf,找到daemonize并设置取值为yes,启动时加载配置文件: ./bin/redis-server redis.conf 三、Redis数据类型 Redis支持5种数据类型:String(字符串)、hash(哈希)、列表(list)、集合(set)以及set(有序集合). 3.1 String字符串 String是Redis最基本的类型,一个key对应一个value. 127.0.0.1:6379> SET name "w3cschool.cc" OK 127.0.0.1:6379> get name "w3cschool.cc" 上面的例子中使用了Redis的SET和GET命令.key为name,值为”w3cschool.cc” 3.2 Hash Redis hash 是一个键值对集合。 Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 127.0.0.1:6379> HMSET user:1 username w3cschool.cc password w3cschool.cc points 200 OK 127.0.0.1:6379> HGETALL user:1 1) "username" 2) "w3cschool.cc" 3) "password" 4) "w3cschool.cc" 5) "points" 6) "200" 以上实例中 hash 数据类型存储了包含用户脚本信息的用户对象。 每个 hash 可以存储 2^32 - 1 键值对(40多亿)。 3.3 List(列表) 列表是简单的字符串列表,按照插入顺序,可以添加一个元素到列表到头部或尾部. List中的key是可重复的. 127.0.0.1:6379> lpush course English (integer) 1 127.0.0.1:6379> lpush course Math (integer) 2 127.0.0.1:6379> lpush course Music (integer) 3 127.0.0.1:6379> lrange course 0 100 1) "Music" 2) "Math" 3) "English" 127.0.0.1:6379> lpop course "Music" 127.0.0.1:6379> lrange course 0 100 1) "Math" 2) "English" 列表最多可存储 232 -1 元素 (4294967295, 每个列表可存储40多亿)。 3.4 Set(集合) Set集合是String类型的无序列表.内部使用hash,添加、删除、查找的时间复杂度都是O(1)。 集合中的元素具有唯一性,初次添加成功返回1,添加的元素已存在返回0. 127.0.0.1:6379> sadd users zhangsan (integer) 1 127.0.0.1:6379> sadd users lisi (integer) 1 127.0.0.1:6379> sadd users wangwu (integer) 1 127.0.0.1:6379> sadd users zhangsan (integer) 0 127.0.0.1:6379> smembers users 1) "wangwu" 2) "lisi" 3) "zhangsan" 3.5 zset(有序集合) Redis 和 set 一样也是string类型元素的集合,且不允许重复的成员。 不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。 zset的成员是唯一的,但分数(score)却可以重复。 127.0.0.1:6379> zadd grade 100 xiaoming (integer) 1 127.0.0.1:6379> zadd grade 80 xiaohua (integer) 1 127.0.0.1:6379> zadd grade 95 xiaohong (integer) 1 127.0.0.1:6379> zadd grade 88 xiaohei (integer) 1 127.0.0.1:6379> ZRANGEBYSCORE grade 0 100 1) "xiaohua" 2) "xiaohei" 3) "xiaohong" 4) "xiaoming" 127.0.0.1:6379> 四、Redis Java API 4.1.参考资料 http://www.runoob.com/redis/redis-java.html 4.2.下载jar包 http://repo1.maven.org/maven2/redis/clients/jedis/2.9.0/ 4.3.导入jar包 新建java工程,导入jedis-version.jar 4.4 测试连接 先启动redis-server. Jedis jedis = new Jedis("localhost"); System.out.println(jedis.ping()); console中打印PONG说明成功连接到服务器. 4.5 String操作 //设置 redis 字符串数据 jedis.set("w3ckey", "Redis tutorial"); // 获取存储的数据并输出 System.out.println("w3ckey: " + jedis.get("w3ckey")); //获取一个不存在的key会打印null System.out.println("w3ckey2: " + jedis.get("w3ckey2")); //拼接字符串 jedis.append("w3ckey", " Second Edition"); // 输出appedn之后的value System.out.println("append之后的w3ckey: " + jedis.get("w3ckey")); //一次设置多个键值对 jedis.mset("name", "张三", "age", "24", "QQ", "175400XX"); System.out.println("name:" + jedis.get("name")); System.out.println("age:" + jedis.get("age")); System.out.println("QQ:" + jedis.get("QQ")); //对age执行加1操作 只能对value为整型的key进行incr()操作. jedis.incr("age"); System.out.println("age:" + jedis.get("age")); 4.6.List操作 System.out.println("**********List操作**********"); //存储数据到列表 jedis.lpush("course-list", "Math"); jedis.lpush("course-list", "English"); jedis.lpush("course-list", "Music"); jedis.lpush("course-list", "Math"); //读取List数据 List<String> list = jedis.lrange("course-list", 0, 10); for (String str : list) { System.out.println(str); } 4.7.Set操作 System.out.println("**********Set操作**********"); jedis.sadd("color", "White"); jedis.sadd("color", "Blue"); jedis.sadd("color", "Red"); jedis.sadd("color", "Blue"); jedis.sadd("color","Black","Yellow","Purple"); //移除一个元素 jedis.srem("color","Yellow"); System.out.println(jedis.smembers("color")); //set中的元素不重复 循环遍历 Set<String> color = jedis.smembers("color"); Iterator<String> iter = color.iterator(); while (iter.hasNext()) { System.out.println(iter.next()); } //判断集合中是否存在某个元素 System.out.println(jedis.sismember("color", "Red")); //返回集合中的元素个数 System.out.println(jedis.scard("color")); //返回集合中的一个随机数 System.out.println(jedis.srandmember("color")); //返回集合中多个随机数 System.out.println(jedis.srandmember("color",2)); 4.8 map操作 //存入一个map Map<String,String> map=new HashMap<String,String>(); map.put("title","Redis In Action"); map.put("author","Jos"); map.put("price","45$"); jedis.hmset("book",map); //读取book的一个字段 返回的是一个list System.out.println(jedis.hmget("book","title")); //读取book的一个多个字段 System.out.println(jedis.hmget("book","title","author","price")); //删除map中的某个key jedis.hdel("book","price"); //新增一个key jedis.hset("book","description","Useful Book!"); //判断是否存在一个map System.out.println("if exists map book:"+jedis.exists("book")); //打印一个map System.out.println("get all:"+jedis.hgetAll("book")); //打印map中所有的key System.out.println("get all keys:"+jedis.hkeys("book")); //打印map中所有的value System.out.println("get all values:"+jedis.hvals("book")); //map中key的个数 System.out.println(jedis.hlen("book")); //遍历一个map Iterator<String> iter2=jedis.hkeys("book").iterator(); while(iter2.hasNext()){ String key=iter2.next(); System.out.println(key+":"+jedis.hmget("book",key)); } 4.9.zset操作 System.out.println("***zset操作***"); jedis.zadd("grade",100,"StudentA"); jedis.zadd("grade",70,"StudentB"); jedis.zadd("grade",85,"StudentC"); jedis.zadd("grade",95,"StudentD"); System.out.println(jedis.zrange("grade",70,100)); System.out.println(jedis.zrangeByScore("grade",0,100)); 4.10.Keys操作 //删除一个key jedis.del("course-list"); jedis.del(""); System.out.println("**********All Keys**********"); Set<String> listKeys = jedis.keys("*"); for (String str : listKeys) { System.out.println(str); //jedis.del(str); } 五、Redis占用内存大小 启动redis-server,redis-cli 查看redis占用了多少内存,使用info命令来查看. 输入命令 127.0.0.1:6379> info memory 返回信息: # Memory used_memory:1008784 //数据占用了多少内存(字节 used_memory_human:985.14K //数据占用了多少内存(带单位的,可读性好) used_memory_rss:2117632 //redis占用了多少内存 used_memory_rss_human:2.02M //redis占用了多少内存 (带单位的,可读性好) used_memory_peak:1009568 //占用内存的峰值(字节) used_memory_peak_human:985.91K //占用内存的峰值(带单位的,可读性好) total_system_memory:8589934592 //系统总内存大小(字节) total_system_memory_human:8.00G //系统总内存大小(带单位) used_memory_lua:37888 //lua引擎所占用的内存大小(字节) used_memory_lua_human:37.00K //lua引擎所占用的内存大小(带单位) maxmemory:0 //允许Redis使用的最大内存(字节) maxmemory_human:0B //允许Redis使用的最大内存(带单位) maxmemory_policy:no eviction //超过最大内存后的内存回收策略 mem_fragmentation_ratio:2.10 //内存碎片率 mem_allocator:libc //redis内存分配器版本,在编译时指定的。有libc、jemalloc、tcmalloc这3种。 设置maxmemory之后,redis的内存使用不得超过设定的值,如果redis的内存使用量超过了最大值,redis会尝试使用回收策略回收相应的keys. 如果不能根据回收策略移除keys,或者回收策略设置成no eviction,那么redis没有内存可用,对于需要写操作的命令返回错误信息,对于只读操作会继续响应. 测试100万个key-value占用的内存大小: 写入之前,Redis占用的内存大小:26.23M 写入100万个key-value: for (int i = 0; i < 1000000; i++) { jedis.set("key " + i, "redis performance " + i); } 写入之后,Redis占用的内存大小:100.53M 数据占内存的大小与key和key value的长度正相关。 六、Redis相关命令 6.1 键值相关命令 6.1.1 数据库选择 redis数据库0-15一共16个 默认使用第0个 使用第一个: 127.0.0.1:6379> SELECT 1 OK 127.0.0.1:6379[1]> keys * (empty list or set) 服务器、端口后面会有数据库名称. 6.1.2.过期时间设置 默认的key不设置过期时间是持久存储的。设置过期时间命令: expire keyname livetime 比如:expire name 50 ,这条命令设置key为name的存活时间长度为50秒. 通过ttl命令可以获取key的有效时长: ttl keyname 如果一个key已经设置了过期时间,想要持久化,可以移除过期时间: 127.0.0.1:6379> expire bloghash 300 (integer) 1 127.0.0.1:6379> ttl bloghash (integer) 295 127.0.0.1:6379> ttl bloghash (integer) 294 127.0.0.1:6379> persist bloghash (integer) 1 127.0.0.1:6379> ttl bloghash (integer) -1 ttl keyname返回值为-1说明是持久化的key. 6.13.随机返回一个key 127.0.0.1:6379> randomkey "mylist" 127.0.0.1:6379> randomkey "set4" 127.0.0.1:6379> randomkey "bloghash" 6.1.4.查看返回值类型 127.0.0.1:6379> type mylist list 127.0.0.1:6379> type myset set 127.0.0.1:6379> type myhash hash 127.0.0.1:6379> type name string 6.2 服务器相关命令 测试链接是否存活 127.0.0.1:6379> ping Could not connect to Redis at 127.0.0.1:6379: Connection refused not connected> ping PONG 打印内容 127.0.0.1:6379> echo helloworld "helloworld" 退出 quit 返回当前数据库中key的数目 127.0.0.1:6379> dbsize (integer) 11 获取服务器信息和统计 127.0.0.1:6379> info info会返回所有的信息 127.0.0.1:6379> info server # Server # Clients # Memory # Persistence # Stats # Replication # CPU # Cluster # Keyspace 删除当前数据库中所有的key flushdb 删除所有数据库中所有的key flushall 七、Redis宣言 Redis宣言里面阐述了Redis的七大特性,值得学习,原文和翻译
在软件开发中,很多时候需要在特定时间的时间执行某些操作,比如每天的凌晨三点、每周的周日、每个月的15号,Apache Quartz就是一个开源的作业调度框架,可以让计划的程序任务一个预定义的日期和时间运行。这篇博客记录一下Quartz的安装、重要概念和入门例子。 一、Quartz下载 Quartz官网:http://www.quartz-scheduler.org/ 以2.2.3版本为例,下载 Quartz 2.2.3 .tar.gz,解压,把quartz-2.2.3-distribution\quartz-2.2.3\lib目录下jar包添加到工程中即可,如果使用maven,添加一下依赖: <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> <version>2.2.1</version> </dependency> 二、入门例子 先写一个类,MyJob.java,注意,这个类一定要是public的。 import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import java.util.Date; public class MyJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("My QuarTz Job"); System.out.println("执行时间:"+new Date()); } } 再写一个QuartzTest.java,实现每天24:00执行MyJob中的任务: import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import java.util.Date; public class QuartzTest{ public static void main(String[] args) { //创建一个SchedulerFactory对象 SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = null; try { //通过SchedulerFactory对象获取任务调度器 scheduler = schedulerFactory.getScheduler(); //创建一个job JobDetail job = JobBuilder.newJob(MyJob.class) .withIdentity("job1", "group1") .build(); //定义触发器,也就是执行job的规则 Trigger trigger=TriggerBuilder.newTrigger() .withSchedule(CronScheduleBuilder.cronSchedule("0 0 0 * * ?")) .build(); //把job和触发器注册到调度器中 scheduler.scheduleJob(job,trigger); //启动调度器 scheduler.start(); } catch (SchedulerException e) { e.printStackTrace(); } } } 执行QuartzTest类中的main方法,等到00:00的时候就会执行MyJob中的任务: My Quartz Job 执行时间:Tue Aug 01 00:00:00 CST 2017 三、核心概念 先扫盲单词,查词典: Quartz:石英,一种矿石 有一种手表叫石英手表: 我一直没想明白为什么定时任务调度框架以一块石头命名,如果和石英手表联系起来,和精准的时间联系起来,似乎有点道理了。我猜测开发这个框架的程序员给框架命名的时候刚好戴了一块石英手表,就以Quartz命名了。好了,这段纯属瞎扯,为什么命名为Quartz有待考证(有知道的请告知),脑洞时间结束。 3.1 Scheduler(调度器) schedule:名词,任务,时刻表 scheduler:名词,程序调度器 如果想通过Quartz创建定时任务,那么首先创建一个用于任务调度的调度器,也就是Scheduler的对象。Scheduler对象要通过调度器工厂类SchedulerFactory创建。 SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); 3.2 Job(任务) job就是我们要执行的任务,也就是MyJob中的代码。一个Java类,先实现Job接口,再复写父类的execute()方法,然后创建JobDetail对象,传通过反射机制(也就是MyJob.class)实现JobDetail对象。 JobDetail job = JobBuilder.newJob(MyJob.class) .withIdentity("job1", "group1") .build(); 3.3 Trigger(触发器) 查单词: tiger:老虎 trigger:枪的扳机、触发器 既然是定时任务,调度器也好了,任务也有了,什么时候执行任务就是在Trigger中配置的,每天的00:00扣动扳机: Trigger trigger=TriggerBuilder.newTrigger() .withSchedule(CronScheduleBuilder.cronSchedule("0 0 0 * * ?")) 把Job和Trigger装载到Scheduler中: scheduler.scheduleJob(job,trigger); 启动: scheduler.start(); 等到00:00,砰,不是枪声,而是Quartz的定时任务执行了。 继续学习之后再补充。。。。
最近参考各种资料,尤其是《深入理解Java虚拟机 JVM高级特性和最佳实践》,大牛之作。把最近学习的Java虚拟机组成和垃圾回收机制总结一下。 你不会的都是新知识,学无止境,每天进步一点点。 一、认识Java虚拟机 在开始学Java之时,必做的一件事就是从Java官网下载并安装Java到我们的电脑之上,然后从HelloWorld开始走上编程的不归路。 上图中下载的Java安装包全称是Java SE Development Kit(单词依次翻译:Java 标准版本 开发 工具包),简称JDK,也就是供程序员使用的Java开发工具包。另外,JDK一般都是和它的2个小弟一起出现的,一个是JRE,一个是JVM,它们的关系如下图所示: JDK=JRE+Java编译器、开发工具和更多的类库 简单来说JDK支持Java程序的开发。 JRE是Java Runtime Environment的简称 JRE=Java虚拟机+Java基础类库 是Java程序运行所需要的软件环境,简单的来说JRE支持Java程序的运行。图片中也提到了,JRE只支持java字节码的运行,是没办法把Java代码编译成.class文件的。 最内部也是最核心的就是Java虚拟机了,那么问题来了,什么是Java虚拟机? Java虚拟机实际上是一种规范,是一种抽象机器,依附在真实的操作系统之上,这个规范描述了一个指令集,一组寄存器,一个堆栈,一个“垃圾堆”,和一个方法区。一旦一个Java虚拟机在给定的平台上运行,任何Java程序都能在这个平台上运行。 二、JVM运行时数据区 编写好的java代码交给java虚拟机,java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,称为运行时数据区,如下图所示。 图中绿色标记的区域是每个线程私有的,也就是线程安全的。灰色标记的区域是所有线程共享的,非线程安全。这幅图中我们只需要关注运行时数据区中的5个部分。 2.1 程序计数器 程序计数器的功能: 当前线程所执行的字节码的行号指示器。 .java文件编译成.class文件,最终交给jvm执行,.class文件也是要按逻辑找到行号执行的,程序计数器就是对当前线程应该执行哪一行字节码做指示的。假设线程A暂停,过一段时间后线程A继续执行,这时候线程A应该从哪个地方继续就是靠程序计数器来完成的。 每个线程一个程序计数器,不同线程之间的程序计数器互不影响。 2.2 虚拟机栈 虚拟机栈描述的是java方法执行的内存模型。 一个线程一个栈,一个方法一个栈帧。栈帧可以理解成栈中的一小块,一个栈中有多个栈帧。栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。 如下,MethodStackTest类中有三个方法:methodA、methodB和methodC,methodA调用了methodB,methodB调用了methodC,在main方法中执行methodA。 代码及打印结果: class MethodStackTest{ public static void methodA(){ methodB(); System.out.println("Method A"); } public static void methodB(){ methodC(); System.out.println("Method B"); } public static void methodC(){ System.out.println("Method C"); //methodA();取消注释会出现栈内存溢出 } public static void main(String[] args) { methodA(); } } 结果: Method C Method B Method A 这个小例子有助于解释了栈的含义,A调用了B,B调用了C,只有C执行结束,B才会结束,最终B执行完成A才会结束。 另外,如果在C中再调用A的话就一直循环调用,超过虚拟机所允许的栈的深度以后就会抛出StackOverflowError异常,如果不能申请到足够的内存,就会抛出OutOfMemory异常。 2.3 本地方法栈 本地方法栈和虚拟机栈发挥的作用相似,本地方法栈为本地方法服务。 2.4 堆 Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。堆被划分成三个不同的区域:新生代 ( Young )、老年代 ( Old )和永久区。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。Java堆也是垃圾回收的主战场,也被称为GC堆。 堆大小=新生代+老年代+永久区 新生代=Eden+From Survivor+To Survivor 默认:Eden:From:To=8:1:1 另外新生代和老年代的比例可以通过参数设置,并不一定是1:1。 2.5 方法区 方法区也被称为永久区,里面有类信息、常量、静态变量等数据,别名为非堆,为各个线程所共享。 三、垃圾回收机制 3.1 什么是垃圾? Java中的垃圾是指已经分配内存但不再有任何引用(不完全准确,后面会再说)的对象,垃圾回收(GC)就是自动清理这部分内存。 3.2 两种垃圾判断算法 3.2.1 引用计数法 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。 这种算法的有点事实现简单,判定效率也高,但是主流虚拟机并没有采用,主要是对于循环引用的对象无法进行回收。 3.2.3 可达性分析法 主流的JVM回收垃圾主要采用可达性分析法,算法的核心思想是选定一系列GC ROOTS对象作为起始点,从节点开始向下搜索,搜索路径称为引用链。一个对象到GC ROOTS没有任何引用链证明对象不可用。 可作为GC ROOTS的对象: 虚拟机栈中引用的对象 方法区中静态属性引用的对象 方法区中常量引用的对象 本地方法中JNI引用的对象 3.3 四种垃圾回收算法 3.3.1 标记-清除法(Marked-Sweep) 标记清除算法是最基础的收集算法,其他收集算法都是基于这种思想。标记清除算法分为“标记”和“清除”两个阶段:首先标记出需要回收的对象,标记完成之后统一清除对象。 它的主要缺点: 标记和清除过程效率不高 。 标记清除之后会产生大量不连续的内存碎片。 3.3.2 复制算法(Copying) 它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次理掉。这样使得每次都是对其中的一块进行内存回收,不会产生碎片等情况,只要移动堆订的指针,按顺序分配内存即可,实现简单,运行高效。 主要缺点:内存缩小为原来的一半。 3.3.3 标记-整理法 标记操作和“标记-清除”算法一致,后续操作不只是直接清理对象,而是在清理无用对象完成后让所有存活的对象都向一端移动,并更新引用其对象的指针。 主要缺点:在标记-清除的基础上还需进行对象的移动,成本相对较高,好处则是不会产生内存碎片。 3.3.4 分代算法 分代算法就是根据堆内存中新生代和老年代对象的特点,不同的区域采用不同的垃圾回收算法。新生代大多数对象“朝生夕死”,每次都有大量对象被回收,因此采用复制算法,上图堆内存中FROM和TO就是为了复制算法而设计的。老年代对象存活率较高,没有额外空间进行分配担保,使用“标记-清理”或者“标记整理”算法。 3.4 七个垃圾收集器 七种作用于不同分代的垃圾收集器: 垃圾收集器这部分没有写的太详细,这里有一篇已经整理好的:http://www.jianshu.com/p/50d5c88b272d,其内容也是根据《深入理解Java虚拟机 JVM高级特性和最佳实践》整理的。 3.4.1 Serial收集器 Serial收集器是最基本、发展历史最悠久的单线程收集器,最大的特点是Stop The World,垃圾回收时要终止用户进程,等GC结束用户进程方可继续。 JVM参数-XX:+UseSerialGC 3.4.2 ParNew收集器 ParNew收集器是Serial收集器的多线程版本。 3.4.3 Parallel Scavenge收集器 Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。 3.4.4 Serial Old收集器 Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。 3.4.5 Parallel Old收集器 Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。 3.4.6 CMS收集器 CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。 3.4.7 G1收集器 G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。 3.4 GC范围 按范围分,GC有Minor GC、Major GC和Full GC。 从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC,从老年代回收内存被称为Major GC,同时回收新生代和老年代称为Full GC。 四、内存分配策略 4.1 对象优先分配在Eden区 对象优先在新生代Eden中分配,Eden中没有足够空间进行分配时,虚拟机发起一次Minor GC。 4.2 大对象直接进入老年代 大对象是指需要大量连续内存空间的Java对象,典型的是长字符串和数组,这种大对象会被分配到老年代之中。 4.3 老不死的对象进入老年代 新生代中的对象经过不断GC仍然存活,达到一定的“年龄”就会进入老年代,每经历一次Minor GC,年龄+1,达到15时(默认)就会进入老年代。晋升老年代的年龄阈值可以通过-XX:MaxTenuringThreshold参数指定。 4.4 动态对象年龄判定 JVM也不是严格执行4.3中的年龄要求,Survivor空间相同年龄所有对象大小的总和大于等于Suivivor空间的一半,年龄大于或等于该年龄的对象就会直接进入老年代,无需达到XX:MaxTenuringThreshold的要求年龄。 4.5 空间分配担保 Minor GC之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果是,GC是确保安全的。JVM继续检查老年代最大的可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则正常进行一次YGC,尽管有风险(因为判断的是平均大小,有可能这次的晋升对象比平均值大很多); 如果小于,或者HandlePromotionFailure设置不允许空间分配担保,这时要进行一次Full GC。 五、小结 JVM这一部分初步整理这么多,欢迎批评指正。
一、集合框架 集合是容纳数据的容器,java常用的集合体系图如下。以集合中是否运行重复元素来分,主要有List和Set接口,List集合中可以有重复元素,Set集合集合中的元素不可重复,Iterator和List Iterator是遍历集合的2个迭代器,Map是存储Key/Value键值对的容器。 java集合体系图 二、迭代器 迭代器的用法写在后面,这里说明Iterator和ListIterator的区别: Iterator在遍历一个集合的过程中不能修改集合中的对象,ListIterator可以修改对象 ListIterator从后往前遍历,有hasPrevious()方法,Iterator没有 ListIterator可以在遍历过程中进行添加元素操作,Iterator不能,否则会报java.util.ConcurrentModificationException异常。 三、List集合 List集合存储不唯一、有序的对象,可以操作角标。 3.1 ArrayList 3.1.1内部实现 ArrayList也叫变长数组,数组的长度是固定的,ArrayList可以随着元素的增多长度增加,内部实现为数组。ArrayList在添加元素时首先会判断长度,长度不够时,长度扩展到原来到1.5倍,原数组复制到新数组。 3.1.2关键属性 属性 取值或结论 是否允许重复 允许 是否有序 有序 是否允许为空 允许 是否线程安全 否 使用场景 多查询、少增删 3.1.3 测试示例 创建一个ArrayList对象,添加5个元素,其中一个为null: arrayList.add("a1"); arrayList.add("a2"); arrayList.add(null); arrayList.add("a3"); arrayList.add("a4"); System.out.println(arrayList); 打印结果: [a1, a2, null, a3, a4] 使用迭代器for循环方式遍历: for (Iterator iter=arrayList.iterator();iter.hasNext();){ System.out.println(iter.next()); } 使用迭代器while循环方式遍历: Iterator iter=arrayList.iterator(); while (iter.hasNext()){ System.out.println(iter.next()); } 存储自定义对象: public class Boy { private String name; private int age; //省略setter、getter、构造器和toString()方法 } 添加Boy对象到ArrayList中并打印: ArrayList<Boy> boys=new ArrayList<>(); Boy b1=new Boy("Tom",12); Boy b2=new Boy("Jack",11); Boy b3=new Boy("Mike",15); boys.add(b1); boys.add(b2); boys.add(b3); System.out.println(boys); Iterator<Boy> iter=boys.iterator(); while (iter.hasNext()){ Boy b=iter.next(); System.out.println(b.getName()+"----"+b.getAge()); } 结果: [Boy{name='Tom', age=12}, Boy{name='Jack', age=11}, Boy{name='Mike', age=15}] Tom----12 Jack----11 Mike----15 3.1.4 转成线程安全 非线程安全的ArrayList ArrayList<String> arrayList = new ArrayList(); 线程安全的ArrayList List arrayList =Collections.synchronizedList(new ArrayList<String>()) ; 3.1.5 常用方法 clear():移除列表中的所有元素 contains(Object o): 包含指定元素 get(int index):返回列表中指定位置上的元素 indexOf(Object o): 返回列表中首次出现指定元素的索引,如果列表不包含,则返回-1 isEmpty():列表为空返回true lastIndexOf(Object o):返回列表中最后一次出现指定元素的索引,如果列表不包含元素则返回-1 remove(int index):移除列表中指定位置的元素 remove(Object o):移除列表中首次出现的指定元素 removeRange(int fromIndex,int toIndex):移除列表中索引在fromIndex(包括)和toIndex(不包括)之间的所有元素。 size():返回集合的元素个数 set(int index,E element):修改指定位置上的元素 toArray():按顺序返回包含此列表中所有元素的数组 3.2 LinkedList 3.2.1内部实现 内部实现为双向链表,删除和增加速度快,查找速度慢。 3.2.2关键属性 属性 取值或结论 是否允许重复 允许 是否有序 有序 是否允许为空 允许 是否线程安全 否 使用场景 多增删、少查询 3.2.3 测试示例 LinkedList当作FIFO的队列使用,也就是常用的add方法添加元素: LinkedList quene=new LinkedList(); quene.add("a"); quene.add("b"); quene.add("c"); quene.add("d"); System.out.println("打印队列:"+quene); System.out.println("获取队头:"+quene.getFirst()); System.out.println("获取队尾:"+quene.getLast()); System.out.println("移除队头:"+quene.pop()); System.out.println("移除队头之后的队列:"+quene); 打印结果: 打印队列:[a, b, c, d] 获取队头:a 获取队尾:d 移除队头:a 移除队头之后的队列:[b, c, d] LinkedList当作FILO的栈使用: LinkedList stack = new LinkedList(); stack.push("1"); stack.push("2"); stack.push("3"); stack.push("4"); System.out.println("打印栈:"+stack); System.out.println("获取栈顶元素:"+stack.peek()); System.out.println("打印栈:"+stack); System.out.println("取出栈顶元素:"+stack.pop()); System.out.println("打印栈:"+stack); 打印结果: 打印栈:[4, 3, 2, 1] 获取栈顶元素:4 打印栈:[4, 3, 2, 1] 取出栈顶元素:4 打印栈:[3, 2, 1] 除了ArrayList中包含的基本方法以为,LinkedList中多了getFirst()、getLast()、addFirst()、addLast()、peek()、peekFirst()、peekLast()、removeFirst()、removeLast()等方法。 3.2.4 转成线程安全 List<Integer> linkedList=Collections.synchronizedList(new LinkedList<Integer>()); 3.3 Vector Vector内部是数组结构,线程安全,速度较慢,几乎不用。 四、Set集合 Set集合中的元素唯一、无序,没有角标。 4.1HashSet 4.1.1内部实现 内部结构是哈希表,对象存入HashSet之前,要先确保对象重写equals()和hashCode()方法,这样才能比较对象的值是否相等,确保set中没有存储相同的对象。判断两个元素是否相同,首先通过hashCode()方法判断的是2个元素的哈希值是否相同,再根据equals()方法判断值是否相同,只有2者都相同才是统一元素。 4.1.2 基本属性 属性 取值或结论 是否允许重复 不允许 是否有序 无序 是否允许为空 允许(只有一个null) 是否线程安全 否 使用场景 对象不重复和需要快速查找的场景 4.1.3 测试示例 HashSet<String> hashSet=new HashSet(); hashSet.add("abc"); hashSet.add("张三"); hashSet.add("李四"); hashSet.add("tim"); hashSet.add(null); hashSet.add(null); System.out.println("HashSet 大小:"+hashSet.size()); Iterator iter=hashSet.iterator(); while (iter.hasNext()){ System.out.println(iter.next()); } 打印结果: HashSet 大小:5 null 李四 张三 abc tim 添加自定义对象,仍然添加Boy类中的对象 Boy b1=new Boy("神乐",12); Boy b2=new Boy("神乐",12); HashSet<Boy> boys=new HashSet<>(); boys.add(b1); boys.add(b2); System.out.println(boys); 结果: [Boy{name='神乐', age=12}, Boy{name='神乐', age=12}] 这时候b1和b2其实是一个对象,在Boy类中重写hashCode()和equals()方法: public class Boy { private String name; private int age; //省略setter、getter、构造器和toString()方法 @Override public int hashCode() { return name.hashCode()+age; } @Override public boolean equals(Object obj) { if (this==obj) return true; if (!(obj instanceof Boy)) throw new ClassCastException("类型错误"); Boy boy=(Boy) obj; return this.name.equals(boy.name)&&(this.age==boy.age); } } 重写equals和hashCode方法以后,上述集合中就会只添加一个对象: [Boy{name='神乐', age=12}] 4.1.4 转成线程安全 Set<String> hSet=Collections.synchronizedSet(new HashSet<String>()); 4.1.5 唯一且有序 LinkedHashSet集合中都元素唯一且有序,这里都有序是指添加顺序。 LinkedHashSet<String> lHashSet= new LinkedHashSet<String>(); lHashSet.add("abc"); lHashSet.add("张三"); lHashSet.add("李四"); lHashSet.add("tim"); lHashSet.add(null); iter=lHashSet.iterator(); while (iter.hasNext()){ System.out.println(iter.next()); } 打印结果: abc 张三 李四 tim null 4.2 TreeSet 4.2.1 内部实现 TreeSet内部实现为二叉树,可以对元素进行排序 4.2.2 基本属性 属性 取值或结论 是否允许重复 不允许 是否有序 无序 是否允许为空 允许(只有一个null) 是否线程安全 否 使用场景 去重且排序 4.2.3 测试示例 Boy b1=new Boy("定春",16); Boy b2=new Boy("神乐",12); Boy b3=new Boy("桑巴",13); TreeSet<Boy> treeSet=new TreeSet<>(new Comparator<Boy>() { @Override public int compare(Boy o1, Boy o2) { return o1.getAge()-o2.getAge(); } }); treeSet.add(b1); treeSet.add(b2); treeSet.add(b3); System.out.println(treeSet); 打印结果: [Boy{name='神乐', age=12}, Boy{name='桑巴', age=13}, Boy{name='定春', age=16}] 4.2.4 转成线程安全 TreeSet<Boy> treeSet=new TreeSet<>(new Comparator<Boy>() { @Override public int compare(Boy o1, Boy o2) { return o1.getAge()-o2.getAge(); } }); Set<Boy> treeSet1=Collections.synchronizedSet(treeSet); 五、Map集合 Map只存储的是键值对,Key必须唯一且不为空,key允许为null。 5.1HashMap 5.1.1基本属性 属性 取值或结论 是否允许重复 key重复会被覆盖,value可重复 是否有序 无序 是否允许为空 key和value都允许为空 是否线程安全 否 5.1.2 测试示例 词频统计: String[] arr={"Hadoop","Lucene","ES","ES","ES","Hadoop","Java","JS"}; //TreeMap<String,Integer> map=new TreeMap<>(); HashMap<String,Integer> map=new HashMap<>(); for (String str:arr){ if (map.containsKey(str)){ map.put(str,map.get(str)+1); }else{ map.put(str,1); } } Set<String> keys=map.keySet(); Iterator<String> iter=keys.iterator(); while (iter.hasNext()){ String str=iter.next(); System.out.println(str+"---"+map.get(str)); } 打印结果: Java---1 Hadoop---2 JS---1 Lucene---1 ES---3 如果想按value排序,可以改成TreeMap(); 六、性能对比 ArrayList遍历性能对比 ArrayList的遍历可以用一般for循环、foreach循环、Iterator迭代器三种方式实现,为了测试它们的性能,先创建一个ArrayList对象,添加5万个字符串: ArrayList<String > a = new ArrayList(); for (int i = 0; i < 60000; i++) { a.add(""+i); } for 循环打印并记录耗时: long start = System.currentTimeMillis(); for (int i = 0; i < a.size(); i++) { System.out.print(a.get(i)); } long end = System.currentTimeMillis(); System.out.println("\n下标for循环:" + (end - start)); 结果: 150 foreach循环打印并记录耗时: start = System.currentTimeMillis(); for (String i : a) { System.out.print(i); } end = System.currentTimeMillis(); System.out.println("\nforeach:" + (end - start)); 耗时: 95 Iterator循环打印: start=System.currentTimeMillis(); Iterator iter=a.iterator(); while (iter.hasNext()){ System.out.print(iter.next()); } end=System.currentTimeMillis(); System.out.println("\nIter:"+(end-start)); 耗时: 60 结论:一般for 循环最慢、foreach次之、Iterator最快。 另外,Iterator遍历还有另外一种变形方式,效率和while形式一样。 for(Iterator it=a.iterator();it.hasNext();){ System.out.print(it.next()); } 七、参考文章 有些文章总结的很好,列出来供参考学习: Java集合框架
一、用户组和用户 Linux是一个多用户、多任务环境,如下图,GroupA代表一个用户组,GroupB代表一个用户组,root是超级用户。 Linux中,任何一个文件都有User、Group和others3种身份的级别。以UserB为例,UserB属于GroupA,UserE相对于UserB就是others。 二、文件属性 查看文件属性的命令: ls -al 在一个文件夹下使用上述命令: 返回结果从左到右一共有7列: 1:文件的类型与权限 第一列的前10个字符(@除外,mac特有)代表文件组的读、写、执行权限。 第一个字符代表文件是目录、文件或者连接文件。 d:代表目录 -:代表文件 l:连接文件 b: 可供存储的接口设备 c:串行端口设备(鼠标、键盘等) 后面9个每3个一组,均为”rwx”的组合: r:代表可读 w:代表可写 x:代表可执行 第一组为文件所有者的权限、第二组为用户组的权限、第三组为其它非本用户组的权限。 以drwxr-xr-x为列: d rwx r-x r-x 1 234 567 890 1:说明文件类型为文件夹 234:文件所有者可读、可写、可执行(rwx) 567:文件所有者的用户组可读、可执行不可写(r-x) 890: others用户可读、可执行不可写(r-x) 2:连接 第二列代表有多少个文件名连接到此节点。 3:所有者 第三列代表文件的所有者 4:用户组 第四列代表文件所属的用户组 5:文件大小 第五列代表文件大小,单位为B。如果想使用可读模型,使用-h参数,即: ls -alh 6:修改日期 第六列代表文件的创建日期或者最近修改日期。格式为月、日和时间,若文件第修改时间较久,会显示年份。 7:文件名 第七列代表文件名,如果文件前面有一个.代表该文件是隐藏文件。 三、改变文件属性与权限 如果需要改变文件(夹)的文件所属用户组、文件所有者、文件的权限,Linux提供列了相应的修改命令。 3.1改变文件所属用户组 chgrp:change group的缩写,把install.log文件的用户组改为users: chgrp users intall.log 如果是文件夹,需要添加-R参数,文件夹下的子文件和子文件夹都会递归添加。 3.2 改变文件所有者 chown:change owner的缩写,改变文件的所有者: chown bee intall.log 同样,文件夹可以使用-R参数。 3.3改变文件的权限 chmod:改变文件的权限 linux的文件基本权限有9个,分别是owner、group和others三种身份各自有的读、写、执行权限,各权限读分数如下: r:4 w:2 x:1 每种身份的权限都需要累加,例如,对.bashrc文件,修改其执行权限为:owner=rwx=4+2+1,group=rwx=4+2+1,others=---=0+0+0=0,执行命令: chmod 770 .bashrc 四、笔试题 在Linux中,对file.sh文件执行chmod 645 file.sh,该文件对权限是:(D) A -rw-r--r-- B -rw-r--rx- C -rw-r--rw- D -rw-r--r-x 五、参考资料 《鸟哥的Linux私房菜》
一、需求 A、B、C代表3个用户,第二列代表各自的得分,求A、B、C的最好成绩以及A、B、C最好成绩的均值 A 10 A 11 A 13 B 11 B 11 B 12 C 10 C 10 C 11 C 15 二、思路 先terms分组,求最大值,最后加一个pipeline均值。一开始想用bucket_script解决,实验发现走不通,但是bucket_script在聚合结果之上操作很有用 三、测试数据 PUT sport { "mappings": { "grade": { "properties": { "user": { "type": "keyword" }, "grade":{ "type": "integer" } } } } } PUT sport/grade/1 { "user":"A", "grade":10 } PUT sport/grade/2 { "user":"A", "grade":11 } PUT sport/grade/3 { "user":"A", "grade":13 } PUT sport/grade/4 { "user":"B", "grade":11 } PUT sport/grade/5 { "user":"B", "grade":11 } PUT sport/grade/6 { "user":"B", "grade":12 } PUT sport/grade/7 { "user":"C", "grade":10 } PUT sport/grade/8 { "user":"C", "grade":10 } PUT sport/grade/9 { "user":"C", "grade":11 } PUT sport/grade/10 { "user":"C", "grade":15 } 四、聚合 GET sport/_search { "size": 0, "aggs": { "avg_score": { "terms": { "field": "user" }, "aggs": { "max_score": { "max": { "field": "grade" } } } }, "avg_max_score": { "avg_bucket": { "buckets_path": "avg_score>max_score" } } } } 结果: { "took": 4, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 10, "max_score": 0, "hits": [] }, "aggregations": { "avg_score": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "C", "doc_count": 4, "max_score": { "value": 15 } }, { "key": "A", "doc_count": 3, "max_score": { "value": 13 } }, { "key": "B", "doc_count": 3, "max_score": { "value": 12 } } ] }, "avg_max_score": { "value": 13.333333333333334 } } }
一、工具 1.1 正则表达式验证工具 http://regexr.com/ 1.2 练习工具 https://alf.nu/RegexGolf 二、例子 使用正则表达式匹配以下文本,很明显所有文本都是以ick结尾的,对应的正则为ick$ Mick Rick allocochick backtrick bestick candlestick java中处理正则表达式的类位于java.util.regex包中,包含pattern、matcher和PatternSyntaxException类。 判断010-62761234是不是一个电话号码: import java.util.regex.Pattern; public class ResTest1 { public static void main(String[] args) { String str = "010-62731234"; String pattern = "0\\d{2}-\\d{8}"; boolean isMatch = Pattern.matches(pattern, str); System.out.println(isMatch); } } 正则表达式语法: 子表达式 匹配对应 ^ 匹配一行的开头 $ 匹配一行的结尾 . 匹配除了换行符的任何单个字符,也可以利用 m 选项允许它匹配换行符 […] 匹配括号内的任意单个字符。 [^…] 匹配不在括号内的任意单个字符。 \A 整个字符串的开始 \z 整个字符串的结束 \Z 整个字符串的结束,除了最后一行的结束符 re* 匹配0或者更多的前表达事件 re+ 匹配1个或更多的之前的事件 re? 匹配0或者1件前表达事件 re{ n} 匹配特定的n个前表达事件 re{ n,} 匹配n或者更多的前表达事件 re{ n, m} 匹配至少n最多m件前表达事件 a|b 匹配a或者b (re) 正则表达式组匹配文本记忆 (?: re) 没有匹配文本记忆的正则表达式组 (?> re) 匹配无回溯的独立的模式 \w 匹配单词字符 \W 匹配非单词字符 \s 匹配空格。等价于 [\t\n\r\f] \S 匹配非空格 \d 匹配数字. 等价于 [0-9] \D 匹配非数字 \A 匹配字符串的开始 \Z 匹配字符串的末尾,如果存在新的一行,则匹配新的一行之前 \z 匹配字符串的末尾 \G 匹配上一次匹配结束的地方 \n 返回参考捕获组号“N” \b 不在括号里时匹配单词边界。在括号里时匹配退格键 \B 匹配非词边界 \n, \t, etc. 匹配换行符,回车符,制表符,等 \Q 引用字符的初始,结束于\E \E 结束由\Q开始的引用
一、下载Jmeter 下载地址:http://jmeter.apache.org/download_jmeter.cgi 解压之后运行: cd /apache-jmeter-3.2/bin ./jmeter 二、添加线程组 依次店测试计划->添加->threads->线程组: 在线程组中添加线程数和用户数,模拟用户访问: 10个用户,每个用户200个线程,循环10次。 三、添加请求 在线程组下,依次添加->Sampler->HTTP请求: http请求中指定ES店地址、端口、查询命令: 四、添加报告 依次店添加->监听器->Summary Report: 之后点击保存,生成一个.jmx文件。 五、测试 运行,在summary report中查看结果:
Elasticsearch提供了丰富的查询语句DSL,查询可分2类: Leaf Query:查询特定字段特定值的查询,可以单独使用,比如match查询、term查询、range查询。 Compound Query:组合查询,组合多个简单查询或者特殊查询。 先占坑,有时间更新Y(^_^)Y 一、Query和Filter的区别 Query是真正的信息检索,会用到检索模型进行评分,回答的是查询和文档的匹配程度;Filter是过滤机制,回答的是Yes Or NO。 二、Match All Query match all query会返回所有文档,文档的得分都是1。 GET /_search { "query": { "match_all": {} } } 也可以通过boost参数修改得分: GET /_search { "query": { "match_all": { "boost" : 1.2 } } } match_none是match_all的取反,不返回任何文档: GET /_search { "query": { "match_none": {} } } 三、Full Text Query(全文查询) 3.1 match query GET /_search { "query": { "match" : { "message" : "this is a test" } } } message是被搜索字段,this is a test是query内容,分词后query中的任何一个关键字被匹配文档就会被搜索到。如果想查询匹配所有关键词的文档,可以用and操作符连接: GET /_search { "query": { "match" : { "message" : { "query" : "this is a test", "operator" : "and" } } } } 3.2 match phrase query match phrase query首先会把query内容分词,分词器可以自定义,同时文档还要满足以下两个条件才会被搜索到: 分词后到所有词项都要出现在该字段中 字段中到词项顺序要一致 例如,有三个文档: { "foo":"I just said hello world" } { "foo":"Hello world" } { "foo":"World Hello" } 使用match_phrase查询”hello world”: { "query": { "match_phrase": { "foo": "Hello World" } } } 只有前2个文档会被匹配。 3.3 match phrase prefix query match_phrase_prefix和match_phrase类似,只不过match_phrase_prefix支持最后一个term前缀匹配: GET /_search { "query": { "match_phrase_prefix" : { "message" : "quick brown f" } } } 3.4 multi match query multi_match是match的升级,用于搜索多个字段: GET books/_search { "query": { "multi_match" : { "query": "java虚拟机", "fields": [ "title", "description" ], "operator" : "and" } } } 字段的名称可以使用通配符: GET /_search { "query": { "multi_match" : { "query": "Will Smith", "fields": [ "title", "*_name" ] } } } 同时,也可以用指数符指定权重: GET /_search { "query": { "multi_match" : { "query" : "this is a test", "fields" : [ "subject^3", "message" ] } } } 3.5 common terms query 3.6 query string query 3.7 simple query string query 四、Term Level Query(term级别查询) 4.1 term query 4.2 terms query 4.3 range query 4.4 exists query 4.5 prefix query 4.6 wildcard query 4.7 regexp query 4.8 fuzzy query 4.9 type query 4.10 ids query 五、Compound Query(组合查询) 5.1 constant score query 5.2 bool query 5.3 Dis max query 5.4 Function Score query 5.5 boosting query 5.6 indices query 六、Joining Query(嵌套查询) 6.1 nested query 6.2 has child query 6.3 has parent query 6.4 parent id query 七、Geo Query(地理位置查询) geoshape query geo bounding query geo distance query geo distance range query geo polygon query 八、Specialized Query(特殊查询) 8.1 more like this query 8.2 template query 8.3 script query 8.4 percolate query 九、Span Query 9.1 span term query 9.2 span multi term query 9.3 span first query 9.4 span near query 9.5 span or query 9.6 span not query 9.7 span containing query 9.8 span within query 9.9 span field masking query 十、minimum should match 十一、multi term query rewrite
IntelliJ IDEA For Mac 快捷键 Mac键盘符号和修饰键说明 一Editing编辑 二SearchReplace查询替换 三Usage Search使用查询 四Compile and Run编译和运行 五Debugging调试 六Navigation导航 七Refactoring重构 八VCSLocal History版本控制本地历史记录 九Live Templates动态代码模板 十General通用 十一Other一些官方文档上没有体现的快捷键 转载声明 IntelliJ IDEA For Mac 快捷键 根据官方pdf翻译:https://www.jetbrains.com/idea/docs/IntelliJIDEA_ReferenceCard_Mac.pdf 在 IntelliJ IDEA 中有两个 Mac 版本的快捷键,一个叫做:Mac OS X,一个叫做:Mac OS X 10.5+ 目前都是用:Mac OS X 10.5+ 有两套的原因:https://intellij-support.jetbrains.com/hc/en-us/community/posts/206159109-Updated-Mac-OS-X-keymap-Feedback-needed 建议将 Mac 系统中与 IntelliJ IDEA 冲突的快捷键取消或更改,不建议改 IntelliJ IDEA 的默认快捷键。 Mac键盘符号和修饰键说明 ⌘ Command ⇧ Shift ⌥ Option ⌃ Control ↩︎ Return/Enter ⌫ Delete ⌦ 向前删除键(Fn+Delete) ↑ 上箭头 ↓ 下箭头 ← 左箭头 → 右箭头 ⇞ Page Up(Fn+↑) ⇟ Page Down(Fn+↓) Home Fn + ← End Fn + → ⇥ 右制表符(Tab键) ⇤ 左制表符(Shift+Tab) ⎋ Escape (Esc) 一、Editing(编辑) Control + Space 基本的代码补全(补全任何类、方法、变量) Control + Shift + Space 智能代码补全(过滤器方法列表和变量的预期类型) Command + Shift + Enter 自动结束代码,行末自动添加分号 Command + P 显示方法的参数信息 Control + J 快速查看文档 Shift + F1 查看外部文档(在某些代码上会触发打开浏览器显示相关文档) Command + 鼠标放在代码上 显示代码简要信息 Command + F1 在错误或警告处显示具体描述信息 Command + N, Control + Enter, Control + N 生成代码(getter、setter、构造函数、hashCode/equals,toString) Control + O 覆盖方法(重写父类方法) Control + I 实现方法(实现接口中的方法) Command + Option + T 包围代码(使用if..else, try..catch, for, synchronized等包围选中的代码) Command + / 注释/取消注释与行注释 Command + Option + / 注释/取消注释与块注释 Option + 方向键上 连续选中代码块 Option + 方向键下 减少当前选中的代码块 Control + Shift + Q 显示上下文信息 Option + Enter 显示意向动作和快速修复代码 Command + Option + L 格式化代码 Control + Option + O 优化import Control + Option + I 自动缩进线 Tab / Shift + Tab 缩进代码 / 反缩进代码 Command + X 剪切当前行或选定的块到剪贴板 Command + C 复制当前行或选定的块到剪贴板 Command + V 从剪贴板粘贴 Command + Shift + V 从最近的缓冲区粘贴 Command + D 复制当前行或选定的块 Command + Delete 删除当前行或选定的块的行 Control + Shift + J 智能的将代码拼接成一行 Command + Enter 智能的拆分拼接的行 Shift + Enter 开始新的一行 Command + Shift + U 大小写切换 Command + Shift + ] / Command + Shift + [ 选择直到代码块结束/开始 Option + Fn + Delete 删除到单词的末尾 Option + Delete 删除到单词的开头 Command + 加号 / Command + 减号 展开 / 折叠代码块 Command + Shift + 加号 展开所以代码块 Command + Shift + 减号 折叠所有代码块 Command + W 关闭活动的编辑器选项卡 二、Search/Replace(查询/替换) Double Shift 查询任何东西 Command + F 文件内查找 Command + G 查找模式下,向下查找 Command + Shift + G 查找模式下,向上查找 Command + R 文件内替换 Command + Shift + F 全局查找(根据路径) Command + Shift + R 全局替换(根据路径) Command + Shift + S 查询结构(Ultimate Edition 版专用,需要在Keymap中设置) Command + Shift + M 替换结构(Ultimate Edition 版专用,需要在Keymap中设置) 三、Usage Search(使用查询) Option + F7 / Command + F7 在文件中查找用法 / 在类中查找用法 Command + Shift + F7 在文件中突出显示的用法 Command + Option + F7 显示用法 四、Compile and Run(编译和运行) Command + F9 编译Project Command + Shift + F9 编译选择的文件、包或模块 Control + Option + R 弹出 Run 的可选择菜单 Control + Option + D 弹出 Debug 的可选择菜单 Control + R 运行 Control + D 调试 Control + Shift + R, Control + Shift + D 从编辑器运行上下文环境配置 五、Debugging(调试) F8 进入下一步,如果当前行断点是一个方法,则不进入当前方法体内 F7 进入下一步,如果当前行断点是一个方法,则进入当前方法体内,如果该方法体还有方法,则不会进入该内嵌的方法中 Shift + F7 智能步入,断点所在行上有多个方法调用,会弹出进入哪个方法 Shift + F8 跳出 Option + F9 运行到光标处,如果光标前有其他断点会进入到该断点 Option + F8 计算表达式(可以更改变量值使其生效) Command + Option + R 恢复程序运行,如果该断点下面代码还有断点则停在下一个断点上 Command + F8 切换断点(若光标当前行有断点则取消断点,没有则加上断点) Command + Shift + F8 查看断点信息 六、Navigation(导航) Command + O 查找类文件 Command + Shift + O 查找所有类型文件、打开文件、打开目录,打开目录需要在输入的内容前面或后面加一个反斜杠/ Command + Option + O 前往指定的变量 / 方法 Control + 方向键左 / Control + 方向键右 左右切换打开的编辑tab页 F12 返回到前一个工具窗口 Esc 从工具窗口进入代码文件窗口 Shift + Esc 隐藏当前或最后一个活动的窗口,且光标进入代码文件窗口 Command + Shift + F4 关闭活动run/messages/find/… tab Command + L 在当前文件跳转到某一行的指定处 Command + E 显示最近打开的文件记录列表 Option + 方向键左 / Option + 方向键右 光标跳转到当前单词 / 中文句的左 / 右侧开头位置 Command + Option + 方向键左 / Command + Option + 方向键右 退回 / 前进到上一个操作的地方 Command + Shift + Delete 跳转到最后一个编辑的地方 Option + F1 显示当前文件选择目标弹出层,弹出层中有很多目标可以进行选择(如在代码编辑窗口可以选择显示该文件的Finder) Command + B / Command + 鼠标点击 进入光标所在的方法/变量的接口或是定义处 Command + Option + B 跳转到实现处,在某个调用的方法名上使用会跳到具体的实现处,可以跳过接口 Option + Space, Command + Y 快速打开光标所在方法、类的定义 Control + Shift + B 跳转到类型声明处 Command + U 前往当前光标所在方法的父类的方法 / 接口定义 Control + 方向键下 / Control + 方向键上 当前光标跳转到当前文件的前一个/后一个方法名位置 Command + ] / Command + [ 移动光标到当前所在代码的花括号开始/结束位置 Command + F12 弹出当前文件结构层,可以在弹出的层上直接输入进行筛选(可用于搜索类中的方法) Control + H 显示当前类的层次结构 Command + Shift + H 显示方法层次结构 Control + Option + H 显示调用层次结构 F2 / Shift + F2 跳转到下一个/上一个突出错误或警告的位置 F4 / Command + 方向键下 编辑/查看代码源 Option + Home 显示到当前文件的导航条 F3选中文件/文件夹/代码行,添加/取消书签 Option + F3 选中文件/文件夹/代码行,使用助记符添加/取消书签 Control + 0...Control + 9 定位到对应数值的书签位置 Command + F3 显示所有书签 七、Refactoring(重构) F5 复制文件到指定目录 F6 移动文件到指定目录 Command + Delete 在文件上为安全删除文件,弹出确认框 Shift + F6 重命名文件 Command + F6 更改签名 Command + Option + N 一致性 Command + Option + M 将选中的代码提取为方法 Command + Option + V 提取变量 Command + Option + F 提取字段 Command + Option + C 提取常量 Command + Option + P 提取参数 八、VCS/Local History(版本控制/本地历史记录) Command + K 提交代码到版本控制器 Command + T 从版本控制器更新代码 Option + Shift + C 查看最近的变更记录 Control + C 快速弹出版本控制器操作面板 九、Live Templates(动态代码模板) Command + Option + J 弹出模板选择窗口,将选定的代码使用动态模板包住 Command + J 插入自定义动态代码模板 十、General(通用) Command + 1...Command + 9 打开相应编号的工具窗口 Command + S 保存所有 Command + Option + Y 同步、刷新 Control + Command + F 切换全屏模式 Command + Shift + F12 切换最大化编辑器 Option + Shift + F 添加到收藏夹 Option + Shift + I 检查当前文件与当前的配置文件 Control + ` 快速切换当前的scheme(切换主题、代码样式等) Command + , 打开IDEA系统设置 Command + ; 打开项目结构对话框 Shift + Command + A 查找动作(可设置相关选项) Control + Shift + Tab 编辑窗口标签和工具窗口之间切换(如果在切换的过程加按上delete,则是关闭对应选中的窗口) 十一、Other(一些官方文档上没有体现的快捷键) Command + Shift +8 竖编辑模式 转载声明 本文转载自:https://github.com/judasn/IntelliJ-IDEA-Tutorial/,感谢作者。
LeetCode数组习题 26.Remove Duplicates from Sorted Array 题目描述: Given a sorted array, remove the duplicates in place such that each element appear only once and return the new length. Do not allocate extra space for another array, you must do this in place with constant mem- ory. For example, Given input array A = [1,1,2], Your function should return length = 2, and A is now [1,2]. 中文: 一个有序数组,返回不重复的元素的个数。 Java代码 public int removeDuplicates(int[] nums) { int index=0; for(int i=1;i<nums.length;++i){ if (nums[i]!=nums[index]){ index=index+1; nums[index]=nums[i]; } } return index+1; } 时间复杂度 空间复杂度 O(n) O(1) 80. Remove Duplicates from Sorted Array II Follow up for "Remove Duplicates": What if duplicates are allowed at most twice? For example, Given sorted array nums = [1,1,1,2,2,3], Your function should return length = 5, with the first five elements of nums being 1, 1, 2, 2 and 3. It doesn't matter what you leave beyond the new length. Java代码 public int removeDuplicates(int[] nums) { int index=0; int occur=1; for(int i=1;i<nums.length;i++){ if(nums[index]==nums[i]){ if(occur<2){ index=index+1; nums[index]=nums[i]; occur++; } }else{ index=index+1; nums[index]=nums[i]; occur=1; } } return index+1; } 时间复杂度 空间复杂度 O(n) O(1) 1. Two Sum Given an array of integers, return indices of the two numbers such that they add up to a specific target. You may assume that each input would have exactly one solution, and you may not use the same element twice. Example: Given nums = [2, 7, 11, 15], target = 9, Because nums[0] + nums[1] = 2 + 7 = 9, return [0, 1]. 中文: 给定一个整型数组和一个目标值,返回和为目标值的2个数的位置。 解法一:冒泡排序思想遍历 public int[] twoSum(int[] nums, int target) { int[] index = new int[2]; for (int i = 0; i < nums.length-1; i++) { for (int j=i+1;j<nums.length;j++){ if (nums[i] +nums[j]==target){ index[0]=i; index[1]=j; break; } } } return index; } 时间复杂度 空间复杂度 O(n^2) O(1) 解法二:使用HashMap public int[] twoSum(int[] nums, int target) { int[] index = new int[2]; HashMap<Integer,Integer> map=new HashMap<>(); for (int i = 0; i < nums.length; i++) { if (map.containsKey(target-nums[i])){ index[0]=map.get(target-nums[i]); index[1]=i; } map.put(nums[i],i); } return index; } 时间复杂度 空间复杂度 O(n) O(n) 7. Reverse Integer 题目 Reverse digits of an integer. Example1: x = 123, return 321 Example2: x = -123, return -321 click to show spoilers. Note: The input is assumed to be a 32-bit signed integer. Your function should return 0 when the reversed integer overflows. 中文: 整数逆序输出,超过32位无符号数的返回0. Java代码: public int inverse(int x) { long result = 0; while (x != 0) { result = result * 10 + x % 10; if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE) { return 0; } x = x / 10; } return (int) result; } 时间复杂度 空间复杂度 O(n) O(1) 4. Median of Two Sorted Arrays 有序数组nums1和nums2长度分别为m和n,返回中位数。要求时间复杂度为O(log(m+n))。 例子1: nums1 = [1, 3] nums2 = [2] The median is 2.0 例子2: nums1 = [1, 2] nums2 = [3, 4] The median is (2 + 3)/2 = 2.5 先合并有序数组,再返回中位数,java代码如下: public double findMedianSortedArrays(int[] nums1, int[] nums2) { int m=nums1.length; int n=nums2.length; int N=m+n; int[] nums=new int[N]; int i=0,j=0,k=0; while (i<m&&j<n){ if (nums1[i]<nums2[j]){ nums[k++]=nums1[i++]; }else{ nums[k++]=nums2[j++]; } } while (i<m) nums[k++]=nums1[i++]; while (j<n) nums[k++]=nums2[j++]; double result=0; if (N%2!=0){ result=(double) nums[N/2]; }else{ result =(double)(nums[N/2-1]+nums[N/2])/2; } return result; } 时间复杂度 空间复杂度 O(log(m+n)) O(log(m+n)) 136. Single Number 给定一个整型数组,数组里的元素都出现2次,只有1个除外。找出只出现1次的元素: 解法一 Java代码: public int singleNumber(int[] nums) { Map<Integer,Integer> map=new HashMap<>(); for (int a:nums){ if (map.containsKey(a)){ map.remove(a); }else{ map.put(a,1); } } return (int)map.keySet().toArray()[0]; } 解法二 通过求与操作: public int singleNumber(int[] nums) { int n = nums.length; int goal=nums[0]; for (int i=1;i<n;i++){ goal^=nums[i]; } return goal; } 189. Rotate Array For example, with n = 7 and k = 3, the array [1,2,3,4,5,6,7] is rotated to [5,6,7,1,2,3,4]. 思路:三步反转法 java代码: class Solution { public void rotate(int[] nums, int k) { k%=nums.length; reverseArr(nums,0,nums.length-1); reverseArr(nums,0,k-1); reverseArr(nums,k,nums.length-1); } public void reverseArr(int[] nums,int from,int to){ while(from<to){ int temp=nums[from]; nums[from]=nums[to]; nums[to]=temp; from++; to--; } } } 时间复杂度 空间复杂度 O(n) O(1) 《编程之法第二章》 2. 最小的k个数
一单文档API 1 Index API 2 Get API 3 Delete API 4 Update API 二多文档API 1 Multi Get API 2 Bulk API 3 Delete By Query API 4 Update By Query API 5 Reindex API ELasticsearch文档的CRUD主要包括以下2个大的方面:单文档和多文档,翻译如下: 一、单文档API 1.1 Index API 写入文档,索引为twitter,type为tweet,id为1: PUT twitter/tweet/1 { "user" : "kimchy", "post_date" : "2009-11-15T14:12:12", "message" : "trying out Elasticsearch" } 返回结果: { "_index": "twitter", "_type": "tweet", "_id": "1", "_version": 1, "result": "created", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "created": true } 1.2 Get API get api可以通过id查看文档: GET twitter/tweet/0 查看文档是否存在: HEAD twitter/tweet/0 1.3 Delete API 根据ID删除: DELETE twitter/tweet/1 1.4 Update API Update API允许通过脚本更新文档,更新操作会先读取文档,执行脚本,最后重新索引。更新操作意味着重新索引文档,当然执行更新操作不能关闭_source字段。 写入一条文档做测试: PUT test/type1/1 { "counter" : 1, "tags" : ["red"] } 把counter的值更新为5,执行脚本如下: POST test/type1/1/_update { "script" : { "inline": "ctx._source.counter += params.count", "lang": "painless", "params" : { "count" : 4 } } } 给tags增加一个值: POST test/type1/1/_update { "script" : { "inline": "ctx._source.tags.add(params.tag)", "lang": "painless", "params" : { "tag" : "blue" } } } 通过ctx可以操作_index, _type, _id, _version, _routing, _parent, and _now(当前的timestamp)。 给文档增加一个新的字段: POST test/type1/1/_update { "script" : "ctx._source.new_field = \"value_of_new_field\"" } 删除一个字段: POST test/type1/1/_update { "script" : "ctx._source.remove(\"new_field\")" } 脚本中还可以执行逻辑语句,以下脚本将会删除tag字段中含有green的文档。 POST test/type1/1/_update { "script" : { "inline": "if (ctx._source.tags.contains(params.tag)) { ctx.op = \"delete\" } else { ctx.op = \"none\" }", "lang": "painless", "params" : { "tag" : "green" } } } 也可以通过传递文档的一部分,新增的内容会合并到原始文档中,例如增加字段: POST test/type1/1/_update { "doc" : { "name" : "new_name" } } 如果同时又doc和script,doc会被忽略。 如果新传入的文档原始文档中已经存在,再次更新会被忽略,也就是覆盖一模一样的字段内容会被忽略。把detect_noop设为false,即使文档内容一样,也会执行。 POST test/type1/1/_update { "doc" : { "name" : "new_name" }, "detect_noop": false } 二、多文档API 2.1 Multi Get API 通过ID一次获取多个文档的方式: GET _mget { "docs" : [ { "_index" : "test", "_type" : "type", "_id" : "1" }, { "_index" : "test", "_type" : "type", "_id" : "2" } ] } 如果索引相同: GET test/_mget { "docs" : [ { "_type" : "type", "_id" : "1" }, { "_type" : "type", "_id" : "2" } ] } 如果type相同: GET test/type/_mget { "docs" : [ { "_id" : "1" }, { "_id" : "2" } ] } 简写: GET test/type/_mget { "ids" : ["1", "2"] } 缺省type会返回索引下所有的type下的所有符合id的文档: GET test/_mget { "ids" : ["1", "1"] } 如果文档不存在,使用使用upserts插入新文档: POST test/type1/1/_update { "script" : { "inline": "ctx._source.counter += params.count", "lang": "painless", "params" : { "count" : 4 } }, "upsert" : { "counter" : 1 } } 2.2 Bulk API Bulk API可以批量插入: POST _bulk { "index" : { "_index" : "test", "_type" : "type1", "_id" : "1" } } { "field1" : "value1" } { "delete" : { "_index" : "test", "_type" : "type1", "_id" : "2" } } { "create" : { "_index" : "test", "_type" : "type1", "_id" : "3" } } { "field1" : "value3" } { "update" : {"_id" : "1", "_type" : "type1", "_index" : "test"} } { "doc" : {"field2" : "value2"} } 2.3 Delete By Query API 通过查询条件删除: POST twitter/_delete_by_query { "query": { "match": { "message": "some message" } } } 2.4 Update By Query API 通过查询更新文档: POST twitter/_update_by_query { "script": { "inline": "ctx._source.likes++", "lang": "painless" }, "query": { "term": { "user": "kimchy" } } } 2.5 Reindex API reindex api用于从一个索引拷贝文档到另外一个索引,注意,mapping、setting中的分片数和副本数都不会被拷贝。 把索引twitter中的文档拷贝到new_twitter: POST _reindex { "source": { "index": "twitter" }, "dest": { "index": "new_twitter" } }
前言 一索引管理 1 创建索引 2 删除索引 3 查看索引信息 4 索引是否存在 5 关闭打开索引 6 索引收缩 7 翻滚索引 二mapping管理 1 设置mapping 2 查看mapping 3 获取字段mapping 4 类型是否存在 三别名管理 1 索引别名设置 四索引配置 1 获取索引设置 2 更新索引设置 3 分析器 4 索引模板 五监控管理 1 索引统计信息 2 索引段 3 索引恢复 4 索引分片存储 六状态管理 1 清除缓存 2 刷新 3 flush 4 强制段合并force merge 前言 声明:本博客根据ELasticsearch官网文档翻译整理,转载请注明出处:http://blog.csdn.net/napoay 索引API可以用于管理单个索引、索引设置、别名、映射和索引模板。 一、索引管理 1.1 创建索引 创建索引 PUT twitter 默认分片为5,副本为1. 创建索引并指定分片数和副本数: PUT twitter { "settings" : { "index" : { "number_of_shards" : 3, "number_of_replicas" : 2 } } } 或者简写为: PUT twitter { "settings" : { "number_of_shards" : 3, "number_of_replicas" : 2 } } 创建索引并指定mapping: PUT test { "settings" : { "number_of_shards" : 1 }, "mappings" : { "type1" : { "properties" : { "field1" : { "type" : "text" } } } } } 1.2 删除索引 DELETE /twitter 1.3 查看索引信息 查看所有的settings、别名、mapping,命令: GET /twitter 添加参数过滤信息: GET twitter/_settings,_mappings 1.4 索引是否存在 如果想知道集群中是否存在某个索引,可以使用以下命令: HEAD twitter 如果存在,返回状态码200: 200 - OK 如果不存在,返回状态码404: 404 - Not Found 1.5 关闭/打开索引 对于不使用的索引,关闭索引可以节省开销,但是索引关闭以后读写操作是无法进行的。 打开索引: POST /my_index/_close 关闭索引: POST /my_index/_open 可以同时关闭多个索引,如果其中有索引不存在会报异常,可以使用ignore_unavailable=true参数忽略不存在索引。 1.6 索引收缩 shrink index AP可以把一个索引变成一个更少分片的索引,但是收缩后的分片数必须是原始分片数的因子(因子就是所有可以整除这个数的数,不包括这个数自身),比如有8个分片的索引可以收缩为4、2、1,有15个分片的索引可以收缩为5、3、1,如果分片数为素数(7、11等),那么只能收缩为1个分片。 收缩索引之前,索引中的每个分片都要在同一个节点上。 收缩索引的完成过程: 首先,创建了一个新的目标索引,设置与源索引相同,但新索引的分片数量较少。 然后把源索引的段到硬链接到目标索引。(如果文件系统不支持硬链接,那么所有段都被复制到新索引中,这是一个耗费更多时间的过程。) 最后,新的索引恢复使用,好像它是一个刚刚重新开放的封闭索引。 搜索之前,使索引为只读状态并使分片重新分配到同一个节点: PUT /my_source_index/_settings { "settings": { "index.routing.allocation.require._name": "shrink_node_name", "index.blocks.write": true } } 设置目标索引名和分片数,别名可选: POST my_source_index/_shrink/my_target_index { "settings": { "index.number_of_replicas": 1, "index.number_of_shards": 1, "index.codec": "best_compression" }, "aliases": { "my_search_indices": {} } } 1.7 翻滚索引 二、mapping管理 2.1 设置mapping put mapping可以给一个已存在的索引增加type的mapping,也可以给一个存在的type增加字段的mapping。 PUT twitter { "mappings": { "tweet": { "properties": { "message": { "type": "text" } } } } } PUT twitter/_mapping/user { "properties": { "name": { "type": "text" } } } PUT twitter/_mapping/tweet { "properties": { "user_name": { "type": "text" } } } 一般情况下字段的mapping设置是不可以更新的,有几个特例除外: properties嵌套属性可以新增 ignore_above 参数的值可以更新 PUT my_index { "mappings": { "user": { "properties": { "name": { "properties": { "first": { "type": "text" } } }, "user_id": { "type": "keyword" } } } } } PUT my_index/_mapping/user { "properties": { "name": { "properties": { "last": { "type": "text" } } }, "user_id": { "type": "keyword", "ignore_above": 100 } } } 2.2 查看mapping 查看一个索引的mapping: GET /twitter/_mapping 查看一个索引的一个type的mapping: GET /twitter/_mapping/tweet 查看所有索引的mapping: GET /_mapping 或者: GET /_all/_mapping 2.3 获取字段mapping get field mapping api可以查看索引的一个或多个字段的mapping,设置创建一个索引做测试: PUT publications { "mappings": { "article": { "properties": { "id": { "type": "text" }, "title": { "type": "text"}, "abstract": { "type": "text"}, "author": { "properties": { "id": { "type": "text" }, "name": { "type": "text" } } } } } } } GET publications/_mapping/article/field/title GET publications/_mapping/article/field/id GET publications/_mapping/article/field/author.id 2.4 类型是否存在 查看索引是否存在某个type: HEAD twitter/_mapping/tweet 返回值为200说明存在,404说明不存在。 三、别名管理 3. 1 索引别名设置 可以给一个或多个索引设置别名,但是别名不能和已有索引名称相同。 给索引名为test1的索引设置别名为alias1: POST /_aliases { "actions" : [ { "add" : { "index" : "test1", "alias" : "alias1" } } ] } 移除别名: POST /_aliases { "actions" : [ { "remove" : { "index" : "test1", "alias" : "alias1" } } ] } 更新别名的映射关系就是先移除再添加: POST /_aliases { "actions" : [ { "remove" : { "index" : "test1", "alias" : "alias1" } }, { "add" : { "index" : "test2", "alias" : "alias1" } } ] } 也可以同时给多个索引设置同一个别名: POST /_aliases { "actions" : [ { "add" : { "index" : "test1", "alias" : "alias1" } }, { "add" : { "index" : "test2", "alias" : "alias1" } } ] } 也可以使用通配符,一下所有以test开头的索引都设置别名为all_test_indices: POST /_aliases { "actions" : [ { "add" : { "index" : "test*", "alias" : "all_test_indices" } } ] } 四、索引配置 4.1 获取索引设置 查看索引的settings: GET /twitter/_settings 查看多个索引的settings: GET /twitter,kimchy/_settings GET /_all/_settings GET /log_2013_*/_settings 4.2 更新索引设置 修改副本: PUT /twitter/_settings { "index" : { "number_of_replicas" : 2 } } 修改settings用于提高Bulk的导入性能,bulk之前设置刷新时间为-1,也就是bulk导入期间不再刷新: PUT /twitter/_settings { "index" : { "refresh_interval" : "-1" } } bulk导入之后恢复刷新时间并强制段合并 PUT /twitter/_settings { "index" : { "refresh_interval" : "1s" } } POST /twitter/_forcemerge?max_num_segments=5 4.3 分析器 查看分词标准分词结果: GET _analyze { "analyzer" : "standard", "text" : "this is a test" } 查看IK分词结果: GET _analyze { "analyzer" : "ik_smart", "text" : "北京今天局地高温" } 4.4 索引模板 索引模板可以自动匹配新创建的索引。 PUT _template/template_1 { "template": "te*", "settings": { "number_of_shards": 1 }, "mappings": { "type1": { "_source": { "enabled": false }, "properties": { "host_name": { "type": "keyword" }, "created_at": { "type": "date", "format": "EEE MMM dd HH:mm:ss Z YYYY" } } } } } 删除索引模板: DELETE /_template/template_1 查看索引模板: GET /_template 查看一个或多个: GET /_template/template_1 GET /_template/template_1,template_2 五、监控管理 5.1 索引统计信息 GET /_stats GET /index1,index2/_stats 以上命令返回的索引的相关信息非常多,可以通过参数过滤https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-stats.html 5.2 索引段 segment是比Lucene索引更小的单位,通过segment可以获取更多的关于分片和索引的信息。 查看索引的段信息: GET test/_segments 返回结果: { "_shards": { "total": 5, "successful": 5, "failed": 0 }, "indices": { "test": { "shards": { "0": [ { "routing": { "state": "STARTED", "primary": true, "node": "3dQd1RRVTMiKdTckM68nPQ" }, "num_committed_segments": 0, "num_search_segments": 0, "segments": {} } ], "1": [ { "routing": { "state": "STARTED", "primary": true, "node": "3dQd1RRVTMiKdTckM68nPQ" }, "num_committed_segments": 0, "num_search_segments": 0, "segments": {} } ], "2": [ { "routing": { "state": "STARTED", "primary": true, "node": "3dQd1RRVTMiKdTckM68nPQ" }, "num_committed_segments": 0, "num_search_segments": 0, "segments": {} } ], "3": [ { "routing": { "state": "STARTED", "primary": true, "node": "3dQd1RRVTMiKdTckM68nPQ" }, "num_committed_segments": 1, "num_search_segments": 1, "segments": { "_1": { "generation": 1, "num_docs": 1, "deleted_docs": 0, "size_in_bytes": 3727, "memory_in_bytes": 2588, "committed": true, "search": true, "version": "6.5.0", "compound": true } } } ], "4": [ { "routing": { "state": "STARTED", "primary": true, "node": "3dQd1RRVTMiKdTckM68nPQ" }, "num_committed_segments": 1, "num_search_segments": 1, "segments": { "_0": { "generation": 0, "num_docs": 1, "deleted_docs": 0, "size_in_bytes": 3206, "memory_in_bytes": 2042, "committed": true, "search": true, "version": "6.5.0", "compound": true } } } ] } } } } 统计索引占段内存: curl -s "http://localhost:9200/_cat/segments/test?v&h=shard,segment,size,size.memory" |awk '{sum += $NF} END {print sum}' 5.3 索引恢复 GET index1,index2/_recovery?human GET _recovery?human&detailed=true 5.4 索引分片存储 六、状态管理 6.1 清除缓存 POST /twitter/_cache/clear POST /kimchy,elasticsearch/_cache/clear POST /_cache/clear 6.2 刷新 POST /twitter/_refresh POST /kimchy,elasticsearch/_refresh POST /_refresh 6.3 flush POST twitter/_flush POST kimchy,elasticsearch/_flush POST _flush 6.4 强制段合并(force merge) POST /twitter/_forcemerge POST /kimchy,elasticsearch/_forcemerge POST /_forcemerge
前言 一Field datatype字段数据类型 1string类型 2 text类型 3 keyword类型 4 数字类型 5 Object类型 6 date类型 7 Array类型 8 binary类型 9 ip类型 10 range类型 11 nested类型 12token_count类型 13 geo point 类型 二Meta-Fields元数据 1 _all 2 _field_names 3 _id 4 _index 4 _meta 5 _parent 6 _routing 7 _source 8 _type 9 _uid 三Mapping参数 1 analyzer 2 normalizer 3 boost 4 coerce 5 copy_to 6 doc_values 7 dynamic 8 enabled 9 fielddata 10 format 11 ignore_above 12 ignore_malformed 13 include_in_all 14 index 15 index_options 16 fields 17 norms 18 null_value 19 position_increment_gap 20 properties 21 search_analyzer 22 similarity 23 store 24 term_vector 四动态Mapping 1 default mapping 2 Dynamic field mapping 3 Dynamic templates 4 Override default template 前言 声明:本博客根据ELasticsearch官网文档翻译整理,转载请注明出处:http://blog.csdn.net/napoay 一、Field datatype(字段数据类型) 1.1string类型 ELasticsearch 5.X之后的字段类型不再支持string,由text或keyword取代。 如果仍使用string,会给出警告。 测试: PUT my_index { "mappings": { "my_type": { "properties": { "title": { "type": "string" } } } } } 结果: #! Deprecation: The [string] field is deprecated, please use [text] or [keyword] instead on [title] { "acknowledged": true, "shards_acknowledged": true } 1.2 text类型 text取代了string,当一个字段是要被全文搜索的,比如Email内容、产品描述,应该使用text类型。设置text类型以后,字段内容会被分析,在生成倒排索引以前,字符串会被分析器分成一个一个词项。text类型的字段不用于排序,很少用于聚合(termsAggregation除外)。 把full_name字段设为text类型的Mapping如下: PUT my_index { "mappings": { "my_type": { "properties": { "full_name": { "type": "text" } } } } } 1.3 keyword类型 keyword类型适用于索引结构化的字段,比如email地址、主机名、状态码和标签。如果字段需要进行过滤(比如查找已发布博客中status属性为published的文章)、排序、聚合。keyword类型的字段只能通过精确值搜索到。 1.4 数字类型 对于数字类型,ELasticsearch支持以下几种: 类型 取值范围 long -2^63至2^63-1 integer -2^31至2^31-1 short -32,768至32768 byte -128至127 double 64位双精度IEEE 754浮点类型 float 32位单精度IEEE 754浮点类型 half_float 16位半精度IEEE 754浮点类型 scaled_float 缩放类型的的浮点数(比如价格只需要精确到分,price为57.34的字段缩放因子为100,存起来就是5734) 对于float、half_float和scaled_float,-0.0和+0.0是不同的值,使用term查询查找-0.0不会匹配+0.0,同样range查询中上边界是-0.0不会匹配+0.0,下边界是+0.0不会匹配-0.0。 对于数字类型的数据,选择以上数据类型的注意事项: 在满足需求的情况下,尽可能选择范围小的数据类型。比如,某个字段的取值最大值不会超过100,那么选择byte类型即可。迄今为止吉尼斯记录的人类的年龄的最大值为134岁,对于年龄字段,short足矣。字段的长度越短,索引和搜索的效率越高。 优先考虑使用带缩放因子的浮点类型。 例子: PUT my_index { "mappings": { "my_type": { "properties": { "number_of_bytes": { "type": "integer" }, "time_in_seconds": { "type": "float" }, "price": { "type": "scaled_float", "scaling_factor": 100 } } } } } 1.5 Object类型 JSON天生具有层级关系,文档会包含嵌套的对象: PUT my_index/my_type/1 { "region": "US", "manager": { "age": 30, "name": { "first": "John", "last": "Smith" } } } 上面的文档中,整体是一个JSON,JSON中包含一个manager,manager又包含一个name。最终,文档会被索引成一平的key-value对: { "region": "US", "manager.age": 30, "manager.name.first": "John", "manager.name.last": "Smith" } 上面文档结构的Mapping如下: PUT my_index { "mappings": { "my_type": { "properties": { "region": { "type": "keyword" }, "manager": { "properties": { "age": { "type": "integer" }, "name": { "properties": { "first": { "type": "text" }, "last": { "type": "text" } } } } } } } } } 1.6 date类型 JSON中没有日期类型,所以在ELasticsearch中,日期类型可以是以下几种: 日期格式的字符串:e.g. “2015-01-01” or “2015/01/01 12:10:30”. long类型的毫秒数( milliseconds-since-the-epoch) integer的秒数(seconds-since-the-epoch) 日期格式可以自定义,如果没有自定义,默认格式如下: "strict_date_optional_time||epoch_millis" 例子: PUT my_index { "mappings": { "my_type": { "properties": { "date": { "type": "date" } } } } } PUT my_index/my_type/1 { "date": "2015-01-01" } PUT my_index/my_type/2 { "date": "2015-01-01T12:10:30Z" } PUT my_index/my_type/3 { "date": 1420070400001 } GET my_index/_search { "sort": { "date": "asc"} } 查看三个日期类型: { "took": 0, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 3, "max_score": 1, "hits": [ { "_index": "my_index", "_type": "my_type", "_id": "2", "_score": 1, "_source": { "date": "2015-01-01T12:10:30Z" } }, { "_index": "my_index", "_type": "my_type", "_id": "1", "_score": 1, "_source": { "date": "2015-01-01" } }, { "_index": "my_index", "_type": "my_type", "_id": "3", "_score": 1, "_source": { "date": 1420070400001 } } ] } } 排序结果: { "took": 2, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 3, "max_score": null, "hits": [ { "_index": "my_index", "_type": "my_type", "_id": "1", "_score": null, "_source": { "date": "2015-01-01" }, "sort": [ 1420070400000 ] }, { "_index": "my_index", "_type": "my_type", "_id": "3", "_score": null, "_source": { "date": 1420070400001 }, "sort": [ 1420070400001 ] }, { "_index": "my_index", "_type": "my_type", "_id": "2", "_score": null, "_source": { "date": "2015-01-01T12:10:30Z" }, "sort": [ 1420114230000 ] } ] } } 1.7 Array类型 ELasticsearch没有专用的数组类型,默认情况下任何字段都可以包含一个或者多个值,但是一个数组中的值要是同一种类型。例如: 字符数组: [ “one”, “two” ] 整型数组:[1,3] 嵌套数组:[1,[2,3]],等价于[1,2,3] 对象数组:[ { “name”: “Mary”, “age”: 12 }, { “name”: “John”, “age”: 10 }] 注意事项: 动态添加数据时,数组的第一个值的类型决定整个数组的类型 混合数组类型是不支持的,比如:[1,”abc”] 数组可以包含null值,空数组[ ]会被当做missing field对待。 1.8 binary类型 binary类型接受base64编码的字符串,默认不存储也不可搜索。 PUT my_index { "mappings": { "my_type": { "properties": { "name": { "type": "text" }, "blob": { "type": "binary" } } } } } PUT my_index/my_type/1 { "name": "Some binary blob", "blob": "U29tZSBiaW5hcnkgYmxvYg==" } 搜索blog字段: GET my_index/_search { "query": { "match": { "blob": "test" } } } 返回结果: { "error": { "root_cause": [ { "type": "query_shard_exception", "reason": "Binary fields do not support searching", "index_uuid": "fgA7UM5XSS-56JO4F4fYug", "index": "my_index" } ], "type": "search_phase_execution_exception", "reason": "all shards failed", "phase": "query", "grouped": true, "failed_shards": [ { "shard": 0, "index": "my_index", "node": "3dQd1RRVTMiKdTckM68nPQ", "reason": { "type": "query_shard_exception", "reason": "Binary fields do not support searching", "index_uuid": "fgA7UM5XSS-56JO4F4fYug", "index": "my_index" } } ] }, "status": 400 } Base64加密、解码工具:http://www1.tc711.com/tool/BASE64.htm 1.9 ip类型 ip类型的字段用于存储IPV4或者IPV6的地址。 PUT my_index { "mappings": { "my_type": { "properties": { "ip_addr": { "type": "ip" } } } } } PUT my_index/my_type/1 { "ip_addr": "192.168.1.1" } GET my_index/_search { "query": { "term": { "ip_addr": "192.168.0.0/16" } } } 1.10 range类型 range类型支持以下几种: 类型 范围 integer_range -2^31至2^31-1 float_range 32-bit IEEE 754 long_range -2^63至2^63-1 double_range 64-bit IEEE 754 date_range 64位整数,毫秒计时 range类型的使用场景:比如前端的时间选择表单、年龄范围选择表单等。 例子: PUT range_index { "mappings": { "my_type": { "properties": { "expected_attendees": { "type": "integer_range" }, "time_frame": { "type": "date_range", "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" } } } } } PUT range_index/my_type/1 { "expected_attendees" : { "gte" : 10, "lte" : 20 }, "time_frame" : { "gte" : "2015-10-31 12:00:00", "lte" : "2015-11-01" } } 上面代码创建了一个range_index索引,expected_attendees的人数为10到20,时间是2015-10-31 12:00:00至2015-11-01。 查询: POST range_index/_search { "query" : { "range" : { "time_frame" : { "gte" : "2015-08-01", "lte" : "2015-12-01", "relation" : "within" } } } } 查询结果: { "took": 2, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 1, "max_score": 1, "hits": [ { "_index": "range_index", "_type": "my_type", "_id": "1", "_score": 1, "_source": { "expected_attendees": { "gte": 10, "lte": 20 }, "time_frame": { "gte": "2015-10-31 12:00:00", "lte": "2015-11-01" } } } ] } } 1.11 nested类型 nested嵌套类型是object中的一个特例,可以让array类型的Object独立索引和查询。 使用Object类型有时会出现问题,比如文档 my_index/my_type/1的结构如下: PUT my_index/my_type/1 { "group" : "fans", "user" : [ { "first" : "John", "last" : "Smith" }, { "first" : "Alice", "last" : "White" } ] } user字段会被动态添加为Object类型。 最后会被转换为以下平整的形式: { "group" : "fans", "user.first" : [ "alice", "john" ], "user.last" : [ "smith", "white" ] } user.first和user.last会被平铺为多值字段,Alice和White之间的关联关系会消失。上面的文档会不正确的匹配以下查询(虽然能搜索到,实际上不存在Alice Smith): GET my_index/_search { "query": { "bool": { "must": [ { "match": { "user.first": "Alice" }}, { "match": { "user.last": "Smith" }} ] } } } 使用nested字段类型解决Object类型的不足: PUT my_index { "mappings": { "my_type": { "properties": { "user": { "type": "nested" } } } } } PUT my_index/my_type/1 { "group" : "fans", "user" : [ { "first" : "John", "last" : "Smith" }, { "first" : "Alice", "last" : "White" } ] } GET my_index/_search { "query": { "nested": { "path": "user", "query": { "bool": { "must": [ { "match": { "user.first": "Alice" }}, { "match": { "user.last": "Smith" }} ] } } } } } GET my_index/_search { "query": { "nested": { "path": "user", "query": { "bool": { "must": [ { "match": { "user.first": "Alice" }}, { "match": { "user.last": "White" }} ] } }, "inner_hits": { "highlight": { "fields": { "user.first": {} } } } } } } 1.12token_count类型 token_count用于统计词频: PUT my_index { "mappings": { "my_type": { "properties": { "name": { "type": "text", "fields": { "length": { "type": "token_count", "analyzer": "standard" } } } } } } } PUT my_index/my_type/1 { "name": "John Smith" } PUT my_index/my_type/2 { "name": "Rachel Alice Williams" } GET my_index/_search { "query": { "term": { "name.length": 3 } } } 1.13 geo point 类型 地理位置信息类型用于存储地理位置信息的经纬度: PUT my_index { "mappings": { "my_type": { "properties": { "location": { "type": "geo_point" } } } } } PUT my_index/my_type/1 { "text": "Geo-point as an object", "location": { "lat": 41.12, "lon": -71.34 } } PUT my_index/my_type/2 { "text": "Geo-point as a string", "location": "41.12,-71.34" } PUT my_index/my_type/3 { "text": "Geo-point as a geohash", "location": "drm3btev3e86" } PUT my_index/my_type/4 { "text": "Geo-point as an array", "location": [ -71.34, 41.12 ] } GET my_index/_search { "query": { "geo_bounding_box": { "location": { "top_left": { "lat": 42, "lon": -72 }, "bottom_right": { "lat": 40, "lon": -74 } } } } } 二、Meta-Fields(元数据) 2.1 _all _all字段是把其它字段拼接在一起的超级字段,所有的字段用空格分开,_all字段会被解析和索引,但是不存储。当你只想返回包含某个关键字的文档但是不明确地搜某个字段的时候就需要使用_all字段。 例子: PUT my_index/blog/1 { "title": "Master Java", "content": "learn java", "author": "Tom" } _all字段包含:[ “Master”, “Java”, “learn”, “Tom” ] 搜索: GET my_index/_search { "query": { "match": { "_all": "Java" } } } 返回结果: { "took": 1, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 1, "max_score": 0.39063013, "hits": [ { "_index": "my_index", "_type": "blog", "_id": "1", "_score": 0.39063013, "_source": { "title": "Master Java", "content": "learn java", "author": "Tom" } } ] } } 使用copy_to自定义_all字段: PUT myindex { "mappings": { "mytype": { "properties": { "title": { "type": "text", "copy_to": "full_content" }, "content": { "type": "text", "copy_to": "full_content" }, "full_content": { "type": "text" } } } } } PUT myindex/mytype/1 { "title": "Master Java", "content": "learn Java" } GET myindex/_search { "query": { "match": { "full_content": "java" } } } 2.2 _field_names _field_names字段用来存储文档中的所有非空字段的名字,这个字段常用于exists查询。例子如下: PUT my_index/my_type/1 { "title": "This is a document" } PUT my_index/my_type/2?refresh=true { "title": "This is another document", "body": "This document has a body" } GET my_index/_search { "query": { "terms": { "_field_names": [ "body" ] } } } 结果会返回第二条文档,因为第一条文档没有title字段。 同样,可以使用exists查询: GET my_index/_search { "query": { "exists" : { "field" : "body" } } } 2.3 _id 每条被索引的文档都有一个_type和_id字段,_id可以用于term查询、temrs查询、match查询、query_string查询、simple_query_string查询,但是不能用于聚合、脚本和排序。例子如下: PUT my_index/my_type/1 { "text": "Document with ID 1" } PUT my_index/my_type/2 { "text": "Document with ID 2" } GET my_index/_search { "query": { "terms": { "_id": [ "1", "2" ] } } } 2.4 _index 多索引查询时,有时候只需要在特地索引名上进行查询,_index字段提供了便利,也就是说可以对索引名进行term查询、terms查询、聚合分析、使用脚本和排序。 _index是一个虚拟字段,不会真的加到Lucene索引中,对_index进行term、terms查询(也包括match、query_string、simple_query_string),但是不支持prefix、wildcard、regexp和fuzzy查询。 举例,2个索引2条文档 PUT index_1/my_type/1 { "text": "Document in index 1" } PUT index_2/my_type/2 { "text": "Document in index 2" } 对索引名做查询、聚合、排序并使用脚本新增字段: GET index_1,index_2/_search { "query": { "terms": { "_index": ["index_1", "index_2"] } }, "aggs": { "indices": { "terms": { "field": "_index", "size": 10 } } }, "sort": [ { "_index": { "order": "asc" } } ], "script_fields": { "index_name": { "script": { "lang": "painless", "inline": "doc['_index']" } } } } 2.4 _meta 忽略 2.5 _parent _parent用于指定同一索引中文档的父子关系。下面例子中现在mapping中指定文档的父子关系,然后索引父文档,索引子文档时指定父id,最后根据子文档查询父文档。 PUT my_index { "mappings": { "my_parent": {}, "my_child": { "_parent": { "type": "my_parent" } } } } PUT my_index/my_parent/1 { "text": "This is a parent document" } PUT my_index/my_child/2?parent=1 { "text": "This is a child document" } PUT my_index/my_child/3?parent=1&refresh=true { "text": "This is another child document" } GET my_index/my_parent/_search { "query": { "has_child": { "type": "my_child", "query": { "match": { "text": "child document" } } } } } 2.6 _routing 路由参数,ELasticsearch通过以下公式计算文档应该分到哪个分片上: shard_num = hash(_routing) % num_primary_shards 默认的_routing值是文档的_id或者_parent,通过_routing参数可以设置自定义路由。例如,想把user1发布的博客存储到同一个分片上,索引时指定routing参数,查询时在指定路由上查询: PUT my_index/my_type/1?routing=user1&refresh=true { "title": "This is a document" } GET my_index/my_type/1?routing=user1 在查询的时候通过routing参数查询: GET my_index/_search { "query": { "terms": { "_routing": [ "user1" ] } } } GET my_index/_search?routing=user1,user2 { "query": { "match": { "title": "document" } } } 在Mapping中指定routing为必须的: PUT my_index2 { "mappings": { "my_type": { "_routing": { "required": true } } } } PUT my_index2/my_type/1 { "text": "No routing value provided" } 2.7 _source 存储的文档的原始值。默认_source字段是开启的,也可以关闭: PUT tweets { "mappings": { "tweet": { "_source": { "enabled": false } } } } 但是一般情况下不要关闭,除法你不想做一些操作: 使用update、update_by_query、reindex 使用高亮 数据备份、改变mapping、升级索引 通过原始字段debug查询或者聚合 2.8 _type 每条被索引的文档都有一个_type和_id字段,可以根据_type进行查询、聚合、脚本和排序。例子如下: PUT my_index/type_1/1 { "text": "Document with type 1" } PUT my_index/type_2/2?refresh=true { "text": "Document with type 2" } GET my_index/_search { "query": { "terms": { "_type": [ "type_1", "type_2" ] } }, "aggs": { "types": { "terms": { "field": "_type", "size": 10 } } }, "sort": [ { "_type": { "order": "desc" } } ], "script_fields": { "type": { "script": { "lang": "painless", "inline": "doc['_type']" } } } } 2.9 _uid _uid和_type和_index的组合。和_type一样,可用于查询、聚合、脚本和排序。例子如下: PUT my_index/my_type/1 { "text": "Document with ID 1" } PUT my_index/my_type/2?refresh=true { "text": "Document with ID 2" } GET my_index/_search { "query": { "terms": { "_uid": [ "my_type#1", "my_type#2" ] } }, "aggs": { "UIDs": { "terms": { "field": "_uid", "size": 10 } } }, "sort": [ { "_uid": { "order": "desc" } } ], "script_fields": { "UID": { "script": { "lang": "painless", "inline": "doc['_uid']" } } } } 三、Mapping参数 3.1 analyzer 指定分词器(分析器更合理),对索引和查询都有效。如下,指定ik分词的配置: PUT my_index { "mappings": { "my_type": { "properties": { "content": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_max_word" } } } } } 3.2 normalizer normalizer用于解析前的标准化配置,比如把所有的字符转化为小写等。例子: PUT index { "settings": { "analysis": { "normalizer": { "my_normalizer": { "type": "custom", "char_filter": [], "filter": ["lowercase", "asciifolding"] } } } }, "mappings": { "type": { "properties": { "foo": { "type": "keyword", "normalizer": "my_normalizer" } } } } } PUT index/type/1 { "foo": "BÀR" } PUT index/type/2 { "foo": "bar" } PUT index/type/3 { "foo": "baz" } POST index/_refresh GET index/_search { "query": { "match": { "foo": "BAR" } } } BÀR经过normalizer过滤以后转换为bar,文档1和文档2会被搜索到。 3.3 boost boost字段用于设置字段的权重,比如,关键字出现在title字段的权重是出现在content字段中权重的2倍,设置mapping如下,其中content字段的默认权重是1. PUT my_index { "mappings": { "my_type": { "properties": { "title": { "type": "text", "boost": 2 }, "content": { "type": "text" } } } } } 同样,在查询时指定权重也是一样的: POST _search { "query": { "match" : { "title": { "query": "quick brown fox", "boost": 2 } } } } 推荐在查询时指定boost,第一中在mapping中写死,如果不重新索引文档,权重无法修改,使用查询可以实现同样的效果。 3.4 coerce coerce属性用于清除脏数据,coerce的默认值是true。整型数字5有可能会被写成字符串“5”或者浮点数5.0.coerce属性可以用来清除脏数据: 字符串会被强制转换为整数 浮点数被强制转换为整数 PUT my_index { "mappings": { "my_type": { "properties": { "number_one": { "type": "integer" }, "number_two": { "type": "integer", "coerce": false } } } } } PUT my_index/my_type/1 { "number_one": "10" } PUT my_index/my_type/2 { "number_two": "10" } mapping中指定number_one字段是integer类型,虽然插入的数据类型是String,但依然可以插入成功。number_two字段关闭了coerce,因此插入失败。 3.5 copy_to copy_to属性用于配置自定义的_all字段。换言之,就是多个字段可以合并成一个超级字段。比如,first_name和last_name可以合并为full_name字段。 PUT my_index { "mappings": { "my_type": { "properties": { "first_name": { "type": "text", "copy_to": "full_name" }, "last_name": { "type": "text", "copy_to": "full_name" }, "full_name": { "type": "text" } } } } } PUT my_index/my_type/1 { "first_name": "John", "last_name": "Smith" } GET my_index/_search { "query": { "match": { "full_name": { "query": "John Smith", "operator": "and" } } } } 3.6 doc_values doc_values是为了加快排序、聚合操作,在建立倒排索引的时候,额外增加一个列式存储映射,是一个空间换时间的做法。默认是开启的,对于确定不需要聚合或者排序的字段可以关闭。 PUT my_index { "mappings": { "my_type": { "properties": { "status_code": { "type": "keyword" }, "session_id": { "type": "keyword", "doc_values": false } } } } } 注:text类型不支持doc_values。 3.7 dynamic dynamic属性用于检测新发现的字段,有三个取值: true:新发现的字段添加到映射中。(默认) flase:新检测的字段被忽略。必须显式添加新字段。 strict:如果检测到新字段,就会引发异常并拒绝文档。 例子: PUT my_index { "mappings": { "my_type": { "dynamic": false, "properties": { "user": { "properties": { "name": { "type": "text" }, "social_networks": { "dynamic": true, "properties": {} } } } } } } } PS:取值为strict,非布尔值要加引号。 3.8 enabled ELasticseaech默认会索引所有的字段,enabled设为false的字段,es会跳过字段内容,该字段只能从_source中获取,但是不可搜。而且字段可以是任意类型。 PUT my_index { "mappings": { "session": { "properties": { "user_id": { "type": "keyword" }, "last_updated": { "type": "date" }, "session_data": { "enabled": false } } } } } PUT my_index/session/session_1 { "user_id": "kimchy", "session_data": { "arbitrary_object": { "some_array": [ "foo", "bar", { "baz": 2 } ] } }, "last_updated": "2015-12-06T18:20:22" } PUT my_index/session/session_2 { "user_id": "jpountz", "session_data": "none", "last_updated": "2015-12-06T18:22:13" } 3.9 fielddata 搜索要解决的问题是“包含查询关键词的文档有哪些?”,聚合恰恰相反,聚合要解决的问题是“文档包含哪些词项”,大多数字段再索引时生成doc_values,但是text字段不支持doc_values。 取而代之,text字段在查询时会生成一个fielddata的数据结构,fielddata在字段首次被聚合、排序、或者使用脚本的时候生成。ELasticsearch通过读取磁盘上的倒排记录表重新生成文档词项关系,最后在Java堆内存中排序。 text字段的fielddata属性默认是关闭的,开启fielddata非常消耗内存。在你开启text字段以前,想清楚为什么要在text类型的字段上做聚合、排序操作。大多数情况下这么做是没有意义的。 “New York”会被分析成“new”和“york”,在text类型上聚合会分成“new”和“york”2个桶,也许你需要的是一个“New York”。这是可以加一个不分析的keyword字段: PUT my_index { "mappings": { "my_type": { "properties": { "my_field": { "type": "text", "fields": { "keyword": { "type": "keyword" } } } } } } } 上面的mapping中实现了通过my_field字段做全文搜索,my_field.keyword做聚合、排序和使用脚本。 3.10 format format属性主要用于格式化日期: PUT my_index { "mappings": { "my_type": { "properties": { "date": { "type": "date", "format": "yyyy-MM-dd" } } } } } 更多内置的日期格式:https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html 3.11 ignore_above ignore_above用于指定字段索引和存储的长度最大值,超过最大值的会被忽略: PUT my_index { "mappings": { "my_type": { "properties": { "message": { "type": "keyword", "ignore_above": 15 } } } } } PUT my_index/my_type/1 { "message": "Syntax error" } PUT my_index/my_type/2 { "message": "Syntax error with some long stacktrace" } GET my_index/_search { "size": 0, "aggs": { "messages": { "terms": { "field": "message" } } } } mapping中指定了ignore_above字段的最大长度为15,第一个文档的字段长小于15,因此索引成功,第二个超过15,因此不索引,返回结果只有”Syntax error”,结果如下: { "took": 2, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 2, "max_score": 0, "hits": [] }, "aggregations": { "messages": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [] } } } 3.12 ignore_malformed ignore_malformed可以忽略不规则数据,对于login字段,有人可能填写的是date类型,也有人填写的是邮件格式。给一个字段索引不合适的数据类型发生异常,导致整个文档索引失败。如果ignore_malformed参数设为true,异常会被忽略,出异常的字段不会被索引,其它字段正常索引。 PUT my_index { "mappings": { "my_type": { "properties": { "number_one": { "type": "integer", "ignore_malformed": true }, "number_two": { "type": "integer" } } } } } PUT my_index/my_type/1 { "text": "Some text value", "number_one": "foo" } PUT my_index/my_type/2 { "text": "Some text value", "number_two": "foo" } 上面的例子中number_one接受integer类型,ignore_malformed属性设为true,因此文档一种number_one字段虽然是字符串但依然能写入成功;number_two接受integer类型,默认ignore_malformed属性为false,因此写入失败。 3.13 include_in_all include_in_all属性用于指定字段是否包含在_all字段里面,默认开启,除索引时index属性为no。 例子如下,title和content字段包含在_all字段里,date不包含。 PUT my_index { "mappings": { "my_type": { "properties": { "title": { "type": "text" }, "content": { "type": "text" }, "date": { "type": "date", "include_in_all": false } } } } } include_in_all也可用于字段级别,如下my_type下的所有字段都排除在_all字段之外,author.first_name 和author.last_name 包含在in _all中: PUT my_index { "mappings": { "my_type": { "include_in_all": false, "properties": { "title": { "type": "text" }, "author": { "include_in_all": true, "properties": { "first_name": { "type": "text" }, "last_name": { "type": "text" } } }, "editor": { "properties": { "first_name": { "type": "text" }, "last_name": { "type": "text", "include_in_all": true } } } } } } } 3.14 index index属性指定字段是否索引,不索引也就不可搜索,取值可以为true或者false。 3.15 index_options index_options控制索引时存储哪些信息到倒排索引中,接受以下配置: 参数 作用 docs 只存储文档编号 freqs 存储文档编号和词项频率 positions 文档编号、词项频率、词项的位置被存储,偏移位置可用于临近搜索和短语查询 offsets 文档编号、词项频率、词项的位置、词项开始和结束的字符位置都被存储,offsets设为true会使用Postings highlighter 3.16 fields fields可以让同一文本有多种不同的索引方式,比如一个String类型的字段,可以使用text类型做全文检索,使用keyword类型做聚合和排序。 PUT my_index { "mappings": { "my_type": { "properties": { "city": { "type": "text", "fields": { "raw": { "type": "keyword" } } } } } } } PUT my_index/my_type/1 { "city": "New York" } PUT my_index/my_type/2 { "city": "York" } GET my_index/_search { "query": { "match": { "city": "york" } }, "sort": { "city.raw": "asc" }, "aggs": { "Cities": { "terms": { "field": "city.raw" } } } } 3.17 norms norms参数用于标准化文档,以便查询时计算文档的相关性。norms虽然对评分有用,但是会消耗较多的磁盘空间,如果不需要对某个字段进行评分,最好不要开启norms。 3.18 null_value 值为null的字段不索引也不可以搜索,null_value参数可以让值为null的字段显式的可索引、可搜索。例子: PUT my_index { "mappings": { "my_type": { "properties": { "status_code": { "type": "keyword", "null_value": "NULL" } } } } } PUT my_index/my_type/1 { "status_code": null } PUT my_index/my_type/2 { "status_code": [] } GET my_index/_search { "query": { "term": { "status_code": "NULL" } } } 文档1可以被搜索到,因为status_code的值为null,文档2不可以被搜索到,因为status_code为空数组,但是不是null。 3.19 position_increment_gap 为了支持近似或者短语查询,text字段被解析的时候会考虑此项的位置信息。举例,一个字段的值为数组类型: "names": [ "John Abraham", "Lincoln Smith"] 为了区别第一个字段和第二个字段,Abraham和Lincoln在索引中有一个间距,默认是100。例子如下,这是查询”Abraham Lincoln”是查不到的: PUT my_index/groups/1 { "names": [ "John Abraham", "Lincoln Smith"] } GET my_index/groups/_search { "query": { "match_phrase": { "names": { "query": "Abraham Lincoln" } } } } 指定间距大于100可以查询到: GET my_index/groups/_search { "query": { "match_phrase": { "names": { "query": "Abraham Lincoln", "slop": 101 } } } } 在mapping中通过position_increment_gap参数指定间距: PUT my_index { "mappings": { "groups": { "properties": { "names": { "type": "text", "position_increment_gap": 0 } } } } } 3.20 properties Object或者nested类型,下面还有嵌套类型,可以通过properties参数指定。 PUT my_index { "mappings": { "my_type": { "properties": { "manager": { "properties": { "age": { "type": "integer" }, "name": { "type": "text" } } }, "employees": { "type": "nested", "properties": { "age": { "type": "integer" }, "name": { "type": "text" } } } } } } } 对应的文档结构: PUT my_index/my_type/1 { "region": "US", "manager": { "name": "Alice White", "age": 30 }, "employees": [ { "name": "John Smith", "age": 34 }, { "name": "Peter Brown", "age": 26 } ] } 可以对manager.name、manager.age做搜索、聚合等操作。 GET my_index/_search { "query": { "match": { "manager.name": "Alice White" } }, "aggs": { "Employees": { "nested": { "path": "employees" }, "aggs": { "Employee Ages": { "histogram": { "field": "employees.age", "interval": 5 } } } } } } 3.21 search_analyzer 大多数情况下索引和搜索的时候应该指定相同的分析器,确保query解析以后和索引中的词项一致。但是有时候也需要指定不同的分析器,例如使用edge_ngram过滤器实现自动补全。 默认情况下查询会使用analyzer属性指定的分析器,但也可以被search_analyzer覆盖。例子: PUT my_index { "settings": { "analysis": { "filter": { "autocomplete_filter": { "type": "edge_ngram", "min_gram": 1, "max_gram": 20 } }, "analyzer": { "autocomplete": { "type": "custom", "tokenizer": "standard", "filter": [ "lowercase", "autocomplete_filter" ] } } } }, "mappings": { "my_type": { "properties": { "text": { "type": "text", "analyzer": "autocomplete", "search_analyzer": "standard" } } } } } PUT my_index/my_type/1 { "text": "Quick Brown Fox" } GET my_index/_search { "query": { "match": { "text": { "query": "Quick Br", "operator": "and" } } } } 3.22 similarity similarity参数用于指定文档评分模型,参数有三个: BM25 :ES和Lucene默认的评分模型 classic :TF/IDF评分 boolean:布尔模型评分 例子: PUT my_index { "mappings": { "my_type": { "properties": { "default_field": { "type": "text" }, "classic_field": { "type": "text", "similarity": "classic" }, "boolean_sim_field": { "type": "text", "similarity": "boolean" } } } } } default_field自动使用BM25评分模型,classic_field使用TF/IDF经典评分模型,boolean_sim_field使用布尔评分模型。 3.23 store 默认情况下,自动是被索引的也可以搜索,但是不存储,这也没关系,因为_source字段里面保存了一份原始文档。在某些情况下,store参数有意义,比如一个文档里面有title、date和超大的content字段,如果只想获取title和date,可以这样: PUT my_index { "mappings": { "my_type": { "properties": { "title": { "type": "text", "store": true }, "date": { "type": "date", "store": true }, "content": { "type": "text" } } } } } PUT my_index/my_type/1 { "title": "Some short title", "date": "2015-01-01", "content": "A very long content field..." } GET my_index/_search { "stored_fields": [ "title", "date" ] } 查询结果: { "took": 1, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 1, "max_score": 1, "hits": [ { "_index": "my_index", "_type": "my_type", "_id": "1", "_score": 1, "fields": { "date": [ "2015-01-01T00:00:00.000Z" ], "title": [ "Some short title" ] } } ] } } Stored fields返回的总是数组,如果想返回原始字段,还是要从_source中取。 3.24 term_vector 词向量包含了文本被解析以后的以下信息: 词项集合 词项位置 词项的起始字符映射到原始文档中的位置。 term_vector参数有以下取值: 参数取值 含义 no 默认值,不存储词向量 yes 只存储词项集合 with_positions 存储词项和词项位置 with_offsets 词项和字符偏移位置 with_positions_offsets 存储词项、词项位置、字符偏移位置 例子: PUT my_index { "mappings": { "my_type": { "properties": { "text": { "type": "text", "term_vector": "with_positions_offsets" } } } } } PUT my_index/my_type/1 { "text": "Quick brown fox" } GET my_index/_search { "query": { "match": { "text": "brown fox" } }, "highlight": { "fields": { "text": {} } } } 四、动态Mapping 4.1 default mapping 在mapping中使用default字段,那么其它字段会自动继承default中的设置。 PUT my_index { "mappings": { "_default_": { "_all": { "enabled": false } }, "user": {}, "blogpost": { "_all": { "enabled": true } } } } 上面的mapping中,default中关闭了all字段,user会继承_default中的配置,因此user中的all字段也是关闭的,blogpost中开启_all,覆盖了_default的默认配置。 当default被更新以后,只会对后面新加的文档产生作用。 4.2 Dynamic field mapping 文档中有一个之前没有出现过的字段被添加到ELasticsearch之后,文档的type mapping中会自动添加一个新的字段。这个可以通过dynamic属性去控制,dynamic属性为false会忽略新增的字段、dynamic属性为strict会抛出异常。如果dynamic为true的话,ELasticsearch会自动根据字段的值推测出来类型进而确定mapping: JSON格式的数据 自动推测的字段类型 null 没有字段被添加 true or false boolean类型 floating类型数字 floating类型 integer long类型 JSON对象 object类型 数组 由数组中第一个非空值决定 string 有可能是date类型(开启日期检测)、double或long类型、text类型、keyword类型 日期检测默认是检测符合以下日期格式的字符串: [ "strict_date_optional_time","yyyy/MM/dd HH:mm:ss Z||yyyy/MM/dd Z"] 例子: PUT my_index/my_type/1 { "create_date": "2015/09/02" } GET my_index/_mapping mapping 如下,可以看到create_date为date类型: { "my_index": { "mappings": { "my_type": { "properties": { "create_date": { "type": "date", "format": "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis" } } } } } } 关闭日期检测: PUT my_index { "mappings": { "my_type": { "date_detection": false } } } PUT my_index/my_type/1 { "create": "2015/09/02" } 再次查看mapping,create字段已不再是date类型: GET my_index/_mapping 返回结果: { "my_index": { "mappings": { "my_type": { "date_detection": false, "properties": { "create": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } } } } } 自定义日期检测的格式: PUT my_index { "mappings": { "my_type": { "dynamic_date_formats": ["MM/dd/yyyy"] } } } PUT my_index/my_type/1 { "create_date": "09/25/2015" } 开启数字类型自动检测: PUT my_index { "mappings": { "my_type": { "numeric_detection": true } } } PUT my_index/my_type/1 { "my_float": "1.0", "my_integer": "1" } 4.3 Dynamic templates 动态模板可以根据字段名称设置mapping,如下对于string类型的字段,设置mapping为: "mapping": { "type": "long"} 但是匹配字段名称为long_*格式的,不匹配*_text格式的: PUT my_index { "mappings": { "my_type": { "dynamic_templates": [ { "longs_as_strings": { "match_mapping_type": "string", "match": "long_*", "unmatch": "*_text", "mapping": { "type": "long" } } } ] } } } PUT my_index/my_type/1 { "long_num": "5", "long_text": "foo" } 写入文档以后,long_num字段为long类型,long_text扔为string类型。 4.4 Override default template 可以通过default字段覆盖所有索引的mapping配置,例子: PUT _template/disable_all_field { "order": 0, "template": "*", "mappings": { "_default_": { "_all": { "enabled": false } } } }
一、Spring JDBC Spring JDBC是在JDBC API的基础上定义一个抽象层,用以简化JDBC操作。Spring JdbcTemplate是Spring JDBC框架的核心,为不同类型的JDBC操作提供模板方法,每个模板方法都能控制整个过程,并允许覆盖过程中的特定任务。基于此,可以在保留灵活性的情况下,将数据库存取的工作量降到最低。 二、Bean配置 在Spring配置文件中,配置一个Spring JdbcTemplate Bean: <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> 三、准备测试数据 登录mysql,创建一张用于测试的数据表: create database testdb; use testdb 创建一张数据表: CREATE TABLE `user` ( `uid` int(1) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(200) NOT NULL, PRIMARY KEY (`uid`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8; 四、JdbcTemplate提供的常用方法 import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import javax.sql.DataSource; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; /** * Created by bee on 17/5/17. */ public class JDBCTest { private ApplicationContext ctx = null; private JdbcTemplate jdbcTemplate; { ctx = new ClassPathXmlApplicationContext("beans-jdbc.xml"); jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate"); } /** * * @throws SQLException */ @Test public void TestDataSource() throws SQLException { DataSource dataSource = ctx.getBean(DataSource.class); System.out.println(dataSource.getConnection()); } /** *更新属性 */ @Test public void updateTest() { String sql = "UPDATE user SET username = ? WHERE uid = ?"; jdbcTemplate.update(sql, "神乐", 2); } /** *插入数据 */ @Test public void insertTest() { String sql = "INSERT user(username,password) values('Tonny','123456')"; jdbcTemplate.execute(sql); } /** *删除记录 */ @Test public void deleteTest() { String sql = "DELETE FROM user where uid = ?"; jdbcTemplate.update(sql, 3); } /** *批量插入 */ @Test public void batchUpdateTest() { String sql = "INSERT INTO user(username,password) values(?,?)"; List<Object[]> batchArgs = new ArrayList<Object[]>(); batchArgs.add(new Object[]{"Tina", "876543"}); batchArgs.add(new Object[]{"Judy", "587641"}); batchArgs.add(new Object[]{"Sam", "987632"}); jdbcTemplate.batchUpdate(sql, batchArgs); } /** *查询 */ @Test public void queryForObject() { String sql = "SELECT uid AS id,username AS userName,password AS passWord " + "FROM user WHERE uid = ?"; RowMapper<User> rowMapper = new BeanPropertyRowMapper<User>(User.class); User user = (User) jdbcTemplate.queryForObject(sql, rowMapper, 2); System.out.println(user); } /** *批量查询 */ @Test public void queryForList() { String sql = "SELECT uid AS id,username AS userName,password AS passWord FROM user WHERE uid > ?"; RowMapper<User> rowMapper = new BeanPropertyRowMapper<User>(User.class); List<User> users = jdbcTemplate.query(sql, rowMapper, 3); System.out.println(users); } /** *统计 */ @Test public void countTest() { String sql = "SELECT COUNT(*) FROM user"; long count = jdbcTemplate.queryForObject(sql, Long.class); System.out.println(count); } }
一、AOP核心思想 AOP是Aspect-Oriented Programming的缩写,翻译为面向切面编程。我个人理解切面就是一个方面。 例子,一个接口里面有增删改查四个方法: package com.stuspring.aop.impl; /** * Created by bee on 17/5/15. */ public interface ArithmeticCalculator { int add(int i,int j); int sub(int i,int j); int mul(int i,int j); int div(int i,int j); } 实现类: package com.stuspring.aop.impl; import org.springframework.stereotype.Component; /** * Created by bee on 17/5/15. */ @Component("arithmeticCalculator") public class ArithmeticCalculatorImpl implements ArithmeticCalculator { @Override public int add(int i, int j) { int result=i+j; return result; } @Override public int sub(int i, int j) { int result=i-j; return result; } @Override public int mul(int i, int j) { int result=i*j; return result; } @Override public int div(int i, int j) { int result=i/j; return result; } } 如果想给这四个方法分别加上前置日志功能,以其中一个为例,add方法变这样: @Override public int add(int i, int j) { System.out.println("The method add begins with " +i+","+j); int result=i+j; return result; } 手动给每个方法都加上固然可行,但是维护起来过于麻烦,面向切面编程就是为了解决这一问题。AOP的好处就是每个逻辑位于一个位置,代码不分散便于维护和升级,业务模块更简洁。 加一个日志切面: package com.stuspring.aop.impl; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; /** * Created by bee on 17/5/15. */ @Aspect @Component public class LoggingAspect { /** * 前置通知 方法开始之前执行 * @param joinPoint */ @Before("execution(public int com.stuspring.aop.impl.ArithmeticCalculatorImpl.*(int,int))") public void beforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); List<Object> agrs = Arrays.asList(joinPoint.getArgs()); System.out.println("The method " + methodName + " begins with " + agrs); } /** * 后置通知,方法执行完之后执行,不论方法是否出现异常 * 后置通知中不能访问目标方法的执行结果 */ @After("execution(public int com.stuspring.aop.impl.ArithmeticCalculatorImpl.*(int,int))") public void afterMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("The method " + methodName + " ends with " + args); } } 新建spring配置文件beans-aspect.xml: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:content="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <content:component-scan base-package="com.stuspring.aop.impl"/> <aop:aspectj-autoproxy/> </beans> 附maven依赖: <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.3.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.3.8.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.10</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.10</version> </dependency> 二、五种类型通知 @Before前置通知:方法开始之前执行。 /** * 前置通知 方法开始之前执行 * @param joinPoint */ @Before("execution(public int com.stuspring.aop.impl.ArithmeticCalculatorImpl.*(int,int))") public void beforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); List<Object> agrs = Arrays.asList(joinPoint.getArgs()); System.out.println("The method " + methodName + " begins with " + agrs); } @After后置通知:方法开始之后执行,不论方法是否出现异常。 /** * 后置通知,方法执行完之后执行,不论方法是否出现异常 * 后置通知中不能访问目标方法的执行结果 */ @After("execution(public int com.stuspring.aop.impl.ArithmeticCalculatorImpl.*(int,int))") public void afterMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("The method " + methodName + " ends with " + args); } @AfterRunning:返回通知,在方法返回结果之后执行。 /** * 返回通知,在方法正常结束之后执行的代码 * 返回通知可以访问方法的返回值 */ @AfterReturning(value="execution(public int com.stuspring.aop.impl.ArithmeticCalculatorImpl.*(int,int))",returning = "result") public void afterReturning(JoinPoint joinPoint,Object result){ String methodName=joinPoint.getSignature().getName(); System.out.println("The method "+methodName+" ends with "+result); } @AfterThrowing:异常通知,在方法抛出异常之后。 /** * 异常通知:在方法抛出异常之后执行 * @param joinPoint * @param e */ @AfterThrowing(value="execution(public int com.stuspring.aop.impl.ArithmeticCalculatorImpl.*(int,int))",throwing = "e") public void afterThrowing(JoinPoint joinPoint,Exception e){ String methodName=joinPoint.getSignature().getName(); System.out.println("The method "+methodName+" occurs execution: "+e); } @Around:环绕通知,围绕方法执行。 /** * 环绕通知需要携带ProceedingJoinPoint类型的参数 * 环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法 * 环绕通知必须有返回值,返回值即为目标方法的返回值 * * @param pjd */ @Around("execution(public int com.stuspring.aop.impl.ArithmeticCalculatorImpl.*(..))") public Object aroundMethod(ProceedingJoinPoint pjd) { Object result = null; String methodName = pjd.getSignature().getName(); try { //前置通知 System.out.println("--->The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs())); //执行目标方法 result = pjd.proceed(); //后置通知 System.out.println("--->The method " + methodName + " ends with " + result); } catch (Throwable e) { //异常通知 System.out.println("The method "+methodName+" occurs exception : "+e); } //后置通知 System.out.println("The Method "+methodName+" ends!"); return result; } 三、指定切面的优先级 切面的优先级可以用@Order注解指定,传入的整数值越小,优先级越高。 四、复用切点表达式 /** * 定义一个方法用于声明切入点表达式。一般地,该方法不需要再写其它代码。 */ @Pointcut("execution(public int com.stuspring.aop.impl.ArithmeticCalculatorImpl.*(int,int))") public void declareJoinPointExpression(){ } /** * 前置通知 方法开始之前执行 * @param joinPoint */ @Before("declareJoinPointExpression()") public void beforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); List<Object> agrs = Arrays.asList(joinPoint.getArgs()); System.out.println("The method " + methodName + " begins with " + agrs); } 外部类引用可以使用报名加方法名的方法。 五、配置文件方式配置AOP 接口: package com.stuspring.aop.fileimpl; /** * Created by bee on 17/5/16. */ public interface ArithmeticCalculator { int add(int i,int j); int sub(int i,int j); int mul(int i,int j); int div(int i,int j); } 实现类: package com.stuspring.aop.fileimpl; /** * Created by bee on 17/5/16. */ public class ArithmeticCalculatorImpl implements ArithmeticCalculator { @Override public int add(int i, int j) { int result=i+j; return result; } @Override public int sub(int i, int j) { int result=i-j; return result; } @Override public int mul(int i, int j) { int result=i*j; return result; } @Override public int div(int i, int j) { int result=i/j; return result; } } 日志切面: package com.stuspring.aop.fileimpl; import org.aspectj.lang.JoinPoint; /** * Created by bee on 17/5/16. */ public class LoggingAspect { public void beforeMethod(JoinPoint joinPoint){ String methodName=joinPoint.getSignature().getName(); System.out.println("The method begins with "+methodName); } } 参数验证切面: package com.stuspring.aop.fileimpl; import org.aspectj.lang.JoinPoint; /** * Created by bee on 17/5/16. */ public class ValidationAspect { public void validateArgs(JoinPoint joinPoint){ System.out.println("validationMethod......"); } } Spring配置文件: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!--配置bean--> <bean id="arithmeticCalculator" class="com.stuspring.aop.fileimpl.ArithmeticCalculatorImpl"/> <bean id="loggingAspect" class="com.stuspring.aop.fileimpl.LoggingAspect"/> <bean id="validationAspect" class="com.stuspring.aop.fileimpl.ValidationAspect"/> <aop:config> <!--配置切面表达式--> <aop:pointcut id="pointcut" expression="execution(public int com.stuspring.aop.fileimpl.ArithmeticCalculatorImpl.*(int,int))"/> <!--配置切面通知--> <aop:aspect ref="loggingAspect" order="2"> <aop:before method="beforeMethod" pointcut-ref="pointcut"/> </aop:aspect> <aop:aspect ref="validationAspect" order="1"> <aop:before method="validateArgs" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans> Main测试方法: package com.stuspring.aop.fileimpl; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Created by bee on 17/5/16. */ public class Main { public static void main(String[] args) { ApplicationContext ctx=new ClassPathXmlApplicationContext("beans-aspectfile.xml"); ArithmeticCalculator calculator= (ArithmeticCalculator) ctx.getBean("arithmeticCalculator"); System.out.println(calculator.add(2,4)); } }
Spring是简化j2ee开发的一个框架,通过指定id、类名配置bean虽然简单,但是当bean很多的时候,spring的配置文件会过于臃肿,使用注解技术配置bean使配置更加简介。 一、注解配置bean步骤 注解配置bean的原理是组件扫描,通过在spring配置文件中定义包扫描器,spring就能从classpath下自动扫描、侦测和实例化具有特定注解的bean。 Spring注解有以下四个,位于org.springframework.stereotype目录下。 Component 基本注解,标识了一个受Spring管理的组件,源码如下: package org.springframework.stereotype; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Component { String value() default ""; } Repository 标识持久层组件,源码如下: package org.springframework.stereotype; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Repository { String value() default ""; } Service 标识业务层组件 package org.springframework.stereotype; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Service { String value() default ""; } Controller 标识表现层组件 package org.springframework.stereotype; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Controller { String value() default ""; } 配置bean的步骤: 在java类中加上Spring标准 配置文件中扫类所在的包 IOC容器获取bean Spring有一个默认的bean命名策略:使用非限定类名,第一个字母小写,或者通过value属性设置。举例,一个UserService类,通过Service注解标准了: package com.stuspring.annbeans.service; import org.springframework.stereotype.Service; /** * Created by bee on 17/5/4. */ @Service public class UserService { public void add(){ System.out.println("UserService add.."); } } 在Spring的配置文件中定义组件扫描器(注意引入context命名空间): <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <context:component-scan base-package="com.stuspring.annbeans" /> </beans> 现在Bean已经可以使用了,它的id就是类名UserService首字母小写,即userService。获取bean并调用add方法: package com.stuspring.annbeans; import com.stuspring.annbeans.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Created by bee on 17/5/4. */ public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-annotation.xml"); UserService userService = (UserService) ctx.getBean("userService"); System.out.println(userService); userService.add(); } } 打印结果: com.stuspring.annbeans.service.UserService@c81cdd1 UserService add.. 可以通过value属性来设置bean的id,代码如下,这样bean的id就改为uService了。 @Service("uService") public class UserService { public void add(){ System.out.println("UserService add.."); } } 二、组件扫描器中的一些配置 <context:component-scan >可以定义一些属性及配置: base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包及其子包中的所有类。需要扫描多个包时,可以使用逗号分开。 如果仅需要扫描特定的类而不是基类包下的所有类,可以使用resource-pattern属性过滤特定的类。例如: <context:component-scan base-package="com.stuspring.annbeans" resource-pattern="repository/8.class"/> <context:exclude-filter>子节点表示排除在外的目标类 <context:include-filter>子节点表示要包含的目标类 三、bean直接的关联关系 在UserController类中创建一个UserService对象: @Controller public class UserController { private UserService userService; public void execute(){ System.out.println("UserController execute..."); userService.add(); } } 这时候获取UserController对象: public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-annotation.xml"); UserController userController = (UserController) ctx.getBean("userController"); userController.execute(); } } 运行会报空指针异常: Exception in thread "main" java.lang.NullPointerException at com.stuspring.annbeans.controller.UserController.execute(UserController.java:18) at com.stuspring.annbeans.Main.main(Main.java:15) UserController execute... 从打印结果中可以看出UserService在UserController类中没有被标准。<context:component-scan>元素会自动注册AutowiredAnnotationBeanPostProcessor实例,该实例可以自动装配具有@Autowired、@Resource、@Inject注解的属性。 在UserService对象上添加注解@Autowired: @Autowired private UserService userService; 这样UserService对象就会自动装配上。 @Autowired注解会自动装配具有兼容类型的单个Bean属性: 构造器、普通字段,一切具有参数的方法都可以用@Autowired注解 默认情况下,所有使用@Autowired注解的属性都需要被设置。当Spring找不到匹配的Bean装配属性时会抛出异常。若某一属性允许不被设置,可以设置@Autowired注解的required属性为false 默认情况下, 当IOC容器中存在多个类型兼容的Bean时,通过类型的自动装配将会无法工作,此时可以在@Qualifier注解里提供Bean的名称。Spring允许对方法的入参标注@Qualifier已指定注入的Bean的名称。 @Autowired注解也可以应用在数组类型的属性上,此时Spring会把所有匹配的Bean进行自动装配 @Autowired注解也可以应用在集合属性上,此时Spring读取积累的类型信息,然后自动装配所有与之兼容的Bean。 @Autowired注解用在java.util.map上时,若该map的键值为String,那么Spring将自动装配与之Map类值兼容的Bean,此时Bean的名称作为键值。
一、Java注解技术的基本概念 Java注解又称Java标注,通俗的说注解就是对某一事物添加注释说明,是Java 5.0版本开始支持加入源代码的特殊语法元数据。Java语言中的类、方法、变量、参数和包都可以被标注,Java标注可以通过反射获取标注内容。在编译器生成类文件是,标注可以嵌入到字节码中。Java虚拟机可以保留标注内容,在运行时可以获取到标注内容。 注解提供了安全的类似注释的机制,用来将任何的信息或元数据与程序元素进行管理。 注解的原理: 注解不会影响程序代码的执行。 二、Java标准注解 @Override 检查该方法是否是重载方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。 @Deprecated 标记过时方法。如果使用该方法,会报编译警告。 @SuppressWarnings 指示编译器去忽略注解中声明的警告。 测试,新建一个Person接口,里面定义三个方法,name()个age()用来定义姓名和年龄。 package com.stuspring.annotation; public interface Person { public String name(); public int age(); @Deprecated public void sing(); } Son类继承Person类: package com.stuspring.annotation; public class Son implements Person { @Override public String name() { return null; } @Override public int age() { return 0; } @Override public void sing() { } } @Override表明该方法是从父类继承而来的。如果在Person类中删除一个sing()方法,Son类的sing()方法仍然使用@Override来标注编译器就会报错,因为父类中并没有这个方法。 在父类中把sing()方法标注为@Deprecated,表明该方法已经过时,不推荐使用了。调用的时候会给出警告: 如果想忽略警告,可以使用@SuppressWarnings标注: 三、Java注解分类 按运行机制分,注解分为: 源码注解:注解只在源码中存在,在.class文件中不存在 编译时注解:注解在源码和.class文件中都存在 运行时注解:注解在运行阶段还起作用,甚至会影响程序的运行逻辑。 按照注解来源可以分为: 来自JDK的注解 来自第三方的注解 自定义的注解 *四、Java元注解 元注解是对Java注解进行注解。Java 5.0定义的元注解在 import java.lang.annotation包中,有以下四种类型: @Target:用于描述注解的使用范围,即被描述的注解可以用在什么地方,取值有以下值: - CONSTRUCTOR - FIELD - LOCAL_VARIABLE - METHOD - PACKAGE - PARAMETER - TYPE 如下两个注解,它们的作用域分别为方法和域。 @Target(ElementType.TYPE) public @interface Table{ //数据表名称注解,默认值为类名称 public String tableName() default "className"; } @Target(ElementType.FIELD) public @interface NoDBColumn{} @Retention 用于描述注解的生命周期。取值有以下几个 SOURCE:在源文件中有效 CLASS:在源文件、.class文件中保留 RUNTIME:在运行时有效 @Documented:标记注解,标记Documented后javadoc中会生成注解的文档说明 @Inherited:标记注解,阐述了某个注解的类型是被继承的。使用@Inherited修饰以后,这个注解将被用于该class的子类。只会集成父类类上的注解,不会继承父类方法上的注解。 五、Java自定义注解 自定义注解的语法: @<注解名>(<成员1>=<成员值1>,<成员1>=<成员值1>) @Description(desc="I am eyeColor",author="Mooc boy",age=18) 新建一个Description.java,代码如下: package com.stuspring.annotation; import java.lang.annotation.*; /** * Created by bee on 17/4/30. */ @Target({ElementType.METHOD}) //作用域 用在方法上 @Retention(RetentionPolicy.RUNTIME) //声明周期 @Inherited //允许子继承 @Documented //生成javadoc时会包含注解 public @interface Description { String desc();//成员无参无异常方式声明 String author(); int age() default 18;// 使用default为成员指定一个默认值 } 上面代码中定义了一个注解,注解的名字为Description,里面有desc、author、age三个成员,注解作用在方法上,生命周期为在运行时扔有作用,允许子继承,生成javadoc会包含注解。 六、通过反射解析注解 通过反射来获取类、函数或成员上的运行时注解信息,从而实现动态控制程序运行的逻辑。 首先自定义一个注解,Desc.java package com.stuspring.annotation; import java.lang.annotation.*; /** * Created by bee on 17/5/3. */ @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Desc { String value(); } 在Son.java中使用注解: package com.stuspring.annotation; /** * Created by bee on 17/4/30. */ @Desc("I am class annotation") public class Son implements Person { @Override @Desc("I am method annotation") public String name() { return null; } @Override public int age() { return 0; } @Override public void sing() { } } 通过反射获取注解的信息,ParseAnn.java package com.stuspring.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.Method; /** * Created by bee on 17/5/3. */ public class ParseAnn { public static void main(String[] args) { //1.使用类加载器加载类 try { Class c = Class.forName("com.stuspring.annotation.Son"); //2.找到类上面的注解 boolean isExist = c.isAnnotationPresent(Desc.class); if (isExist) { //3.拿到注解实例 Desc desc = (Desc) c.getAnnotation(Desc.class); //4.打印注解的值 System.out.println(desc.value()); } //5.找到方法上的注解 Method[] ms = c.getMethods();//得到类上的所有方法 for (Method m : ms) { //遍历 boolean isExistMethod = m.isAnnotationPresent(Desc.class); if (isExistMethod) { Desc desc = m.getAnnotation(Desc.class); System.out.println(desc.value()); } } //6.另一种解析方法 for (Method m:ms){ Annotation[] anns=m.getAnnotations(); for (Annotation a:anns){ if (a instanceof Desc){ Desc desc= (Desc) a; System.out.println(desc.value()); } } } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } 运行结果: I am class annotation I am method annotation I am method annotation 七、注解项目实战 项目说明: 实现一个持久层框架,用来代替Hibernate的解决方案,核心代码是通过注解来实现。 需求: 1.有一张用户表,包括用户ID、用户名、昵称、年龄、性别、所在城市、邮箱、手机号 2.方便的对每个字段或字段的组合条件进行检索,打印出SQL。 3.使用方式要足够简单 Filter类: package com.stuspring.annotation.inaction; /** * Created by bee on 17/5/3. */ @Table("user") public class Filter { @Column("id") private int id; @Column("userName") private String userName; @Column("nickName") private String nickName; @Column("age") private int age; @Column("city") private String city; @Column("email") private String email; @Column("mobile") private String mobile; public void setId(int id) { this.id = id; } public void setUserName(String userName) { this.userName = userName; } public void setNickName(String nickName) { this.nickName = nickName; } public void setAge(int age) { this.age = age; } public void setCity(String city) { this.city = city; } public void setEmail(String email) { this.email = email; } public void setMobile(String mobile) { this.mobile = mobile; } public int getId() { return id; } public String getUserName() { return userName; } public String getNickName() { return nickName; } public int getAge() { return age; } public String getCity() { return city; } public String getEmail() { return email; } public String getMobile() { return mobile; } } Table注解 package com.stuspring.annotation.inaction; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Created by bee on 17/5/3. */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Table { String value(); } Column注解: package com.stuspring.annotation.inaction; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Created by bee on 17/5/3. */ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Column { String value(); } 测试: package com.stuspring.annotation.inaction; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * Created by bee on 17/5/3. */ public class Test { public static void main(String[] args) { Filter f1 = new Filter(); f1.setId(10); Filter f2 = new Filter(); f2.setUserName("lucy"); Filter f3 = new Filter(); f3.setEmail("liu@sina.com,zh@163.com,1235@qq.com"); Filter f4 = new Filter(); f4.setEmail("liu@sina.com"); Filter f5 = new Filter(); f5.setUserName("Tom"); f5.setAge(20); String sq1 = query(f1); String sq2 = query(f2); String sq3 = query(f3); String sq4 = query(f4); String sq5 = query(f5); System.out.println(sq1); System.out.println(sq2); System.out.println(sq3); System.out.println(sq4); System.out.println(sq5); } public static String query(Filter filter) { StringBuilder sb = new StringBuilder(); //1.获取到Class Class c = filter.getClass(); //获取到Table的名字 boolean exist = c.isAnnotationPresent(Table.class); if (!exist) { return null; } Table table = (Table) c.getAnnotation(Table.class); String tbName = table.value(); sb.append("SELECT * FROM ").append(tbName).append(" WHERE 1=1 "); //遍历所有字段 Field[] fArray = c.getDeclaredFields(); for (Field field : fArray) { boolean fExists = field.isAnnotationPresent(Column.class); if (!fExists) { continue; } Column column = field.getAnnotation(Column.class); String columnName = column.value(); //拿到字段的值 String fieldName = field.getName(); Object fieldValue = null; String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); try { Method getMethod = c.getMethod(getMethodName); fieldValue = getMethod.invoke(filter, null); } catch (Exception e) { e.printStackTrace(); } //拼装SQL if (fieldValue == null || (fieldValue instanceof Integer && (Integer) fieldValue == 0)) { continue; } if (fieldValue instanceof String) { if (((String) fieldValue).contains(",")) { String[] values = ((String) fieldValue).split(","); sb.append("in("); for (String v : values) { sb.append("\'").append(v).append("\'").append(","); } sb.deleteCharAt(sb.length() - 1); sb.append(")"); } else { fieldValue = "\'" + fieldValue + "\'"; sb.append(" and ").append(fieldName).append(" = ").append(fieldValue); } }else{ sb.append(" and ").append(fieldName).append(" = ").append(fieldValue); } } return sb.toString(); } } 输出: SELECT * FROM user WHERE 1=1 and id = 10 SELECT * FROM user WHERE 1=1 and userName = 'lucy' SELECT * FROM user WHERE 1=1 in('liu@sina.com','zh@163.com','1235@qq.com') SELECT * FROM user WHERE 1=1 and email = 'liu@sina.com' SELECT * FROM user WHERE 1=1 and userName = 'Tom' and age = 20
十一、工厂方法配置Bean 通过全类名方法配置Bean底层采用的是反射,除此之外还可以通过工厂方法(静态工厂方法&实例工厂方法)、FactoryBean来配置Bean。 静态工厂方法创建Bean是将对象创建的过程封装到静态方法中,当客户端需要对象时,只需要简单地调用静态方法而不需要关系创建对象的细节。 要声明通过静态方法调用Bean,需要在Bean的class属性里指定该工厂的方法的类,同时在factory-method属性中指定工厂方法的名称,最后使用<constructor-arg>元素为该方法传递方法参数。 11.1静态工厂方法 Car类: package com.stuspring.factory; /** * Created by bee on 17/4/28. */ public class Car { private String brand; private double price; public void setBrand(String brand) { this.brand = brand; } public void setPrice(double price) { this.price = price; } public String getBrand() { return brand; } public double getPrice() { return price; } public Car() { } public Car(String brand, double price) { this.brand = brand; this.price = price; } @Override public String toString() { return "Car{" + "brand='" + brand + '\'' + ", price=" + price + '}'; } } 静态工厂方法: package com.stuspring.factory; import java.util.HashMap; import java.util.Map; /** * Created by bee on 17/4/28. */ public class StaticCarFactory { private static Map<String,Car> cars=new HashMap<String, Car>(); static { cars.put("audi",new Car("Audi",300000)); cars.put("ford",new Car("Ford",200000)); } //静态工厂方法 public static Car getCar(String name){ return cars.get(name); } } 配置Bean: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--通过静态工厂方法配置Bean实例。注意,不是静态工厂方法实例--> <bean id="car1" class="com.stuspring.factory.StaticCarFactory" factory-method="getCar"> <constructor-arg value="audi"/> </bean> </beans> 测试: package com.stuspring.factory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Created by bee on 17/4/28. */ public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-factory.xml"); Car car1 = (Car) ctx.getBean("car1"); System.out.println(car1); } } 运行结果: Car{brand='Audi', price=300000.0} 11.2 实例工厂方法 package com.stuspring.factory; import java.util.HashMap; import java.util.Map; /** * Created by bee on 17/4/28. */ public class InstanceCarFactory { private Map<String,Car> cars=null; public InstanceCarFactory(){ cars=new HashMap<String, Car>(); cars.put("audi",new Car("Audi",300000)); cars.put("ford",new Car("Ford",400000)); } public Car getCar(String carname){ return cars.get(carname); } } 配置Bean: <!--配置工厂实例--> <bean id="instanceFactory" class="com.stuspring.factory.InstanceCarFactory"/> <bean id="car2" factory-bean="instanceFactory" factory-method="getCar"> <constructor-arg value="ford"/> </bean> 测试: Car car2= (Car) ctx.getBean("car2"); System.out.println(car2); 打印结果: Car{brand='Ford', price=400000.0} 十二、FactoryBean配置Bean Spring中有2种类型的Bean,一种是普通Bean,另一种是工厂Bean,即FactoryBean。工厂Bean跟普通Bean不同,其返回的对象不是指定类的一个实例,而是工厂Bean的getObjective方法所返回的对象。 创建一个CarFactoryBean类,继承FactoryBean接口: package com.stuspring.factorybean; import org.springframework.beans.factory.FactoryBean; /** * Created by bee on 17/4/28. */ public class CarFactoryBean implements FactoryBean<Car>{ private String brand; public void setBrand(String brand) { this.brand = brand; } public Car getObject() throws Exception { return new Car(brand,8000000); } public Class<?> getObjectType() { return Car.class; } public boolean isSingleton() { return false; } } 配置Bean: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="car1" class="com.stuspring.factorybean.CarFactoryBean"> <property name="brand" value="Ford"/> </bean> <bean id="car2" class="com.stuspring.factorybean.CarFactoryBean"> <property name="brand" value="BMW"/> </bean> </beans> 测试: package com.stuspring.factorybean; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Created by bee on 17/4/28. */ public class Main { public static void main(String[] args) { ApplicationContext ctx=new ClassPathXmlApplicationContext("beans-beanfactory.xml"); Car car1= (Car) ctx.getBean("car1"); System.out.println(car1); Car car1Copy= (Car) ctx.getBean("car1"); System.out.println(car1Copy); System.out.println(car1==car1Copy); Car car2= (Car) ctx.getBean("car2"); System.out.println(car2); } } 打印结果: Car{brand='Ford', price=8000000.0} Car{brand='Ford', price=8000000.0} false Car{brand='BMW', price=8000000.0} 13.通过注解配置Bean 13.1基于注解配置Bean 13.2基于注解来装配Bean的属性
十、Bean生命周期 10.1 Bean的生命周期 Spring IOC容器可以管理Bean的生命周期,也允许在Bean生命周期的特定点执行定制的任务。 Spring IOC容器对Bean的生命周期进行管理的过程如下: 通过构造器或者工厂方法创建Bean实例 为Bean的属性设置值和对其他Bean的引用 调用Bean的初始化方法 使用Bean 容器关闭时,调用Bean的销毁方法 在Bean的声明里设置init-method和destory-method属性,为Bean指定初始化和销毁方法。 测试,创建一个Car类: package com.stuspring.cycle; /** * Created by bee on 17/4/26. */ public class Car { public Car() { System.out.println("Constructor...."); } private String brand; public void setBrand(String brand) { System.out.println("set Brand..."); this.brand = brand; } public void initMethod(){ System.out.println("init method...."); } public void destoryMethod(){ System.out.println("destory method..."); } } 创建beans-cycle.xml并配置一个bean: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="car" class="com.stuspring.cycle.Car" p:brand="Audi" init-method="initMethod" destroy-method="destoryMethod"/> </bean 测试: package com.stuspring.cycle; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Created by bee on 17/4/26. */ public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-cycle.xml"); Car car = (Car) ctx.getBean("car"); System.out.println(car); ((ConfigurableApplicationContext) ctx).close(); } } 运行结果: Constructor.... set Brand... init method.... com.stuspring.cycle.Car@604ed9f0 destory method... 10.2 Bean的后置处理器 Bean后置处理器允许在调用初始化方法前后对Bean进行额外的处理,Bean后置处理器会对IOC容器中的所有Bean实例逐一处理,要想针对特定Bean进行处理可以根据beanName参数进行判断。Bean后置处理器的典型应用是检查Bean属性的正确性或者根据特定的标准更改Bean的属性。 使用Bean后置处理器需要实现BeanPostProcessor接口,在Bean初始化方法被调用前后,Spring将把每个Bean实例分别传递给Bean接口的Object postProcessBeforeInitialization(Object bean, String beanName)和Object postProcessAfterInitialization(Object bean, String beanName)方法。 下面是测试例子: 新建一个Car类: package com.stuspring.cycle; /** * Created by bee on 17/4/26. */ public class Car { private String brand; public Car() { System.out.println("Constructor...."); } public void setBrand(String brand) { System.out.println("set Brand..."); this.brand = brand; } public void initMethod() { System.out.println("init method...."); } public void destoryMethod() { System.out.println("destory method..."); } public String getBrand() { return brand; } @Override public String toString() { return "Car{" + "brand='" + brand + '\'' + '}'; } } 创建BeanPostProcessor的实现类: package com.stuspring.cycle; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; /** * Created by bee on 17/4/27. */ public class MyBeanPostProcessor implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessBeforeInitialization: " + bean + "," + beanName); if ("car".equals(beanName)){ Car car=new Car(); car.setBrand("BaoMa"); return car; } return bean; } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessAfterInitialization: " + bean + "," + beanName); return bean; } } 创建Bean: <bean id="car" class="com.stuspring.cycle.Car" p:brand="Audi" init-method="initMethod" destroy-method="destoryMethod"/> <bean class="com.stuspring.cycle.MyBeanPostProcessor"/> Main函数中测试: package com.stuspring.cycle; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Created by bee on 17/4/26. */ public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-cycle.xml"); Car car = (Car) ctx.getBean("car"); System.out.println(car.toString()); System.out.println(car.getBrand()); ((ConfigurableApplicationContext) ctx).close(); } } 运行结果: Constructor.... set Brand... postProcessBeforeInitialization: Car{brand='Audi'},car Constructor.... set Brand... init method.... postProcessAfterInitialization: Car{brand='BaoMa'},car Car{brand='BaoMa'} BaoMa destory method... 添加Bean的后置处理器以后Bean的生命周期如下; 通过构造器或者工厂方法创建Bean实例 为Bean的属性设置值和对其他Bean的引用 将Bean实例传递给Bean后置处理器的postProcessBeforeInitialization方法 调用Bean的初始化方法 将Bean实例传递给Bean后置处理器的postProcessAfterInitialization方法 使用Bean 容器关闭时,调用Bean的销毁方法
九、SpEL SpEL是Spring表达式语言(Spring Expression Language)的简称,是一个支持运行查询和操作对象图的强大的表达式语言。 SpEL的语法类似EL,SpEL使用#{}作为界定符,所有在大括号内的字符都将被认为是SpEL。SpEL为bean的属性进行动态赋值提供便利。 通过SpEL可以实现以下内容: 通过bean的id对bean进行引用 调用方法以及引用对象中的属性 计算表达式的值 正则表达式的匹配 9.1字面量 整数: <property name="count" value="#{5}"/> 小数: <property name="price" value="#{5.2}"/> 科学计数法: <property name="count" value="#{1e4}"/> String类型: <property name="name" value="#{'Tom'}"/> Boolean: <property name="enabled" value="#{false}"/> 9.2 bean之间的引用 可以使用SpEL代替ref来引用对象、对象的属性、对象的方法。 <!--bean之间的引用--> <property name="address" value="#{person1.address}"/> <!--引用bean的属性--> <property name="city" value="#{address.city}"/> <!--调用bean的方法--> <property name="street" value="#{address.getStreet()}"/> <!--调用bean方法的连缀--> <property name="street" value="#{address.getStreet().toUpperCase()}"/> 9.3 算术运算符 + <!--算术运算--> <property name="count" value="#{counter.total+42}"/> <!--拼接字符串--> <property name="count" value="#{user.firstName+user.lastName}"/> - <property name="count" value="#{counter.total-5}"/> * <constructor-arg name="circumference" value="#{2*T(java.lang.Math).PI*10}"/> / <property name="count" value="#{counter.total/counter.count}"/> % <property name="count" value="#{counter.total%counter.count}"/> ^ <constructor-arg name="area" value="#{T(java.lang.Math).PI*circe.radius^2}"/> 通过T()调用一个类的静态方法,它将返回一个Class Object对象。 9.4 比较运算符 比较运算符有以下这些: 运算符 含义 < 小于 > 大于 <= 小于等于 >= 大于等于 lt 小于 gt 大于 le 小于等于 ge 大于等于 9.5 逻辑运算符 and、or <property name="largeCircle" value="#{shape.kind=='circle' and shape.perimeter gt 100000}"/> <property name="largeCircle" value="#{shape.kind=='circle' or shape.perimeter gt 100000}"/> not、! <property name="largeCircle" value="#{ not product.available}"/> <property name="largeCircle" value="#{ !product.available}"/> if-else运算符 <property name="info" value="#{car.price>300000?'金领':'白领'}" 正则表达式: <property name="email" value="#{admin.email matches '^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$'}"
五、Bean的自动装配 Spring IOC容器可以自动装配Bean,需要在bean的autowire属性里指定自动装配的模式。 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="address" class="com.stuspring.autowire.Address" p:city="BeiJing" p:street="HuiLongGuan"> </bean> <bean id="car" class="com.stuspring.autowire.Car" p:brand="AuDi" p:price="400000"> </bean> <bean id="car2" class="com.stuspring.autowire.Car" p:brand="AuDi" p:price="400000"> </bean> <bean id="person1" class="com.stuspring.autowire.Person" p:name="Tom" p:address-ref="address" p:car-ref="car"/> <!--使用autowire属性指定自动装配的方式 byName 根据bean的id和bean的setter风格属性进行自动装配,如果有匹配的,则自动装配。 byType 根据类型自动装配。car和car2都满足,byType会报错。 确定:不够灵活,实际开发中很少使用自动装配。 --> <bean id="person2" class="com.stuspring.autowire.Person" p:name="Lily" autowire="byName"> </bean> </beans> 六、Bean之间的关系 6.1 Bean的继承关系 一个Address类,有city和Street 2个属性。 package com.stuspring.relation; /** * Created by bee on 17/4/25. */ public class Address { private String city; private String street; //省略setter、getter和toString方法 } 配置2个Bean: <bean id="address" class="com.stuspring.relation.Address" p:city="BeiJing" p:street="WuDaoKou"/> <bean id="address2" class="com.stuspring.relation.Address" p:city="BeiJing" p:street="XiZhiMen"/> address2和address除了street属性不一样,其他都相同,可以使用bean的集成属性来简化配置: <bean id="address" class="com.stuspring.relation.Address" p:city="BeiJing" p:street="WuDaoKou"/> <bean id="address2" p:street="XiZhiMen" parent="address"/> Spring允许继承bean的配置,被继承的bean称为父bean,继承父bean的bean称为子bean。 子bean从父bean中继承配置,包括bean的属性配置 子bean也可以覆盖父bean继承过来的配置 父bean可以作为配置模板,也可以作为bean的实例。若只想把父bean作为模板,可以设置<bean>的abstract属性为true,这样spring就不会实例化这个bean 可以忽略父bean的class属性,让子bean指定自己的类,共享相同的配置,此时abstract属性必须为true。 抽象bean例子: <bean id="address" class="com.stuspring.autowire.Address" p:city="BeiJing" abstract="true"/> <bean id="address2" p:street="XiZhiMen" parent="address"/> <!-- 覆盖父bean的配置--> <bean id="address3" p:city="NanJing" p:street="DaFengChang" parent="address"/> 忽略父bean的class属性: <bean id="address" p:city="BeiJing^" p:street="XiZhiMen" abstract="true"/> <bean id="address2" class="com.stuspring.autowire.Address" parent="address"/> <!-- 覆盖父bean的配置--> <bean id="address3" class="com.stuspring.autowire.Address" p:city="NanJing" p:street="DaFengChang" parent="address"/> 6.2 Bean的依赖关系 Spring允许用户通过depends-on属性设定bean前置的依赖bean,前置依赖的bean会在本bean实例化之前创建好。 如果前置依赖多个bean,可以通过逗号、空格的方式配置bean的名称。 <bean id ="car" class="com.stuspring.autowire.Car" p:brand="Ford" p:price="200000"/> <bean id="person" class="com.stuspring.autowire.Person" depends-on="car"/> bean person依赖car,car不存在person无法实例化。 七、Bean作用域 定义一个bean: <bean id="car" class="com.stuspring.autowire.Car"/> 测试: ApplicationContext ctx=new ClassPathXmlApplicationContext ("beans-scope.xml"); Car car1= (Car) ctx.getBean("car"); Car car2= (Car) ctx.getBean("car"); System.out.println(car1==car2); 打印结果: true car1和car2是同一个对象。 把作用域改为prototype: <bean id="car" class="com.stuspring.autowire.Car" scope="prototype"/> 再次比较car1和car2,运行结果: false 作用域改成prototype后,car1和car2不再是同一个对象。 在Spring中,可以在<Bean>元素的scope属性里设置Bean的作用域,默认情况下Spring只为每个在IOC容器中声明的Bean创建唯一一个实例,整个IOC容器范围内都能共享该实例。 类别 说明 singleton 在Spring的IOC容器中仅存在一个Bean实例,以单例的方式存在 prototype 每次调用getBean()都会返回一个新的实例 request 每次Http请求都会创建一个Bean,该作用域仅适用于WebApplicationContext属性 session 同一个HTTP SESSION共享一个BEAN,不同的HTTP SESSION使用不同的BEAN。该作用域仅适用于WebApplicationContext属性。 八、使用外部属性文件 配置文件里配置Bean时,有时需要在Bean的配置里混入系统部署的细节信息,比如文件路径、数据源配置信息等,这些部署细节需要和Bean配置相分离。 Spring提供了一个PropertyPlaceholderConfigurer的BeanFactory后置处理器,这个处理器允许用户将Bean配置的部分内容外移到属性文件中。在Bean配置文件里使用形式为${var}的变量,PropertyPlaceholderConfigurer从属性文件里加载属性并使用这些属性来替换变量。 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="root"/> <property name="password" value="123456"/> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql:///blog"/> </bean> 测试: package com.stuspring.properties; import javax.sql.DataSource; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Created by bee on 17/4/25. */ public class Main { public static void main(String[] args) { ApplicationContext ctx=new ClassPathXmlApplicationContext ("beans-properties.xml"); DataSource dataSources= (DataSource) ctx.getBean("dataSource"); System.out.println(dataSources); } } resource目录下新建db.properties,写入连接数据库的配置信息: jdbc.username=root jdbc.password=123456 jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull 在Spring Bean的配置文件中引入context命名空间。重新配置: <bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="driverClass" value="${jdbc.driverClassName}"/> <property name="jdbcUrl" value="${jdbc.url}"/> </bean> 测试连接是否成功: ApplicationContext ctx=new ClassPathXmlApplicationContext ("beans-properties.xml"); DataSource dataSource= (DataSource) ctx.getBean("dataSource"); DataSource dataSource2= (DataSource) ctx.getBean("dataSource2"); System.out.println(dataSource); System.out.println(dataSource2); Connection conn=dataSource.getConnection(); System.out.println(conn);
一、认识Spring 1.1 Spring简介 Spring是一个开源框架,为简化企业级应用而生,是一个IOC(DI)和AOP容器框架。 IOC和AOP不太好理解,这里有一篇文章值得参考 IOC和AOP的一些基本概念 IOC(Inversion of Control): 反转资源获取的方向,传统的资源查找方式要求组件向容器发起请求查找资源,容器适时返回资源作为回应。应用IOC之后,容器主动地讲资源推送给它所管理的组件,组件要做的就是选择一种合适的方式接受资源,这种行为也被称为查找的被动形式。 DI(Dependency Injection): IOC的另一种表述方式,即组件以一些预先定义好的方式(比如setter方法)接受来自容器的资源注入。相对于IOC而言,DI在表述上更直接。 1.2 Spring特点 轻量级:Spring是非入侵的,基于Spring开发的应用中的对象可以不依赖Spring的API 依赖注入 面向切面编程 容器:Spring是一个容器,它包含并且管理应用对象的生命周期 框架:Spring使用简单的组件配置组合成一个复杂的应用,在Spring中可以使用XML和Java注解组合这些对象 一站式:Spring是一个一站式的框架,在IOC和AOP的基础上可以整合企业应用的开源框架和第三方类库。使用Spring可以把Java EE中的知识点都包括起来,Spring等同于Java EE。 1.3 Spring模块 Spring框架包含了大约20个模块,如下图所示。这些模块再细分成Core Container, Data Access/Integration, Web, AOP (Aspect Oriented Programming), Instrumentation, Messaging, and Test。 二、Spring HelloWorld 2.1 搭建开发环境 在IDEA中新建Maven工程,选择maven-archetype-webapp。 在pom.xml中添加spring的maven依赖: <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.8.RELEASE</version> </dependency> 导包完成以后,在External Libraries目录下可以看到新导入的五个jar包:spring-aop、spring-beans、spring-contex、spring-core、spring-expression. 在src/main目录下新建Directory,设置资源文件夹。 在java目录下新建一个包,再增加一个HelloWorld类。HelloWorld类中设置了一个私有域name,一个setName方法和一个sayHello方法。 package com.stuspring; /** * Created by bee on 17/4/22. */ public class HelloWorld { private String name; public void setName(String name) { this.name = name; } public void sayHello(){ System.out.println("Hello:"+name); } } 在新增一个Main类做测试,创建一个HelloWorld对象,设置name,调用sayHello方法: package com.stuspring; /** * Created by bee on 17/4/22. */ public class Main { public static void main(String[] args) { HelloWorld hw1=new HelloWorld(); hw1.setName("Doraemon"); hw1.sayHello(); } } 运行结果: Hello:Doraemon 上面是传统的创建对象的方法,下面通过Spring来创建HelloWorld的对象。 2.2 使用Spring管理对象 首先创建Spring的配置文件,点击resources文件夹,右键->New->XML Configuration File->Spring Config,如下如所示。文件名为beans, 点击OK以后,会在resources目录下生成beans.xml。 在beans.xml定义一个新的HelloWorld对象,方式如下: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="hw2" class="com.stuspring.HelloWorld"> <property name="name" value="Timmy" /> </bean> </beans> 在Main类中获取id为hw2的HelloWorld对象: package com.stuspring; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Created by bee on 17/4/22. */ public class Main { public static void main(String[] args) { //1.创建一个HelloWorld对象 HelloWorld hw1=new HelloWorld(); //2.对name属性赋值 hw1.setName("Doraemon"); //3.调用sayHello方法 hw1.sayHello(); //1.创建IOC容器对象 ApplicationContext ctx=new ClassPathXmlApplicationContext("beans.xml"); //2.从IOC容器中获取对象 HelloWorld hw2= (HelloWorld) ctx.getBean("hw2"); //3.调用sayHello方法 hw2.sayHello(); } } 再次运行,打印结果: Hello:Doraemon Hello:Timmy ClassPathXmlApplicationContext用于加载CLASSPATH下的配置文件,传入参数问文件名,这里就是加载resources目录下的beans.xml,通过ApplicationContext对象的getBean方法获取对象,传入参数为配置文件中bean的id。 在创建IOC容器对象的时候,所有Bean实例都已经初始化完成。可以通过一个简单的方法进行测试,在HelloWorld类的setName方法中打印一行提示信息,增加一个无参构造方法: package com.stuspring; /** * Created by bee on 17/4/22. */ public class HelloWorld { private String name; public void setName(String name) { System.out.println("setName:"+name); this.name = name; } public void sayHello(){ System.out.println("Hello:"+name); } public HelloWorld(){ System.out.println("HelloWorld的无参构造方法!"); } } 在Main类的Main函数中只创建一个ApplicationContext对象: package com.stuspring; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Created by bee on 17/4/22. */ public class Main { public static void main(String[] args) { ApplicationContext ctx=new ClassPathXmlApplicationContext("beans.xml"); } } 运行,打印结果如下: HelloWorld的无参构造方法! setName:Timmy 三、Bean配置细节 Bean的配置形式: 基于XML文件的方式 基于注解的方式 Bean的配置方式: 通过全类名(反射) 通过工厂方法(静态工厂方法、实例工厂方法) FactoryBean 依赖注入的方式: 属性注入 构造器注入 Spring容器: BeanFactory:IOC容器的基本实现,是Spring框架的基础设施,面向Spring本身 ApplicationContext:提供了更高级的特性,是BeanFactory的子接口,面向Spring框架的开发者,几乎所有的应用场合都直接使用ApplicationContext而非底层的BeanFactory。 3.1 构造器注入 新建一个Car类,四个属性、2个重载的构造方法、一个toString()方法。 package com.stuspring; /** * Created by bee on 17/4/23. */ public class Car { private String brand; private String corp; private double price; private int maxSpeed; public Car(String brand, String corp, int maxSpeed) { this.brand = brand; this.corp = corp; this.maxSpeed = maxSpeed; } public Car(String brand, String corp, double price) { this.brand = brand; this.corp = corp; this.price = price; } @Override public String toString() { return "Car{" + "brand='" + brand + '\'' + ", corp='" + corp + '\'' + ", price=" + price + ", maxSpeed=" + maxSpeed + '}'; } } 配置bean: <!--使用构造器注入属性可以指定参数的位置和参数的类型以区分重载的构造器--> <bean id="car1" class="com.stuspring.Car"> <constructor-arg name="brand" value="Ford" index="0"/> <constructor-arg name="corp" value="ShangHai" index="1"/> <constructor-arg name="price" value="100000" index="2" /> </bean> <!--属性值可以通过Value子节点进行配置,特殊字符可以使用<![CDATA[]]>包裹--> <bean id="car2" class="com.stuspring.Car"> <constructor-arg name="brand" value="Audi"/> <constructor-arg name="corp" > <value><![CDATA[<Shanghai>]]></value> </constructor-arg> <constructor-arg name="maxSpeed" value="280"/> </bean> <bean id="car3" class="com.stuspring.Car"> <constructor-arg value="DaZhong"/> <constructor-arg value="ShangHai"/> <constructor-arg type="int"> <value>300</value> </constructor-arg> </bean> 3.2 Bean之间的引用 应用程序之间的Bean经常需要相互协作以完成应用程序的功能,要使Bean之间能相互访问,就必须在Bean配置文件中指定对Bean的引用。可以通过ref属性指定Bean的引用。 新建一个Person类,有姓名、年龄、拥有的汽车三个属性: package com.stuspring; /** * Created by bee on 17/4/24. */ public class Person { private String name; private int age; private Car car; //省略setter、getter和toString()方法 public Person() { } public Person(String name, int age, Car car) { this.name = name; this.age = age; this.car = car; } } 在beans.xml中创建Person对象: <bean id="person1" class="com.stuspring.Person"> <property name="name" value="LiHua"/> <property name="age" value="28"/> <property name="car" ref="car1"/> </bean> <bean id="person2" class="com.stuspring.Person"> <property name="name" value="ZhaoHua"/> <property name="age" value="29"/> <property name="car"> <ref bean="car2"/> </property> </bean> <!--内部bean,不能被外部引用,只能在内部使用--> <bean id="person3" class="com.stuspring.Person"> <property name="name" value="LiSi"/> <property name="age" value="31"/> <property name="car"> <bean class="com.stuspring.Car"> <constructor-arg name="brand" value="Ford"/> <constructor-arg name="corp" value="ShangHai"/> <constructor-arg name="price" value="20000"/> </bean> </property> </bean> <bean id="person4" class="com.stuspring.Person"> <constructor-arg name="name" value="Jerry"/> <constructor-arg name="age" value="25"/> <constructor-arg name="car" ref="car3"/> </bean> <!--测试null值--> <bean id="person5" class="com.stuspring.Person"> <constructor-arg name="name" value="John"/> <constructor-arg name="age" value="31"/> <constructor-arg name="car"><null/></constructor-arg> </bean> <!--级联属性赋值,需要对相应的属性提供setter方法。注意:属性需要先初始化以后再为级联属性赋值--> <bean id="person6" class="com.stuspring.Person"> <constructor-arg name="name" value="Linda"/> <constructor-arg name="age" value="33"/> <constructor-arg name="car" ref="car1"/> <property name="car.maxSpeed" value="240"/> </bean> 3.2 Bean的集合属性 集合属性赋值:在xml中可以通过内置的xml标签,例如<List>、<set>、<map>来配置集合属性。 3.2.1 Set属性赋值 一个Person有多个汽车,改进Person类: package com.stuspring.collections; import com.stuspring.Car; import java.util.List; /** * Created by bee on 17/4/24. */ public class Person { private String name; private int age; private List<Car> cars; public String getName() { return name; } public int getAge() { return age; } public List<Car> getCars() { return cars; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public void setCars(List<Car> cars) { this.cars = cars; } public Person() { } public Person(String name, int age, List<Car> cars) { this.name = name; this.age = age; this.cars = cars; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", cars=" + cars + '}'; } } 在beans.xml中创建bean: <bean id="person7" class="com.stuspring.collections.Person"> <property name="name" value="Mike"/> <property name="age" value="29"/> <property name="cars"> <list> <ref bean="car1"/> <ref bean="car2"/> <bean class="com.stuspring.Car"> <constructor-arg value="DaZhong"/> <constructor-arg value="ShangHai"/> <constructor-arg type="int"> <value>300</value> </constructor-arg> </bean> </list> </property> </bean> 把集合独立出来,可以共享被多个bean引用(需要导入util命名空间): <!--导入util命名空间 --> <util:list id="cars"> <ref bean="car1"/> <ref bean="car2"/> </util:list> <bean id="person9" class="com.stuspring.collections.Person"> <property name="name" value="NewPerson"/> <property name="age" value="29"/> <property name="cars" ref="cars"></property> </bean> 3.2.2 Map属性赋值 package com.stuspring.collections; import com.stuspring.Car; import java.util.Map; /** * Created by bee on 17/4/24. */ public class NewPerson { private String name; private int age; private Map<String,Car> cars; //省略setter、getter、无参构造方法、有参构造方法和toString方法 } 配置bean: <bean id="person8" class="com.stuspring.collections.NewPerson"> <property name="name" value="Tina"/> <property name="age" value="26"/> <property name="cars"> <map> <entry key="AA" value-ref="car1"/> <entry key="BB" value-ref="car2"/> </map> </property> </bean> 3.2.3 Properties属性赋值 新建一个DataSource类模拟数据库连接: package com.stuspring.collections; import java.util.Properties; /** * Created by bee on 17/4/24. */ public class DataSource { private Properties properties; public void setProperties(Properties properties) { this.properties = properties; } public Properties getProperties() { return properties; } @Override public String toString() { return "DataSource{" + "properties=" + properties + '}'; } } 在beans.xml中新建bean: <!-- 配置properties--> <bean id="dataSource" class="com.stuspring.collections.DataSource"> <property name="properties"> <props> <prop key="user">root</prop> <prop key="password">123456</prop> <prop key="jdbcUrl">jdbc:mysql:///test</prop> <prop key="driverClass">com.mysql.jdbc.Driver</prop> </props> </property> </bean> 四、命名空间 Spring整合了各种工具,为了简化操作,spring提供了对各种工具的xml scheme 的配置方式。各种命名空间以及相应的定义文件地址:http://www.springframework.org/schema/ 导入util和p命名空间,xsi:schemaLocation 指定了用于解析和校验xml的定义文件(xsd)的位置。 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
今天发现了一个好用的Linux工具-Tmux,用于在终端中管理多个会话窗口。捯饬了一会效果如下: 图一:在一个窗口中启动ELasticsearch、head插件、Kibana 图2:一个窗口中登录四台Linux服务器 下面简介一下安装步骤和窗口分隔的命令。 一、安装 Ubuntu安装命令: sudo apt-get install tmux Mac安装命令: brew install tmux 安装完成以后在终端中输入命令: tmux 回车,启动了第一个tmux会话,下面会有一行提示信息。 二、窗口分割 左右分隔,先按Ctrl+b,再输入%: Ctrl+b % 对右侧垂直切分: Ctrl+B " 从3回到2: Ctrl+b ↑ 从2回到1: Ctrl+b ← 我这里只写了入门的简单操作,更多参考资料: 1.A Quick and Easy Guide to tmux 2.Tmux 速成教程:技巧和调整
一、Filebeat简介 Beats是Elastic Stack技术栈中轻量级的日志采集器,Beats家族包括以下五个成员: Filebeat:轻量级的日志采集器,可用于收集文件数据。 Metricbeat:5.0版本之前名为Topbeat,搜集系统、进程和文件系统级别的 CPU 和内存使用情况等数据。 Packetbeat:收集网络流数据,可以实时监控系统应用和服务,可以将延迟时间、错误、响应时间、SLA性能等信息发送到Logstash或Elasticsearch。 Winlogbeat:搜集Windows事件日志数据。 Heartbeat:监控服务器运行状态。 二、Filebeat和Logstash ELK架构中使用Logstash收集、解析日志,但是Logstash对内存、cpu、io等资源消耗比较高。相比 Logstash,Beats所占系统的CPU和内存几乎可以忽略不计。 Elasticsearch、Logstash、Kibana组合成为ELK Stack,Beats+ELK Stack=Elastic Stack 三、Filebeat工作原理 Filebeat是使用GO语言开发,工作原理如下:当Filebeat启动时,它会启动一个或者多个prospector监控日志路径或日志文件,每个日志文件会有一个对应的harvester,harvester按行读取日志内容并转发至后台程序。Filebeat维护一个记录文件读取信息的注册文件,记录每个harvester最后读取位置的偏移量。 四、Filebeat配置 下面是一个简单的Filebeat配置,采集2个文件夹下的日志并转发至Logstash。 filebeat: prospectors: - paths: - /dir1/access_log.* input_type: log document_type: dir1_log - paths: - /dir2/ofbiz.log.* input_type: log document_type: dir2_log output: logstash: hosts: ["10.90.4.9:5044"] 在Logstash中根据 document_type定义解析日志的正则并输出到ELasticsearch集群。 input { beats{ host => "192.2.11.145" port => 5044 } } filter { if[type]=="dir1_log"{ grok { match => { "message" => "%{COMBINEDAPACHELOG}"} } } else if ([type]=="dir2_log") { grok { match => { "message" => "%{TIMESTAMP_ISO8601:time}\s*%{NUMBER:logtime} \[\s*%{JAVAFILE:class}\:%{NUMBER:lineNumber}\s*\:%{LOGLEVEL:level}\s*\]\s*(?<info>([\s\S]*))"} } } } output { elasticsearch { hosts => ["10.90.4.9","10.90.4.8","10.90.4.7"] } } 五、参考资料 https://www.elastic.co/guide/en/beats/filebeat/1.3/filebeat-overview.html
版权声明:本文为博主原创文章,地址:http://blog.csdn.net/napoay,转载请留言。 总结Jackcard相似度和余弦相似度。 一、集合的Jackcard相似度 1.1Jackcard相似度 Jaccard相似指数用来度量两个集合之间的相似性,它被定义为两个集合交集的元素个数除以并集的元素个数。 数学公式描述: J(A,B)=|A∩B||A∪B| 这个看似简单的算法有很大的用处,比如: 抄袭文档 高明的抄袭者为了掩盖自己抄袭的事实,会选择性的抄袭文档中的一些段落,或者对词语或原始文本中的句序进行改变。jackcard相似度计算适合从字面上进行计算,如果是更高级的抄袭改变了语义jackcard相似度计算就无能为力了 镜像页面 多个主机上建立镜像以共享加载内容,同一份内容有多个副本,这种情况实现jackcard相似度计算十分有效。 同源新闻稿 一个记者撰写了一份新闻稿件投稿多家媒体,稿件经过少量修改后发布,使用这些同源新闻稿可以用jackcard相似度算法来检测出来 1.2 Java实现 import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Created by bee on 17/4/12. */ public class JackcardSim { public static double calJackcardSim(Set<String> s1, Set<String> s2) { Set<String> all = new HashSet<>(); all.addAll(s1); all.addAll(s2); System.out.println(all); Set<String> both = new HashSet<>(); both.addAll(s1); both.retainAll(s2); System.out.println(both); return (double) both.size() / all.size(); } public static void main(String[] args) { Set<String> s1 = new HashSet<String>(); s1.add("互联网"); s1.add("金融"); s1.add("房产"); s1.add("融资"); s1.add("科技"); Set<String> s2 = new HashSet<String>(); s2.add("互联网"); s2.add("开源"); s2.add("人工智能"); s2.add("软件"); s2.add("科技"); System.out.println(calJackcardSim(s1, s2)); } } 运行结果 [科技, 房产, 软件, 融资, 人工智能, 互联网, 开源, 金融] [科技, 互联网] 0.25 二、向量空间模型 2.1简介 向量空间模型是一个把文本文件表示为标识符(比如索引)向量的代数模型。它应用于信息过滤、信息检索、索引以及相关排序。 文档和查询都用向量来表示。 dj=(w1,j,w2,j,...,wt,j)q=(w1,q,w2,q,...,wt,q) cosθ=d2⋅q∥d2∥∥∥q∥∥=∑i=1Nwi,jwi,q∑i=1Nw2i,j‾‾‾‾‾‾√∑i=1Nw2i,q‾‾‾‾‾‾√ 2.2、java实现 import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Created by bee on 17/4/10. */ public class Vsm { public static double calCosSim(Map<String, Double> v1, Map<String, Double> v2) { double sclar = 0.0,norm1=0.0,norm2=0.0,similarity=0.0; Set<String> v1Keys = v1.keySet(); Set<String> v2Keys = v2.keySet(); Set<String> both= new HashSet<>(); both.addAll(v1Keys); both.retainAll(v2Keys); System.out.println(both); for (String str1 : both) { sclar += v1.get(str1) * v2.get(str1); } for (String str1:v1.keySet()){ norm1+=Math.pow(v1.get(str1),2); } for (String str2:v2.keySet()){ norm2+=Math.pow(v2.get(str2),2); } similarity=sclar/Math.sqrt(norm1*norm2); System.out.println("sclar:"+sclar); System.out.println("norm1:"+norm1); System.out.println("norm2:"+norm2); System.out.println("similarity:"+similarity); return similarity; } public static void main(String[] args) { Map<String, Double> m1 = new HashMap<>(); m1.put("Hello", 1.0); m1.put("css", 2.0); m1.put("Lucene", 3.0); Map<String, Double> m2 = new HashMap<>(); m2.put("Hello", 1.0); m2.put("Word", 2.0); m2.put("Hadoop", 3.0); m2.put("java", 4.0); m2.put("html", 1.0); m2.put("css", 2.0); calCosSim(m1, m2); } } 运行结果: [css, Hello] sclar:5.0 norm1:14.0 norm2:35.0 similarity:0.22587697572631282 三、参考资料 https://zh.wikipedia.org/wiki/%E5%90%91%E9%87%8F%E7%A9%BA%E9%96%93%E6%A8%A1%E5%9E%8B http://baike.baidu.com/link?url=enqtEW1bEXe0iZvil1MBk8m2upnfmN118p4cgjNpYdoJYe2l-FC5_s_yYQAq_3GUtiQW0jgwfMMBBxM0U16JiRKeFToPQ0fj058H7P8mHlZ5RV7rERN9Je7jdrYdA3gI7SRMUNTDnNyGoGgBJZN7sq
如果 你正在学习如何使用Linux操作系统 你正在学习ruby 你正在学习Elasticsearch 你正在学习shell编程 你正在学习Hadoop, 需要搭建Hadoop的完全分布式集群 你正在学习Spark,需要搭建Spark的完全分布式集群 …… 那么 我建议你抽出来半天的时间,下载好软件安装包,参考本博客,动手实践一把如何使用VirtualBox安装CentOS 7虚拟机。 一、下载软件 软件名称 版本 下载地址 virtualbox 5.0.24 http://rj.baidu.com/soft/detail/15321.html https://www.virtualbox.org/wiki/Downloads CentOS 7 CentOS-7-x86_64-Everything-1511.iso http://mirrors.163.com/centos/ http://ftp.riken.jp/Linux/centos/ Xmanager Enterprise(包含xshell、xftp等) Xmanager Enterprise 4 http://download.csdn.net/detail/napoay/9807691 Virtualbox 5.0.24、Xmanager Enterprise 4下载完成以后安装即可,其中CentOS-7-x86_64-Everything-1511.iso大约有7个G,下载好备用。 二、VirtualBox安装CentOS 7虚拟机 启动VirtualBox,这里已经创建好2台虚拟机,下面以第三台的安装作为演示。 点击新建,虚拟机名称设置spark-slave2,类型为Linux,版本为Other Linux 64bit。 下一步,设置分配给虚拟机的内存大小,我这里设置为1G。 下一步,虚拟硬盘选择现在创建虚拟硬盘。 点击创建,虚拟硬盘文件类型选择VDI. 下一步,选择固定大小。 下一步,选择虚拟机文件的存储位置和磁盘大小,这里设置为20G. 点击创建,虚拟机就创建完成了。 启动新创建的虚拟机,第一次运行会弹出一个选择镜像文件的窗口,启动盘中选择CentOS-7-x86_64-Everything-1511.iso。 点击启动,选择install centos 7. 语言选择English,United States 点击System 下面的installation destination,安装位置选择默认的即可,点击DONE。 设置完安装位置之后,选择System下的NETWORK &HOST NAME。 Ethenet(enpOs3),开启。 下一步,设置事情,点击地图上上海的位置。 下一步,设置root密码,重复输入2次。 等待安装完成: 点击reboot,启动虚拟机。输入root用户和root密码,登录到虚拟机。 三、关闭Windows防火墙 打开windows的控制面板,选择系统和安全,选择Windows防火墙,关闭Windows防火墙。 四、网络设置 在Windows上安装虚拟机,Windows被成为宿主机,下面介绍宿主机和虚拟机通信的网络设置。 4.1 安装net-tools 使用ping命令测试虚拟机网络是否是畅通的: ping www.baidu.com 使用查看ip的ifconfig命令,提示command not found,这是因为没有安装net-tools。运行安装net-tools的命令: yum -y install net-tools 执行ifconfig命令查看网络。 4.2 绑定静态ip 切换到 /etc/sysconfig/network-scripts/ [root@localhost ~]# cd /etc/sysconfig/network-scripts/ [root@localhost network-scripts]# ls ifcfg-enp0s3 ifdown-ppp ifup-ib ifup-Team ifcfg-lo ifdown-routes ifup-ippp ifup-TeamPort ifdown ifdown-sit ifup-ipv6 ifup-tunnel ifdown-bnep ifdown-Team ifup-isdn ifup-wireless ifdown-eth ifdown-TeamPort ifup-plip init.ipv6-global ifdown-ib ifdown-tunnel ifup-plusb network-functions ifdown-ippp ifup ifup-post network-functions-ipv6 ifdown-ipv6 ifup-aliases ifup-ppp ifdown-isdn ifup-bnep ifup-routes ifdown-post ifup-eth ifup-sit 编辑ifcfg-enp0s3: vi ifcfg-enp0s3 ifcfg-enp0s3中的配置如下: TYPE="Ethernet" BOOTPROTO="static" DEFROUTE="yes" PEERDNS="yes" PEERROUTES="yes" IPV4_FAILURE_FATAL="no" IPV6INIT="yes" IPV6_AUTOCONF="yes" IPV6_DEFROUTE="yes" IPV6_PEERDNS="yes" IPV6_PEERROUTES="yes" IPV6_FAILURE_FATAL="no" NAME="enp0s3" UUID="cb0e3849-dff1-4e71-880f-16bfcde23400" DEVICE="enp0s3" ONBOOT="yes" IPADDR=192.168.1.106 GATEWAY=192.168.1.1 NM_CONTROLLED=no NETMASK=255.255.255.0 DNS=114.114.114.114 IPADDR中绑定的是虚拟机的静态地址,GATEWAY为和宿主机同一网段的网关。我的宿主机IP为192.168.1.102,这里的GATEWAY设置为192.168.1.1,DNS选择宿主机中设置的DNS地址。 4.3关闭虚拟机防火墙 查看虚拟机防火墙的命令如下: systemctl status firewalld.service 没有关闭之前,防火墙状态是active的。 停止网络防火墙服务,然后disable: systemctl stop firewalld.service systemctl disable firewalld.service 最后重启网络服务: systemctl restart network.service 4.4 设置虚拟机网络为桥接模式 在VirtualBox中设置虚拟机网络的网络连接方式为桥接网卡。 4.5 测试 宿主机和虚拟机互ping,联通,说明安装成功。 五、使用XShell工具 打开Xmanager终端xshell,Connection中设置ip为虚拟机ip,端口为22. authentication中设置用户名和密码。 连接,进入命令行界面。 六、安装多台虚拟机 重复上述安装方法,即可安装多台虚拟机。 致谢 感谢大神fxsdbt520的指导。
一、倒排索引简介 倒排索引(英语:Inverted index),也常被称为反向索引、置入档案或反向档案,是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。它是文档检索系统中最常用的数据结构。 以英文为例,下面是要被索引的文本: T0="it is what it is" T1="what is it" T2="it is a banana" 我们就能得到下面的反向文件索引: "a": {2} "banana": {2} "is": {0, 1, 2} "it": {0, 1, 2} "what": {0, 1} 检索的条件”what”, “is” 和 “it” 将对应这个集合:{0, 1}&{0, 1, 2}& {0, 1, 2}={0,1} 对于中文分词,可以使用开源的中文分词工具,这里使用ik-analyzer。 准备几个文本文件,写入内容做测试。 file1.txt内容如下: 事实上我们发现,互联网裁员潮频现甚至要高于其他行业领域 file2.txt内容如下: 面对寒冬,互联网企业不得不调整人员结构,优化雇员的投入产出 file3.txt内容如下: 在互联网内部,由于内部竞争机制以及要与竞争对手拼进度 file4.txt内容如下: 互联网大公司职员虽然可以从复杂性和专业分工中受益 互联网企业不得不调整人员结构 二、添加依赖 出了hadoop基本的jar包意外,加入中文分词的lucene-analyzers-common和ik-analyzers: <!--Lucene分词模块--> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-common</artifactId> <version>6.0.0</version> </dependency> <!--IK分词 --> <dependency> <groupId>cn.bestwu</groupId> <artifactId>ik-analyzers</artifactId> <version>5.1.0</version> </dependency> 三、MapReduce程序 关于Lucene 6.0中IK分词的配置参考http://blog.csdn.net/napoay/article/details/51911875,MapReduce程序如下。 import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.input.FileSplit; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import java.io.IOException; import java.io.StringReader; import java.util.HashMap; import java.util.Map; /** * Created by bee on 4/4/17. */ public class InvertIndexIk { public static class InvertMapper extends Mapper<Object, Text, Text, Text> { public void map(Object key, Text value, Context context) throws IOException, InterruptedException { String filename = ((FileSplit) context.getInputSplit()).getPath().getName() .toString(); Text fname = new Text(filename); IKAnalyzer6x analyzer = new IKAnalyzer6x(true); String line = value.toString(); StringReader reader = new StringReader(line); TokenStream tokenStream = analyzer.tokenStream(line, reader); tokenStream.reset(); CharTermAttribute termAttribute = tokenStream.getAttribute (CharTermAttribute.class); while (tokenStream.incrementToken()) { Text word = new Text(termAttribute.toString()); context.write(word, fname); } } } public static class InvertReducer extends Reducer<Text, Text, Text, Text> { public void reduce(Text key, Iterable<Text> values,Reducer<Text,Text, Text,Text>.Context context) throws IOException, InterruptedException { Map<String, Integer> map = new HashMap<String, Integer>(); for (Text val : values) { if (map.containsKey(val.toString())) { map.put(val.toString(),map.get(val.toString())+1); } else { map.put(val.toString(),1); } } int termFreq=0; for (String mapKey:map.keySet()){ termFreq+=map.get(mapKey); } context.write(key,new Text(map.toString()+" "+termFreq)); } } public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { HadoopUtil.deleteDir("output"); Configuration conf=new Configuration(); String[] otherargs=new String[]{"input/InvertIndex", "output"}; if (otherargs.length!=2){ System.err.println("Usage: mergesort <in> <out>"); System.exit(2); } Job job=Job.getInstance(); job.setJarByClass(InvertIndexIk.class); job.setMapperClass(InvertIndexIk.InvertMapper.class); job.setReducerClass(InvertIndexIk.InvertReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); FileInputFormat.addInputPath(job,new Path(otherargs[0])); FileOutputFormat.setOutputPath(job,new Path(otherargs[1])); System.exit(job.waitForCompletion(true) ? 0: 1); } } 四、运行结果 输出如下: 专业分工 {file4.txt=1} 1 中 {file4.txt=1} 1 事实上 {file1.txt=1} 1 互联网 {file1.txt=1, file3.txt=1, file4.txt=2, file2.txt=1} 5 人员 {file4.txt=1, file2.txt=1} 2 企业 {file4.txt=1, file2.txt=1} 2 优化 {file2.txt=1} 1 内部 {file3.txt=2} 2 发现 {file1.txt=1} 1 受益 {file4.txt=1} 1 复杂性 {file4.txt=1} 1 大公司 {file4.txt=1} 1 寒冬 {file2.txt=1} 1 投入产出 {file2.txt=1} 1 拼 {file3.txt=1} 1 潮 {file1.txt=1} 1 现 {file1.txt=1} 1 竞争对手 {file3.txt=1} 1 竞争机制 {file3.txt=1} 1 结构 {file4.txt=1, file2.txt=1} 2 职员 {file4.txt=1} 1 行业 {file1.txt=1} 1 裁员 {file1.txt=1} 1 要与 {file3.txt=1} 1 调整 {file4.txt=1, file2.txt=1} 2 进度 {file3.txt=1} 1 雇员 {file2.txt=1} 1 面对 {file2.txt=1} 1 领域 {file1.txt=1} 1 频 {file1.txt=1} 1 高于 {file1.txt=1} 1 结果有三列,依次为词项、词项在单个文件中的词频以及总的词频。 五、参考资料 1.https://zh.wikipedia.org/wiki/ 倒排索引 2. Lucene 6.0下使用IK分词器
一、Elasticsearch for Hadoop安装 Elasticsearch for Hadoop并不像logstash、kibana一样是一个独立的软件,而是Hadoop和Elasticsearch交互所需要的jar包。所以,有直接下载和maven导入2种方式。安装之前确保JDK版本不要低于1.8,Elasticsearch版本不能低于1.0。 官网对声明是对Hadoop 1.1.x、1.2.x、2.2.x、2.4.x、2.6.x、2.7.x测试通过,支持较好,其它版本的也并不是不能用。 1.1 官网下载zip安装包 官网给的Elasticsearch for Hadoop的地址https://www.elastic.co/downloads/hadoop,下载后解压后,把dist目录下jar包导入hadoop即可。 1.2 maven方式下载 使用maven管理jar包更加方便,在pom.xml文件中加入以下依赖: <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch-hadoop</artifactId> <version>2.3.3</version> </dependency> 我之前使用Intellij Idea搭建了一个MapReduce的环境,参考这里MapReduce编程(一) Intellij Idea配置MapReduce编程环境,pom.xml中再加入上面elasticsearch-hadoop的依赖即可,注意版本。 上面的依赖包含了MapReduce、Pig、Hive、Spark等完整的依赖,如果只想单独使用某一个功能,可以细化分别加入。 Map/Reduce: <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch-hadoop-mr</artifactId> <version>2.3.3</version> </dependency> Apache Hive: <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch-hadoop-hive</artifactId> <version>2.3.3</version> </dependency> Apache Pig. <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch-hadoop-pig</artifactId> <version>2.3.3</version> </dependency> Apache Spark. <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch-spark-20_2.10</artifactId> <version>2.3.3</version> </dependency> 1.3 将ES-hadoop 的jar包加入环境变量 把ES-HADOOP的jar包导入环境变量,编辑/etc/profile(或.bash_profile),我的路径为: /usr/local/Cellar/elasticsearch-hadoop-2.4.3 jar包位于elasticsearch-hadoop-2.4.3的dist目录下。编辑/etc/profile,加入一行: #ES-HADOOP HOME export EsHadoop_HOME=/usr/local/Cellar/elasticsearch-hadoop-2.4.3 export CLASSPATH=$CLASSPATH:$EsHadoop_HOME/dist/* 最后使profile文件生效: source /etc/profile 二、准备数据 准备一些测试数据,数据内容为json格式,每行是一条文档。把下列内容保存到blog.json中。 {"id":"1","title":"git简介","posttime":"2016-06-11","content":"svn与git的最主要区别..."} {"id":"2","title":"ava中泛型的介绍与简单使用","posttime":"2016-06-12","content":"基本操作:CRUD ..."} {"id":"3","title":"SQL基本操作","posttime":"2016-06-13","content":"svn与git的最主要区别..."} {"id":"4","title":"Hibernate框架基础","posttime":"2016-06-14","content":"Hibernate框架基础..."} {"id":"5","title":"Shell基本知识","posttime":"2016-06-15","content":"Shell是什么..."} 启动hadoop,上传blog.json到hdfs: hadoop fs -put blog.json /work 三、从HDFS读取文档索引到ES 从HDFS读取文档索引到Elasticsearch的代码: import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.input.TextInputFormat; import org.elasticsearch.hadoop.mr.EsOutputFormat; import java.io.IOException; /** * Created by bee on 4/1/17. */ public class HdfsToES { public static class MyMapper extends Mapper<Object, Text, NullWritable, BytesWritable> { public void map(Object key, Text value, Mapper<Object, Text, NullWritable, BytesWritable>.Context context) throws IOException, InterruptedException { byte[] line = value.toString().trim().getBytes(); BytesWritable blog = new BytesWritable(line); context.write(NullWritable.get(), blog); } } public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { Configuration conf = new Configuration(); conf.setBoolean("mapred.map.tasks.speculative.execution", false); conf.setBoolean("mapred.reduce.tasks.speculative.execution", false); conf.set("es.nodes", "192.168.1.111:9200"); conf.set("es.resource", "blog/csdn"); conf.set("es.mapping.id", "id"); conf.set("es.input.json", "yes"); Job job = Job.getInstance(conf, "hadoop es write test"); job.setMapperClass(HdfsToES.MyMapper.class); job.setInputFormatClass(TextInputFormat.class); job.setOutputFormatClass(EsOutputFormat.class); job.setMapOutputKeyClass(NullWritable.class); job.setMapOutputValueClass(BytesWritable.class); // 设置输入路径 FileInputFormat.setInputPaths(job, new Path ("hdfs://localhost:9000//work/blog.json")); job.waitForCompletion(true); } } 倒入完成后会在Elasticsearch中生成blog索引。查看内容如下: 四、API分析 Map过程,按行读入,input kye的类型为Object,input value的类型为Text。输出的key为NullWritable类型,NullWritable是Writable的一个特殊类,实现方法为空实现,不从数据流中读数据,也不写入数据,只充当占位符。MapReduce中如果不需要使用键或值,就可以将键或值声明为NullWritable,这里把输出的key设置NullWritable类型。输出为BytesWritable类型,把json字符串序列化。 因为只需要写入,没有Reduce过程。在main函数中,首先创Configuration()类的一个对象conf,通过conf配置一些参数。 conf.setBoolean("mapred.map.tasks.speculative.execution", false); 关闭mapper阶段的执行推测 conf.setBoolean("mapred.reduce.tasks.speculative.execution", false); 关闭reducer阶段的执行推测 conf.set("es.nodes", "192.168.1.111:9200"); 配置Elasticsearch的IP和端口 conf.set("es.resource", "blog/csdn"); 设置索引到Elasticsearch的索引名和类型名。 conf.set("es.mapping.id", "id"); 设置文档id,这个参数”id”是文档中的id字段 conf.set("es.input.json", "yes"); 指定输入的文件类型为json。 job.setInputFormatClass(TextInputFormat.class); 设置输入流为文本类型 job.setOutputFormatClass(EsOutputFormat.class); 设置输出为EsOutputFormat类型。 job.setMapOutputKeyClass(NullWritable.class); 设置Map的输出key类型为NullWritable类型 job.setMapOutputValueClass(BytesWritable.class); 设置Map的输出value类型为BytesWritable类型 2017年8月21日更新: 5.4版本的map函数: public static class MyMapper extends Mapper<Object, Text, NullWritable, Text> { private Text line = new Text(); public void map(Object key, Text value, Mapper<Object, Text, NullWritable, Text>.Context context) throws IOException, InterruptedException { if(value.getLength()>0){ line.set(value); context.write(NullWritable.get(), line); } } } 五、参考资料 https://www.elastic.co/guide/en/elasticsearch/hadoop/current/mapreduce.html
一、问题描述 下面给出一个child-parent的表格,要求挖掘其中的父子辈关系,给出祖孙辈关系的表格。 输入文件内容如下: child parent Steven Lucy Steven Jack Jone Lucy Jone Jack Lucy Mary Lucy Frank Jack Alice Jack Jesse David Alice David Jesse Philip David Philip Alma Mark David Mark Alma 根据父辈和子辈挖掘爷孙关系。比如: Steven Jack Jack Alice Jack Jesse 根据这三条记录,可以得出Jack是Steven的长辈,而Alice和Jesse是Jack的长辈,很显然Steven是Alice和Jesse的孙子。挖掘出的结果如下: grandson grandparent Steven Jesse Steven Alice 要求通过MapReduce挖掘出所有的爷孙关系。 二、分析 解决这个问题要用到一个小技巧,就是单表关联。具体实现步骤如下,Map阶段每一行的key-value输入,同时也把value-key输入。以其中的两行为例: Steven Jack Jack Alice key-value和value-key都输入,变成4行: Steven Jack Jack Alice Jack Steven Alice Jack shuffle以后,Jack作为key值,起到承上启下的桥梁作用,Jack对应的values包含Alice、Steven,这时候Alice和Steven肯定是爷孙关系。为了标记哪些是孙子辈,哪些是爷爷辈,可以在Map阶段加上前缀,比如小辈加上前缀”-“,长辈加上前缀”+”。加上前缀以后,在Reduce阶段就可以根据前缀进行分类。 三、MapReduce程序 package com.javacore.hadoop; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import java.io.IOException; import java.util.ArrayList; import java.util.StringTokenizer; /** * Created by bee on 3/29/17. */ public class RelationShip { public static class RsMapper extends Mapper<Object, Text, Text, Text> { private static int linenum = 0; public void map(Object key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString(); if (linenum == 0) { ++linenum; } else { StringTokenizer tokenizer = new StringTokenizer(line, "\n"); while (tokenizer.hasMoreElements()) { StringTokenizer lineTokenizer = new StringTokenizer(tokenizer.nextToken()); String son = lineTokenizer.nextToken(); String parent = lineTokenizer.nextToken(); context.write(new Text(parent), new Text( "-" + son)); context.write(new Text(son), new Text ("+" + parent)); } } } } public static class RsReducer extends Reducer<Text, Text, Text, Text> { private static int linenum = 0; public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException { if (linenum == 0) { context.write(new Text("grandson"), new Text("grandparent")); ++linenum; } ArrayList<Text> grandChild = new ArrayList<Text>(); ArrayList<Text> grandParent = new ArrayList<Text>(); for (Text val : values) { String s = val.toString(); if (s.startsWith("-")) { grandChild.add(new Text(s.substring(1))); } else { grandParent.add(new Text(s.substring(1))); } } for (Text text1 : grandChild) { for (Text text2 : grandParent) { context.write(text1, text2); } } } } public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { FileUtil.deleteDir("output"); Configuration cong = new Configuration(); String[] otherArgs = new String[]{"input/relations/table.txt", "output"}; if (otherArgs.length != 2) { System.out.println("参数错误"); System.exit(2); } Job job = Job.getInstance(); job.setJarByClass(RelationShip.class); job.setMapperClass(RsMapper.class); job.setReducerClass(RsReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); FileInputFormat.addInputPath(job, new Path(otherArgs[0])); FileOutputFormat.setOutputPath(job, new Path(otherArgs[1])); System.exit(job.waitForCompletion(true) ? 0 : 1); } } 四、输出结果 grandson grandparent Mark Jesse Mark Alice Philip Jesse Philip Alice Jone Jesse Jone Alice Steven Jesse Steven Alice Steven Frank Steven Mary Jone Frank Jone Mary
一、问题描述 三个文件中分别存储了学生的语文、数学和英语成绩,输出每个学生的平均分。 数据格式如下: Chinese.txt 张三 78 李四 89 王五 96 赵六 67 Math.txt 张三 88 李四 99 王五 66 赵六 77 English.txt 张三 80 李四 82 王五 84 赵六 86 二、MapReduce编程 package com.javacore.hadoop; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.DoubleWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import java.io.IOException; /** * Created by bee on 3/29/17. */ public class StudentAvgDouble { public static class MyMapper extends Mapper<Object, Text, Text, DoubleWritable> { public void map(Object key, Text value, Context context) throws IOException, InterruptedException { String eachline = value.toString(); StringTokenizer tokenizer = new StringTokenizer(eachline, "\n"); while (tokenizer.hasMoreElements()) { StringTokenizer tokenizerLine = new StringTokenizer(tokenizer .nextToken()); String strName = tokenizerLine.nextToken(); String strScore = tokenizerLine.nextToken(); Text name = new Text(strName); IntWritable score = new IntWritable(Integer.parseInt(strScore)); context.write(name, score); } } } public static class MyReducer extends Reducer<Text, DoubleWritable, Text, DoubleWritable> { public void reduce(Text key, Iterable<DoubleWritable> values, Context context) throws IOException, InterruptedException { double sum = 0.0; int count = 0; for (DoubleWritable val : values) { sum += val.get(); count++; } DoubleWritable avgScore = new DoubleWritable(sum / count); context.write(key, avgScore); } } public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { //删除output文件夹 FileUtil.deleteDir("output"); Configuration conf = new Configuration(); String[] otherArgs = new String[]{"input/studentAvg", "output"}; if (otherArgs.length != 2) { System.out.println("参数错误"); System.exit(2); } Job job = Job.getInstance(); job.setJarByClass(StudentAvgDouble.class); job.setMapperClass(MyMapper.class); job.setReducerClass(MyReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(DoubleWritable.class); FileInputFormat.addInputPath(job, new Path(otherArgs[0])); FileOutputFormat.setOutputPath(job, new Path(otherArgs[1])); System.exit(job.waitForCompletion(true) ? 0 : 1); } } 三、StringTokenizer和Split的用法对比 map函数里按行读入,每行按空格切开,之前我采用的split()函数切分,代码如下。 String eachline = value.toString(); for (String eachline : lines) { System.out.println("eachline:\t"+eachline); String[] words = eachline.split("\\s+"); Text name = new Text(words[0]); IntWritable score = new IntWritable(Integer.parseInt(words[1])); context.write(name, score); } 这种方式简单明了,但是也存在缺陷,对于非正常编码的空格有时候会出现切割失败的情况。 StringTokenizer是java.util包中分割解析类,StringTokenizer类的构造函数有三个: StringTokenizer(String str):java默认的分隔符是“空格”、“制表符(‘\t’)”、“换行符(‘\n’)”、“回车符(‘\r’)。 StringTokenizer(String str,String delim):可以构造一个用来解析str的StringTokenizer对象,并提供一个指定的分隔符。 StringTokenizer(String str,String delim,boolean returnDelims):构造一个用来解析str的StringTokenizer对象,并提供一个指定的分隔符,同时,指定是否返回分隔符。 StringTokenizer和Split都可以对字符串进行切分,StringTokenizer的性能更高一些,分隔符如果用到一些特殊字符,StringTokenizer的处理结果更好。 四、运行结果 张三 82.0 李四 90.0 王五 82.0 赵六 76.66666666666667
一、问题描述 文件中存储了商品id和商品价格的信息,文件中每行2列,第一列文本类型代表商品id,第二列为double类型代表商品价格。数据格式如下: pid0 334589.41 pid1 663306.49 pid2 499226.8 pid3 130618.22 pid4 513708.8 pid5 723470.7 pid6 998579.14 pid7 831682.84 pid8 87723.96 要求使用MapReduce,按商品的价格从低到高排序,输出格式仍为原来的格式:第一列为商品id,第二列为商品价格。 为了方便测试,写了一个DataProducer类随机产生数据。 package com.javacore.hadoop; import java.io.*; import java.util.Random; /** * Created by bee on 3/25/17. */ public class DataProducer { public static void doubleProcuder() throws Exception { File f = new File("input/productDouble"); if (f.exists()) { f.delete(); } Random generator = new Random(); double rangeMin = 1.0; double rangeMax = 999999.0; FileOutputStream fos = new FileOutputStream(f); OutputStreamWriter osq = new OutputStreamWriter(fos); BufferedWriter bfw = new BufferedWriter(osq); for (int i = 0; i < 100; i++) { double pValue = rangeMin + (rangeMax - rangeMin) * generator.nextDouble(); pValue = (double) Math.round(pValue * 100) / 100; try { bfw.write("pid" + i + " " + pValue + "\n"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } bfw.close(); osq.close(); fos.close(); System.out.println("写入完成!"); } public static void main(String[] args) throws Exception { doubleProcuder(); } } 二、MapReduce程序 package com.javacore.hadoop; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.DoubleWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import java.io.IOException; /** * Created by bee on 3/28/17. */ public class DataSortText { public static class Map extends Mapper<Object, Text, DoubleWritable, Text> { public static DoubleWritable pValue = new DoubleWritable(); public static Text pId = new Text(); // public void map(Object key, Text value, Context context) throws IOException, InterruptedException { String[] line = value.toString().split("\\s+"); pValue.set(Double.parseDouble(line[1])); pId.set(new Text(line[0])); context.write(pValue, pId); } } public static class Reduce extends Reducer<DoubleWritable, Text, Text, DoubleWritable> { public void reduce(DoubleWritable key,Iterable<Text> values, Context context) throws IOException, InterruptedException { for (Text val:values){ context.write(val,key); } } } public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { FileUtil.deleteDir("output"); Configuration conf=new Configuration(); conf.set("fs.defaultFS","hdfs://localhost:9000"); String[] otherargs=new String[]{"input/productDouble", "output"}; if (otherargs.length!=2){ System.err.println("Usage: mergesort <in> <out>"); System.exit(2); } Job job=Job.getInstance(); job.setJarByClass(DataSortText.class); job.setMapperClass(Map.class); job.setReducerClass(Reduce.class); job.setOutputKeyClass(DoubleWritable.class); job.setOutputValueClass(Text.class); FileInputFormat.addInputPath(job,new Path(otherargs[0])); FileOutputFormat.setOutputPath(job,new Path(otherargs[1])); System.exit(job.waitForCompletion(true) ? 0: 1); } } 三、输出 运行之后,输出结果如下。 pid8 87723.96 pid3 130618.22 pid9 171804.65 pid0 334589.41 pid10 468768.65 pid2 499226.8 pid4 513708.8 pid1 663306.49 pid5 723470.7 pid7 831682.84 pid6 998579.14 四、性能分析 为了测试MapReduce排序的性能,数据量分别用1万、10万、100万、1000万、1亿、5亿做测试,结果如下。 数量 文件大小 排序耗时 1万 177KB 6秒 10万 1.9MB 6秒 100 万 19.7MB 13秒 1000 万 206.8MB 60秒 1亿 2.17GB 9分钟 5亿 11.28GB 41分钟 附机器硬件配置: 内存:8 GB 1867 MHz DDR3 CPU:2.7 GHz Intel Core i5 磁盘:SSD
一、问题描述 对输入的多个文件进行合并,并剔除其中重复的内容,去重后的内容输出到一个文件中。 file1.txt中的内容: 20150101 x 20150102 y 20150103 x 20150104 y file2.txt中的内容: 20150105 z 20150106 x 20150101 y 20150102 y file3.txt中的内容: 20150103 x 20150104 z 20150105 y 二、MapReduce程序 编写MapReduce程序,运行环境参考我的上一篇博客Intellij Idea配置MapReduce编程环境 package com.javacore.hadoop; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import java.io.IOException; /** * Created by bee on 17/3/25. */ public class FileMerge { public static class Map extends Mapper<Object, Text, Text, Text> { private static Text text = new Text(); public void map(Object key, Text value, Context content) throws IOException, InterruptedException { text = value; content.write(text, new Text("")); } } public static class Reduce extends Reducer<Text, Text, Text, Text> { public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException { context.write(key, new Text("")); } } public static void main(String[] args) throws Exception { // delete output directory FileUtil.deleteDir("output"); Configuration conf = new Configuration(); conf.set("fs.defaultFS", "hdfs://localhost:9000"); String[] otherArgs = new String[]{"input/filemerge/f*.txt", "output"}; if (otherArgs.length != 2) { System.err.println("Usage:Merge and duplicate removal <in> <out>"); System.exit(2); } Job job = Job.getInstance(); job.setJarByClass(FileMerge.class); job.setMapperClass(Map.class); job.setReducerClass(Reduce.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); FileInputFormat.addInputPath(job, new Path(otherArgs[0])); FileOutputFormat.setOutputPath(job, new Path(otherArgs[1])); System.exit(job.waitForCompletion(true) ? 0 : 1); } } 三、输出 20150101 x 20150101 y 20150102 y 20150103 x 20150104 y 20150104 z 20150105 y 20150105 z 20150106 x
介绍如何在Intellij Idea中通过创建maven工程配置MapReduce的编程环境。 一、软件环境 我使用的软件版本如下: Intellij Idea 2017.1 Maven 3.3.9 Hadoop伪分布式环境( 安装教程可参考这里) 二、创建maven工程 打开Idea,file->new->Project,左侧面板选择maven工程。(如果只跑MapReduce创建java工程即可,不用勾选Creat from archetype,如果想创建web工程或者使用骨架可以勾选) 设置GroupId和ArtifactId,下一步。 设置工程存储路径,下一步。 Finish之后,空白工程的路径如下图所示。 完整的工程路径如下图所示: 三、添加maven依赖 在pom.xml添加依赖,对于hadoop 2.7.3版本的hadoop,需要的jar包有以下几个: hadoop-common hadoop-hdfs hadoop-mapreduce-client-core hadoop-mapreduce-client-jobclient log4j( 打印日志) pom.xml中的依赖如下: <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-hdfs</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-mapreduce-client-core</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-mapreduce-client-jobclient</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies> 四、配置log4j 在src/main/resources目录下新增log4j的配置文件log4j.properties,内容如下: log4j.rootLogger = debug,stdout ### 输出信息到控制抬 ### log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n 五、启动Hadoop 启动Hadoop,运行命令: cd hadoop-2.7.3/ ./sbin/start-all.sh 访问http://localhost:50070/查看hadoop是否正常启动。 六、运行WordCount(从本地读取文件) 在工程根目录下新建input文件夹,input文件夹下新增dream.txt,随便写入一些单词: I have a dream a dream 在src/main/java目录下新建包,新增FileUtil.java,创建一个删除output文件的函数,以后就不用手动删除了。内容如下: package com.mrtest.hadoop; import java.io.File; /** * Created by bee on 3/25/17. */ public class FileUtil { public static boolean deleteDir(String path) { File dir = new File(path); if (dir.exists()) { for (File f : dir.listFiles()) { if (f.isDirectory()) { deleteDir(f.getName()); } else { f.delete(); } } dir.delete(); return true; } else { System.out.println("文件(夹)不存在!"); return false; } } } 编写WordCount的MapReduce程序WordCount.java,内容如下: package com.mrtest.hadoop; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import java.io.IOException; import java.util.Iterator; import java.util.StringTokenizer; /** * Created by bee on 3/25/17. */ public class WordCount { public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> { public static final IntWritable one = new IntWritable(1); private Text word = new Text(); public void map(Object key, Text value, Context context) throws IOException, InterruptedException { StringTokenizer itr = new StringTokenizer(value.toString()); while (itr.hasMoreTokens()) { this.word.set(itr.nextToken()); context.write(this.word, one); } } } public static class IntSumReduce extends Reducer<Text, IntWritable, Text, IntWritable> { private IntWritable result = new IntWritable(); public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; IntWritable val; for (Iterator i = values.iterator(); i.hasNext(); sum += val.get()) { val = (IntWritable) i.next(); } this.result.set(sum); context.write(key, this.result); } } public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { FileUtil.deleteDir("output"); Configuration conf = new Configuration(); String[] otherArgs = new String[]{"input/dream.txt","output"}; if (otherArgs.length != 2) { System.err.println("Usage:Merge and duplicate removal <in> <out>"); System.exit(2); } Job job = Job.getInstance(conf, "WordCount"); job.setJarByClass(WordCount.class); job.setMapperClass(WordCount.TokenizerMapper.class); job.setReducerClass(WordCount.IntSumReduce.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); FileInputFormat.addInputPath(job, new Path(otherArgs[0])); FileOutputFormat.setOutputPath(job, new Path(otherArgs[1])); System.exit(job.waitForCompletion(true) ? 0 : 1); } } 运行完毕以后,会在工程根目录下增加一个output文件夹,打开output/part-r-00000,内容如下: I 1 a 2 dream 2 have 1 这里在main函数中新增了一个String类型的数组,如果想用main函数的args数组接受参数,在运行时指定输入和输出路径也是可以的。运行WordCount之前,配置Configuration并指定Program arguments即可。 七、运行WordCount(从HDFS读取文件) 在HDFS上新建文件夹: hadoop fs -mkdir /worddir 如果出现Namenode安全模式导致的不能创建文件夹提示: mkdir: Cannot create directory /worddir. Name node is in safe mode. 运行以下命令关闭safe mode: hadoop dfsadmin -safemode leave 上传本地文件: hadoop fs -put dream.txt /worddir 修改otherArgs参数,指定输入为文件在HDFS上的路径: String[] otherArgs = new String[]{"hdfs://localhost:9000/worddir/dream.txt","output"}; 八、代码下载 代码下载地址:http://download.csdn.net/detail/napoay/9799523
如果想阅读Elasticsearch源码,定制功能,不可避免的要编译Elasticsearch。本文图文并茂,介绍如何使用Intellij Idea编译Elasticsearch源码包。 一、软件环境 Intellij Idea:2017.1版本 Elasticsearch源码版本:2.3.3 JDK:jdk1.7.0_80.jdk OS:OS X Yosemite 10.10.3 二、下载Elasticsearch源码 Elasticsearch的发行版本,也就是用户使用的版本要到Elastic官网下载,源码包要到github上下载,下载地址:https://github.com/elastic/elasticsearch/releases,如下图所示下载Elasticsearch 2.3.3版本,选择tar.gz格式。 解压缩到本地,我这里的路径为/Users/bee/Documents/es/elasticsearch-2.3.3,解压后elasticsearch-2.3.3目录下的core文件夹就是源码包的位置,如下图所示。 三、加入config目录 把运行版本的elasticsearch-2.3.3目录下的config文件(elasticsearch.yml所在位置)夹拷贝到/Users/bee/Documents/es/elasticsearch-2.3.3/core目录下,config目录下的文件如下。 elasticsearch-2.3.3 --config --elasticsearch.yml --logging.yml --scripts 四、Intellij Idea导入源码 打开Intellij Idea,选择Import Project,下一步。 选择elasticsearch-2.3.3/core所在路径,下一步。 选择默认的pom,下一步。 选择默认的配置,下一步。 选择JDK版本,如果有多个JDK,可以在这里进行选择。 设置工程名称,下一步。 工程导入完成以后,在src/main/java目录下找到org.elasticsearch.bootstrap包下的elasticsearch.java,这时候单独运行会报错,运行之前配置elasticsearch.java的参数,点击Edit Eonfiguration(菜单路径Run->Edit Eonfiguration)。 配置参数: VM Options中增加-Des.path.home路径为ES源码包位置。 -Des.path.home=/Users/bee/Documents/es/elasticsearch-2.3.3/core Program arguments中增加 start 点击OK,运行elasticsearch.java。 如果一切顺利,可以在console中看到以下输出。 访问http://localhost:9200/: 至此,Intellij Idea编译Elasticsearch源码成功! 五、关于JDK版本 第一次编译的时候使用的是jdk1.8.0_112.jdk,出现以下错误,改成最新的jdk1.8.0_121.jdk仍然不行,后来发现是mac版JDK的一个bug,换成jdk1.7.0_80.jdk一切OK。 objc[413]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/bin/java and /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/libinstrument.dylib. One of the two will be used. Which one is undefined. 参考资料 http://stackoverflow.com/questions/23590613/java-8-class-javalaunchhelper-is-implemented-in-both
一、TF-IDF 词项频率: df:term frequency。 term在文档中出现的频率.tf越大,词项越重要. 文档频率: tf:document frequecy。有多少文档包含此term,df越大词项越不重要. 词项权重计算公式: tf-idf=tf(t,d)*log(N/df(t)) W(t,d):the weight of the term in document d tf(t,d):the frequency of term t in document d N:the number of documents df(t):the number of documents that contain term t 二、JAVA实现 package com.javacore.algorithm; import java.util.Arrays; import java.util.List; /** * Created by bee on 17/3/13. * @version 1.0 * @author blog.csdn.net/napoay */ public class TfIdfCal { /** *calculate the word frequency * @param doc word vector of a doc * @param term a word * @return the word frequency of a doc */ public double tf(List<String> doc, String term) { double termFrequency = 0; for (String str : doc) { if (str.equalsIgnoreCase(term)) { termFrequency++; } } return termFrequency / doc.size(); } /** *calculate the document frequency * @param docs the set of all docs * @param term a word * @return the number of docs which contain the word */ public int df(List<List<String>> docs, String term) { int n = 0; if (term != null && term != "") { for (List<String> doc : docs) { for (String word : doc) { if (term.equalsIgnoreCase(word)) { n++; break; } } } } else { System.out.println("term不能为null或者空串"); } return n; } /** *calculate the inverse document frequency * @param docs the set of all docs * @param term a word * @return idf */ public double idf(List<List<String>> docs, String term) { System.out.println("N:"+docs.size()); System.out.println("DF:"+df(docs,term)); return Math.log(docs.size()/(double)df(docs,term)); } /** * calculate tf-idf * @param doc a doc * @param docs document set * @param term a word * @return inverse document frequency */ public double tfIdf(List<String> doc, List<List<String>> docs, String term) { return tf(doc, term) * idf(docs, term); } public static void main(String[] args) { List<String> doc1 = Arrays.asList("人工", "智能", "成为", "互联网", "大会", "焦点"); List<String> doc2 = Arrays.asList("谷歌", "推出", "开源", "人工", "智能", "系统", "工具"); List<String> doc3 = Arrays.asList("互联网", "的", "未来", "在", "人工", "智能"); List<String> doc4 = Arrays.asList("谷歌", "开源", "机器", "学习", "工具"); List<List<String>> documents = Arrays.asList(doc1, doc2, doc3,doc4); TfIdfCal calculator = new TfIdfCal(); System.out.println(calculator.tf(doc2, "开源")); System.out.println(calculator.df(documents, "开源")); double tfidf = calculator.tfIdf(doc2, documents, "谷歌"); System.out.println("TF-IDF (谷歌) = " + tfidf); System.out.println(Math.log(4/2)*1.0/7); } } 运行结果: 0.14285714285714285 2 N:4 DF:2 TF-IDF (谷歌) = 0.09902102579427789
一、简介 Grok是迄今为止使蹩脚的、无结构的日志结构化和可查询的最好方式。Grok在解析 syslog logs、apache and other webserver logs、mysql logs等任意格式的文件上表现完美。 Grok内置了120多种的正则表达式库,地址:https://github.com/logstash-plugins/logstash-patterns-core/tree/master/patterns。 二、入门例子 下面是一条tomcat日志: 83.149.9.216 - - [04/Jan/2015:05:13:42 +0000] "GET /presentations/logstash-monitorama-2013/images/kibana-search.png HTTP/1.1" 200 203023 "http://semicomplete.com/presentations/logstash-monitorama-2013/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36" 从filebeat中输出到logstash,配置如下: input { beats { port => "5043" } } filter { grok { match => { "message" => "%{COMBINEDAPACHELOG}"} } } output { stdout { codec => rubydebug } } fileter中的message代表一条一条的日志,%{COMBINEDAPACHELOG}代表解析日志的正则表达式,COMBINEDAPACHELOG的具体内容见:https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/httpd。解析后: { "request" => "/presentations/logstash-monitorama-2013/images/kibana-search.png", "agent" => "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36\"", "offset" => 325, "auth" => "-", "ident" => "-", "input_type" => "log", "verb" => "GET", "source" => "/path/to/file/logstash-tutorial.log", "message" => "83.149.9.216 - - [04/Jan/2015:05:13:42 +0000] \"GET /presentations/logstash-monitorama-2013/images/kibana-search.png HTTP/1.1\" 200 203023 \"http://semicomplete.com/presentations/logstash-monitorama-2013/\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36\"", "type" => "log", "tags" => [ [0] "beats_input_codec_plain_applied" ], "referrer" => "\"http://semicomplete.com/presentations/logstash-monitorama-2013/\"", "@timestamp" => 2016-10-11T21:04:36.167Z, "response" => "200", "bytes" => "203023", "clientip" => "83.149.9.216", "@version" => "1", "beat" => { "hostname" => "My-MacBook-Pro.local", "name" => "My-MacBook-Pro.local" }, "host" => "My-MacBook-Pro.local", "httpversion" => "1.1", "timestamp" => "04/Jan/2015:05:13:42 +0000" } 再比如,下面这条日志: 55.3.244.1 GET /index.html 15824 0.043 这条日志可切分为5个部分,IP(55.3.244.1)、方法(GET)、请求文件路径(/index.html)、字节数(15824)、访问时长(0.043),对这条日志的解析模式(正则表达式匹配)如下: %{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes} %{NUMBER:duration} 写到filter中: filter { grok { match => { "message" => "%{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes} %{NUMBER:duration}"} } } 解析后: client: 55.3.244.1 method: GET request: /index.html bytes: 15824 duration: 0.043 三、解析任意格式日志 解析任意格式日志的步骤: 先确定日志的切分原则,也就是一条日志切分成几个部分。 对每一块进行分析,如果Grok中正则满足需求,直接拿来用。如果Grok中没用现成的,采用自定义模式。 学会在Grok Debugger中调试。 下面给出例子,来两条日志: 2017-03-07 00:03:44,373 4191949560 [ CASFilter.java:330:DEBUG] entering doFilter() 2017-03-16 00:00:01,641 133383049 [ UploadFileModel.java:234:INFO ] 上报内容准备写入文件 切分原则: 2017-03-16 00:00:01,641:时间 133383049:编号 UploadFileModel.java:java类名 234:代码行号 INFO:日志级别 entering doFilter():日志内容 前五个字段用Grok中已有的,分别是TIMESTAMP_ISO8601、NUMBER、JAVAFILE、NUMBER、LOGLEVEL,最后一个采用自定义正则的形式,日志级别的]之后的内容不论是中英文,都作为日志信息处理,使用自定义正则表达式子的规则如下: (?<field_name>the pattern here) 最后一个字段的内容用info表示,正则如下: (?<info>([\s\S]*)) 上面两条日志对应的完整的正则如下,其中\s*用于剔除空格。 \s*%{TIMESTAMP_ISO8601:time}\s*%{NUMBER:num} \[\s*%{JAVAFILE:class}\s*\:\s*%{NUMBER:lineNumber}\s*\:%{LOGLEVEL:level}\s*\]\s*(?<info>([\s\S]*)) 正则解析容易出错,强烈建议使用Grok Debugger调试,姿势如下。 四、参考资料 plugins-filters-grok Parsing Logs with Logstash
介绍一下如何从Java工程中导出log4J日志到Logstash。 一、log4j基础 不能免俗的官方介绍: Log4j 是一个使用 Java 语言编写的,可靠、快速、灵活的日志框架(API),使用 Apache Software License 授权。它被移植到 C、C++、C#、Perl、Python、Ruby 和 Eiffel 语言中。 Log4j 是高度可配置的,在运行期使用外部的配置文件对其进行配置。它按照优先级别记录日志,并可将日志信息定向输出到各种介质,比如数据库、文件、控制台、Unix Syslog等。 Log4j 主要由三部分组成: loggers:负责采集日志信息。 appenders:负责将日志信息发布到不同地方。 layouts:负责以各种风格格式化日志信息。 二、新建Java工程 下面通过实际的工程配置学习如何配置log4j。 打开Eclipse或者Intellij Idea,新建一个maven工程。工程目录结构如下图所示: pom.xml文件中加入log4j的依赖,版本为1.2.17,pom.xml中的代码如下: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.logs</groupId> <artifactId>log4idemo1</artifactId> <version>1.0-SNAPSHOT</version> <!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies> </project> 在resource目录下新建log4j.properties,加入以下配置: ### 设置### log4j.rootLogger = debug,stdout,D,E,logstash ### 输出信息到控制抬 ### log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n ### 输出DEBUG 级别以上的日志到=/Users/bee/Documents/elk/log4j/debug.log### log4j.appender.D = org.apache.log4j.DailyRollingFileAppender log4j.appender.D.File = /Users/bee/Documents/elk/log4j/debug.log log4j.appender.D.Append = true log4j.appender.D.Threshold = DEBUG log4j.appender.D.layout = org.apache.log4j.PatternLayout log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n ### 输出ERROR 级别以上的日志到=/Users/bee/Documents/elk/log4j/error.log ### log4j.appender.E = org.apache.log4j.DailyRollingFileAppender log4j.appender.E.File =/Users/bee/Documents/elk/log4j/error.log log4j.appender.E.Append = true log4j.appender.E.Threshold = ERROR log4j.appender.E.layout = org.apache.log4j.PatternLayout log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n #输出日志到logstash log4j.appender.logstash=org.apache.log4j.net.SocketAppender log4j.appender.logstash.RemoteHost=127.0.0.1 log4j.appender.logstash.port=4560 log4j.appender.logstash.ReconnectionDelay=60000 log4j.appender.logstash.LocationInfo=true 配置文件中,把日志输出了四份: 第一份输出到控制台 第二份把DEBUG 级别以上的日志到文件 第三份把输出ERROR 级别以上的日志到文件 第四份输出到logstash 在java目录下添加Log4jTest.java,内容如下: import org.apache.log4j.Logger; /** * Created by bee on 17/3/6. */ public class Log4jTest { public static final Logger logger=Logger.getLogger(Log4jTest.class); public static void main(String[] args) { logger.debug("This is a debug message!"); logger.info("This is info message!"); logger.warn("This is a warn message!"); logger.error("This is error message!"); try{ System.out.println(5/0); }catch(Exception e){ logger.error(e); } } } 三、配置logstash (logstash的安装和hello world教程请点这里http://blog.csdn.net/napoay/article/details/53276758)这里使用logstash2.3.3和Elasticsearch 2.3.3,首先启动Elasticsearch,然后在logstash-2.3.3/conf目录下新建配置文件log4j-es.conf,文件内容如下: input { log4j { host => "127.0.0.1" port => 4560 } } output { stdout { codec => rubydebug } elasticsearch{ hosts => ["localhost:9200"] index => "log4j-%{+YYYY.MM.dd}" document_type => "log4j_type" } } 配置文件中指定日志输出有2份,一份输出到console,一份输出到Elasticsearch。 启动logstash: sudo ./bin/logstash -f conf/log4j-es.conf 如果你已经启动了Elasticsearch,IP和端口都是通畅的,配置文件无误,启动成功后的界面如下: 运行Log4jTest.java,在终端中可以看到以下输出: 在Elasticsearch中查看导入的日志: 四、总结 上述配置完成了日志的产生,到logstash,再到Elasticsearch,介绍完毕。
一、导入数据 Mysql中的新闻数据: 二、搜索框 三、搜索结果
Elasticsearch分析聚合介绍了分析聚合的REST命令,这篇博客介绍一下如何使用Java API。 一、准备数据 测试数据请参考我的上一篇博客:Elasticsearch分析聚合。 二、需求 查询title中包含关键字”程序”的文档,统计查询按编程语言分组,统计每组的文档数量。 三、REST命令行 REST命令行如下,有三部分,query查询所有title中包含关键词程序的文档,size指定返回结果中文档数量,其值为0只返回聚合的结果,aggs部分通过language字段进行分组。 GET books/_search { "query": { "match": { "title": "程序" } }, "size": 0, "aggs": { "per_count": { "terms": { "field": "language" } } } } 查询结果如下: { "took": 5, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 2, "max_score": 0, "hits": [] }, "aggregations": { "per_count": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "java", "doc_count": 1 }, { "key": "javascript", "doc_count": 1 } ] } } } 四、聚合的JAVA API //创建TransportClient对象 client=EsUtils.getEsClient(); QueryBuilder matchQuery = QueryBuilders.matchQuery("title", "程序"); AbstractAggregationBuilder aggregation = AggregationBuilders.terms("per_count").field("language"); SearchResponse response = client.prepareSearch("books").setTypes("IT") .setQuery(matchQuery) .addAggregation(aggregation) .execute() .actionGet(); SearchHits hits = response.getHits(); for(SearchHit hit:hits){ System.out.println("id:"+hit.getId()+"\ttitle:"+hit.getSource().get("title")); } Terms terms = response.getAggregations().get("per_count"); List<Bucket> buckets = terms.getBuckets(); for(Bucket bucket:buckets){ System.out.println(bucket.getKey()+"----"+bucket.getDocCount()); } client.close(); 运行结果: id:2 title:Java程序性能优化 id:5 title:JavaScript高级程序设计 java----1 javascript----1 五、更多聚合操作 Bucket aggregations Metrics aggregations
一、扩展停用词字典 IK Analyzer默认的停用词词典为IKAnalyzer2012_u6/stopword.dic,这个停用词词典并不完整,只有30多个英文停用词。可以扩展停用词字典,新增ext_stopword.dic,文件和IKAnalyzer.cfg.xml在同一目录,编辑IKAnalyzer.cfg.xml把新增的停用词字典写入配置文件,多个停用词字典用逗号隔开,如下所示。 <entry key="ext_stopwords">stopword.dic;ext_stopword.dic</entry> 二、扩展自定义词库 IK Analyzer也支持自定义词典,在IKAnalyzer.cfg.xml同一目录新建ext.dic,把新的词语按行写入文件,编辑IKAnalyzer.cfg.xml把新增的停用词字典写入配置文件,多个字典用空格隔开,如下所示: <entry key="ext_dict">ext.dic;</entry> 比如,对于网络流行语“厉害了我的哥”,默认的词库中没有这个词,在自定义字典中写入以后才能分成一个词。 三、测试自定义词典效果 import java.io.IOException; import java.io.StringReader; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import tup.lucene.ik.IKAnalyzer6x; public class ExtDicTest { private static String str = "厉害了我的哥!中国环保部门发布了治理北京雾霾的的方法!"; public static void main(String[] args) throws IOException { Analyzer analyzer = new IKAnalyzer6x(true); StringReader reader = new StringReader(str); TokenStream toStream = analyzer.tokenStream(str, reader); toStream.reset(); CharTermAttribute teAttribute= toStream.getAttribute(CharTermAttribut e.class); System.out.println("分词结果:"); while (toStream.incrementToken()) { System.out.print(teAttribute.toString() + "|"); } System.out.println("\n"); analyzer.close(); } } 运行结果: 加载扩展词典:ext.dic 加载扩展停止词典:stopword.dic 分词结果: 厉|害了|的哥|中国|环保部门|发布|治理|北京|雾|霾|方法| 在ext.dic中添加自定义词项: 中国环保部门 北京雾霾 厉害了我的哥 再次运行,结果如下: 加载扩展词典:ext.dic 加载扩展停止词典:stopword.dic 分词结果: 厉害了我的哥|中国环保部门|发布|治理|北京雾霾|方法|
一、需求 给出一篇新闻文档,统计出现频率最高的有哪些词语。 二、思路 关于文本关键词提取的算法有很多,开源工具也不止一种。这里只介绍如何从Lucene索引中提取词项频率的TopN。索引过程的本质是一个词条化的生存倒排索引的过程,词条化会从文本中去除标点符号、停用词等,最后生成词项。在代码中实现的思路是使用IndexReader的getTermVector获取文档的某一个字段的Terms,从terms中获取tf(term frequency)。拿到词项的tf以后放到map中降序排序,取出Top-N。 三、代码实现 工程目录如下: 关于Lucene 6.0中如何使用IK分词,请参考http://blog.csdn.net/napoay/article/details/51911875。工程里重要的只有2个类,IndexDocs.java和GetTopTerms.java。 3.1索引新闻 在百度新闻上随机找了一篇新闻:李开复:无人驾驶进入黄金时代 AI有巨大投资机会,新闻内容为李开复关于人工智能的主题演讲。把新闻的文本内容放在testfile/news.txt文件中。IndexDocs中的内容: package lucene.test; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.nio.file.Paths; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.FieldType; import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.IndexWriterConfig.OpenMode; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import lucene.ik.IKAnalyzer6x; public class IndexDocs { public static void main(String[] args) throws IOException { File newsfile = new File("testfile/news.txt"); String text1 = textToString(newsfile); // Analyzer smcAnalyzer = new SmartChineseAnalyzer(true); Analyzer smcAnalyzer = new IKAnalyzer6x(true); IndexWriterConfig indexWriterConfig = new IndexWriterConfig(smcAnalyzer); indexWriterConfig.setOpenMode(OpenMode.CREATE); // 索引的存储路径 Directory directory = null; // 索引的增删改由indexWriter创建 IndexWriter indexWriter = null; directory = FSDirectory.open(Paths.get("indexdir")); indexWriter = new IndexWriter(directory, indexWriterConfig); // 新建FieldType,用于指定字段索引时的信息 FieldType type = new FieldType(); // 索引时保存文档、词项频率、位置信息、偏移信息 type.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS); type.setStored(true);// 原始字符串全部被保存在索引中 type.setStoreTermVectors(true);// 存储词项量 type.setTokenized(true);// 词条化 Document doc1 = new Document(); Field field1 = new Field("content", text1, type); doc1.add(field1); indexWriter.addDocument(doc1); indexWriter.close(); directory.close(); } public static String textToString(File file) { StringBuilder result = new StringBuilder(); try { BufferedReader br = new BufferedReader(new FileReader(file));// 构造一个BufferedReader类来读取文件 String str = null; while ((str = br.readLine()) != null) {// 使用readLine方法,一次读一行 result.append(System.lineSeparator() + str); } br.close(); } catch (Exception e) { e.printStackTrace(); } return result.toString(); } } 3.2获取热词 package lucene.test; import java.io.IOException; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Terms; import org.apache.lucene.index.TermsEnum; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.util.BytesRef; public class GetTopTerms { public static void main(String[] args) throws IOException { // Directory directory = FSDirectory.open(Paths.get("indexdir")); IndexReader reader = DirectoryReader.open(directory); // 因为只索引了一个文档,所以DocID为0,通过getTermVector获取content字段的词项 Terms terms = reader.getTermVector(0, "content"); // 遍历词项 TermsEnum termsEnum = terms.iterator(); BytesRef thisTerm = null; Map<String, Integer> map = new HashMap<String, Integer>(); while ((thisTerm = termsEnum.next()) != null) { // 词项 String termText = thisTerm.utf8ToString(); // 通过totalTermFreq()方法获取词项频率 map.put(termText, (int) termsEnum.totalTermFreq()); } // 按value排序 List<Map.Entry<String, Integer>> sortedMap = new ArrayList<Map.Entry<String, Integer>>(map.entrySet()); Collections.sort(sortedMap, new Comparator<Map.Entry<String, Integer>>() { public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) { return (o2.getValue() - o1.getValue()); } }); // System.out.println(sortedMap); getTopN(sortedMap, 10); } // 获取top-n public static void getTopN(List<Entry<String, Integer>> sortedMap, int N) { for (int i = 0; i < N; i++) { System.out.println(sortedMap.get(i).getKey() + ":" + sortedMap.get(i).getValue()); } } } 四、结果分析 4.1SmartChineseAnalyzer提取结果 第一次的结果是使用Lucene自带的SmartChineseAnalyzer分词得出来的top-10的结果.很显然这个结果并不是我们期待的。 4.2IKAnalyzer提取结果 换成IK分词,结果依然糟糕。问题出在停用词太多,的、是、我、这、了这种词出现频率非常高,但是并没有意义。 4.3Ik+扩展停用词表提取结果 下载哈工大中文停用词词表,加到src/stopword.dic中,重新索引文档,再次运行GetTopTerms.java。结果如下: 新闻的内容是李开复关于人工智能、无人驾驶、AI的演讲,这次的提取结果比较靠谱。 五、参考文献 1、In lucene 4, IndexReader.getTermVector(docID, fieldName) returns null for every doc 2、Lucene索引过程分析 六、源码 欢迎批评指正。