ElasticSearch实战系列三: ElasticSearch的JAVA API运用教程

媒介

在上一篇中引见了ElasticSearch实战系列二: ElasticSearch的DSL语句运用教程—图文详解,本篇文章就来讲解下 ElasticSearch 6.x官方Java API的运用。

ElasticSearch JAVA API

现在市情上有几种罕见的ElasticSearch Java API架包,JestClient、SpringBoot整合的SpringData、Spring整合的ElasticsearchTemplate、Elasticsearch Bboss等一些开源架包,上述这些第三方整合的架包中,基础已支撑一样平常的运用,除了支撑的ES版本会低一些罢了。

本文引见的是ElasticSearch官方的Java High Level REST Client的运用,Java High Level REST Client是ElasticSearch官方现在引荐运用的,适用于6.x以上的版本,要求JDK在1.8以上,能够很好的在大版本中举行兼容,而且该架包本身也包括Java Low Level REST Client中的要领,能够应对一些特需的状况举行特别的处置惩罚, 它关于一些经常使用的要领封装Restful作风,能够直接对应操纵名挪用运用即可,支撑同步和异步(Async)挪用。

这里我们的运用也能够直接对应上一篇文章中的DSL语句运用,如许的话能够非常轻易的对比和进修运用。

在对下述举行操纵时,我们先来看下Elasticsearch Java High Level REST Client的初始化衔接写法吧。


    RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost(elasticIp, elasticPort)));

是不是是很简朴呢,封闭也很简朴,client不为空直接close即可!

一、新增数据

ElasticSearch能够直接新增数据,只需你指定了index(索引库称号)和type(范例)即可。在新增的时刻你能够本身指定主键ID,也能够不指定,由 ElasticSearch本身天生。Elasticsearch Java High Level REST Client新增数据供应了三种要领,这里我们就来看一下这三种写法吧。

新增数据代码示例一,经由过程jsonString举行建立:

    String index = "test1";
    String type = "_doc";
    // 唯一编号
    String id = "1";
    IndexRequest request = new IndexRequest(index, type, id);

    String jsonString = "{" + "\"uid\":\"1234\","+ "\"phone\":\"12345678909\","+ "\"msgcode\":\"1\"," + "\"sendtime\":\"2019-03-14 01:57:04\","
            + "\"message\":\"xuwujing study Elasticsearch\"" + "}";
    request.source(jsonString, XContentType.JSON);
    IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);

新增数据代码示例二,经由过程map建立,会自动转换成json的数据:

    String index = "test1";
    String type = "_doc";
    // 唯一编号
    String id = "1";
    IndexRequest request = new IndexRequest(index, type, id);
    Map<String, Object> jsonMap = new HashMap<>();
    jsonMap.put("uid", 1234);
    jsonMap.put("phone", 12345678909L);
    jsonMap.put("msgcode", 1);
    jsonMap.put("sendtime", "2019-03-14 01:57:04");
    jsonMap.put("message", "xuwujing study Elasticsearch");
    request.source(jsonMap);
    IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);

新增数据代码示例三,经由过程XContentBuilder对象举行建立:

   String index = "test1";
    String type = "_doc";
    // 唯一编号
    String id = "1";
    IndexRequest request = new IndexRequest(index, type, id);
    XContentBuilder builder = XContentFactory.jsonBuilder();
    builder.startObject();
    {
        builder.field("uid", 1234);
        builder.field("phone", 12345678909L);
        builder.field("msgcode", 1);
        builder.timeField("sendtime", "2019-03-14 01:57:04");
        builder.field("message", "xuwujing study Elasticsearch");
    }
    builder.endObject();
    request.source(builder);
    IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);

上述三种要领中,个人引荐第二种,比较轻易明白和运用。

二、建立索引库

在上述示例中,我们经由过程直接经由过程建立数据从而建立了索引库,然则没有建立索引库而经由过程ES本身天生的这类并不友爱,由于它会运用默许的设置,字段构造都是text(text的数据会分词,在存储的时刻也会分外的占用空间),分片和索引副本采纳默许值,默许是5和1,ES的分片数在建立以后就不能修正,除非reindex,所以这里我们照样指定数据模板举行建立。
运用JAVA API 建立索引库的要领和上述中新增数据的一样,有三种体式格局,不过这里就只引见一种。

新增索引库的代码示例:

private static void createIndex() throws IOException {
    String type = "_doc";
    String index = "test1";
    // setting 的值
    Map<String, Object> setmapping = new HashMap<>();
    // 分区数、副本数、缓存革新时候
    setmapping.put("number_of_shards", 10);
    setmapping.put("number_of_replicas", 1);
    setmapping.put("refresh_interval", "5s");
    Map<String, Object> keyword = new HashMap<>();
    //设置范例
    keyword.put("type", "keyword");
    Map<String, Object> lon = new HashMap<>();
    //设置范例
    lon.put("type", "long");
    Map<String, Object> date = new HashMap<>();
    //设置范例
    date.put("type", "date");
    date.put("format", "yyyy-MM-dd HH:mm:ss");

    Map<String, Object> jsonMap2 = new HashMap<>();
    Map<String, Object> properties = new HashMap<>();
    //设置字段message信息
    properties.put("uid", lon);
    properties.put("phone", lon);
    properties.put("msgcode", lon);
    properties.put("message", keyword);
    properties.put("sendtime", date);
    Map<String, Object> mapping = new HashMap<>();
    mapping.put("properties", properties);
    jsonMap2.put(type, mapping);

    GetIndexRequest getRequest = new GetIndexRequest();
    getRequest.indices(index);
    getRequest.local(false);
    getRequest.humanReadable(true);
    boolean exists2 = client.indices().exists(getRequest, RequestOptions.DEFAULT);
    //假如存在就不建立了
    if(exists2) {
        System.out.println(index+"索引库已存在!");
        return;
    }
    // 最先建立库
    CreateIndexRequest request = new CreateIndexRequest(index);
    try {
        // 加载数据范例
        request.settings(setmapping);
        //设置mapping参数
        request.mapping(type, jsonMap2);
        //设置别号
        request.alias(new Alias("pancm_alias"));
        CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
        boolean falg = createIndexResponse.isAcknowledged();
        if(falg){
            System.out.println("建立索引库:"+index+"胜利!" );
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

}

注:建立索引库的时刻,一定要先推断索引库是不是存在!!!
这里建立索引库的时刻趁便也指定了别号(alias),这个别号是一个好东西,运用适当能够提拔查询机能,这里我们留着下次在讲。

三、修正数据

ES供应修正API的时刻,有两种体式格局,一种是直接修正,然则若数据不存在会抛出非常,另一种则是存在更新,不存着就插进去。比拟第一种,第二种会越发好用一些,不过在写入速度上是不如第一种的。

ES修正的代码示例:

private static void update() throws IOException {
    String type = "_doc";
    String index = "test1";
    // 唯一编号
    String id = "1";
    UpdateRequest upateRequest = new UpdateRequest();
    upateRequest.id(id);
    upateRequest.index(index);
    upateRequest.type(type);

    // 照旧能够运用Map这类鸠合作为更新前提
    Map<String, Object> jsonMap = new HashMap<>();
    jsonMap.put("uid", 12345);
    jsonMap.put("phone", 123456789019L);
    jsonMap.put("msgcode", 2);
    jsonMap.put("sendtime", "2019-03-14 01:57:04");
    jsonMap.put("message", "xuwujing study Elasticsearch");
    upateRequest.doc(jsonMap);
    // upsert 要领示意假如数据不存在,那末就新增一条
    upateRequest.docAsUpsert(true);
    client.update(upateRequest, RequestOptions.DEFAULT);
    System.out.println("更新胜利!");

}

注:upsert 要领示意假如数据不存在,那末就新增一条,默许是false。

四、删除数据

依据上述的几个操纵,想必不必多说,已知道了是DELETE要领了,那我们就直接最先吧。

ES依据ID删除代码示例:

private static void delete() throws IOException {

    String type = "_doc";
    String index = "test1";
    // 唯一编号
    String id = "1";
    DeleteRequest deleteRequest = new DeleteRequest();
    deleteRequest.id(id);
    deleteRequest.index(index);
    deleteRequest.type(type);
    // 设置超时时候
    deleteRequest.timeout(TimeValue.timeValueMinutes(2));
    // 设置革新战略"wait_for"
    // 坚持此要求翻开,直到革新使此要求的内容能够搜刮为止。此革新战略与高索引和搜刮吞吐量兼容,但它会致使要求守候相应,直到发作革新
    deleteRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL);
    // 同步删除
    DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT);
}       

ES依据前提举行删除:

   private static void deleteByQuery() throws IOException {
    String type = "_doc";
    String index = "test1";
    DeleteByQueryRequest request = new DeleteByQueryRequest(index,type);
    // 设置查询前提
    request.setQuery(QueryBuilders.termsQuery("uid",1234));
    // 同步实行
    BulkByScrollResponse bulkResponse = client.deleteByQuery(request, RequestOptions.DEFAULT);
}

测试效果

示例图:

查询语句

几个经常使用的查询API这里就简朴的引见下用法,然后再直接给出一切的查询语句代码。

查询API

  • 等值(term查询:QueryBuilders.termQuery(name,value);
  • 多值(terms)查询:QueryBuilders.termsQuery(name,value,value2,value3…);
  • 局限(range)查询:QueryBuilders.rangeQuery(name).gte(value).lte(value);
  • 存在(exists)查询:QueryBuilders.existsQuery(name);
  • 隐约(wildcard)查询:QueryBuilders.wildcardQuery(name,+value+);
  • 组合(bool)查询: BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

查询一切代码示例

 private static void allSearch() throws IOException {
    SearchRequest searchRequestAll = new SearchRequest();
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.query(QueryBuilders.matchAllQuery());
    searchRequestAll.source(searchSourceBuilder);
    // 同步查询
    SearchResponse searchResponseAll = client.search(searchRequestAll, RequestOptions.DEFAULT);
    System.out.println("一切查询总数:" + searchResponseAll.getHits().getTotalHits());
}

平常查询代码示例

实在就是等值查询,只不过在内里加入了分页、排序、超时、路由等等设置,而且在查询效果内里增加了一些处置惩罚。

   private static void genSearch() throws IOException {
    String type = "_doc";
    String index = "test1";
    // 查询指定的索引库
    SearchRequest searchRequest = new SearchRequest(index);
    searchRequest.types(type);
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    // 设置查询前提
    sourceBuilder.query(QueryBuilders.termQuery("uid", "1234"));
    // 设置起止和完毕
    sourceBuilder.from(0);
    sourceBuilder.size(5);
    sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
    // 设置路由
//      searchRequest.routing("routing");
    // 设置索引库表达式
    searchRequest.indicesOptions(IndicesOptions.lenientExpandOpen());
    // 查询挑选当地分片,默许是集群分片
    searchRequest.preference("_local");

    // 排序
    // 依据默许值举行降序排序
//  sourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC));
    // 依据字段举行升序排序
//  sourceBuilder.sort(new FieldSortBuilder("id").order(SortOrder.ASC));

    // 封闭suorce查询
//  sourceBuilder.fetchSource(false);

    String[] includeFields = new String[]{"title", "user", "innerObject.*"};
    String[] excludeFields = new String[]{"_type"};
    // 包括或消除字段
//  sourceBuilder.fetchSource(includeFields, excludeFields);

    searchRequest.source(sourceBuilder);
    System.out.println("一般查询的DSL语句:"+sourceBuilder.toString());
    // 同步查询
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

    // HTTP状况代码、实行时候或要求是不是提早停止或超时
    RestStatus status = searchResponse.status();
    TimeValue took = searchResponse.getTook();
    Boolean terminatedEarly = searchResponse.isTerminatedEarly();
    boolean timedOut = searchResponse.isTimedOut();

    // 供关于受搜刮影响的切分总数的统计信息,以及胜利和失利的切分
    int totalShards = searchResponse.getTotalShards();
    int successfulShards = searchResponse.getSuccessfulShards();
    int failedShards = searchResponse.getFailedShards();
    // 失利的缘由
    for (ShardSearchFailure failure : searchResponse.getShardFailures()) {
        // failures should be handled here
    }
    // 效果
    searchResponse.getHits().forEach(hit -> {
        Map<String, Object> map = hit.getSourceAsMap();
        System.out.println("一般查询的效果:" + map);
    });
    System.out.println("\n=================\n");
}

或查询

实在这个或查询也是bool查询中的一种,这里的查询语句相当于SQL语句中的

SELECT * FROM test1 where (uid = 1 or uid =2) and phone = 12345678919

代码示例:

private static void orSearch() throws IOException {
    SearchRequest searchRequest = new SearchRequest();
    searchRequest.indices("test1");
    searchRequest.types("_doc");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
    BoolQueryBuilder boolQueryBuilder2 = new BoolQueryBuilder();
 
      /**
     *  SELECT * FROM test1 where (uid = 1234 or uid =12345)  and phone = 12345678909
     * */
    boolQueryBuilder2.should(QueryBuilders.termQuery("uid", 1234));
    boolQueryBuilder2.should(QueryBuilders.termQuery("uid", 12345));
    boolQueryBuilder.must(boolQueryBuilder2);
    boolQueryBuilder.must(QueryBuilders.termQuery("phone", "12345678909"));
    searchSourceBuilder.query(boolQueryBuilder);
    System.out.println("或查询语句:" + searchSourceBuilder.toString());
    searchRequest.source(searchSourceBuilder);
    // 同步查询
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

    searchResponse.getHits().forEach(documentFields -> {

        System.out.println("查询效果:" + documentFields.getSourceAsMap());
    });

}

隐约查询

相当于SQL语句中的like查询。

private static void likeSearch() throws IOException {
    String type = "_doc";
    String index = "test1";
    SearchRequest searchRequest = new SearchRequest();
    searchRequest.indices(index);
    searchRequest.types(type);
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

   /**
     *  SELECT * FROM p_test where  message like '%xu%';
     * */
    boolQueryBuilder.must(QueryBuilders.wildcardQuery("message", "*xu*"));
    searchSourceBuilder.query(boolQueryBuilder);
    System.out.println("隐约查询语句:" + searchSourceBuilder.toString());
    searchRequest.source(searchSourceBuilder);
    // 同步查询
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    searchResponse.getHits().forEach(documentFields -> {
        System.out.println("隐约查询效果:" + documentFields.getSourceAsMap());
    });
    System.out.println("\n=================\n");
}

多值查询

也就是相当于SQL语句中的in查询。

     private static void inSearch() throws IOException {
        String type = "_doc";
        String index = "test1";
        // 查询指定的索引库
        SearchRequest searchRequest = new SearchRequest(index,type);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        /**
         *  SELECT * FROM p_test where uid in (1,2)
         * */
        // 设置查询前提
        sourceBuilder.query(QueryBuilders.termsQuery("uid", 1, 2));
        searchRequest.source(sourceBuilder);
        System.out.println("in查询的DSL语句:"+sourceBuilder.toString());
        // 同步查询
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        // 效果
        searchResponse.getHits().forEach(hit -> {
            Map<String, Object> map = hit.getSourceAsMap();
            String string = hit.getSourceAsString();
            System.out.println("in查询的Map效果:" + map);
            System.out.println("in查询的String效果:" + string);
        });

        System.out.println("\n=================\n");
    }

存在查询

推断是不是存在该字段,用法和SQL语句中的exist相似。

  private static void existSearch() throws IOException {
    String type = "_doc";
    String index = "test1";
    // 查询指定的索引库
    SearchRequest searchRequest = new SearchRequest(index);
    searchRequest.types(type);
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

    // 设置查询前提
     sourceBuilder.query(QueryBuilders.existsQuery("msgcode"));
    searchRequest.source(sourceBuilder);
    System.out.println("存在查询的DSL语句:"+sourceBuilder.toString());
    // 同步查询
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    // 效果
    searchResponse.getHits().forEach(hit -> {
        Map<String, Object> map = hit.getSourceAsMap();
        String string = hit.getSourceAsString();
        System.out.println("存在查询的Map效果:" + map);
        System.out.println("存在查询的String效果:" + string);
    });
    System.out.println("\n=================\n");
}

局限查询

和SQL语句中<>运用要领一样,个中gt是大于,lt是小于,gte是大于即是,lte是小于即是。

private static void rangeSearch() throws IOException{
    String type = "_doc";
    String index = "test1";
    SearchRequest searchRequest = new SearchRequest(index);
    searchRequest.types(type);
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

    // 设置查询前提
    sourceBuilder.query(QueryBuilders.rangeQuery("sendtime").gte("2019-01-01 00:00:00").lte("2019-12-31 23:59:59"));
    searchRequest.source(sourceBuilder);
     System.out.println("局限查询的DSL语句:"+sourceBuilder.toString());
    // 同步查询
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    // 效果
    searchResponse.getHits().forEach(hit -> {
        String string = hit.getSourceAsString();
        System.out.println("局限查询的String效果:" + string);
    });
    System.out.println("\n=================\n");
}

正则查询

ES能够运用正则举行查询,查询体式格局也非常的简朴,代码示例以下:

 private static void regexpSearch() throws IOException{
    String type = "_doc";
    String index = "test1";
    // 查询指定的索引库
    SearchRequest searchRequest = new SearchRequest(index);
    searchRequest.types(type);
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    // 设置查询前提
    sourceBuilder.query(QueryBuilders.regexpQuery("message","xu[0-9]"));
    searchRequest.source(sourceBuilder);
     System.out.println("正则查询的DSL语句:"+sourceBuilder.toString());
    // 同步查询
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    // 效果
    searchResponse.getHits().forEach(hit -> {
        Map<String, Object> map = hit.getSourceAsMap();
        String string = hit.getSourceAsString();
        System.out.println("正则查询的Map效果:" + map);
        System.out.println("正则查询的String效果:" + string);
    });

    System.out.println("\n=================\n");
}

查询测试效果

一切查询总数:6
一般查询的DSL语句:{“from”:0,”size”:5,”timeout”:”60s”,”query”:{“term”:{“uid”:{“value”:”1234″,”boost”:1.0}}}}

=================

或查询语句:{“query”:{“bool”:{“must”:[{“bool”:{“should”:[{“term”:{“uid”:{“value”:1234,”boost”:1.0}}},{“term”:{“uid”:{“value”:12345,”boost”:1.0}}}],”adjust_pure_negative”:true,”boost”:1.0}},{“term”:{“phone”:{“value”:”12345678909″,”boost”:1.0}}}],”adjust_pure_negative”:true,”boost”:1.0}}}
或查询效果:{msgcode=1, uid=12345, phone=12345678909, message=qq, sendtime=2019-03-14 01:57:04}

=================

隐约查询语句:{“query”:{“bool”:{“must”:[{“wildcard”:{“message”:{“wildcard”:”xu“,”boost”:1.0}}}],”adjust_pure_negative”:true,”boost”:1.0}}}
隐约查询效果:{msgcode=2, uid=12345, phone=123456789019, sendtime=2019-03-14 01:57:04, message=xuwujing study Elasticsearch}
隐约查询效果:{uid=123456, phone=12345678909, message=xu1, sendtime=2019-03-14 01:57:04}

=================

存在查询的DSL语句:{“query”:{“exists”:{“field”:”msgcode”,”boost”:1.0}}}
存在查询的Map效果:{msgcode=2, uid=12345, phone=123456789019, sendtime=2019-03-14 01:57:04, message=xuwujing study Elasticsearch}
存在查询的String效果:{“uid”:12345,”phone”:123456789019,”msgcode”:2,”sendtime”:”2019-03-14 01:57:04″,”message”:”xuwujing study Elasticsearch”}
存在查询的Map效果:{msgcode=1, uid=12345, phone=12345678909, message=qq, sendtime=2019-03-14 01:57:04}
存在查询的String效果:{“uid”:”12345″,”phone”:”12345678909″,”message”:”qq”,”msgcode”:”1″,”sendtime”:”2019-03-14 01:57:04″}

=================

局限查询的DSL语句:{“query”:{“range”:{“sendtime”:{“from”:”2019-01-01 00:00:00″,”to”:”2019-12-31 23:59:59″,”include_lower”:true,”include_upper”:true,”boost”:1.0}}}}
局限查询的String效果:{“uid”:12345,”phone”:123456789019,”msgcode”:2,”sendtime”:”2019-03-14 01:57:04″,”message”:”xuwujing study Elasticsearch”}
局限查询的String效果:{“uid”:”123456″,”phone”:”12345678909″,”message”:”xu1″,”sendtime”:”2019-03-14 01:57:04″}
局限查询的String效果:{“uid”:”12345″,”phone”:”12345678909″,”message”:”qq”,”msgcode”:”1″,”sendtime”:”2019-03-14 01:57:04″}

=================

正则查询的DSL语句:{“query”:{“regexp”:{“message”:{“value”:”xu[0-9]”,”flags_value”:65535,”max_determinized_states”:10000,”boost”:1.0}}}}
正则查询的Map效果:{uid=123456, phone=12345678909, message=xu1, sendtime=2019-03-14 01:57:04}
正则查询的String效果:{“uid”:”123456″,”phone”:”12345678909″,”message”:”xu1″,”sendtime”:”2019-03-14 01:57:04″}

=================

组合查询的DSL语句:{“query”:{“bool”:{“must”:[{“term”:{“uid”:{“value”:12345,”boost”:1.0}}},{“term”:{“msgcode”:{“value”:1,”boost”:1.0}}}],”adjust_pure_negative”:true,”boost”:1.0}}}
组合查询的String效果:{“uid”:”12345″,”phone”:”12345678909″,”message”:”qq”,”msgcode”:”1″,”sendtime”:”2019-03-14 01:57:04″}

=================

别的

参考ES官方文档:
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html

关于SpringBoot集成ElasticSearch和JestClient的运用能够检察这篇文章:SpringBoot整合ElasticSearch完成多版本的兼容

关于ElasticSearch Java API的挑选,假如ElasticSearch版本在6.x之前的话,引荐运用JestClient。假如是6.x以后而且故意升级到7.x的话,那末直接运用ES官方的Java High Level REST Client,由于在7.x以后将直接会舍弃Transport client的衔接体式格局,现在Spring和SpringBoot集成的ES就是运用该体式格局(不知后续是不是会做调解)。

本篇文章的代码已收录在本人的java-study项目中,如有兴致,迎接star、fork和issues。
项目地点:https://github.com/xuwujing/java-study

ElasticSearch实战系列:
ElasticSearch实战系列一: ElasticSearch集群+Kinaba装置教程
ElasticSearch实战系列二: ElasticSearch的DSL语句运用教程—图文详解

音乐引荐

原创不容易,假如觉得不错,愿望给个引荐!您的支撑是我写作的最大动力!
版权声明:
作者:虚无境
博客园出处:http://www.cnblogs.com/xuwujing
CSDN出处:http://blog.csdn.net/qazwsxpcm    
个人博客出处:http://www.panchengming.com

Up Next:

青梅竹马的爱恨情仇--java =+和+=揭秘

青梅竹马的爱恨情仇--java =+和+=揭秘