分類: 3C資訊

  • Gogoro積極搶市,南臺灣首站小琉球

    Gogoro積極搶市,南臺灣首站小琉球

    台灣本土電動機車品牌Gogoro又展開新動作!Gogoro與遠傳電信合作,推出申辦門號即享購車折扣的優惠,未來還將建立更廣泛的物聯網(IoT)功能。同時,Gogoro也正式走入南台灣,首個佈點將位於離島小琉球。

    搶市,Gogoro與通訊業者合作

    Gogoro日前宣布與遠傳電信合作推出「超級騎機」優惠購車方案,從5月13日起,凡到Gogoro門市申辦特定遠傳電信方案,就享有最高新台幣2萬元的購車優惠。若加上政府對民眾換購電動載具所提出的補貼,Gogoro最低只要新台幣4.3萬元就能騎回家,比目前主流的汽油機車還便宜。

    Gogoro表示,未來將與遠傳合作推動更多IoT服務,強化「智慧城市」與「量身打造」功能。

    此外,Gogoro為了替客戶降低使用成本,還將推出購車送免費里程1,200公里、電池租金低資費方案等服務,電池每月最低租金599元,可行駛400公里,超過後每1公里1元新台幣,積極搶市。

    台灣另一家電動機車業者中華汽車也大力推動e-moving車款,直接刺激台灣市場需求。目前,台灣每月電動機車銷量已達1,200輛左右,占整體機車銷售量2%以上。

    Gogoro登上小琉球島!

    成功在大台北、桃園、新竹等北台灣縣市佈點後,Gogoro進軍南台灣的第一站,被直擊將登陸離島小琉球。報導指出,小琉球去年底結束一間電動機車公司的合約,空出約300輛機車的需求缺口;有業者看上此一商機,因此與Gogoro商議,協助Gogoro南進,初期先引入100輛電動車試水溫。

    相較於舊款電動機車,Gogoro的馬力、續航力更好。且小琉球空間不大,設置一座24小時運作的電池交換站就很足夠。

    (照片來源:Gogoro Taiwan 臉書專頁)

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

    【其他文章推薦】

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

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

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

    南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

  • 特斯拉第 2 季淨損擴大 二度下修銷售預測

    美國電動車大廠特斯拉(Tesla)公佈第 2 季財報,凈虧損為 1.843 億美元,而因車輛銷售持續攀高,營收成長 24%,達到 9.55 億美元,但與去年同期的凈虧損 1.542 億美元相比有所擴大,且一年內第二度下修銷售預測。   特斯拉表示,今年交車目標為 5 萬至 5.5 萬輛之間。去年執行長穆斯克發下豪語,說今年的銷售量將達 6 萬輛,今年稍早已調降到 5.5 萬輛,如今再度下修。不過,特斯拉仍預期今年 9 月底這款車將開始「少量交車」,但進度就算只延後一周,整體產量就會減少約 800 輛。     特斯拉上季資本支出總計 4.052 億美元,主要是持續用在 Model X 新車,以及預定 2016 年開幕的內華達州電池新廠。今年上半年的總支出達 8.312 億美元,預估全年支出約為 15 億美元。經調整後特斯拉上季每股虧損 48 美分,優於分析師預估的每股虧損 60 美分,去年同期則是每股獲利 13 美分。         

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

    【其他文章推薦】

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

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

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

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

    ※別再煩惱如何寫文案,掌握八大原則!

  • 中碳展開電動車、電池材料投資

    台灣中碳尋求投資轉型,因看好中國大陸電動車與電池相關產業的發展,因此在30日的年終記者會上宣布將啟動新投資案,搶攻中國大陸市場。

    中碳於記者會上表示,公司業績與油價高度相關,近日國際油價重挫,使公司尋求轉型以降低油價衝擊。其中,中國大陸因霾害嚴重,政府帶頭推動電動車產業,公司因此認為值得發展,將以公司的電池負極材料搶攻中國大陸的電動車市場。

    根據MoneyDJ的報導,中碳將投資約27億新台幣,在台灣屏東、小港、越南、中國大陸常州等地進行新的投資或擴產計畫,藉著這四大投資案來將碳材料與石化產品擴充為兩大營收來源。目前,中碳的碳材料營收比重已從去年的8%成長到今年的12%,明年可望進一步成長到16%。隨著擴產計畫,希望能在2020年拉高到50%。

    在屏東廠方面,中碳董事會將通過18.8億新台幣的擴產方案,規劃碳化、石墨化、篩分廠的年產能各2,000噸,預計於2018年第一季投產,供應電動車與超級電容的市場需求,

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

    【其他文章推薦】

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

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

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

    南投搬家前需注意的眉眉角角,別等搬了再說!

    新北清潔公司,居家、辦公、裝潢細清專業服務

  • “電動汽車就是大家電” 中國首次將電動汽車引入家電連鎖賣場

    中國首家電動汽車跨平臺運營商聯合電動21日在北京與蘇甯易購達成全面戰略合作,首次將電動汽車引入家電連鎖賣場,電動汽車作為新的電器商品門類與消費者見面。這是中國電動車在汽車流通領域的一次大膽嘗試,並將引發銷售管道顛覆性的變革。

    自此,消費者在逛電器商城時可以零距離地瞭解、購買電動汽車。此舉不僅為消費者帶來極大的便利,而且也論證了“電動汽車就是大家電”的全新理念。業內稱,這將為中國電動汽車行業的發展帶來革命性的思考和進步。

    中國汽車流通協會秘書長肖政三表示,將電動汽車引進家電連鎖賣場,迎合了汽車銷售多元化發展問題,汽車超市的概念是一種模式的創新,給消費者帶來的便利是可以預見的。

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

    【其他文章推薦】

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

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

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

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※別再煩惱如何寫文案,掌握八大原則!

  • SpringBoot 源碼解析 (一)—– SpringBoot核心原理入門

    SpringBoot 源碼解析 (一)—– SpringBoot核心原理入門

    Spring Boot 概述

    Build Anything with Spring Boot:Spring Boot is the starting point for building all Spring-based applications. Spring Boot is designed to get you up and running as quickly as possible, with minimal upfront configuration of Spring.

    上面是引自官網的一段話,大概是說: Spring Boot 是所有基於 Spring 開發的項目的起點。Spring Boot 的設計是為了讓你盡可能快的跑起來 Spring 應用程序並且盡可能減少你的配置文件。

    什麼是 Spring Boot

    • 它使用 “習慣優於配置” (項目中存在大量的配置,此外還內置一個習慣性的配置,讓你無須手動配置)的理念讓你的項目快速運行起來。
    • 它並不是什麼新的框架,而是默認配置了很多框架的使用方式,就像 Maven 整合了所有的 jar 包一樣,Spring Boot 整合了所有框架

    使用 Spring Boot 有什麼好處

    回顧我們之前的 SSM 項目,搭建過程還是比較繁瑣的,需要:

    • 1)配置 web.xml,加載 spring 和 spring mvc
    • 2)配置數據庫連接、配置日誌文件
    • 3)配置家在配置文件的讀取,開啟註解
    • 4)配置mapper文件
    • …..

    而使用 Spring Boot 來開發項目則只需要非常少的幾個配置就可以搭建起來一個 Web 項目,並且利用 IDEA 可以自動生成生成

    • 划重點:簡單、快速、方便地搭建項目;對主流開發框架的無配置集成;極大提高了開發、部署效率。

    Spring Boot HelloWorld

    導入依賴spring boot相關的依賴

    <?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>cn.chenhao</groupId>
        <artifactId>springboot</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>springboot</name>
        <description>Demo project for Spring Boot</description>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.1.RELEASE</version>
            <relativePath/>
        </parent>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>

    編寫主程序

    /**
     * @SpringBootApplication來標註一個主程序類,說明這是一個SpringBoot應用
     */ @SpringBootApplication public class HelloWorldMainApplication {
    
        public static void main(String[] args) {
            //Spring應用啟動
            SpringApplication.run(HelloWorldMainApplication.class, args);
        }
    }

    編寫Controller、Service

    @RestController
    public class HelloController {
    
        @RequestMapping("/hello")
        public String hello(){
            return "Hello world";
        }
    }

    運行主程序測試

    使用maven打包命令將其打包成jar包后,直接使用命令:

    java -jar xxx.jar

    Hello World探究

    POM文件

    父項目

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/>
    </parent>

    其父項目是

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath>../../spring-boot-dependencies</relativePath>
    </parent>

    該父項目是真正管理Spring Boot應用裏面的所有依賴的版本:Spring Boot的版本仲裁中心,所以以後導入的依賴默認是不需要版本號。如下

    還有很多版本號沒有截圖出來

    啟動器

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    spring-boot-starter : spring boot場景啟動器;幫助導入web模塊正常運行所依賴的組件;

    ​ Spring Boot將所有的功能場景抽取出來,做成一個個的starter(啟動器),只需要在項目中引入這些starter,那麼相關的場景的所有依賴都會導入進項目中。要用什麼功能就導入什麼場景的啟動器。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
    </dependency>

    添加了 spring-boot-starter-web 依賴,會自動添加 Tomcat 和 Spring MVC 的依賴

    spring-boot-starter-web中又引入了spring-boot-starter-tomcat

    主程序類(主入口類)

    @SpringBootApplication public class HelloWorldMainApplication {
    
        public static void main(String[] args) {
            //Spring應用啟動
            SpringApplication.run(HelloWorldMainApplication.class, args);
        }
    }

    @SpringBootApplication

    • Spring Boot應用標註在某個類上,說明這個類是SpringBoot的主配置類,SpringBoot就應該運行這個類的main方法來啟動SpringBoot應用。

    註解定義如下:

    @SpringBootConfiguration @EnableAutoConfiguration
    @ComponentScan(excludeFilters = {
            @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
            @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {}

    @SpringBootConfiguration

    • Spring Boot的配置類
    • 標註在某個類上,表示這是一個Spring Boot的配置類

    註解定義如下:

    @Configuration public @interface SpringBootConfiguration {}

    其實就是一個Configuration配置類,意思是HelloWorldMainApplication最終會被註冊到Spring容器中

    @EnableAutoConfiguration

    • 開啟自動配置功能
    • 以前使用Spring需要配置的信息,Spring Boot幫助自動配置;
    • @EnableAutoConfiguration通知SpringBoot開啟自動配置功能,這樣自動配置才能生效。

    註解定義如下:

    @AutoConfigurationPackage
    @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {}

    @AutoConfigurationPackage

    • 自動配置包註解
    @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage {}

    @Import(AutoConfigurationPackages.Registrar.class):默認將主配置類(
    @SpringBootApplication)所在的包及其子包裏面的所有組件掃描到Spring容器中。如下

    @Order(Ordered.HIGHEST_PRECEDENCE)
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
              //默認將會掃描@SpringBootApplication標註的主配置類所在的包及其子包下所有組件
            register(registry, new PackageImport(metadata).getPackageName());
        }
    
        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.<Object>singleton(new PackageImport(metadata));
        }
    }

    @Import(EnableAutoConfigurationImportSelector.class)

    EnableAutoConfigurationImportSelector: 導入哪些組件的選擇器,將所有需要導入的組件以全類名的方式返回,這些組件就會被添加到容器中。

     1 //EnableAutoConfigurationImportSelector的父類:AutoConfigurationImportSelector
     2 @Override
     3 public String[] selectImports(AnnotationMetadata annotationMetadata) {
     4     if (!isEnabled(annotationMetadata)) {
     5         return NO_IMPORTS;
     6     }
     7     try {
     8         AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
     9             .loadMetadata(this.beanClassLoader);
    10         AnnotationAttributes attributes = getAttributes(annotationMetadata);
    11         List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); 12         configurations = removeDuplicates(configurations);
    13         configurations = sort(configurations, autoConfigurationMetadata);
    14         Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    15         checkExcludedClasses(configurations, exclusions);
    16         configurations.removeAll(exclusions);
    17         configurations = filter(configurations, autoConfigurationMetadata);
    18         fireAutoConfigurationImportEvents(configurations, exclusions);
    19         return configurations.toArray(new String[configurations.size()]);
    20     }
    21     catch (IOException ex) {
    22         throw new IllegalStateException(ex);
    23     }
    24 }

    我們主要看第11行List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);會給容器中注入眾多的自動配置類(xxxAutoConfiguration),就是給容器中導入這個場景需要的所有組件,並配置好這些組件。我們跟進去看看

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
                AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
            getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        //...
        return configurations;
    }
    
    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }
    
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        try {
            //從類路徑的META-INF/spring.factories中加載所有默認的自動配置類
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                                     ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            List<String> result = new ArrayList<String>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                //獲取EnableAutoConfiguration指定的所有值,也就是EnableAutoConfiguration.class的值
                String factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

    SpringBoot啟動的時候從類路徑下的
    META-INF/spring.factories中獲取EnableAutoConfiguration指定的值,並將這些值作為自動配置類導入到容器中,自動配置類就會生效,最後完成自動配置工作。EnableAutoConfiguration默認在spring-boot-autoconfigure這個包中,如下圖

    最終有96個自動配置類被加載並註冊進Spring容器中

    J2EE的整體整合解決方案和自動配置都在spring-boot-autoconfigure-xxx.jar中。在這些自動配置類中會通過@ConditionalOnClass等條件註解判斷是否導入了某些依賴包,從而通過@Bean註冊相應的對象進行自動配置。後面我們會有單獨文章講自動配置的內容

     

     

     

     

     

     

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

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

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

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

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

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

  • Elastic Stack 開源的大數據解決方案

    Elastic Stack 開源的大數據解決方案

    目的

    本文主要介紹的內容有以下三點:
    一. Elastic Stack是什麼以及組成部分
    二. Elastic Stack前景以及業務應用
    三. Elasticsearch原理(索引方向)
    四. Elasticsearch相對薄弱的地方

    一、Elastic Stack是什麼以及組成部分

    介紹Elastic Stack是什麼,其實只要一句話就可以,就是: 一套完整的大數據處理堆棧,從攝入、轉換到存儲分析、可視化

    它是不同產品的集合,各司其職,形成完整的數據處理鏈,因此Elastic Stack也可以簡稱為BLEK。

    Beats 輕量型數據採集器

    Logstash 輸入、過濾器和輸出

    Elasticsearch 查詢和分析

    Kibana 可視化,可自由選擇如何呈現數據

    1. Beats – 全品類採集器,搞定所有數據類型

    Filebeat(日誌文件):對成百上千、甚至上萬的服務器生成的日誌匯總,可搜索。

    Metricbeat(指標): 收集系統和服務指標,CPU 使用率、內存、文件系統、磁盤 IO 和網絡 IO 統計數據。

    Packetbeat(網絡數據):網絡數據包分析器,了解應用程序動態。

    Heartbeat(運行時間監控):通過主動探測來監測服務的可用性
    ……
    Beats支持許許多多的beat,這裏列的都是比較的常用的beat,了解更多可以點擊鏈接:

    2. Logstash – 服務器端數據處理管道

    介紹Logstash之前,我們先來看下Linux下常用的幾個命令

    cat alldata.txt | awk ‘{print $1}’ | sort | uniq | tee filterdata.txt

    只要接觸過Linux同學,應該都知道這幾個命名意思

    cat alldata.txt #將alldata.txt的內容輸出到標準設備上
    awk ‘{print $1}’ #對上面的內容做截取,只取每一行第一列數據
    sort | uniq  #對截取后的內容,進行排序和唯一性操作
    tee filterdata.txt #將上面的內容寫到filterdata.txt

    上面的幾個簡單的命令就可以看出來,這是對數據進行了常規的處理,用名詞修飾的話就是:數據獲取/輸入數據清洗數據過濾數據寫入/輸出

    而Logstash做的也是相同的事(看下圖)。

    將系統的日誌文件、應用日誌文件、系統指標等數據,輸入到Input,再通過數據清洗以及過濾,輸入到存儲設備中,這裏當然是輸入到Elasticsearch

    3. Elasticsearch – 分佈式文檔存儲、RESTful風格的搜索和數據分析引擎

    Elasticsearch主要也是最原始的功能就是搜索和分析功能。這裏就簡單說一下,下面講原理的時候會着重講到Elasticsearch

    搜索:全文搜索,完整的信息源轉化為計算機可以識別、處理的信息單元形成的數據集合 。

    分析:相關度,搜索所有內容,找到所需的具體信息(詞頻或熱度等對結果排序)

    4. Kibana- 可視化

    可視化看下圖(來源官網)便知

    可以對日誌分析、業務分析等做可視化

    現在從總體上來了解下,在心中對Elastic Stack有個清楚的認知(下圖)。

    二、Elastic Stack前景以及業務應用

    1. DB-Engines 排名

    Elasticsearch是Elastic Stack核心,由圖可以看出在搜索領域Elasticsearch暫時沒有對手。

    2. ES社區

    ES中文社區也是相當活躍的,會定期做一下分享,都是大公司的寶貴經驗,值得參考。

    3. 2018年攜程的使用情況(讓我們看看能處理多大的數據)

    集群數量是94個,最小的集群一般是3個節點。全部節點數量大概700+。

    最大的一個集群是做日誌分析的,其中數據節點330個,最高峰一天產生1600億文檔,寫入值300w/s。

    現在有2.5萬億文檔,大概是幾個PB的量

    三、Elasticsearch(ES)原理

    因為篇目有限,本篇只介紹ES的索引原理。

    ES為什麼可以做全文搜索,主要就是用了倒排索引,先來看下面的一張圖

    看圖可以簡單的理解倒排索引就是:關鍵字 + 頁碼

    對倒排索引有個基本的認識后,下面來做個簡單的數據例子。

    現在對Name做到排索引,記住:關鍵字 + ID(頁碼)。

    對Age做到排索引。

    對Intersets做到排索引。

    現在搜索Age等於18的,通過倒排索引就可以快速得到1和3的id,再通過id就可以得到具體數據,看,這樣是不是快的狠。

    如果是用Mysql等關係數據庫,現在有十多億數據(大數據嘛),就要一條一條的掃描下去找id,效率可想而知。而用倒排索引,找到所有的id就輕輕鬆鬆了。

    在ES中,關鍵詞叫Term,頁碼叫Posting List。

    但這樣就行了嗎? 如果Name有上億個Term,要找最後一個Term,效率豈不是還是很低?

    再來看Name的倒排索引,你會發現,將Armani放在了第一個,Tyloo放在了第三個,可以看出來,對Term做了簡單的排序。雖然簡單,但很實用。這樣查找Term就可以用二分查找法來查找了,將複雜度由n變成了logn。

    在ES中,這叫Term Dictionary。

    到這裏,再來想想MySQL的b+tree, 你有沒有發現原理是差不多的,那為什麼說ES搜索比MySQL快很多,究竟快在哪裡? 接下來再看。

    有一種數據結構叫Trie樹,又稱前綴樹或字典樹,是一種有序樹。這種數據結構的好處就是可以壓縮前綴和提高查詢數據。

    現在有這麼一組Term: apps, apple, apply, appear, back, backup, base, bear,用Trie樹表示如下圖。

    通過線路路徑字符連接就可以得到完成的Term,並且合用了前綴,比如apps, apple, apply, appear合用了app路徑,節省了大量空間。

    這個時候再來找base單詞,立即就可以排除了a字符開頭的單詞,比Term Dictionary快了不知多少。

    在ES中,這叫Term Index

    現在我們再從整體看下ES的索引

    先通過Trie樹快速定位block(相當於頁碼), 再到Term Dictionary 做二分查找,得到Posting List。

    索引優化

    ES是為了大數據而生的,這意味着ES要處理大量的數據,它的Term數據量也是不可想象的。比如一篇文章,要做全文索引,就會對全篇的內容做分詞,會產生大量的Term,而ES查詢的時候,這些Term肯定要放在內存裏面的。

    雖然Trie樹對前綴做了壓縮,但在大量Term面前還是不夠,會佔用大量的內存使用,於是就有ES對Trie樹進一步演化。

    FST(Finite State Transducer )確定無環有限狀態轉移器 (看下圖)

    可以看appear、bear 對共同的後綴做了壓縮。

    Posting List磁盤壓縮

    假設有一億的用戶數據,現在對性別做搜索,而性別無非兩種,可能”男”就有五千萬之多,按int4個字節存儲,就要消耗50M左右的磁盤空間,而這僅僅是其中一個Term。

    那麼面對成千上萬的Term,ES究竟是怎麼存儲的呢?接下來,就來看看ES的壓縮方法。

    Frame Of Reference (FOR) 增量編碼壓縮,將大數變小數,按字節存儲

    只要能把握“增量,大數變小數,按字節存儲”這幾個關鍵詞,這個算法就很好理解,現在來具體看看。

    現在有一組Posting List:[60, 150, 300,310, 315, 340], 按正常的int型存儲,size = 6 * 4(24)個字節。

    1. 按增量存儲:60 + 90(150)+ 150(300) + 10(310) + 5(315)+ 25(340),也就是[60, 90, 150, 10, 5, 25],這樣就將大數變成了小數。

    2. 切分成不同的block:[60, 90, 150]、[10, 5, 25],為什麼要切分,下面講。

    3. 按字節存儲:對於[60, 90, 150]這組block,究竟怎麼按字節存儲,其實很簡單,就是找其中最大的一個值,看X個比特能表示這個最大的數,那麼剩下的數也用X個比特表示(切分,可以盡可能的壓縮空間)。

    [60, 90, 150]最大數150 < 2^8 = 256,也就是這組每個數都用8個比特表示,也就是 3*8 = 24個比特,再除以8,也就是3個字節存在,再加上一個8的標識位(說明每個數是8個比特存儲),佔用一個字節,一共4個字節。

    [10, 5, 25]最大數25 < 2^5 = 32,每個數用5個比特表示,3*5=15比特,除以8,大約2個字節,加上5的標識位,一共3個字節。

    那麼總體size = 4 + 3(7)個字節,相當於24個字節,大大壓縮了空間。

    再看下圖表示

    Posting List內存壓縮

    同學們應該都知道越複雜的算法消耗的CPU性能就越大,比如常見的https,第一次交互會用非對稱密碼來驗證,驗證通過後就轉變成了對稱密碼驗證,FOR同樣如此,那麼ES是用什麼算法壓縮內存中的Posting List呢?

    Roaring Bitmaps 壓縮位圖索引

    Roaring Bitmaps 涉及到兩種數據結構 short[] 、bitmap。

    short好理解就是2個字節的整型。

    bitmap就是用比特表示數據,看下面的例子。

    Posting List:[1, 2, 4, 7, 10] -> [1, 1, 0, 1, 0, 0, 1,0, 0, 1],取最大的值10,那麼就用10個比特表示這組Posting List,第1, 2, 4, 7, 10位存在,就將相對應的“位”置為1,其他的為0。

    但這種bitmap數據結構有缺陷,看這組Posting List: [1, 3, 100000000] -> [1, 0, 1, 0, 0, 0, …, 0, 0, 1 ],最大數是1億,就要1億的比特表示,這麼算下來,反而消耗了更多的內存。

    那如何解決這個問題,其實也很簡單,跟上面一樣,將大數變成小數

    看下圖:

    第一步:將每個數除以65536,得到(商,餘數)。

    第二步:按照商,分成不同的block,也就是相同的商,放在同一個block裏面,餘數就是這個值在這個block裏面的位置(永遠不會超過65536,餘數嘛)。

    第三步:判斷底層block用什麼數據結構存儲數據,如果block裏面的餘數的個數超過4096個,就用short存儲,反之bitmap。

    上面那個圖是官網的圖,看下我畫的圖,可能更好理解些。

    到這裏,ES的索引原理就講完了,希望大家都能理解。

    四、Elasticsearch(ES)相對薄弱的地方

    1. 多表關聯

    其實ES有一個很重要的特性這裏沒有介紹到,也就是分佈式,每一個節點的數據和,才是整體數據。

    這也導致了多表關聯問題,雖然ES裏面也提供了Nested& Join 方法解決這個問題,但這裏還是不建議用。

    那這個問題在實際應用中應該如何解決? 其實也很簡單,裝換思路,ES無法解決,就到其他層解決,比如:應用層,用面向對象的架構,拆分查詢。

    2. 深度分頁

    分佈式架構下,取數據便不是那麼簡單,比如取前1000條數據,如果是10個節點,那麼每個節點都要取1000條,10個節點就是10000條,排序后,返回前1000條,如果是深度分頁就會變的相當的慢。

    ES提供的是Scroll + Scroll_after,但這個採取的是緩存的方式,取出10000條后,緩存在內存里,再來翻頁的時候,直接從緩存中取,這就代表着存在實時性問題。

    來看看百度是怎麼解決這個問題的。

    一樣在應用層解決,翻頁到一定的深度后,禁止翻頁。

    3. 更新應用

    頻繁更新的應用,用ES會有瓶頸,比如一些遊戲應用,要不斷的更新數據,用ES不是太適合,這個看大家自己的應用情況。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

  • Java 8 Streams API 詳解

    Java 8 Streams API 詳解

    流式編程作為Java 8的亮點之一,是繼Java 5之後對集合的再一次升級,可以說Java 8幾大特性中,Streams API 是作為Java 函數式的主角來設計的,誇張的說,有了Streams API之後,萬物皆可一行代碼。

    什麼是Stream

    Stream被翻譯為流,它的工作過程像將一瓶水導入有很多過濾閥的管道一樣,水每經過一個過濾閥,便被操作一次,比如過濾,轉換等,最後管道的另外一頭有一個容器負責接收剩下的水。

    示意圖如下:

    首先通過source產生流,然後依次通過一些中間操作,比如過濾,轉換,限制等,最後結束對流的操作。

    Stream也可以理解為一個更加高級的迭代器,主要的作用便是遍歷其中每一個元素。

    為什麼需要Stream

    Stream作為Java 8的一大亮點,它專門針對集合的各種操作提供各種非常便利,簡單,高效的API,Stream API主要是通過Lambda表達式完成,極大的提高了程序的效率和可讀性,同時Stram API中自帶的并行流使得併發處理集合的門檻再次降低,使用Stream API編程無需多寫一行多線程的大門就可以非常方便的寫出高性能的併發程序。使用Stream API能夠使你的代碼更加優雅。

    流的另一特點是可無限性,使用Stream,你的數據源可以是無限大的。

    在沒有Stream之前,我們想提取出所有年齡大於18的學生,我們需要這樣做:

    List<Student> result=new ArrayList<>();
    for(Student student:students){
     
        if(student.getAge()>18){
            result.add(student);
        }
    }
    return result;

    使用Stream,我們可以參照上面的流程示意圖來做,首先產生Stream,然後filter過濾,最後歸併到容器中。

    轉換為代碼如下:

    return students.stream().filter(s->s.getAge()>18).collect(Collectors.toList());
    • 首先stream()獲得流
    • 然後filter(s->s.getAge()>18)過濾
    • 最後collect(Collectors.toList())歸併到容器中

    是不是很像在寫sql?

    如何使用Stream

    我們可以發現,當我們使用一個流的時候,主要包括三個步驟:

    • 獲取流
    • 對流進行操作
    • 結束對流的操作

    獲取流

    獲取流的方式有多種,對於常見的容器(Collection)可以直接.stream()獲取
    例如:

    • Collection.stream()
    • Collection.parallelStream()
    • Arrays.stream(T array) or Stream.of()

    對於IO,我們也可以通過lines()方法獲取流:

    • java.nio.file.Files.walk()
    • java.io.BufferedReader.lines()

    最後,我們還可以從無限大的數據源中產生流:

    • Random.ints()

    值得注意的是,JDK中針對基本數據類型的昂貴的裝箱和拆箱操作,提供了基本數據類型的流:

    • IntStream
    • LongStream
    • DoubleStream

    這三種基本數據類型和普通流差不多,不過他們流裏面的數據都是指定的基本數據類型。

    Intstream.of(new int[]{1,2,3});
    Intstream.rang(1,3);

    對流進行操作

    這是本章的重點,產生流比較容易,但是不同的業務系統的需求會涉及到很多不同的要求,明白我們能對流做什麼,怎麼做,才能更好的利用Stream API的特點。

    流的操作類型分為兩種:

    • Intermediate:中間操作,一個流可以後面跟隨零個或多個intermediate操作。其目的主要是打開流,做出某種程度的數據映射/過濾,然後會返回一個新的流,交給下一個操作使用。這類操作都是惰性化的(lazy),就是說,僅僅調用到這類方法,並沒有真正開始流的遍歷。

      map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

    • Terminal:終結操作,一個流只能有一個terminal操作,當這個操作執行后,流就被使用“光”了,無法再被操作。所以這必定是流的最後一個操作。Terminal操作的執行,才會真正開始流的遍歷,並且會生成一個結果,或者一個 side effect。

      forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

    IntermediateTerminal完全可以按照上圖的流程圖理解,Intermediate表示在管道中間的過濾器,水會流入過濾器,然後再流出去,而Terminal操作便是最後一個過濾器,它在管道的最後面,流入Terminal的水,最後便會流出管道。

    下面依次詳細的解讀下每一個操作所能產生的效果:

    中間操作

    對於中間操作,所有的API的返回值基本都是Stream<T>,因此以後看見一個陌生的API也能通過返回值判斷它的所屬類型。

    map/flatMap

    map顧名思義,就是映射,map操作能夠將流中的每一個元素映射為另外的元素。

     <R> Stream<R> map(Function<? super T, ? extends R> mapper);

    可以看到map接受的是一個Function,也就是接收參數,並返回一個值。

    比如:

    //提取 List<Student>  所有student 的名字 
    List<String> studentNames = students.stream().map(Student::getName)
                                                 .collect(Collectors.toList());

    上面的代碼等同於以前的:

    List<String> studentNames=new ArrayList<>();
    for(Student student:students){
        studentNames.add(student.getName());
    }

    再比如:將List中所有字母轉換為大寫:

    List<String> words=Arrays.asList("a","b","c");
    List<String> upperWords=words.stream().map(String::toUpperCase)
                                          .collect(Collectors.toList());

    flatMap顧名思義就是扁平化映射,它具體的操作是將多個stream連接成一個stream,這個操作是針對類似多維數組的,比如容器裡面包含容器等。

    List<List<Integer>> ints=new ArrayList<>(Arrays.asList(Arrays.asList(1,2),
                                              Arrays.asList(3,4,5)));
    List<Integer> flatInts=ints.stream().flatMap(Collection::stream).
                                           collect(Collectors.toList());

    可以看到,相當於降維。

    filter

    filter顧名思義,就是過濾,通過測試的元素會被留下來並生成一個新的Stream

    Stream<T> filter(Predicate<? super T> predicate);

    同理,我們可以filter接收的參數是Predicate,也就是推斷型函數式接口,接收參數,並返回boolean值。

    比如:

    //獲取所有大於18歲的學生
    List<Student> studentNames = students.stream().filter(s->s.getAge()>18)
                                                  .collect(Collectors.toList());

    distinct

    distinct是去重操作,它沒有參數

      Stream<T> distinct();

    sorted

    sorted排序操作,默認是從小到大排列,sorted方法包含一個重載,使用sorted方法,如果沒有傳遞參數,那麼流中的元素就需要實現Comparable<T>方法,也可以在使用sorted方法的時候傳入一個Comparator<T>

    Stream<T> sorted(Comparator<? super T> comparator);
    
    Stream<T> sorted();

    值得一說的是這個ComparatorJava 8之後被打上了@FunctionalInterface,其他方法都提供了default實現,因此我們可以在sort中使用Lambda表達式

    例如:

    //以年齡排序
    students.stream().sorted((s,o)->Integer.compare(s.getAge(),o.getAge()))
                                      .forEach(System.out::println);;

    然而還有更方便的,Comparator默認也提供了實現好的方法引用,使得我們更加方便的使用:

    例如上面的代碼可以改成如下:

    //以年齡排序 
    students.stream().sorted(Comparator.comparingInt(Student::getAge))
                                .forEach(System.out::println);;

    或者:

    //以姓名排序
    students.stream().sorted(Comparator.comparing(Student::getName)).
                              forEach(System.out::println);

    是不是更加簡潔。

    peek

    peek有遍歷的意思,和forEach一樣,但是它是一个中間操作。

    peek接受一個消費型的函數式接口。

    Stream<T> peek(Consumer<? super T> action);

    例如:

    //去重以後打印出來,然後再歸併為List
    List<Student> sortedStudents= students.stream().distinct().peek(System.out::println).
                                                    collect(Collectors.toList());

    limit

    limit裁剪操作,和String::subString(0,x)有點先溝通,limit接受一個long類型參數,通過limit之後的元素只會剩下min(n,size)個元素,n表示參數,size表示流中元素個數

     Stream<T> limit(long maxSize);

    例如:

    //只留下前6個元素並打印
    students.stream().limit(6).forEach(System.out::println);

    skip

    skip表示跳過多少個元素,和limit比較像,不過limit是保留前面的元素,skip是保留後面的元素

    Stream<T> skip(long n);

    例如:

    //跳過前3個元素並打印 
    students.stream().skip(3).forEach(System.out::println);

    終結操作

    一個流處理中,有且只能有一個終結操作,通過終結操作之後,流才真正被處理,終結操作一般都返回其他的類型而不再是一個流,一般來說,終結操作都是將其轉換為一個容器。

    forEach

    forEach是終結操作的遍歷,操作和peek一樣,但是forEach之後就不會再返迴流

     void forEach(Consumer<? super T> action);

    例如:

    //遍歷打印
    students.stream().forEach(System.out::println);

    上面的代碼和一下代碼效果相同:

    for(Student student:students){
        System.out.println(sudents);
    }

    toArray

    toArrayList##toArray()用法差不多,包含一個重載。

    默認的toArray()返回一個Object[]

    也可以傳入一個IntFunction<A[]> generator指定數據類型

    一般建議第二種方式。

    Object[] toArray();
    
    <A> A[] toArray(IntFunction<A[]> generator);

    例如:

     Student[] studentArray = students.stream().skip(3).toArray(Student[]::new);

    max/min

    max/min即使找出最大或者最小的元素。max/min必須傳入一個Comparator

    Optional<T> min(Comparator<? super T> comparator);
    
    Optional<T> max(Comparator<? super T> comparator);

    count

    count返迴流中的元素數量

    long count();

    例如:

    long  count = students.stream().skip(3).count();

    reduce

    reduce為歸納操作,主要是將流中各個元素結合起來,它需要提供一個起始值,然後按一定規則進行運算,比如相加等,它接收一個二元操作 BinaryOperator函數式接口。從某種意義上來說,sum,min,max,average都是特殊的reduce

    reduce包含三個重載:

    T reduce(T identity, BinaryOperator<T> accumulator);
    
    Optional<T> reduce(BinaryOperator<T> accumulator);
    
     <U> U reduce(U identity,
                     BiFunction<U, ? super T, U> accumulator,
                     BinaryOperator<U> combiner);

    例如:

    List<Integer> integers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
            
    long count = integers.stream().reduce(0,(x,y)->x+y);

    以上代碼等同於:

    long count = integers.stream().reduce(Integer::sum).get();

    reduce兩個參數和一個參數的區別在於有沒有提供一個起始值,

    如果提供了起始值,則可以返回一個確定的值,如果沒有提供起始值,則返回Opeational防止流中沒有足夠的元素。

    anyMatch allMatch noneMatch

    測試是否有任意元素\所有元素\沒有元素匹配表達式

    他們都接收一個推斷類型的函數式接口:Predicate

     boolean anyMatch(Predicate<? super T> predicate);
    
     boolean allMatch(Predicate<? super T> predicate);
    
     boolean noneMatch(Predicate<? super T> predicate)

    例如:

     boolean test = integers.stream().anyMatch(x->x>3);

    findFirst、 findAny

    獲取元素,這兩個API都不接受任何參數,findFirt返迴流中第一個元素,findAny返迴流中任意一個元素。

    Optional<T> findFirst();
    
    Optional<T> findAny();

    也有有人會問findAny()這麼奇怪的操作誰會用?這個API主要是為了在并行條件下想要獲取任意元素,以最大性能獲取任意元素

    例如:

    int foo = integers.stream().findAny().get();

    collect

    collect收集操作,這個API放在後面將是因為它太重要了,基本上所有的流操作最後都會使用它。

    我們先看collect的定義:

     <R> R collect(Supplier<R> supplier,
                      BiConsumer<R, ? super T> accumulator,
                      BiConsumer<R, R> combiner);
    
    <R, A> R collect(Collector<? super T, A, R> collector);

    可以看到,collect包含兩個重載:

    一個參數和三個參數,

    三個參數我們很少使用,因為JDK提供了足夠我們使用的Collector供我們直接使用,我們可以簡單了解下這三個參數什麼意思:

    • Supplier:用於產生最後存放元素的容器的生產者
    • accumulator:將元素添加到容器中的方法
    • combiner:將分段元素全部添加到容器中的方法

    前兩個元素我們都很好理解,第三個元素是幹嘛的呢?因為流提供了并行操作,因此有可能一個流被多個線程分別添加,然後再將各個子列表依次添加到最終的容器中。

    ↓ – – – – – – – – –

    ↓ — — —

    ↓ ———

    如上圖,分而治之。

    例如:

    List<String> result = stream.collect(ArrayList::new, List::add, List::addAll);

    接下來看只有一個參數的collect

    一般來說,只有一個參數的collect,我們都直接傳入Collectors中的方法引用即可:

    List<Integer> = integers.stream().collect(Collectors.toList());

    Collectors中包含很多常用的轉換器。toList(),toSet()等。

    Collectors中還包括一個groupBy(),他和Sql中的groupBy一樣都是分組,返回一個Map

    例如:

    //按學生年齡分組
    Map<Integer,List<Student>> map= students.stream().
                                    collect(Collectors.groupingBy(Student::getAge));

    groupingBy可以接受3個參數,分別是

    1. 第一個參數:分組按照什麼分類
    2. 第二個參數:分組最後用什麼容器保存返回(當只有兩個參數是,此參數默認為HashMap
    3. 第三個參數:按照第一個參數分類后,對應的分類的結果如何收集

    有時候單參數的groupingBy不滿足我們需求的時候,我們可以使用多個參數的groupingBy

    例如:

    //將學生以年齡分組,每組中只存學生的名字而不是對象
    Map<Integer,List<String>> map =  students.stream().
      collect(Collectors.groupingBy(Student::getAge,Collectors.mapping(Student::getName,Collectors.toList())));

    toList默認生成的是ArrayList,toSet默認生成的是HashSet,如果想要指定其他容器,可以如下操作:

     students.stream().collect(Collectors.toCollection(TreeSet::new));

    Collectors還包含一個toMap,利用這個API我們可以將List轉換為Map

      Map<Integer,Student> map=students.stream().
                               collect(Collectors.toMap(Student::getAge,s->s));
    

    值得注意的一點是,IntStreamLongStream,DoubleStream是沒有collect()方法的,因為對於基本數據類型,要進行裝箱,拆箱操作,SDK並沒有將它放入流中,對於基本數據類型流,我們只能將其toArray()

    優雅的使用Stream

    了解了Stream API,下面詳細介紹一下如果優雅的使用Steam

    • 了解流的惰性操作

      前面說到,流的中間操作是惰性的,如果一個流操作流程中只有中間操作,沒有終結操作,那麼這個流什麼都不會做,整個流程中會一直等到遇到終結操作操作才會真正的開始執行。

      例如:

      students.stream().peek(System.out::println);

      這樣的流操作只有中間操作,沒有終結操作,那麼不管流裡面包含多少元素,他都不會執行任何操作。

    • 明白流操作的順序的重要性

      Stream API中,還包括一類Short-circuiting,它能夠改變流中元素的數量,一般這類API如果是中間操作,最好寫在靠前位置:

      考慮下面兩行代碼:

      students.stream().sorted(Comparator.comparingInt(Student::getAge)).
                        peek(System.out::println).
                        limit(3).              
                        collect(Collectors.toList());
      students.stream().limit(3).
                        sorted(Comparator.comparingInt(Student::getAge)).
                        peek(System.out::println).
                        collect(Collectors.toList());

      兩段代碼所使用的API都是相同的,但是由於順序不同,帶來的結果都非常不一樣的,

      第一段代碼會先排序所有的元素,再依次打印一遍,最後獲取前三個最小的放入list中,

      第二段代碼會先截取前3個元素,在對這三個元素排序,然後遍歷打印,最後放入list中。

    • 明白Lambda的局限性

      由於Java目前只能Pass-by-value,因此對於Lambda也和有匿名類一樣的final的局限性。

      具體原因可以參考

      因此我們無法再lambda表達式中修改外部元素的值。

      同時,在Stream中,我們無法使用break提前返回。

    • 合理編排Stream的代碼格式

      由於可能在使用流式編程的時候會處理很多的業務邏輯,導致API非常長,此時最後使用換行將各個操作分離開來,使得代碼更加易讀。

      例如:

      students.stream().limit(3).
                        sorted(Comparator.comparingInt(Student::getAge)).
                        peek(System.out::println).
                        collect(Collectors.toList());

      而不是:

      students.stream().limit(3).sorted(Comparator.comparingInt(Student::getAge)).peek(System.out::println).collect(Collectors.toList());

      同時由於Lambda表達式省略了參數類型,因此對於變量,盡量使用完成的名詞,比如student而不是s,增加代碼的可讀性。

      盡量寫出敢在代碼註釋上留下你的名字的代碼!

    總結

    總之,Stream是Java 8 提供的簡化代碼的神器,合理使用它,能讓你的代碼更加優雅。

    尊重勞動成功,轉載註明出處

    參考鏈接:

    《Effective Java》3th

    如果覺得寫得不錯,歡迎關注微信公眾號:逸游Java ,每天不定時發布一些有關Java乾貨的文章,感謝關注

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

    ※評比南投搬家公司費用收費行情懶人包大公開

  • 有了四步解題法模板,再也不害怕動態規劃!(看不懂算我輸)

    有了四步解題法模板,再也不害怕動態規劃!(看不懂算我輸)

    導言

    動態規劃問題一直是算法面試當中的重點和難點,並且動態規劃這種通過空間換取時間的算法思想在實際的工作中也會被頻繁用到,這篇文章的目的主要是解釋清楚 什麼是動態規劃,還有就是面對一道動態規劃問題,一般的 思考步驟 以及其中的注意事項等等,最後通過幾道題目將理論和實踐結合。

    什麼是動態規劃

    如果你還沒有聽說過動態規劃,或者僅僅只有耳聞,或許你可以看看 Quora 上面的這個 回答

    How to explain dynamic

    用一句話解釋動態規劃就是 “記住你之前做過的事”,如果更準確些,其實是 “記住你之前得到的答案”。

    我舉個大家工作中經常遇到的例子。

    在軟件開發中,大家經常會遇到一些系統配置的問題,配置不對,系統就會報錯,這個時候一般都會去 Google 或者是查閱相關的文檔,花了一定的時間將配置修改好。

    過了一段時間,去到另一個系統,遇到類似的問題,這個時候已經記不清之前修改過的配置文件長什麼樣,這個時候有兩種方案,一種方案還是去 Google 或者查閱文檔,另一種方案是借鑒之前修改過的配置,第一種做法其實是萬金油,因為你遇到的任何問題其實都可以去 Google,去查閱相關文件找答案,但是這會花費一定的時間,相比之下,第二種方案肯定會更加地節約時間,但是這個方案是有條件的,條件如下:

    • 之前的問題和當前的問題有着關聯性,換句話說,之前問題得到的答案可以幫助解決當前問題
    • 需要記錄之前問題的答案

    當然在這個例子中,可以看到的是,上面這兩個條件均滿足,大可去到之前配置過的文件中,將配置拷貝過來,然後做些細微的調整即可解決當前問題,節約了大量的時間。

    不知道你是否從這些描述中發現,對於一個動態規劃問題,我們只需要從兩個方面考慮,那就是 找出問題之間的聯繫,以及 記錄答案,這裏的難點其實是找出問題之間的聯繫,記錄答案只是順帶的事情,利用一些簡單的數據結構就可以做到。

    概念

    上面的解釋如果大家可以理解的話,接

      動態規劃算法是通過拆分問題,定義問題狀態和狀態之間的關係,使得問題能夠以遞推(或者說分治)的方式去解決。它的幾個重要概念如下所述。

      階段:對於一個完整的問題過程,適當的切分為若干個相互聯繫的子問題,每次在求解一個子問題,則對應一個階段,整個問題的求解轉化為按照階段次序去求解。

      狀態:狀態表示每個階段開始時所處的客觀條件,即在求解子問題時的已知條件。狀態描述了研究的問題過程中的狀況。

      決策:決策表示當求解過程處於某一階段的某一狀態時,可以根據當前條件作出不同的選擇,從而確定下一個階段的狀態,這種選擇稱為決策。

      策略:由所有階段的決策組成的決策序列稱為全過程策略,簡稱策略。

      最優策略:在所有的策略中,找到代價最小,性能最優的策略,此策略稱為最優策略。

      狀態轉移方程:狀態轉移方程是確定兩個相鄰階段狀態的演變過程,描述了狀態之間是如何演變的。

    思考動態規劃問題的四個步驟

    一般解決動態規劃問題,分為四個步驟,分別是

    • 問題拆解,找到問題之間的具體聯繫
    • 狀態定義
    • 遞推方程推導
    • 實現

    這裏面的重點其實是前兩個,如果前兩個步驟順利完成,後面的遞推方程推導和代碼實現會變得非常簡單。

    這裏還是拿 Quora 上面的例子來講解,“1+1+1+1+1+1+1+1” 得出答案是 8,那麼如何快速計算 “1+ 1+1+1+1+1+1+1+1”,我們首先可以對這個大的問題進行拆解,這裏我說的大問題是 9 個 1 相加,這個問題可以拆解成 1 + “8 個 1 相加的答案”,8 個 1 相加繼續拆,可以拆解成 1 + “7 個 1 相加的答案”,… 1 + “0 個 1 相加的答案”,到這裏,第一個步驟 已經完成。

    狀態定義 其實是需要思考在解決一個問題的時候我們做了什麼事情,然後得出了什麼樣的答案,對於這個問題,當前問題的答案就是當前的狀態,基於上面的問題拆解,你可以發現兩個相鄰的問題的聯繫其實是 后一個問題的答案 = 前一個問題的答案 + 1,這裏,狀態的每次變化就是 +1。

    定義好了狀態,遞推方程就變得非常簡單,就是 dp[i] = dp[i - 1] + 1,這裏的 dp[i] 記錄的是當前問題的答案,也就是當前的狀態,dp[i - 1] 記錄的是之前相鄰的問題的答案,也就是之前的狀態,它們之間通過 +1 來實現狀態的變更。

    最後一步就是實現了,有了狀態表示和遞推方程,實現這一步上需要重點考慮的其實是初始化,就是用什麼樣的數據結構,根據問題的要求需要做那些初始值的設定。

    public int dpExample(int n) {
        int[] dp = new int[n + 1];  // 多開一位用來存放 0 個 1 相加的結果

        dp[0] = 0;      // 0 個 1 相加等於 0

        for (int i = 1; i <= n; ++i) {
            dp[i] = dp[i - 1] + 1;
        }

        return dp[n];
    }

    你可以看到,動態規劃這四個步驟其實是相互遞進的,狀態的定義離不開問題的拆解,遞推方程的推導離不開狀態的定義,最後的實現代碼的核心其實就是遞推方程,這中間如果有一個步驟卡殼了則會導致問題無法解決,當問題的複雜程度增加的時候,這裏面的思維複雜程度會上升。

    接下來我們再來看看 LeetCode 上面的幾道題目,通過題目再來走一下這些個分析步驟。

    題目實戰

    爬樓梯

    但凡涉及到動態規劃的題目都離不開一道例題:爬樓梯(LeetCode 第 70 號問題)。

    題目描述

    假設你正在爬樓梯。需要 n 階你才能到達樓頂。

    每次你可以爬 1 或 2 個台階。你有多少種不同的方法可以爬到樓頂呢?

    注意:給定 n 是一個正整數。

    示例 1:

    輸入: 2
    輸出: 2
    解釋: 有兩種方法可以爬到樓頂。

    1. 1 階 + 1 階
    2. 2 階

    示例 2:

    輸入: 3
    輸出: 3
    解釋: 有三種方法可以爬到樓頂。

    1. 1 階 + 1 階 + 1 階
    2. 1 階 + 2 階
    3. 2 階 + 1 階

    題目解析

    爬樓梯,可以爬一步也可以爬兩步,問有多少種不同的方式到達終點,我們按照上面提到的四個步驟進行分析:

    • 問題拆解:

      我們到達第 n 個樓梯可以從第 n – 1 個樓梯和第 n – 2 個樓梯到達,因此第 n 個問題可以拆解成第 n – 1 個問題和第 n – 2 個問題,第 n – 1 個問題和第 n – 2 個問題又可以繼續往下拆,直到第 0 個問題,也就是第 0 個樓梯 (起點)

    • 狀態定義

      “問題拆解” 中已經提到了,第 n 個樓梯會和第 n – 1 和第 n – 2 個樓梯有關聯,那麼具體的聯繫是什麼呢?你可以這樣思考,第 n – 1 個問題裏面的答案其實是從起點到達第 n – 1 個樓梯的路徑總數,n – 2 同理,從第 n – 1 個樓梯可以到達第 n 個樓梯,從第 n – 2 也可以,並且路徑沒有重複,因此我們可以把第 i 個狀態定義為 “從起點到達第 i 個樓梯的路徑總數”,狀態之間的聯繫其實是相加的關係。

    • 遞推方程

      “狀態定義” 中我們已經定義好了狀態,也知道第 i 個狀態可以由第 i – 1 個狀態和第 i – 2 個狀態通過相加得到,因此遞推方程就出來了 dp[i] = dp[i - 1] + dp[i - 2]

    • 實現

      你其實可以從遞推方程看到,我們需要有一個初始值來方便我們計算,起始位置不需要移動 dp[0] = 0,第 1 層樓梯只能從起始位置到達,因此 dp[1] = 1,第 2 層樓梯可以從起始位置和第 1 層樓梯到達,因此 dp[2] = 2,有了這些初始值,後面就可以通過這幾個初始值進行遞推得到。

    參考代碼

    public int climbStairs(int n) {
        if (n == 1) {
            return 1;
        }

        int[] dp = new int[n + 1];  // 多開一位,考慮起始位置

        dp[0] = 0; dp[1] = 1; dp[2] = 2;
        for (int i = 3; i <= n; ++i) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }

        return dp[n];
    }

    三角形最小路徑和

    LeetCode 第 120 號問題:三角形最小路徑和。

    題目描述

    給定一個三角形,找出自頂向下的最小路徑和。每一步只能移動到下一行中相鄰的結點上。

    例如,給定三角形:

    [
         [2],
        [3,4],
       [6,5,7],
      [4,1,8,3]
    ]

    自頂向下的最小路徑和為 11(即,2 + 3 + 5 + 1 = 11)。

    說明:

    如果你可以只使用 O(n) 的額外空間(n 為三角形的總行數)來解決這個問題,那麼你的算法會很加分。

    題目解析

    給定一個三角形數組,需要求出從上到下的最小路徑和,也和之前一樣,按照四個步驟來分析:

    • 問題拆解:

      這裏的總問題是求出最小的路徑和,路徑是這裏的分析重點,路徑是由一個個元素組成的,和之前爬樓梯那道題目類似,[i][j] 位置的元素,經過這個元素的路徑肯定也會經過 [i - 1][j] 或者 [i - 1][j - 1],因此經過一個元素的路徑和可以通過這個元素上面的一個或者兩個元素的路徑和得到。

    • 狀態定義

      狀態的定義一般會和問題需要求解的答案聯繫在一起,這裏其實有兩種方式,一種是考慮路徑從上到下,另外一種是考慮路徑從下到上,因為元素的值是不變的,所以路徑的方向不同也不會影響最後求得的路徑和,如果是從上到下,你會發現,在考慮下面元素的時候,起始元素的路徑只會從[i - 1][j] 獲得,每行當中的最後一個元素的路徑只會從 [i - 1][j - 1] 獲得,中間二者都可,這樣不太好實現,因此這裏考慮從下到上的方式,狀態的定義就變成了 “最後一行元素到當前元素的最小路徑和”,對於 [0][0] 這個元素來說,最後狀態表示的就是我們的最終答案。

    • 遞推方程

      “狀態定義” 中我們已經定義好了狀態,遞推方程就出來了

      dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle[i][j]
    • 實現

      這裏初始化時,我們需要將最後一行的元素填入狀態數組中,然後就是按照前面分析的策略,從下到上計算即可

    參考代碼

    public int minimumTotal(List<List<Integer>> triangle) {
        int n = triangle.size();

        int[][] dp = new int[n][n];

        List<Integer> lastRow = triangle.get(n - 1);

        for (int i = 0; i < n; ++i) {
            dp[n - 1][i] = lastRow.get(i);
        }

        for (int i = n - 2; i >= 0; --i) {
            List<Integer> row = triangle.get(i);
            for (int j = 0; j < i + 1; ++j) {
                dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + row.get(j);
            }
        }

        return dp[0][0];
    }

    最大子序和

    LeetCode 第 53 號問題:最大子序和。

    題目描述

    給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。

    示例:

    輸入: [-2,1,-3,4,-1,2,1,-5,4],
    輸出: 6
    解釋: 連續子數組 [4,-1,2,1] 的和最大,為 6。

    進階:

    如果你已經實現複雜度為 O(n) 的解法,嘗試使用更為精妙的分治法求解。

    題目解析

    求最大子數組和,非常經典的一道題目,這道題目有很多種不同的做法,而且很多算法思想都可以在這道題目上面體現出來,比如動態規劃、貪心、分治,還有一些技巧性的東西,比如前綴和數組,這裏還是使用動態規劃的思想來解題,套路還是之前的四步驟:

    • 問題拆解:

      問題的核心是子數組,子數組可以看作是一段區間,因此可以由起始點和終止點確定一個子數組,兩個點中,我們先確定一個點,然後去找另一個點,比如說,如果我們確定一個子數組的截止元素在 i 這個位置,這個時候我們需要思考的問題是 “以 i 結尾的所有子數組中,和最大的是多少?”,然後我們去試着拆解,這裏其實只有兩種情況:

    • i 這個位置的元素自成一個子數組

    • i 位置的元素的值 + 以 i – 1 結尾的所有子數組中的子數組和最大的值

      你可以看到,我們把第 i 個問題拆成了第 i – 1 個問題,之間的聯繫也變得清晰

    • 狀態定義

      通過上面的分析,其實狀態已經有了,dp[i] 就是 “以 i 結尾的所有子數組的最大值

    • 遞推方程

      拆解問題的時候也提到了,有兩種情況,即當前元素自成一個子數組,另外可以考慮前一個狀態的答案,於是就有了

      dp[i] = Math.max(dp[i - 1] + array[i], array[i])

      化簡一下就成了:

      dp[i] = Math.max(dp[i - 1], 0) + array[i]
    • 實現

      題目要求子數組不能為空,因此一開始需要初始化,也就是 dp[0] = array[0],保證最後答案的可靠性,另外我們需要用一個變量記錄最後的答案,因為子數組有可能以數組中任意一個元素結尾

    參考代碼

    public int maxSubArray(int[] nums{
        if (nums == null || nums.length == 0) {
            return 0;
        }

        int n = nums.length;

        int[] dp = new int[n];

        dp[0] = nums[0];

        int result = dp[0];

        for (int i = 1; i < n; ++i) {
            dp[i] = Math.max(dp[i - 1], 0) + nums[i];
            result = Math.max(result, dp[i]);
        }

        return result;
    }

    總結

    通過這幾個簡單的例子,相信你不難發現,解動態規劃題目其實就是拆解問題,定義狀態的過程,嚴格說來,動態規劃並不是一個具體的算法,而是凌駕於算法之上的一種 思想

    這種思想強調的是從局部最優解通過一定的策略推得全局最優解,從子問題的答案一步步推出整個問題的答案,並且利用空間換取時間。從很多算法之中你都可以看到動態規劃的影子,所以,還是那句話 技術都是相通的,找到背後的本質思想是關鍵

    公眾號:五分鐘學算法(ID:CXYxiaowu)
    博客:www.cxyxiaowu.com(目前更新了 500 篇算法文章,歡迎訪問學習)
    知乎:程序員吳師兄
    一個正在學習算法的人,致力於將算法講清楚​!

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

    南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

  • Android音頻開發(1):基礎知識

    Android音頻開發(1):基礎知識

    Android音頻開發(1):基礎知識

    導讀

    人的說話頻率基本上為300Hz~3400Hz,但是人耳朵聽覺頻率基本上為20Hz~20000Hz。

    對於人類的語音信號而言,實際處理一般經過以下步驟:

    人嘴說話——>聲電轉換——>抽樣(模數轉換)——>量化(將数字信號用適當的數值表示)——>編碼(數據壓縮)——>

    傳輸(網絡或者其他方式)

    ——> 解碼(數據還原)——>反抽樣(數模轉換)——>電聲轉換——>人耳聽聲。

    • 抽樣率

    實際中,人發出的聲音信號為模擬信號,想要在實際中處理必須為数字信號,即採用抽樣、量化、編碼的處理方案。

    處理的第一步為抽樣,即模數轉換。

    簡單地說就是通過波形採樣的方法記錄1秒鐘長度的聲音,需要多少個數據。

    根據奈魁斯特(NYQUIST)採樣定理,用兩倍於一個正弦波的頻繁率進行採樣就能完全真實地還原該波形。

    所以,對於聲音信號而言,要想對離散信號進行還原,必須將抽樣頻率定為40KHz以上。實際中,一般定為44.1KHz。

    44.1KHz採樣率的聲音就是要花費44000個數據來描述1秒鐘的聲音波形。

    原則上採樣率越高,聲音的質量越好,採樣頻率一般共分為22.05KHz、44.1KHz、48KHz三個等級。

    22.05 KHz只能達到FM廣播的聲音品質,44.1KHz則是理論上的CD音質界限,48KHz則已達到DVD音質了。

    • 碼率

    對於音頻信號而言,實際上必須進行編碼。在這裏,編碼指信源編碼,即數據壓縮。如果,未經過數據壓縮,直接量化進行傳輸則被稱為PCM(脈衝編碼調製)。
    要算一個PCM音頻流的碼率是一件很輕鬆的事情,採樣率值×採樣大小值×聲道數 bps。
    一個採樣率為44.1KHz,採樣大小為16bit,雙聲道的PCM編碼的WAV文件,它的數據速率則為 44.1K×16×2 =1411.2 Kbps。
    我們常說128K的MP3,對應的WAV的參數,就是這個1411.2 Kbps,這個參數也被稱為數據帶寬,它和ADSL中的帶寬是一個概念。將碼率除以8,就可以得到這個WAV的數據速率,即176.4KB/s。

    這表示存儲一秒鐘採樣率為44.1KHz,採樣大小為16bit,雙聲道的PCM編碼的音頻信號,需要176.4KB的空間,1分鐘則約為10.34M,這對大部分用戶是不可接受的,尤其是喜歡在電腦上聽音樂的朋友,要降低磁盤佔用

    只有2種方法,降低採樣指標或者壓縮。降低指標是不可取的,因此專家們研發了各種壓縮方案。最原始的有DPCM、ADPCM,其中最出名的為MP3。

    所以,採用了數據壓縮以後的碼率遠小於原始碼率。

    一、發的主要應用有哪些?

    音頻播放器,錄音機,語音電話,音視頻監控應用,音視頻直播應用,音頻編輯/處理軟件,藍牙耳機/音箱,等等。

    二、頻開發的具體內容有哪些?

    (1)音頻採集/播放

    (2)音頻算法處理(去噪、靜音檢測、回聲消除、音效處理、功放/增強、混音/分離,等等)

    (3)音頻的編解碼和格式轉換

    (4)音頻傳輸協議的開發(SIP,A2DP、AVRCP,等等)

    三、 音頻應用的難點在哪?

    延時敏感、卡頓敏感、噪聲抑制(Denoise)、回聲消除(AEC)、靜音檢測(VAD)、混音算法,等等。

    四、 音頻開發基礎概念有哪些?

    在音頻開發中,下面的這幾個概念經常會遇到。

    1. 採樣率(samplerate)

    採樣就是把模擬信號数字化的過程,不僅僅是音頻需要採樣,所有的模擬信號都需要通過採樣轉換為可以用0101來表示的数字信號,示意圖如下所示:

    藍色代表模擬音頻信號,紅色的點代表採樣得到的量化數值。

    採樣頻率越高,紅色的間隔就越密集,記錄這一段音頻信號所用的數據量就越大,同時音頻質量也就越高。

    根據奈奎斯特理論,採樣頻率只要不低於音頻信號最高頻率的兩倍,就可以無損失地還原原始的聲音。

    通常人耳能聽到頻率範圍大約在20Hz~20kHz之間的聲音,為了保證聲音不失真,採樣頻率應在40kHz以上。常用的音頻採樣頻率有:8kHz、11.025kHz、22.05kHz、16kHz、37.8kHz、44.1kHz、48kHz、96kHz、192kHz等。

    對採樣率為44.1kHz的AAC音頻進行解碼時,一幀的解碼時間須控制在23.22毫秒內。

    通常是按1024個採樣點一幀

    分析:

    1. AAC

    一個AAC原始幀包含某段時間內1024個採樣點相關數據。

    用1024主要是因為AAC是用的1024點的mdct。

    音頻幀的播放時間=一個AAC幀對應的採樣樣本的個數/採樣頻率(單位為s)

    採樣率(samplerate)為 44100Hz,表示每秒 44100個採樣點,

    所以,根據公式,

    音頻幀的播放時長 = 一個AAC幀對應的採樣點個數 / 採樣頻率

    則,當前一幀的播放時間 = 1024 * 1000/44100= 23.22 ms(單位為ms)

    48kHz採樣率,

    則,當前一幀的播放時間 = 1024 * 1000/48000= 21.333ms(單位為ms)

    22.05kHz採樣率,

    則,當前一幀的播放時間 = 1024 * 1000/22050= 46.439ms(單位為ms)

    2. MP3

    mp3 每幀均為1152個字節,

    則:

    每幀播放時長 = 1152 * 1000 / sample_rate

    例如:sample_rate = 44100HZ時,

    計算出的時長為26.122ms,

    這就是經常聽到的mp3每幀播放時間固定為26ms的由來。

    2. 量化精度(位寬)

    上圖中,每一個紅色的採樣點,都需要用一個數值來表示大小,這個數值的數據類型大小可以是:4bit、8bit、16bit、32bit等等,位數越多,表示得就越精細,聲音質量自然就越好,當然,數據量也會成倍增大。

    常見的位寬是:8bit 或者 16bit

    3. 聲道數(channels)

    由於音頻的採集和播放是可以疊加的,因此,可以同時從多個音頻源採集聲音,並分別輸出到不同的揚聲器,故聲道數一般表示聲音錄製時的音源數量或回放時相應的揚聲器數量。

    單聲道(Mono)和雙聲道(Stereo)比較常見,顧名思義,前者的聲道數為1,後者為2

    4. 音頻幀(frame)

    是用於測量显示幀數的量度。所謂的測量單位為每秒显示幀數(Frames per Second,簡稱:FPS)或“赫茲”(Hz)。

    音頻跟視頻很不一樣,視頻每一幀就是一張圖像,而從上面的正玄波可以看出,音頻數據是流式的,本身沒有明確的一幀幀的概念,在實際的應用中,為了音頻算法處理/傳輸的方便,一般約定俗成取2.5ms~60ms為單位的數據量為一幀音頻。

    這個時間被稱之為“採樣時間”,其長度沒有特別的標準,它是根據編×××和具體應用的需求來決定的,我們可以計算一下一幀音頻幀的大小:

    假設某通道的音頻信號是採樣率為8kHz,位寬為16bit,20ms一幀,雙通道,則一幀音頻數據的大小為:

    int size = 8000 x 16bit x 0.02s x 2 = 5120 bit = 640 byte

    五、常見的音頻編碼方式有哪些?

    上面提到過,模擬的音頻信號轉換為数字信號需要經過採樣和量化,量化的過程被稱之為編碼,根據不同的量化策略,產生了許多不同的編碼方式,常見的編碼方式有:PCM 和 ADPCM,這些數據代表着無損的原始数字音頻信號,添加一些文件頭信息,就可以存儲為WAV文件了,它是一種由微軟和IBM聯合開發的用於音頻数字存儲的標準,可以很容易地被解析和播放。

    我們在音頻開發過程中,會經常涉及到WAV文件的讀寫,以驗證採集、傳輸、接收的音頻數據的正確性。

    六、常見的音頻壓縮格式有哪些?

    首先簡單介紹一下音頻數據壓縮的最基本的原理:因為有冗餘信息,所以可以壓縮。

    (1) 頻譜掩蔽效應: 人耳所能察覺的聲音信號的頻率範圍為20Hz~20KHz,在這個頻率範圍以外的音頻信號屬於冗餘信號。

    (2) 時域掩蔽效應: 當強音信號和弱音信號同時出現時,弱信號會聽不到,因此,弱音信號也屬於冗餘信號。

    下面簡單列出常見的音頻壓縮格式:

    MP3,AAC,OGG,WMA,Opus,FLAC,APE,m4a,AMR,等等

    七、Adndroid VoIP相關的開源應用有哪些 ?

    imsdroid,sipdroid,csipsimple,linphone,WebRTC 等等

    八、音頻算法處理的開源庫有哪些 ?

    speex、ffmpeg,webrtc audio module(NS、VAD、AECM、AGC),等等

    九、Android提供了哪些音頻開發相關的API?

    音頻採集: MediaRecoder,AudioRecord

    音頻播放: SoundPool,MediaPlayer,AudioTrack

    音頻編解碼: MediaCodec

    NDK API: OpenSL ES

    十、音頻開發的延時標準是什麼?

    ITU-TG.114規定,對於高質量語音可接受的時延是300ms。一般來說,如果時延在300~400ms,通話的交互性比較差,但還可以接受。時延大於400ms時,則交互通信非常困難。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

  • 軟件測試(開發)工程師的核心競爭力是什麼?

    1. 測試行業正在發生變化

    在互聯網新趨勢和新要求的變革推動下,測試行業也在不知不覺中發生着非常大的改變,從早些年的懵懂發展,大家摸着石頭過河,到大多高校設立軟件測試專業,再到近幾年各種測試培訓盛行。如果說早期軟件測試行業還是一個風口,隨着不斷地轉行人員以及畢業的大學生瘋狂地湧入軟件測試行業,目前軟件測試行業“缺口”已經基本飽和,最基礎的功能測試的崗位需求已經越來越少了。測試的進入門檻,也從真正零基礎,到現在的要求具備專業的計算機專業能力(包括不限於編程能力),軟件測試在企業的受重視程度,特別是互聯網行業,也從可有可無,到不可或缺。

    2. 行業人員分佈呈現兩極勢態

    測試行業不斷髮展,行業已經呈現出嚴重的兩極分化勢態,一邊是資深的測試大牛,屬於全棧複合型人才,但這一類行業中人員占的比例較為稀少。一是由於行業原因,代碼能力強,有架構經驗的人員一般都在開發部門;二是要求高,資深測試開發工程師不僅要精通測試相關的技能,還要會前端設計,服務端開發等等,幾乎是全棧工程師;而做程序的人員一般精通一點或是幾點的較多,從前到后全都能上的越來越少。另一邊是測試小白,即便是有些在測試行業中已經摸爬滾打了幾年,但仍然有很多測試人員還是停留在只會業務功能測試的這個階段。而針對這類型的測試從業人員,除了一些安於現狀的除外,大多數人其實都還是想好好學習,想進步的只是不知道學習方向,或者學習不得其法。

     

    3. 企業需要更多高端綜合人才

    但不管是屬於哪一種,對於企業而言,想快速發展自己的業務,必須有一個強大的測試團隊來保證質量,通過一系列的質量保障手段,如引入CI,CD以及其他的手段來促進項目的快速迭代與交付。這就要求相關的測試工程師要能從多方面來考慮設計和解決問題,不僅要考慮項目的實施成本,還要考慮參与的測試,開發,產品甚至用戶等人員,同時要與公司發展的前景及方向相切合,並能很好地為之服務。提供這類能力的測試人才在公司都是較為吃香的,每年的找工作季節也就那麼幾個人會進入人才市場流通,而且很快就能找到工作,這也是每個測試人員的努力方向,只有具備了相應的價值實力,才有資格向企業要求你期望的回報

     

    4.企業招人與求職者供求總是難以匹配

    很多同學抱怨,企業招人為什麼要求越來越高,除了學歷(本科以上),還要求年齡(35內),以及項目經驗,太難太難 。其實,企業也挺苦惱的:招幾個適合的人選太難了 ,這就是所謂的「供需關係」失調了 。大批測試從業者找不到工作,大量企業找不到適合的人選 。

    而造成不匹配、供需關係失調的最核心的問題歸根到底還是聚焦於能力要求不匹配

    那麼測試人員核心技能或者說測試人員的核心競爭力到底有哪些? 測試人員應該思考這個問題、企業用人單位也應該要思考需要什麼樣的測試人員?相信大家面試求職時或多或少都會有這種感覺,企業在招聘時,要求會各種框架、各種編程語言、各種工具的使用。那在我們學會了測試技術、測試工具的使用,最後核心競爭力到底聚焦在哪些方面?

     

    5. 你的核心競爭力是什麼?

    提到在軟件測試這個行業,你的核心競爭力是什麼?這是個非常有意思的話題,就像我們經常說的“團隊中的價值問題”,你經常看到測試人員自己在想,我們的價值在哪裡、是什麼?但我們很少看到軟件的開發人員或者架構師,或者運維團隊去問這樣一個問題,要去找自己的價值。這是因為測試人員對這個價值本身是不太確定的,那麼這個價值本身不確定,就會帶來的一系列的問題。

    在早期軟件行業中,會發現存在一個普遍的現象,有些大學的本科,或者研究生畢業,他們去面試工作的時候就會發現,面試下來的是代碼能力可能不是太好,這種情況下公司會問你願不願意去做測試?但隨着現在這個時代的變革,現在的軟件測試工程師,他的知識面,以及他需要掌握的內容已經遠遠超過了之前,可以說他的知識面是遠遠超過開發的,比如在一些技術的面上,以及對產品的理解上。

    那麼這種情況下,我們再去提一個優秀的軟件測試工程師的核心價值,我們可以很自信地說,測試工程師是一個不可被替代的,並且是一個專業細分化的領域。像早年的時候,我們談到測試,就是軟件測試,沒有細分市場,但現在你去談測試,測試現在的領域太多了,除了傳統意義上的,基於業務領域的測試,然後還有測試開發。

     

    6. 企業為什麼不願給你開高薪?

    經常會有從業者諮詢我:“怎麼轉行到測試開發崗位?測試開發崗位怎麼入手?測試開發崗位到底是做什麼的?需要掌握哪些知識 ?”

    其實啊,問這些問題的時候,你可能就不太適合此崗位。或者你只是聽說測試開發工資高、奔着薪資來的,也許你完全不適合 。

    正如在之前介紹測試開發的文章 : 中提到過隨着現在測試開發崗在各個公司的設定,且測試開發崗一般會頂着“薪資高”的頭銜(至少在測試這個領域,測試開發的薪資普遍都要比業務手工測試高上許多),越來越多的手工測試人員,都急於想轉崗到測試開發,但需不知往往只是看到了測試開發崗的薪資高,但卻忽略了最重要的一點(那些拿高薪的人付出的努力同樣也是比你多)!我們不妨先看看下面幾則同行人的心聲。(是否曾及何時,正在讀文的你也是這麼認為的?)

    • 很多QQ群、微信群的測試同行經常在抱怨,平日測試工作乾的很苦逼,活沒少干,加班也沒少加,但工資、獎金卻比其它崗(比如開發)要拿的少。
    • 測試工作做了好幾年了,但去外面求職的時候,屢屢碰壁,總得拿不到自己滿意的薪資Offer。
    • 認為測試崗位沒有“錢”途、工作內容做的沒有意義,不如趁早轉開發、產品。

     

    之所以行業中會有許多從業人員有上述幾點心聲,最核心的問題點還是認為自己工作乾的活所得到的薪資待遇和自己希望得到的回報無法相匹配上。正如馬雲之前說過,企業員工離職的原因,歸根結底只有兩個:1、錢沒給夠。2、平台無法施展才能,覺得委屈了

     

    我相信絕大多數人,都是“倒”在了第一點原因上。那為什麼企業開的薪資就總是無法達到“大多數從業人員”的要求呢?難道企業開不起薪資?但身在同一個公司,為何又存在其它崗位“測試開發”、“開發”薪資高這一說法?這顯然並不是企業開不起薪資,而是企業認為TA所能幫助企業帶來的價值只值這麼多。

     

    7. 對高薪崗位的誤解

    不論是“測試開發”或者是“開發”,頂着“薪資高”這一普遍說法,其中大多數對這個說法還是存在誤解的,並不是所謂的“崗位薪資論”,認為做了這個崗位,就一定有高的薪資,試想一下,同樣有很多開發人員,薪資不見的就比測試牛人高。而那些之所以有着“高薪崗位的人”,是因為他們所具備的能力以及能為公司帶來的價值也是越高的。因此,`高薪!= 崗位`,而**應該是高薪要等於與之匹配的能力和能為企業帶來的等同價值**。

    這一觀點,恰好也回應了上述所提到的,現在越來越多的手工測試人員都想轉行測試開發。但轉行到測試開發並不是關鍵,如果能力沒有轉變,只是崗位的頭銜轉變了,即便給你安排一個測試開發或開發的頭銜,但你的能力還只是在干一些不痛不癢的工作,那麼企業仍然是不可能會為你買單的。之所有測試開發有着高薪的說法,是由於現在企業對測試開發的綜合能力已經不亞於開發,他們的技術能力和解決業務問題的能力在某些方面甚至要強於開發。因此企業肯為這些人付出高薪的回報。

    我想對那些想轉崗或者埋怨自己工資低的從業人員,奉切一句:轉崗不是最終目的,提升自身能力才是根本。如果你的能力足夠出眾,能你團隊、企業帶來的價值已經超出測試所需要提供的,即便只是頂着業務測試的頭銜,我相信,企業仍然肯為你付出相應的高回報。

     

    8. 如何打造個人核心競爭力

    那些想拿高薪或者是想轉崗成為測試開發的同學,需要做的應該是要不斷提升自身能力和價值點,這些價值點立足在團隊、公司無非就是兩類能力:1.綜合技術能力、2.幫助產品業務解決問題的能力。

    1. 提升綜合技術能力,說到技術,第一關:開發語言(不管是Python,還是Java,真的無所謂,先搞懂一個再說) 。

    先能獨立開發一套可用的東西。至於你寫的代碼高性能、高可用,先可以放放 。但至少得通過擼代碼,實現業務方需求吧 ?

    很多測試同學問,到底學Python還是學Java ?半年後,你去問他學的咋樣的,他可能還在那糾結:“到底是學Python還是學Java ?”的問題,根本就沒開始學。

    “學習這事,道理都懂,就是缺行動。”,雖然這句話,看起來像廢話,但事實如此。

    很多時候,看着那些:“知道自己能力有問題、想學點啥東西、到處諮詢他人應該學啥、得到答案后、依然半年沒行動”的(別笑,看文章的你,也許就是)。

    否則,怎麼可能會出現:在市場上,想招一些靠譜的從業者,那麼難 。看到很多公司,耗時幾個月招不到適合的人,雖然這裡有公司的原因,但求職者能力不符合,是很大一部分原因 。

    行業在發展,一直守着“自己那點業務知識、測試流程、幾年前的工具”的同學,太多 。借用之前的觀點,定期出來面試聊聊,你會發現,你根本找不到合適的工作 。

    如果還在糾結學啥開發語言的,別糾結,此刻、現在,開始,學Python 。

    Python易入手,簡單,好用 。而且,如果不做測試開發,通過Python也可以玩轉各種自動化測試。

     

    OK ,如上內容,是對測試(開發)工程師核心競爭力的一些看法,摘自本人公眾號中的一部分篇幅內容,僅代表個人觀點 。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

    南投搬家前需注意的眉眉角角,別等搬了再說!