Elasticsearch從入門到放棄:文檔CRUD要牢記

在Elasticsearch中,文檔(document)是所有可搜索數據的最小單位。它被序列化成JSON存儲在Elasticsearch中。每個文檔都會有一個唯一ID,這個ID你可以自己指定或者交給Elasticsearch自動生成。

如果延續我們之前不恰當的對比RDMS的話,我認為文檔可以類比成關係型數據庫中的表。

元數據

前面我們提到,每個文檔都有一個唯一ID來標識,獲取文檔時,“_id”字段記錄的就是文檔的唯一ID,它是元數據之一。當然,文檔還有一些其他的元數據,下面我們來一一介紹

  • _index:文檔所屬的索引名
  • _type:文檔所屬的type
  • _id:文檔的唯一ID

有了這三個,我們就可以唯一確定一個document了,當然,7.0版本以後我們已經不需要_type了。接下來我們再來看看其他的一些元數據

  • _source:文檔的原始JSON數據
  • _field_names:該字段用於索引文檔中值不為null的字段名,主要用於exists請求查找指定字段是否為空
  • _ignore:這個字段用於索引和存儲文檔中每個由於異常(開啟了ignore_malformed)而被忽略的字段的名稱
  • _meta:該字段用於存儲一些自定義的元數據信息
  • _routing:用來指定數據落在哪個分片上,默認值是Id
  • _version:文檔的版本信息
  • _score:相關性打分

創建文檔

創建文檔有以下4種方法:

  • PUT /<index>/_doc/<_id>
  • POST /<index>/_doc/
  • PUT /<index>/_create/<_id>
  • POST /<index>/_create/<_id>

這四種方法的區別是,如果不指定id,則Elasticsearch會自動生成一個id。如果使用_create的方法,則必須保證文檔不存在,而使用_doc方法的話,既可以創建新的文檔,也可以更新已存在的文檔。

在創建文檔時,還可以選擇一些參數。

請求參數

  • if_seq_no:當文檔的序列號是指定值時才更新
  • if_primary_term:當文檔的primary term是指定值時才更新
  • op_type:如果設置為create則指定id的文檔必須不存在,否則操作失敗。有效值為index或create,默認為index
  • op_type:指定預處理的管道id
  • refresh:如果設置為true,則立即刷新受影響的分片。如果是wait_for,則會等到刷新分片后,此次操作才對搜索可見。如果是false,則不會刷新分片。默認值為false
  • routing:指定路由到的主分片
  • timeout:指定響應時間,默認是30秒
  • master_timeout:連接主節點的響應時長,默認是30秒
  • version:顯式的指定版本號
  • version_type:指定版本號類型:internal、 external、external_gte、force
  • wait_for_active_shards:處理操作之前,必須保持活躍的分片副本數量,可以設置為all或者任意正整數。默認是1,即只需要主分片活躍。

響應包體

  • **_shards**:提供分片的信息
  • **_shards.total**:創建了文檔的總分片數量
  • **_shards.successful**:成功創建文檔分片的數量
  • **_shards.failed**:創建文檔失敗的分片數量
  • **_index**:文檔所屬索引
  • **_type**:文檔所屬type,目前只支持_doc
  • **_id**:文檔的id
  • **_version**:文檔的版本號
  • **_seq_no**:文檔的序列號
  • **_primary_term**:文檔的主要術語
  • result:索引的結果,created或者updated

我們在創建文檔時,如果指定的索引不存在,則ES會自動為我們創建索引。這一操作是可以通過設置中的action.auto_create_index字段來控制的,默認是true。你可以修改這個字段,實現指定某些索引可以自動創建或者所有索引都不能自動創建的目的。

更新文檔

了解了如何創建文檔之後,我們再來看看應該如何更新一個已經存在的文檔。其實在創建文檔時我們就提到過,使用PUT /<index>/_doc/<id>的方法就可以更新一個已存在的文檔。除此之外,我們還有另一種更新文檔的方法:

POST /<index>/_update/<_id>

這兩種更新有所不同。_doc方法是先刪除原有的文檔,再創建新的。而_update方法則是增量更新,它的更新過程是先檢索到文檔,然後運行指定腳本,最後重新索引。

還有一個區別就是_update方法支持使用腳本更新,默認的語言是painless,你可以通過參數lang來進行設置。在請求參數方面,_update相較於_doc多了以下幾個參數:

  • lang:指定腳本語言
  • retry_on_conflict:發生衝突時重試次數,默認是0
  • **_source**:設置為false,則不返回任何檢索字段
  • **_source_excludes**:指定要從檢索結果排除的source字段
  • **_source_includes**:指定要返回的檢索source字段

下面的一個例子是用腳本來更新文檔

curl -X POST "localhost:9200/test/_update/1?pretty" -H 'Content-Type: application/json' -d'
{
    "script" : {
        "source": "ctx._source.counter += params.count",
        "lang": "painless",
        "params" : {
            "count" : 4
        }
    }
}
'

Upsert

curl -X POST "localhost:9200/test/_update/1?pretty" -H 'Content-Type: application/json' -d'
{
    "script" : {
        "source": "ctx._source.counter += params.count",
        "lang": "painless",
        "params" : {
            "count" : 4
        }
    },
    "upsert" : {
        "counter" : 1
    }
}
'

當指定的文檔不存在時,可以使用upsert參數,創建一個新的文檔,而當指定的文檔存在時,該請求會執行script中的腳本。如果不想使用腳本,而只想新增/更新文檔的話,可以使用doc_as_upsert。

curl -X POST "localhost:9200/test/_update/1?pretty" -H 'Content-Type: application/json' -d'
{
    "doc" : {
        "name" : "new_name"
    },
    "doc_as_upsert" : true
}
'

update by query

這個API是用於批量更新檢索出的文檔的,具體可以通過一個例子來了解。

curl -X POST "localhost:9200/twitter/_update_by_query?pretty" -H 'Content-Type: application/json' -d'
{
  "script": {
    "source": "ctx._source.likes++",
    "lang": "painless"
  },
  "query": {
    "term": {
      "user": "kimchy"
    }
  }
}
'

獲取文檔

ES獲取文檔用的是GET API,請求的格式是:

GET /<index>/_doc/<_id>

它會返迴文檔的數據和一些元數據,如果你只想要文檔的內容而不需要元數據時,可以使用

GET /<index>/_source/<_id>

請求參數

獲取文檔的有幾個請求參數之前已經提到過,這裏不再贅述,它們分別是:

  • refresh
  • routing
  • **_source**
  • **_source_excludes**
  • **_source_includes**
  • version
  • version_type

而還有一些之前沒提到過的參數,我們來具體看一下

  • preference:用來 指定執行請求的node或shard,如果設置為_local,則會優先在本地的分片執行
  • realtime:如果設置為true,則請求是實時的而不是近實時。默認是true
  • stored_fields:返回指定的字段中,store為true的字段

mget

mget是批量獲取的方法之一,請求的格式有兩種:

  • GET /_mget
  • GET /<index>/_mget

第一種是在請求體中寫index。第二種是把index放到url中,不過這種方式可能會觸發ES的安全檢查。

mget的請求參數和get相同,只是需要在請求體中指定doc的相關檢索條件

request

GET /_mget
{
    "docs" : [
        {
            "_index" : "jackey",
            "_id" : "1"
        },
        {
            "_index" : "jackey",
            "_id" : "2"
        }
    ]
}

response

{
  "docs" : [
    {
      "_index" : "jackey",
      "_type" : "_doc",
      "_id" : "1",
      "_version" : 5,
      "_seq_no" : 6,
      "_primary_term" : 1,
      "found" : true,
      "_source" : {
        "user" : "ja",
        "tool" : "ES",
        "message" : "qwer"
      }
    },
    {
      "_index" : "jackey",
      "_type" : "_doc",
      "_id" : "2",
      "_version" : 1,
      "_seq_no" : 2,
      "_primary_term" : 1,
      "found" : true,
      "_source" : {
        "user" : "zhe",
        "post_date" : "2019-11-15T14:12:12",
        "message" : "learning Elasticsearch"
      }
    }
  ]
}

刪除文檔

CURD操作只剩下最後一個D了,下面我們就一起來看看ES中如何刪除一個文檔。

刪除指定id使用的請求是

DELETE /<index>/_doc/<_id>

在併發量比較大的情況下,我們在刪除時通常會指定版本,以確定刪除的文檔是我們真正想要刪除的文檔。刪除請求的參數我們在之前也都介紹過,想要具體了解的同學可以直接查看。

delete by query

類似於update,delete也有一個delete by query的API。

POST /<index>/_delete_by_query

它也是要先按照條件來查詢匹配的文檔,然後刪除這些文檔。在執行查詢之前,Elasticsearch會先為指定索引做一個快照,如果在執行刪除過程中,要索引發生改變,則會導致操作衝突,同時返回刪除失敗。

如果刪除的文檔比較多,也可以使這個請求異步執行,只需要設置wait_for_completion=false即可。

這個API的refresh與delete API的refresh參數有所不同,delete中的refresh參數是設置操作是否立即可見,即只刷新一個分片,而這個API中的refresh參數則是需要刷新受影響的所有分片。

Bulk API

最後,我們再來介紹一種特殊的API,批量操作的API。它支持兩種寫法,可以將索引名寫到url中,也可以寫到請求體中。

  • POST /_bulk

  • POST /<index>/_bulk

在這個請求中,你可以任意使用之前的CRUD請求的組合。

curl -X POST "localhost:9200/_bulk?pretty" -H 'Content-Type: application/json' -d'
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
'

請求體中使用的語法是newline delimited JSON(NDJSON)。具體怎麼用呢?其實我們在上面的例子中已經有所展現了,對於index或create這樣的請求,如果請求本身是有包體的,那麼用換行符來表示下面的內容與子請求分隔,即為包體的開始。

例如上面例子中的index請求,它的包體就是{ “field1” : “value1” },所以它會在index請求的下一行出現。

對於批量執行操作來說,單條操作失敗並不會影響其他操作,而最終每條操作的結果也都會返回。

上面的例子執行完之後,我們得到的結果應該是

{
   "took": 30,
   "errors": false,
   "items": [
      {
         "index": {
            "_index": "test",
            "_type": "_doc",
            "_id": "1",
            "_version": 1,
            "result": "created",
            "_shards": {
               "total": 2,
               "successful": 1,
               "failed": 0
            },
            "status": 201,
            "_seq_no" : 0,
            "_primary_term": 1
         }
      },
      {
         "delete": {
            "_index": "test",
            "_type": "_doc",
            "_id": "2",
            "_version": 1,
            "result": "not_found",
            "_shards": {
               "total": 2,
               "successful": 1,
               "failed": 0
            },
            "status": 404,
            "_seq_no" : 1,
            "_primary_term" : 2
         }
      },
      {
         "create": {
            "_index": "test",
            "_type": "_doc",
            "_id": "3",
            "_version": 1,
            "result": "created",
            "_shards": {
               "total": 2,
               "successful": 1,
               "failed": 0
            },
            "status": 201,
            "_seq_no" : 2,
            "_primary_term" : 3
         }
      },
      {
         "update": {
            "_index": "test",
            "_type": "_doc",
            "_id": "1",
            "_version": 2,
            "result": "updated",
            "_shards": {
                "total": 2,
                "successful": 1,
                "failed": 0
            },
            "status": 200,
            "_seq_no" : 3,
            "_primary_term" : 4
         }
      }
   ]
}

批量操作的執行過程相比多次單個操作而言,在性能上會有一定的提升。但同時也會有一定的風險,所以我們在使用的時候要非常的謹慎。

總結

本文我們先介紹了文檔的基本概念和文檔的元數據。接着又介紹了文檔的CRUD操作和Bulk API。相信看完文章你對Elasticsearch的文檔也會有一定的了解。那最後就請你啟動你的Elasticsearch,然後親自動手試一試這些操作,看看各種請求的參數究竟有什麼作用。相信親手實驗過一遍之後你會對這些API有更深的印象。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

設計模式之代理模式

什麼是代理模式

代理模式就是為一個對象提供一個代理對象,由這個代理對象控制對該對象的訪問。

理解代理模式,可以對照生活中的一些具體例子,比如房產中介、二手車交易市場、經紀人等。

為什麼要用代理模式

通過使用代理模式,我們避免了直接訪問目標對象時可能帶來的一些問題,比如:遠程調用,需要使用遠程代理來幫我們處理一些網絡傳輸相關的細節邏輯;可能需要基於某種權限控制對目標資源的訪問,可以使用保護代理等。

總的來說,通過是用代理模式,我們可以控制對目標對象的訪問,可以在真實方法被調用前或調用后,通過代理對象加入額外的處理邏輯。

代理模式分類

代理模式分為靜態代理和動態代理。動態代理根據實現不同又可細分為JDK動態代理和cglib動態代理。

靜態代理是由程序員創建或工具生成代理類的源碼,再編譯代理類。所謂靜態也就是在程序運行前就已經存在代理類的字節碼文件,代理類和委託類的關係在運行前就確定了。

動態代理是在實現階段不用關心代理類,而在運行時動態生成代理類的。

靜態代理

以房哥買房子為例,用代碼實現靜態代理。

1、首先建立一個Seller接口

public interface Seller {
    void sell();
}

2、創建實現類,房哥,有一個方法,就是買房子

public class FangGe implements Seller{
    @Override
    public void sell() {
        System.out.println("房哥要出手一套四合院");
    }
}

3、買房子需要找到買家,達成交易后還要辦理過戶等其他手續,房哥只想賣房收錢就完了。因此,需要找一個代理來幫房哥處理這些雜事。

我們創建一個代理類FangGeProxy,代理類也需要實現Seller接口,行為上要保持和FangGe一樣,都是要賣房子。同時該代理類還需要持有房哥的引用。

public class FangGeProxy implements Seller{
    private FangGe fangGe;

    public FangGeProxy(FangGe fangGe){
        this.fangGe = fangGe;
    }
    @Override
    public void sell() {
        findBuyer();
        fangGe.sell();
        afterSell();
    }
    
    public void findBuyer(){
        System.out.println("代理幫助尋找買主");
    }
    
    public void afterSell(){
        System.out.println("達成交易后,辦理相關手續");
    }
}

可以看到,房哥的代理類通過findBuyer()和afterSell()兩個方法幫助房哥完成了其他一些雜事。

4、測試類

public class StaticProxyTest {
    public static void main(String[] args) {
        Seller seller = new FangGeProxy(new FangGe());
        seller.sell();
    }
}

輸出:

代理幫助尋找買主
房哥要出手一套四合院
達成交易后,辦理相關手續

最後,看下類圖

靜態代理的問題:

1、由於靜態代理類在編譯前已經確定了代理的對象,因此靜態代理只能代理一種類型的類,如果要給大量的類做代理,就需要編寫大量的代理類;

2、如果我們要給Seller,也就是目標對象要增加一些方法,則需要同步修改代理類,不符合開閉原則。

JDK動態代理

JDK的動態代理依賴於jdk給我們提供的類庫實現,是一種基於接口實現的動態代理,在編譯時並不知道要代理哪個類,而是在運行時動態生成代理類。同時也解決了靜態代理中存在的問題。

我們接上上面靜態代理的例子,繼續實現JDK的動態代理。

1、我們建一個方法轉發的處理器類,該類需要實現InvocationHandler接口。

public class SellerInvocationHandler implements InvocationHandler {

    // 要代理的真實對象
    private Object target;

    /**
     * 使用Proxy類靜態方法獲取代理類實例
     */
    public Object getProxyInstance(Object target){
        this.target = target;
        Class<?> clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object obj = method.invoke(this.target, args);
        after();
        return obj;
    }

    private void before() {
        System.out.println("執行方法前");
    }
    
    private void after() {
        System.out.println("執行方法后");
    }
}

2、新建JDK動態代理測試類,首先代理房哥賣房子

public class JDKDynamicProxyTest {
    public static void main(String[] args) {

        // new一個房哥,下面幫房哥找個代理
        FangGe fangGe = new FangGe();
        SellerInvocationHandler sellerInvocationHandler = new SellerInvocationHandler();
        
        // 房哥的代理對象
        Seller seller = (Seller) sellerInvocationHandler.getProxyInstance(fangGe);
        seller.sell();

    }
}

輸出:

執行方法前
房哥要出手一套四合院
執行方法后

可以看到,完成了代理。

3、接下來我們新建另外一個類,User類,並使用JDK動態代理完成代理User類

public interface IUser {
    void sayHello();

    void work();
}

public class UserImpl implements IUser{
    @Override
    public void sayHello() {
        System.out.println("hello,我是小明");
    }

    @Override
    public void work() {
        System.out.println("我正在寫代碼");
    }
}

修改測試類,

public class JDKDynamicProxyTest {
    public static void main(String[] args) {

/*        // new一個房哥,下面幫房哥找個代理
        FangGe fangGe = new FangGe();
        SellerInvocationHandler sellerInvocationHandler = new SellerInvocationHandler();

        // 房哥的代理對象
        Seller seller = (Seller) sellerInvocationHandler.getProxyInstance(fangGe);
        seller.sell();*/

        // 代理user類
        IUser user = new UserImpl();
        SellerInvocationHandler sellerInvocationHandler = new SellerInvocationHandler();
        IUser userProxy = (IUser) sellerInvocationHandler.getProxyInstance(user);
        userProxy.sayHello();
        userProxy.work();

    }
}

輸出:

執行方法前
hello,我是小明
執行方法后
執行方法前
我正在寫代碼
執行方法后

可以看到,我們SellerInvocationHandler 並未做任何改動,它便能為UserImpl類生成代理,並在執行方法的前後增加額外的執行邏輯。

cglib動態代理

JDK動態代理有一個局限就是,被代理的類必須要實現接口。如果被代理的類沒有實現接口,則JDK動態代理就無能為力了。這個時候該cglib動態代理上場了。

CGLIB是一個功能強大,高性能的代碼生成包。它為沒有實現接口的類提供代理,為JDK的動態代理提供了很好的補充。通常可以使用Java的動態代理創建代理,但當要代理的類沒有實現接口或者為了更好的性能,CGLIB是一個好的選擇。

1、新建一個MyCglibInterceptor,實現MethodInterceptor接口。該類類似於JDK動態代理中的InvocationHandler實例,是實現cglib動態代理的主要類。

public class MyCglibInterceptor implements MethodInterceptor {

    public Object getCglibProxyInstance(Object object){
        // 相當於Proxy,創建代理的工具類
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(object.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object obj = methodProxy.invokeSuper(o, objects);
        after();
        return obj;
    }

    private void before() {
        System.out.println("執行方法之前");
    }

    private void after() {
        System.out.println("執行方法之後");
    }
}

2、新建cglib動態代理的測試類,先代理上面例子中的User類。

public class CglibDynamicProxyTest {
    public static void main(String[] args) {
        MyCglibInterceptor myCglibInterceptor = new MyCglibInterceptor();
        IUser userCglibProxy = (IUser) myCglibInterceptor.getCglibProxyInstance(new UserImpl());
        userCglibProxy.sayHello();
        userCglibProxy.work();
    }
}

輸出:

執行方法之前
hello,我是小明
執行方法之後
執行方法之前
我正在寫代碼
執行方法之後

3、新建一個類HelloWorld,不實現任何接口,為該類實現動態代理。

public class HelloWorld {
    public void hello(){
        System.out.println("世界這麼大,我想去看看");
    }
}

測試代理類

public class CglibDynamicProxyTest {
    public static void main(String[] args) {
/*        MyCglibInterceptor myCglibInterceptor = new MyCglibInterceptor();
        IUser userCglibProxy = (IUser) myCglibInterceptor.getCglibProxyInstance(new UserImpl());
        userCglibProxy.sayHello();
        userCglibProxy.work();*/

        // 代理未實現任何接口的類
        MyCglibInterceptor myCglibInterceptor = new MyCglibInterceptor();
        HelloWorld helloWorldProxy = (HelloWorld) myCglibInterceptor.getCglibProxyInstance(new HelloWorld());
        helloWorldProxy.hello();
    }
}

輸出:

執行方法之前
世界這麼大,我想去看看
執行方法之後

使用cglib動態代理,我們實現了對普通類的代理。

(完)

設計模式系列文章

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

Java併發之volatile關鍵字

引言

說到多線程,我覺得我們最重要的是要理解一個臨界區概念。

舉個例子,一個班上1個女孩子(臨界區),49個男孩子(線程),男孩子的目標就是這一個女孩子,就是會有競爭關係(線程安全問題)。推廣到實際場景,例如對一個數相加或者相減等等情形,因為操作對象就只有一個,在多線程環境下,就會產生線程安全問題。理解臨界區概念,我們對多線程問題可以有一個好意識。

Jav內存模型(JMM)

談到多線程就應該了解一下Java內存模型(JMM)的抽象示意圖.下圖:

線程A和線程B執行的是時候,會去讀取共享變量(臨界區),然後各自拷貝一份回到自己的本地內存,執行後續操作。
JMM模型是一種規範,就像Java的接口一樣。JMM會涉及到三個問題:原子性,可見性,有序性。
所謂原子性。就是說一個線程的執行會不會被其他線程影響的。他是不可中斷的。舉個例子:

int i=1

這個語句在Jmm中就是原子性的。無論是一個線程執行還是多個線程執行這個語句,讀出來的i就是等於1。那什麼是非原子性呢,按道理如果Java的代碼都是原子性,應該就不會有線程問題了啊。其實JMM這是規定某些語句是原子性罷了。舉個非原子性例子:

i ++;

這個操作就不是原子性的了。因為他就是包含了三個操作:第一讀取i的值,第二將i加上1,第三將結果賦值回來給i,更新i的值。
所謂可見性。可見性表示如果一個值在線程A修改了,線程B就會馬上知道這個結果。
所謂有序性。所謂有序性值的是語意的有序性。就是說代碼順序可能會發生變化。因為有一個指令重排機制。所謂指令重排,他會改變代碼執行順序,為了讓cpu執行效率更高。為了防止重排序出錯,JMM有個happen-before規則,這個規則限制了那些語句執行在前,那些語句執行在後。
Happen-before:
程序順序原則:一個線程內保證語義的串行性
volatile原則:volatile變量的寫發生在讀之前
鎖規則:先加鎖再解鎖
傳遞性:a先於b,b先於c,則a必定先於c
線程的start方法先於他的每一個操作
線程所有的操作先於線程的終結
對象的構造函數執行、結束先於finalize()方法。

volatile

進入正題,volatile可以保證變量(臨界區)的可見性以及有序性,但是不能保證原子性。舉個例子:

public class VolatileTest implements Runnable{
    private static VolatileTest volatileTest = new VolatileTest();
    private  static volatile int i= 0;
    public static void main(String[] args) throws InterruptedException {
        for (int j = 0; j < 20; j++) {
            Thread a = new Thread(new VolatileTest());
            Thread b = new Thread(new VolatileTest());
            a.start();b.start();
            a.join();b.join();
            System.out.print(i+"&&");
        }

    }
    
    @Override
    public void run() {
        for (int j = 0; j < 1000; j++) {
            i++;
        }
    }

}

// 輸出結果
// 2000&&4000&&5852&&7852&&9852&&11852&&13655&&15655&&17655&&19655&&21306     
//&&22566&&24566&&26189&&28189&&30189&&32189&&34189&&36189&&38089&&

有結果看到有問題,雖然i已經添加了volatile關鍵字,說明volatile關鍵字不能保證i++的原子性。

那什麼場景適合使用volatile關鍵字

  1. 輕量級的“讀-寫鎖”策略
private volatile int value;
public int getValue(){ return value;}
public synchronized void doubleValue(){ value = value*value; }

2.單例模式(雙檢查鎖機制

private volatile static Singleton instace;   
public static Singleton getInstance(){  // 沒有使用同步方法,而是同步方法塊
    //第一次null檢查 ,利用volatile的線程間可見性,不需要加鎖,性能提高    
    if(instance == null){            
        synchronized(Singleton.class) {    //鎖住類對象,阻塞其他線程
            //第二次null檢查,以保證不會創建重複的實例       
            if(instance == null){       
                instance = new Singleton(); // 禁止重排序
            }  
        }           
    }  
    return instance;

參考

《現代操作系統(第三版)中文版》
《實戰Java高併發程序設計》
《Java併發編程的藝術》

如果我的文章幫助到您,可以關注我的微信公眾號,第一時間分享文章給您

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

EF Core For MySql查詢中使用DateTime.Now作為查詢條件的一個小問題

背景

最近一直忙於手上澳洲線上項目的整體遷移和升級的準備工作,導致博客和公眾號停更。本周終於艱難的完成了任務,藉此機會,總結一下項目中遇到的一些問題。

EF Core一直是我們團隊中中小型項目常用的ORM框架,在使用SQL Server作為持久化倉儲的場景一下,一直表現還中規中矩。但是在本次項目中,項目使用了MySql作為持久化倉儲。為了與EF Core集成,團隊使用了Pomelo.EntityFrameworkCore.MySql作為EF Core For MySql的擴展。在開發過程中,團隊遇到了各種各樣在SQL Server場景下沒有遇到過的問題,其中最奇怪的,也是隱藏最深的問題,就是將DateTime.Now作為查詢條件,產生了非預期的結果。

問題場景

本周在項目升級的過程中,客戶反饋了一個問題。

在當前系統的Dashboard頁面,有一個消息提醒功能,客戶可以自定義一些消息,並且指定提醒的日期。客戶遇到的問題是通常添加的消息提醒,在指定日期的上午時間段是不會显示,只有在下午時間段才能看到,比如說客戶指定2019年10月26號看到一個的消息提醒,但是在10月26日這天早上8:00-12:00這個時間段,系統總是看不到提醒,只有到了下午的時間段才能看到提醒。

PS:這裏客戶表達的只是個籠統的問題,但問題確實是上午的大部分時間是看不到消息提醒的,但並不是精確到中午12:00點這個時間, 所以此處不必過於糾結於具體的時間。

查看問題代碼

看到這個問題的時候,我自己也很奇怪,難道代碼或者數據庫使用了時區,導致查詢出現了偏差?

於是我就Review了一下此處的查詢, 代碼如下。

var query = DbContext.CRM_Note_Reminders
    .Include(x => x.CRM_Note)
    .Where(x => !x.CRM_Note.Is_Deleted 
             && !x.Is_Deleted
             && x.Reminder_Date.Date <= DateTime.Now.Date)
     .ToList();

PS: 這裏可能有同學會有疑問,為啥不用DbFunctions.DiffDays? 原因是DbFunctions.DiffDays是 EF Core for SQLServer的擴展方法,針對MySql還沒有官方的實現方案。

從這個查詢中,我沒有看出任何問題,於是我直接藉助一些日誌工具,將EF Core生成的查詢語句的輸出了出來。

其中WHERE條件部分如下:

WHERE (((`x.CRM_Note`.`Is_Deleted` = FALSE) 
AND (`x`.`Is_Deleted` = FALSE))
AND (CONVERT(`x`.`Reminder_Date`, date) 
  <= CONVERT(CURRENT_TIMESTAMP(), date)))

這裏CURRENT_TIMESTAMP()是MySql的內置函數,與SQLServer的內置函數GETDATE()不同,CURRENT_TIMESTAMP()默認返回的是UTC時間。因此我們大概能知道,為什麼澳洲客戶會遇到上面的場景了。

PS: 根據7樓兄弟的反饋,我試了一下,改動Mysql的時區配置之後,果然CURRENT_TIMESTAMP()就改為了對應時區的時間。這裏使用UTC時間的原因應該是我在AWS RDS上創建Mysql實例的時候,忽略了時區配置。

由於澳洲處於東10區,與UTC時間有+10個小時的時差,所以當澳洲上午的10點之前,UTC時間都是在當前澳洲日期的前一天,所以系統中出現了當天的消息提醒在上午時間段不能正常显示的問題。

PS: 由於澳洲是分冬令時和夏令時的,夏令時時間要加一個小時,所以實際上客戶在每天的11點之前都無法看到正確的消息提醒。

深入思考

你這可能會非常奇怪,為什麼DateTime.Now會被轉化成內置函數CURRENT_TIMESTAMP(),而沒有使用我們傳入的值DateTime.Now.Date呢?

其實EF/EF Core在查詢是時候是分2個階段的,一個是組合查詢表達式樹的階段,一個是真正的查詢階段。

在組合查詢表達式樹的階段,EF/EF Core只會去組合表達式,而不會去嘗試計算表達式的值,所以這個階段DateTime.Now.Date的值並沒有被計算出來, 在進入正常查詢階段的時候, EF/EF Core會嘗試將查詢表達式樹翻譯成SQL腳本,這時候由於我們的EF ProviderMySql Provider, 恰巧DateTime.Now可以翻譯成Mysql的內置函數CURRENT_TIMESTAMP(), 所以這裏EF/EF Core就跳過了表達式值的計算,直接將其翻譯成了對應的內置函數,所以導致生成的SQL查詢和我們的預期有偏差。

那麼我們該如何解決這個問題呢?

解決方案

經過了以上的思考,其實解決這個問題也就很簡單了,我們可以將DateTime.Now.Date先計算出來,保存在一個變量中,然後將這個變量傳入查詢中。

var today = DateTime.Now.Date;

var query = DbContext.CRM_Note_Reminders
     .Include(x => x.CRM_Note)
     .Where(x => !x.CRM_Note.Is_Deleted 
             && !x.Is_Deleted
             && x.Reminder_Date.Date <= today)
     .ToList();

由此生成的MySQL腳本如下:

WHERE (((`x.CRM_Note`.`Is_Deleted` = FALSE) 
AND (`x`.`Is_Deleted` = FALSE)) 
AND (CONVERT(`x`.`Reminder_Date`, date) <= @__date_0)) 

這樣我們就得到了一個正確的結果,澳洲客戶也就收到了正確的消息。

是不是有種差之毫厘,謬以千里的感覺呢?

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

Java描述設計模式(23):訪問者模式

本文源碼: ||

一、生活場景

1、場景描述

電競是遊戲比賽達到“競技”層面的體育項目。利用电子設備作為運動器械進行的、人與人之間的智力對抗運動。通過電競,可以提高人的反應能力、協調能力、團隊精神等。但是不同人群的對電競的持有的觀念不一樣,有的人認為電競就是沉迷網絡,持反對態度,而有的人就比較贊同。下面基於訪問者模式來描述該場景。

2、場景圖解

3、代碼實現

public class C01_InScene {
    public static void main(String[] args) {
        DataSet dataSet = new DataSet() ;
        dataSet.addCrowd(new Youth());
        dataSet.addCrowd(new MiddleAge());
        CrowdView crowdView = new Against() ;
        dataSet.display(crowdView);
        crowdView = new Approve() ;
        dataSet.display(crowdView);
    }
}
/**
 * 雙分派,不同人群管理
 */
abstract class Crowd {
    abstract void accept(CrowdView action);
}
class Youth extends Crowd {
    @Override
    public void accept(CrowdView view) {
        view.getYouthView(this);
    }
}
class MiddleAge extends Crowd {
    @Override
    public void accept(CrowdView view) {
        view.getMiddleAgeView (this);
    }
}
/**
 * 不同人群觀念的管理
 */
abstract class CrowdView {
    // 青年人觀念
    abstract void getYouthView (Youth youth);
    // 中年人觀念
    abstract void getMiddleAgeView (MiddleAge middleAge);
}
class Approve extends CrowdView {
    @Override
    public void getYouthView(Youth youth) {
        System.out.println("青年人贊同電競");
    }
    @Override
    public void getMiddleAgeView(MiddleAge middleAge) {
        System.out.println("中年人贊同電競");
    }
}
class Against extends CrowdView {
    @Override
    public void getYouthView(Youth youth) {
        System.out.println("青年人反對電競");
    }
    @Override
    public void getMiddleAgeView(MiddleAge middleAge) {
        System.out.println("中年人反對電競");
    }
}
/**
 * 提供一個數據集合
 */
class DataSet {
    private List<Crowd> crowdList = new ArrayList<>();
    public void addCrowd (Crowd crowd) {
        crowdList.add(crowd);
    }
    public void display(CrowdView crowdView) {
        for(Crowd crowd : crowdList) {
            crowd.accept(crowdView);
        }
    }
}

二、訪問者模式

1、基礎概念

訪問者模式是對象的行為模式,把作用於數據結構的各元素的操作封裝,操作之間沒有關聯。可以在不改變數據結構的前提下定義作用於這些元素的不同的操作。主要將數據結構與數據操作分離,解決數據結構和操作耦合問題核心原理:被訪問的類裏面加對外提供接待訪問者的接口。

2、模式圖解

3、核心角色

  • 抽象訪問者角色

聲明多個方法操作,具體訪問者角色需要實現的接口。

  • 具體訪問者角色

實現抽象訪問者所聲明的接口,就是各個訪問操作。

  • 抽象節點角色

聲明接受操作,接受訪問者對象作為參數。

  • 具體節點角色

實現抽象節點所規定的具體操作。

  • 結構對象角色

能枚舉結構中的所有元素,可以提供一個高層的接口,用來允許訪問者對象訪問每一個元素。

4、源碼實現

public class C02_Visitor {
    public static void main(String[] args) {
        ObjectStructure obs = new ObjectStructure();
        obs.add(new NodeA());
        obs.add(new NodeB());
        Visitor visitor = new VisitorA();
        obs.doAccept(visitor);
    }
}
/**
 * 抽象訪問者角色
 */
interface Visitor {
    /**
     * NodeA的訪問操作
     */
    void visit(NodeA node);
    /**
     * NodeB的訪問操作
     */
    void visit(NodeB node);
}
/**
 * 具體訪問者角色
 */
class VisitorA implements Visitor {
    @Override
    public void visit(NodeA node) {
        node.operationA() ;
    }
    @Override
    public void visit(NodeB node) {
        node.operationB() ;
    }
}
class VisitorB implements Visitor {
    @Override
    public void visit(NodeA node) {
        node.operationA() ;
    }
    @Override
    public void visit(NodeB node) {
        node.operationB() ;
    }
}
/**
 * 抽象節點角色
 */
abstract class Node {
    /**
     * 接收訪問者
     */
    abstract void accept(Visitor visitor);
}
/**
 * 具體節點角色
 */
class NodeA extends Node{
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    public void operationA(){
        System.out.println("NodeA.operationA");
    }
}
class NodeB extends Node{
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    public void operationB(){
        System.out.println("NodeB.operationB");
    }
}
/**
 * 結構對象角色類
 */
class ObjectStructure {
    private List<Node> nodes = new ArrayList<>();
    public void detach(Node node) {
        nodes.remove(node);
    }
    public void add(Node node){
        nodes.add(node);
    }
    public void doAccept(Visitor visitor){
        for(Node node : nodes) {
            node.accept(visitor);
        }
    }
}

三、Spring框架應用

1、Bean結構的訪問

BeanDefinitionVisitor類,遍歷bean的各個屬性;接口 BeanDefinition,定義Bean的各樣信息,比如屬性值、構造方法、參數等等。這裏封裝操作bean結構的相關方法,但卻沒有改變bean的結構。

2、核心代碼塊

public class BeanDefinitionVisitor {
    public void visitBeanDefinition(BeanDefinition beanDefinition) {
        this.visitParentName(beanDefinition);
        this.visitBeanClassName(beanDefinition);
        this.visitFactoryBeanName(beanDefinition);
        this.visitFactoryMethodName(beanDefinition);
        this.visitScope(beanDefinition);
        if (beanDefinition.hasPropertyValues()) {
            this.visitPropertyValues(beanDefinition.getPropertyValues());
        }
        if (beanDefinition.hasConstructorArgumentValues()) {
            ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
            this.visitIndexedArgumentValues(cas.getIndexedArgumentValues());
            this.visitGenericArgumentValues(cas.getGenericArgumentValues());
        }
    }
}

四、模式總結

1、優點描述

(1) 訪問者模式符合單一職責原則、使程序具有良好的擴展性、靈活性;

(2) 訪問者模式適用與攔截器與過濾器等常見功能,數據結構相對穩定的場景;

2、缺點描述

(1) 訪問者關注其他類的內部細節,依賴性強,違反迪米特法則,這樣導致具體元素更新麻煩;

(2) 訪問者依賴具體元素,不是抽象元素,面向細節編程,違背依賴倒轉原則;

五、源代碼地址

GitHub·地址
https://github.com/cicadasmile/model-arithmetic-parent
GitEE·地址
https://gitee.com/cicadasmile/model-arithmetic-parent

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

將Swagger2文檔導出為HTML或markdown等格式離線閱讀

網上有很多《使用swagger2構建API文檔》的文章,該文檔是一個在線文檔,需要使用HTTP訪問。但是在我們日常使用swagger接口文檔的時候,有的時候需要接口文檔離線訪問,如將文檔導出為html、markdown格式。又或者我們不希望應用系統與swagger接口文檔使用同一個服務,而是導出HTML之後單獨部署,這樣做保證了對接口文檔的訪問不影響業務系統,也一定程度提高了接口文檔的安全性。核心的實現過程就是:

  • 在swagger2接口文檔所在的應用內,利用swagger2markup將接口文檔導出為adoc文件,也可以導出markdown文件。
  • 然後將adoc文件轉換為靜態的html格式,可以將html發布到nginx或者其他的web應用容器,提供訪問(本文不會講html靜態部署,只講HTML導出)。

注意:adoc是一種文件格式,不是我的筆誤。不是doc文件也不是docx文件。

一、maven依賴類庫

在已經集成了swagger2的應用內,通過maven坐標引入相關依賴類庫,pom.xml代碼如下:

<dependency>
    <groupId>io.github.swagger2markup</groupId>
    <artifactId>swagger2markup</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-core</artifactId>
    <version>1.5.16</version>
</dependency>
<dependency>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-models</artifactId>
    <version>1.5.16</version>
</dependency>

swagger2markup用於將swagger2在線接口文檔導出為html,markdown,adoc等格式文檔,用於靜態部署或離線閱讀。其中第一個maven坐標是必須的。后兩個maven坐標,當你在執行後面的代碼過程中報下圖中的ERROR,或者有的類無法import的時候使用。

產生異常的原因已經有人在github的issues上給出解釋了:當你使用swagger-core版本大於等於1.5.11,並且swagger-models版本小於1.5.11就會有異常發生。所以我們顯式的引入這兩個jar,替換掉swagger2默認引入的這兩個jar。

二、生成adoc格式文件

下面的代碼是通過編碼方式實現的生成adoc格式文件的方式

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class DemoApplicationTests {
    @Test
    public void generateAsciiDocs() throws Exception {
        //    輸出Ascii格式
        Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
                .withMarkupLanguage(MarkupLanguage.ASCIIDOC) //設置生成格式
                .withOutputLanguage(Language.ZH)  //設置語言中文還是其他語言
                .withPathsGroupedBy(GroupBy.TAGS)
                .withGeneratedExamples()
                .withoutInlineSchema()
                .build();

        Swagger2MarkupConverter.from(new URL("http://localhost:8888/v2/api-docs"))
                .withConfig(config)
                .build()
                .toFile(Paths.get("src/main/resources/docs/asciidoc"));
    }
}
  • 使用RunWith註解和SpringBootTest註解,啟動應用服務容器。 SpringBootTest.WebEnvironment.DEFINED_PORT表示使用application.yml定義的端口,而不是隨機使用一個端口進行測試,這很重要。
  • Swagger2MarkupConfig 是輸出文件的配置,如文件的格式和文件中的自然語言等
  • Swagger2MarkupConverter的from表示哪一個HTTP服務作為資源導出的源頭(JSON格式),可以自己訪問試一下這個鏈接。8888是我的服務端口,需要根據你自己的應用配置修改。
  • toFile表示將導出文件存放的位置,不用加後綴名。也可以使用toFolder表示文件導出存放的路徑。二者區別在於使用toFolder導出為文件目錄下按標籤TAGS分類的多個文件,使用toFile是導出一個文件(toFolder多個文件的合集)。
@Test
public void generateMarkdownDocsToFile() throws Exception {
    //    輸出Markdown到單文件
    Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
            .withMarkupLanguage(MarkupLanguage.MARKDOWN)
            .withOutputLanguage(Language.ZH)
            .withPathsGroupedBy(GroupBy.TAGS)
            .withGeneratedExamples()
            .withoutInlineSchema()
            .build();

    Swagger2MarkupConverter.from(new URL("http://localhost:8888/v2/api-docs"))
            .withConfig(config)
            .build()
            .toFile(Paths.get("src/main/resources/docs/markdown"));
}

上面的這一段代碼是生成markdown格式接口文件的代碼。執行上面的2段單元測試代碼,就可以生產對應格式的接口文件。

還有一種方式是通過maven插件的方式,生成adoc和markdown格式的接口文件。筆者不常使用這種方式,沒有使用代碼生成的方式配置靈活,很多配置都放到pom.xml感覺很臃腫。但還是介紹一下,首先配置maven插件swagger2markup-maven-plugin。

<plugin>
    <groupId>io.github.swagger2markup</groupId>
    <artifactId>swagger2markup-maven-plugin</artifactId>
    <version>1.3.1</version>
    <configuration>
        <swaggerInput>http://localhost:8888/v2/api-docs</swaggerInput><!---swagger-api-json路徑-->
        <outputDir>src/main/resources/docs/asciidoc</outputDir><!---生成路徑-->
        <config>
            <swagger2markup.markupLanguage>ASCIIDOC</swagger2markup.markupLanguage><!--生成格式-->
        </config>
    </configuration>
</plugin>

然後運行插件就可以了,如下圖:

三、通過maven插件生成HTML文檔

<plugin>
    <groupId>org.asciidoctor</groupId>
    <artifactId>asciidoctor-maven-plugin</artifactId>
    <version>1.5.6</version>
    <configuration>
         <!--asciidoc文件目錄-->
        <sourceDirectory>src/main/resources/docs</sourceDirectory>
        <!---生成html的路徑-->
        <outputDirectory>src/main/resources/html</outputDirectory>
        <backend>html</backend>
        <sourceHighlighter>coderay</sourceHighlighter>
        <attributes>
            <!--導航欄在左-->
            <toc>left</toc>
            <!--显示層級數-->
            <!--<toclevels>3</toclevels>-->
            <!--自動打数字序號-->
            <sectnums>true</sectnums>
        </attributes>
    </configuration>
</plugin>

adoc的sourceDirectory路徑必須和第三小節中生成的adoc文件路徑一致。然後按照下圖方式運行插件。

HTMl接口文檔显示的效果如下,有了HTML接口文檔你想轉成其他各種格式的文檔就太方便了,有很多工具可以使用。這裏就不一一介紹了。

期待您的關注

  • 向您推薦博主的系列文檔:
  • 本文轉載註明出處(必須帶連接,不能只轉文字):。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

政府重視電動機車電池電芯國產化,2020 持續補助購買電動機車

行政院副院長陳其邁在 7 月 15 日參訪 Gogoro 智能工廠,強調政府對電動機車電池電芯國產化的重視,並表示明年還是會持續補助消費者購買電動機車。

行政院副院長陳其邁與政務委員龔明鑫、經濟部次長林全能和環保署副署長沈志修等人前往桃園市龜山區,參訪 Gogoro 的智能工廠。除了現場試乘 5 月發表的新車種 Gogoro 3 車款並體驗交換電池外,也與 Gogoro 執行長陸學森針對電池電芯國產化進程與傳統機車產業合作等議題進行交流。

陳其邁表示,政府非常重視電動機車電池電芯國產化,目前除了經濟部的科專與 A+ 計畫外,對於國產電池的模組設計、儲能技術與電池的智慧管理也提供計畫類型的補助,希望加速國產電芯量產,並建立相關驗證機制,為國內電動機車產業鏈提供幫助。陳其邁強調,政府也相當重視電動機車產業與傳統機車行合作的問題,希望傳統機車行未來不論在營運、銷售與後續維修,都能與電動機車業者有更多合作機會,協助推動傳統機車行升級和轉型。

根據陳其邁的說法,政府在 2019 年對購買電動機車的補助維持不變,2020 年的補助還會持續下去。政府也研議鼓勵民眾淘汰排放污染的老舊機車,補助購買更環保的燃油機車,讓民眾使用的機車兼顧便捷與環保。

Gogoro 執行長陸學森表示,截至 2019 年 6 月底,Gogoro 生產銷售的電動機車已近 18 萬輛,全台已布建 1,283 個電池交換站,在 6 大都會區更是每 5 分鐘車程就有可即時換電的電池充電站。Gogoro 的每一部車子都是在桃園市龜山區的工廠生產製造,而且機車上的所有零組件,均來自全台 192 家供應商的供應鏈,做到電動機車的國產化。陸學森也向陳其邁建言,希望在台灣目前重型電動機車技術仍領先全球 3 到 5 年的優勢下,政府能夠支持電動機車產業,讓台灣的電動機車產業團結一致,一起為台灣打贏「世界盃」。

(合作媒體:。首圖來源: CC BY 2.0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

eMOVING 極速 100 公里電動機車 iE125 上市,快充 10 分鐘可騎 78 公里

中華汽車在 7 月 19 日發表新款電動機車 eMOVING iE125,極速可達到時速 100 公里,成為市場關注的焦點。

iE 125 的命名除了代表 intelligent electricity,也有 industrial engineering 的含義,125 則象徵 125cc 等級燃油機車的性能與操控。iE125 安全極速達到時速 100 公里,靜止加速到時速 50 公里僅需 3.9 秒,30% 坡度爬坡最高速為時速 32 公里。iE125 在充滿電之後,時速 30 公里之下續航里程為 155 公里,TES 變速續航里程為 82 公里。支援超級快充功能,充電 10 分鐘就能充滿 50% 的電力,可以行駛 78 公里。

iE125 含電池車重為 124 公斤,配備 CBS 連動煞車和 IP67 防水等級。提供 ECO、SPEED 和 BOOST 三種行車模式,讓消費者在不同情境下使用。特別的電動駐車功能只要按下按鈕就能直接立起中柱,車主不再需要為立中柱而困擾。配置 QC 3.0 USB 充電座,方便騎乘時進行充電。

eMOVING iE125 藍色版。

eMOVING iE125 白色版。

eMOVING iE125 橘色版。

eMOVING iE125 灰色版。

iE125 精緻型儀錶板螢幕為彩色液晶螢幕,豪華型和旗艦型則為汽車級 TFT。豪華型和旗艦型搭載車輛診斷系統,能在儀表板上顯示車身、動力、電池和胎壓等資訊,並提供異常提示與保養提醒。儀表板還可以進行個人化設定,自由更換儀表板主題與桌布。

iE125 豪華型和旗艦型具有遙控防盜中控鎖,能夠連結手機進行上鎖、解鎖和座墊開啟。iOS 版本的 App 支援即時來電提醒,來電與訊息通知會即時顯示。旗艦型還特別內建前方行車記錄器和胎壓偵測器,進一步確保行車安全。

儀錶板能顯示各種車身資訊。

eMOVING 將充電分為 3 種類型,分別在不同需求時使用。家用滿足平時充電需求,約 160 分鐘可以充滿 50% 的電力。快速充電站則供在外逛街或用餐時補充電力,約 30 分鐘可以充滿 50% 的電力。超級充電站供臨時路途中繼充電,約 10 分鐘可以充滿 50% 的電力。

消費者可以自行選擇電池租賃方案,eMOVING 提供電池永久保固。基礎型在家充電每月 399 元,輕量型每月 599 元額外提供 100 分鐘的超級充電分鐘數,進階型每月 799 元可以不限時數進行超級充電。為了推廣超級充電,12 月以前輕量型和進階型方案皆以每月 499 元計價,而且享受不限時數的超級充電。

中華汽車預計在 12 月佈建 70 座以上的快速充電站,2020 年 6 月更要佈建超過 150 座快速充電站,早期合作夥伴包括肯德基、家樂福、順益汽車和滙豐汽車。中華汽車也宣布捐贈 5 座超級充電站給桃園市政府,未來讓符合快充共通規格電動機車車主都能免費充電。

中華汽車捐贈 5 座超級充電站給桃園市政府。

eMOVING 推出了 10 月底前購車,就贈送 5,000 元購車金的優惠,可以全額折抵車價或購買配件。iE125 提供藍色、白色、灰色和橘色 4 種顏色讓消費者選擇,精緻型定價為台幣 73,800 元,豪華型定價為台幣 79,800 元,旗艦型定價為台幣 85,800 元。補助最高的桃園市汰換二行程機車換購電動機車補助最高 29,000 元,再加上 10 月底前購車贈送的購車金 5,000 元,精緻型最低台幣 39,800 元起,豪華型最低台幣 45,800 元起,旗艦型最低台幣 51,800 元起。

(合作媒體:。圖片來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

科大訊飛研發的全球中文學習平台上線

  隨着中國國際影響力的日益提升,漢語學習的需求與日俱增,為此,教育部、國家語委在《國家語言文字事業“十三五”發展規劃》中明確提出要“建設適應面廣、影響力大、權威性強的全球中文學習網絡平台”。10 月 25 日,在教育部、國家語委的指導下,由科大訊飛研發的全球中文學習平台正式上線。

  該平台上線發布儀式在京舉行。教育部副部長、國家語委主任田學軍,北京市人民政府副秘書長韓耕、科大訊飛股份有限公司董事長劉慶峰和人民教育出版社社長黃強共同為平台發布舉行了啟動儀式,200 多位中外嘉賓見證了全球中文學習平台(www.chinese-learning.cn)的正式上線。

  針對海外學習者的“譯學中文”模塊,學習者可以通過語音或文本輸入其母語內容,實時翻譯出中文並自動分句。學習者學習每個語句的標準音並錄音跟讀,系統會實時反饋評價,指出發音問題;針對錯誤字詞,可以反覆學習,直到掌握正確中文發音。

  這是科大訊飛承建國家語委的又一個重大項目!2004 年,科大訊飛承擔了國家語委“十五”重點科研項目“智能語音技術在普通話輔助學習中的應用研究”;2016 年,承擔國家語委“十三五”重大科研項目“智能語音及人工智能技術在語言學習中的應用研究”。目前,上述兩大項目均已成功落地,並取得了良好的社會效益。

  全球中文學習平台,匯聚各類中文學習資源,以更好地為廣大中文學習者提供優質服務為宗旨,於 2016 年底啟動建設,是落實《國家語言文字事業“十三五”發展規劃》相關任務要求的具體舉措。在教育部、國家語委與科大訊飛的共同努力下,歷經兩年多時間的不斷完善和改進,平台建設取得积極成效,相關基礎研究取得重要進展,為平台提供了堅實技術保障。其中智能語音、智能寫作和批改等關鍵技術研究成果在中小學語言能力評價、少數民族國家通用語言學習等方面得到實際應用。平台示範功能已分別在“砥礪奮進的五年”大型成就展、第二屆語博會、第十二屆孔子學院大會等不同場合進行展示,得到了各方好評。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

新發現一種抗體有望開發為通用型流感疫苗

  新華社華盛頓 10 月 26 日電(記者周舟)美國科研團隊發現一種能“嵌入”流感病毒表面蛋白的抗體,可保護小鼠免遭多種流感病毒毒株的感染,未來有望開發為通用型流感疫苗。

  血凝素(H蛋白)和神經氨酸酶(N蛋白)是流感病毒表面的兩種蛋白,它們將流感病毒分為不同的亞型。目前開發的流感疫苗主要靶向血凝素。2017 年冬,美國華盛頓大學病理學和免疫學助理教授阿里·艾利貝迪發現一個流感患者的血樣不僅含有靶向血凝素的抗體,還含有可靶向其他蛋白的抗體。

  艾利貝迪將其中三種靶向不明的抗體送至芒特西奈伊坎醫學院進行檢測,該院微生物學教授弗洛里安·克拉默發現其中一種被稱為“1G01”的抗體,可阻斷多種流感病毒毒株上幾乎所有已知的神經氨酸酶的活動。

  克拉默團隊讓實驗小鼠感染致命性劑量的流感病毒,發現這種抗體可以對抗 12 種被測試的流感毒株,其中包括三類人類流感病毒毒株、禽流感和其他不在人際間傳播的病毒毒株。實驗發現,所有小鼠都生存了下來,即便在感染 72 小時以後給葯。相比而言,達菲必須在癥狀出現 24 小時內給葯。

  美國斯克里普斯研究所的結構生物學家伊安·威爾遜分析了這種抗體的結構,發現這種抗體將一個環狀結構嵌入神經氨酸酶的活性部位,阻止了神經氨酸酶從細胞表面釋放新的病毒顆粒。

  研究显示,這種抗體只阻斷神經氨酸酶的活性部位,而不同流感毒株間的活性部位幾乎不發生變異,因此它對多種流感病毒均有效。目前研究人員正在以抗體 1G01 為基礎設計新的流感藥物和疫苗。

  這一研究成果日前發表在美國《科學》雜誌上。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象