標籤: 網頁設計公司

  • Spring AOP學習筆記02:如何開啟AOP

    Spring AOP學習筆記02:如何開啟AOP

      上文簡要總結了一些AOP的基本概念,並在此基礎上敘述了Spring AOP的基本原理,並且輔以一個簡單例子幫助理解。從本文開始,我們要開始深入到源碼層面來一探Spring AOP魔法的原理了。

      要使用Spring AOP,第一步是要將這一功能開啟,一般有兩種方式:

    • 通過xml配置文件的方式;
    • 通過註解的方式;

     

    1. 配置文件開啟AOP功能

      我們先來看一下配置文件的方式,這個上文也提到過,在xml文件中加上對應的標籤,而且別忘了加上對應的名稱空間(即下面的xmlns:aop。。。):

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns="http://www.springframework.org/schema/beans"
           xmlns:aop = "http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
         
         <aop:aspectj-autoproxy/>
    
    </beans>

      這裡是通過標籤<aop:aspectj-autoproxy/>來完成開啟AOP功能,這是一個自定義標籤,需要自定義其解析,而這些spring都已經實現好了,前面專門寫過一篇文章講述spring是如何解析自定義xml標籤的,我們這裏大致回顧一下解析流程:

    • 定義一個XML文件來描述你的自定義標籤元素;
    • 創建一個Handler,擴展自NamespaceHandlerSupport,用於註冊下面的parser;
    • 創建若干個BeanDefinitionParser的實現,用來解析XML文件中的定義;
    • 將上述文件註冊到Spring中,這裏其實是做一下配置;

      我們就不照着這個步驟來了,我們直接參考spring對這個自定義標籤的解析過程,上面的4個步驟只是作為參考,在整個解析過程中都會涉及到。

      前面講解析自定義xml標籤時候提到過,解析的流程大致如下:

    • 首先會去獲取自定義標籤對應的名稱空間;
    • 然後根據名稱空間找到對應的NamespaceHandler;
    • 調用自定義的NamespaceHandler進行解析;

    1.1 獲取名稱空間

      這裏<aop:aspectj-autoproxy/>對應的名稱空間是什麼呢?在上面的開啟aop的配置文件裏面名稱空間那裡給出了一些線索,其實就是下面這個:

    http://www.springframework.org/schema/aop

      至於名稱空間的獲取,也無甚好說的,其實就是直接調用org.w3c.dom.Node提供的相應方法來完成名稱空間的提取。

    1.2 獲取handler

      然後又是如何根據名稱空間找到對應的NamespaceHandler呢?之前也說到過,在找對應的NamespaceHandler時會去META-INF/spring.handlers這個目錄下加載資源文件,我們來找一下spring.handlers這個文件看看(需要去spring-aop對應的jar報下找):

    http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

      看到沒,這裡是以key-value的形式維護着名稱空間和對應handler的關係的,所以對應的handler就是這個AopNamespaceHandler。spring根據名稱空間找到這個handler之後,會通過反射的方式將這個類加載,並緩存起來。

    1.3 解析標籤

      上面的handler只有一個自定義的方法:

    public void init() {
        // In 2.0 XSD as well as in 2.1 XSD.
        registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
    
        // Only in 2.0 XSD: moved to context namespace as of 2.1
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }

      這是一個初始化方法,在加載的時候會執行,主要作用就是註冊一些解析器,這裏我們主要關注AspectJAutoProxyBeanDefinitionParser,這就是我們要找的,它的作用就是解析<aop:aspectj-autoproxy/>標籤的。主要流程就是,spring會調用上一步拿到的AopNamespaceHandler的parse()方法,在這個方法裏面,會將解析的工作委託給AspectJAutoProxyBeanDefinitionParser來完成具體解析工作,我們就來看一下具體幹了啥吧。

      開始解析的工作從這裏開始:

    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));

      此時我們拿到的handler其實是我們自定義的AopNamespaceHandler了,但是它並沒有實現parse()方法,所以這裏這個應該是調用的父類(NamespaceHandlerSupport)中的parse()方法:

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        // 尋找解析器並進行解析操作
        return findParserForElement(element, parserContext).parse(element, parserContext);
    }
    
    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        // 獲取元素名稱,也就是<aop:aspectj-autoproxy/>中的aspectj-autoproxy
        String localName = parserContext.getDelegate().getLocalName(element);
        // 根據aspectj-autoproxy找到對應的解析器,也就是在registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        // 註冊的解析器
        BeanDefinitionParser parser = this.parsers.get(localName);
        if (parser == null) {
            parserContext.getReaderContext().fatal(
                "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
        }
        return parser;
    }

      首先是尋找元素對應的解析器,然後調用其parse()方法。結合我們前面的示例,其實就是首先獲取在AopNamespaceHandler類中的init()方法中註冊對應的AspectJAutoProxyBeanDefinitionParser實例,並調用其parse()方法進行進一步解析:

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
        extendBeanDefinition(element, parserContext);
        return null;
    }
    
    // 下面的代碼在AopConfigUtils中
    public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
            ParserContext parserContext, Element sourceElement) {
    
        BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
                parserContext.getRegistry(), parserContext.extractSource(sourceElement));
        useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
        registerComponentIfNecessary(beanDefinition, parserContext);
    }
    
    public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
        return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
    }
    
    private static BeanDefinition registerOrEscalateApcAsRequired(Class cls, BeanDefinitionRegistry registry, Object source) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
            BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
            if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
                int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
                int requiredPriority = findPriorityForClass(cls);
                if (currentPriority < requiredPriority) {
                    apcDefinition.setBeanClassName(cls.getName());
                }
            }
            return null;
        }
        RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
        beanDefinition.setSource(source);
        beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
        return beanDefinition;
    }

      上面這一堆代碼最核心的部分就在後兩個方法中,就是完成了對AnnotationAwareAspectJAutoProxyCreator類的註冊,到這裏對自定義標籤<aop:aspectj-autoproxy/>的解析也就完成了,可以看到其最核心的部分就是完成了對AnnotationAwareAspectJAutoProxyCreator類的註冊,那為什麼註冊了這個類就開啟了aop功能呢?這裏先賣個關子,後面詳細說。

      這裏再回過頭來看一下上面說到的spring對自定義標籤解析的4個步驟,其實第一步的schema對應的是在org.springframework.aop.config路徑下的spring-aop-3.0.xsd文件,其映射關係是維護在META-INF/spring.schemas文件中的,而spring-aop-3.0.xsd的主要作用就是描述自定義標籤。

      當通過META-INF/spring.handlers找到對應的AopNamespaceHandler,並通過在其加載后執行init()方法過程中完成了AspectJAutoProxyBeanDefinitionParser的註冊,有這個parser再來完成對自定義標籤的解析工作,這對應上面4個步驟中的第二步和第三部。至於第四步的配置工作,無非就是將spring.schemas和spring.handlers這兩個配置文件放在META-INF/目錄下罷了。

      關於這部分解析過程,寫得不是非常詳細,如果有不明白,可以參考之前一篇文章,講spring是如何解析自定義xml標籤。

     

    2. 註解方式開啟aop

      另一種開啟spring aop的方式是通過註解的方式,使用的註解是@EnableAspectJAutoProxy,可以通過配置類的方式完成註冊:

    @Configuration
    @EnableAspectJAutoProxy
    public class AppConfig {
    
    }

       也可以在啟動類上直接加上這個註解,這在springboot中比較常見,其實質也是上面的方式。通過這種方式配置之後,就開啟了aop功能,那具體又是如何實現的呢?我們看一下這個註解:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(AspectJAutoProxyRegistrar.class)
    public @interface EnableAspectJAutoProxy {
    
        /**
         * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
         * to standard Java interface-based proxies. The default is {@code false}.
         */
        boolean proxyTargetClass() default false;
    
        /**
         * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
         * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
         * Off by default, i.e. no guarantees that {@code AopContext} access will work.
         * @since 4.3.1
         */
        boolean exposeProxy() default false;
    
    }

      這裏我們的關注點是其通過@Import(AspectJAutoProxyRegistrar.class)引入了AspectJAutoProxyRegistrar,那這又是什麼?

    class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    
        /**
         * Register, escalate, and configure the AspectJ auto proxy creator based on the value
         * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
         * {@code @Configuration} class.
         */
        @Override
        public void registerBeanDefinitions(
                AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
            AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
    
            AnnotationAttributes enableAspectJAutoProxy =
                    AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
            if (enableAspectJAutoProxy != null) {
                if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                    AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                }
                if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                    AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
                }
            }
        }
    
    }

      看到這裏,是不是有點眼熟了呢?是的,其實它也是和上面說的xml配置使用的方式一樣,通過AopConfigUtils來完成AnnotationAwareAspectJAutoProxyCreator類的註冊。是不是比xml配置文件的方式方便許多呢。

     

    4. 開啟aop的魔法

      通過前面的學習我們了解了可以通過Spring自定義配置完成對AnnotationAwareAspectJAutoProxyCreator類型的自動註冊,而這個類到底是做了什麼工作來實現AOP的操作呢?這裏還是先來看一下AnnotationAwareAspectJAutoProxyCreator的類層次結構:

      這裡有一個很重要的點,就是AnnotationAwareAspectJAutoProxyCreator實現了BeanPostProcessor接口。在IOC部分的文章中有詳細說過,Spring在加載Bean的過程中會在實例化bean前後調用BeanPostProcessor的相關方法(相關邏輯是在initializeBean方法中,調用postProcessBeforeInitialization、postProcessAfterInitialization方法),而AOP的魔法就是從這裏開始的。

      每次看到這裏,我內心對spring的軟件架構設計都是湧現出無比的佩服,通過後處理器的方式來做擴展,對原有模塊是沒有任何改動,也不會產生耦合,spring親自踐行着對修改關閉,對擴展開放的原則。

     

    3. 總結

       本文我們學習了spring是如何開啟aop功能的,無論是通過xml配置文件方式,還是通過Java config這種註解的方式,其最終都是完成了將AnnotationAwareAspectJAutoProxyCreator這個類註冊到spring容器當中,那這個類又有什麼魔法,可以達到將其註冊到容器即達到開啟aop的功效,其實其繼承自BeanPostProcessor接口,通過後處理器的方式擴展出了開啟spring aop的功能。

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

    【其他文章推薦】

    台北網頁設計公司這麼多該如何選擇?

    ※智慧手機時代的來臨,RWD網頁設計為架站首選

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

    ※回頭車貨運收費標準

    網頁設計最專業,超強功能平台可客製化

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

  • Oracle數據遷移後由列的直方圖統計信息引起的執行計劃異常

    Oracle數據遷移後由列的直方圖統計信息引起的執行計劃異常

    (一)問題背景

    在使用impdp進行數據導入的時候,往往在導入表和索引的統計信息的時候,速度非常慢,因此我在使用impdp進行導入時,會使用exclude=table_statistics排除表的統計信息,從而加快導入速度,之後再手動收集統計信息。

                                                  圖.impdp導入數據的時導入統計信息速度非常慢

    導入語句如下:

    impdp user/password directory=DUMPDIR dumpfile=TEST01.dmp logfile=TEST01.log remap_schema=TEST_USER:TEST_USER123 exclude=table_statistics

    手動收集統計信息語句如下:

    EXEC dbms_stats.gather_table_stats(ownname => 'LIJIAMAN',tabname => 'TEST01');

    最近使用以上方法將數據還原到測試環境后,發現與生產環境執行計劃存在偏差,本來應該走全表掃描的,卻走了索引範圍掃描。經過確認,是由於列的直方圖統計信息未收集引發的執行計劃偏差。


    (二)列的直方圖統計信息

    什麼是列的直方圖統計信息呢?在Oracle數據庫中,Oracle默認列上的值是在最小值與最大值之間均分佈的,當在計算cardinatity時,會以均勻分佈的方式計算,但是在實際生活中某些場景下數據並非均勻分佈。舉個列子,某公司有員工10000人,表A的列COL1記錄員工的績效(分別是:A、B、C、D,A最好,D最差),那麼可能A佔了15%,B佔了60,C佔了20%,D佔了5%。很明顯在該場景下數據並非均勻分佈,假如以均勻分佈的方式去統計員工的績效,可能會導致執行計劃失准。

    當列的數據分佈不均勻的時候,就需要統計列上的數據分佈情況,從而走出正確的執行計劃,列的直方圖統計信息就是記錄列上的數據分佈情況的。


    (三)異常模擬

    STEP1:創建測試表test01

    create table test01
    (id number,
    name varchar2(10)
    );
    create index idx_test01_id on test01(id);

    向test01中插入測試數據

    begin
    insert into test01 values(1,'a');
    
    for i in 1..10 loop
    insert into test01 values(2,'b');
    end loop;
    
    for i in 1..100 loop
    insert into test01 values(3,'c');
    end loop;
    
    for i in 1..1000 loop
    insert into test01 values(4,'d');
    end loop;
    
    commit;
    end;

    查看數據分佈情況:

    SQL> SELECT ID,NAME,COUNT(*) FROM test01 GROUP BY ID,NAME ORDER BY COUNT(*);
    
    ID          NAME       COUNT(*)
    ---------- ---------- ----------
    1           a          1       
    2           b          10
    3           c          100
    4           d          1000


    STEP2:收集統計信息,因為上面查詢過id列,故在收集統計信息的時候,會收集直方圖的統計信息

    EXEC dbms_stats.gather_table_stats(ownname => 'LIJIAMAN',tabname => 'TEST01');

    查看是否已經收集了直方圖信息,發現id列上已經收集

    SQL> SELECT a.OWNER,a.TABLE_NAME,a.COLUMN_NAME,a.LOW_VALUE,a.HIGH_VALUE,a.NUM_BUCKETS,a.HISTOGRAM
    2 FROM dba_tab_columns a
    3 WHERE a.OWNER = 'LIJIAMAN' AND a.TABLE_NAME = 'TEST01';
    
    OWNER     TABLE_NAME   COLUMN_NAME   LOW_VALUE     HIGH_VALUE    NUM_BUCKETS   HISTOGRAM
    --------- -----------  ------------  ------------  ------------  -----------  ---------------
    LIJIAMAN  TEST01       ID            C102          C105          4             FREQUENCY
    LIJIAMAN  TEST01       NAME          61            64            1             NONE

    查看直方圖,已經將id列的4個值放入了4個bucket中:

    SQL> SELECT * FROM dba_tab_histograms a WHERE a.OWNER = 'LIJIAMAN' AND a.TABLE_NAME = 'TEST01';
    
    OWNER        TABLE_NAME    COLUMN_NAME    ENDPOINT_NUMBER    ENDPOINT_VALUE ENDPOINT_ACTUAL_VALUE
    -----------  ------------  -------------  ---------------    -------------- ----------------------
    LIJIAMAN     TEST01        ID                           1                 1 
    LIJIAMAN     TEST01        ID                          11                 2 
    LIJIAMAN     TEST01        ID                         111                 3 
    LIJIAMAN     TEST01        ID                        1111                 4 
    LIJIAMAN     TEST01        NAME                         0    5.036527952778 
    LIJIAMAN     TEST01        NAME                         1    5.192296858534


    STEP3:查看id=1和id=4的執行計劃,當id=1時,走索引範圍掃描,當id=4時,走全表掃描

    id列存在直方圖統計信息,當id=1時,走索引範圍掃描 id列存在直方圖統計信息,當id=4時,走全表掃描
    SELECT * FROM test01 WHERE ID=1
    
     Plan Hash Value  : 1151852672 
    
    ----------------------------------------------------------------------------------------
    | Id  | Operation                     | Name          | Rows | Bytes | Cost | Time     |
    ----------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT              |               |    1 |     5 |    2 | 00:00:01 |
    |   1 |   TABLE ACCESS BY INDEX ROWID | TEST01        |    1 |     5 |    2 | 00:00:01 |
    | * 2 |    INDEX RANGE SCAN           | IDX_TEST01_ID |    1 |       |    1 | 00:00:01 |
    ----------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ------------------------------------------
    * 2 - access("ID"=1)
    SELECT * FROM test01 WHERE ID=4
    
     Plan Hash Value  : 262542483 
    
    -----------------------------------------------------------------------
     | Id  | Operation           | Name   | Rows | Bytes | Cost | Time     |
    -----------------------------------------------------------------------
     |   0 | SELECT STATEMENT    |        | 1000 |  5000 |    3 | 00:00:01 |
     | * 1 |   TABLE ACCESS FULL | TEST01 | 1000 |  5000 |    3 | 00:00:01 |
    -----------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
     ------------------------------------------
     * 1 - filter("ID"=4)

    STEP4:接下來模擬數據遷移,排除統計信息

    導出表test01

    expdp lijiaman/lijiaman directory=DUMPDIR tables=LIJIAMAN.TEST01 dumpfile =test01.dmp

    刪除原來的表:

    SQL> drop table test01;
    Table dropped

    再次導入表,排除統計信息:

    impdp lijiaman/lijiaman directory=DUMPDIR dumpfile =test01.dmp exclude=table_statistics

    查看錶的統計信息,不存在統計信息:

    SQL> SELECT   a.OWNER,a.TABLE_NAME,a.COLUMN_NAME,a.LOW_VALUE,a.HIGH_VALUE,a.NUM_BUCKETS,a.HISTOGRAM
       2  FROM     dba_tab_columns a
       3  WHERE    a.OWNER = 'LIJIAMAN' AND a.TABLE_NAME = 'TEST01';
    
    OWNER          TABLE_NAME      COLUMN_NAME     LOW_VALUE    HIGH_VALUE   NUM_BUCKETS HISTOGRAM
     -------------- --------------- --------------- ------------ ------------ ----------- ---------------
     LIJIAMAN       TEST01          ID                                                    NONE
     LIJIAMAN       TEST01          NAME                                                  NONE

    STEP5:手動收集統計信息

    EXEC dbms_stats.gather_table_stats(ownname => 'LIJIAMAN',tabname => 'TEST01');

    發現統計信息已經收集,但是不存在直方圖的統計信息

    SQL> SELECT   a.OWNER,a.TABLE_NAME,a.COLUMN_NAME,a.LOW_VALUE,a.HIGH_VALUE,a.NUM_BUCKETS,a.HISTOGRAM
      2  FROM     dba_tab_columns a
      3  WHERE    a.OWNER = 'LIJIAMAN' AND a.TABLE_NAME = 'TEST01';
    
    OWNER     TABLE_NAME  COLUMN_NAME  LOW_VALUE   HIGH_VALUE  NUM_BUCKETS HISTOGRAM
    --------- ----------- -----------  ----------- ----------- ----------- ---------------
    LIJIAMAN  TEST01      ID           C102        C105                  1 NONE
    LIJIAMAN  TEST01      NAME         61          64                    1 NONE

    STEP6:再次查看id=1和id=4的執行計劃,當id=1或id=4時,都走索引範圍掃描

    id列未收集直方圖統計信息,當id=1時,走索引範圍掃描 id列未收集直方圖統計信息,當id=4時,走索引範圍掃描
    SELECT * FROM test01 WHERE ID=1
     Plan Hash Value  : 1151852672 
    
    ----------------------------------------------------------------------------------------
    | Id  | Operation                     | Name          | Rows | Bytes | Cost | Time     |
    ----------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT              |               |  278 |  1390 |    2 | 00:00:01 |
    |   1 |   TABLE ACCESS BY INDEX ROWID | TEST01        |  278 |  1390 |    2 | 00:00:01 |
    | * 2 |    INDEX RANGE SCAN           | IDX_TEST01_ID |  278 |       |    1 | 00:00:01 |
    ----------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ------------------------------------------
    * 2 - access("ID"=1)
    SELECT * FROM test01 WHERE ID=4
    
     Plan Hash Value  : 1151852672 
    
    ----------------------------------------------------------------------------------------
    | Id  | Operation                     | Name          | Rows | Bytes | Cost | Time     |
    ----------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT              |               |  278 |  1390 |    2 | 00:00:01 |
    |   1 |   TABLE ACCESS BY INDEX ROWID | TEST01        |  278 |  1390 |    2 | 00:00:01 |
    | * 2 |    INDEX RANGE SCAN           | IDX_TEST01_ID |  278 |       |    1 | 00:00:01 |
    ----------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ------------------------------------------
    * 2 - access("ID"=4)

    STEP7:再次收集統計信息,因為使用過了id列作為查詢條件,故再次收集統計信息時,會收集id列的直方圖信息:

    EXEC dbms_stats.gather_table_stats(ownname => 'LIJIAMAN',tabname => 'TEST01');

    可以看到,此時已經收集了id列的直方圖統計信息:

    SQL> SELECT   a.OWNER,a.TABLE_NAME,a.COLUMN_NAME,a.LOW_VALUE,a.HIGH_VALUE,a.NUM_BUCKETS,a.HISTOGRAM
      2  FROM     dba_tab_columns a
      3  WHERE    a.OWNER = 'LIJIAMAN' AND a.TABLE_NAME = 'TEST01';
    
    OWNER                          TABLE_NAME                     COLUMN_NAME                    LOW_VALUE     HIGH_VALUE    NUM_BUCKETS HISTOGRAM
    ------------------------------ ------------------------------ ------------------------------ ------------- ------------- ----------- ---------------
    LIJIAMAN                       TEST01                         ID                             C102          C105                    4 FREQUENCY
    LIJIAMAN                       TEST01                         NAME                           61            64                      1 NONE
    

    執行計劃已經按照我們想要的方式走:

    id列重新收集直方圖統計信息,當id=1時,走索引範圍掃描 id列重新收集直方圖統計信息,當id=4時,走全表掃描
    SELECT * FROM test01 WHERE ID=1
    
     Plan Hash Value  : 1151852672 
    
    ----------------------------------------------------------------------------------------
    | Id  | Operation                     | Name          | Rows | Bytes | Cost | Time     |
    ----------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT              |               |    1 |     5 |    2 | 00:00:01 |
    |   1 |   TABLE ACCESS BY INDEX ROWID | TEST01        |    1 |     5 |    2 | 00:00:01 |
    | * 2 |    INDEX RANGE SCAN           | IDX_TEST01_ID |    1 |       |    1 | 00:00:01 |
    ----------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ------------------------------------------
    * 2 - access("ID"=1)
    SELECT * FROM test01 WHERE ID=4
    
     Plan Hash Value  : 262542483 
    
    -----------------------------------------------------------------------
    | Id  | Operation           | Name   | Rows | Bytes | Cost | Time     |
    -----------------------------------------------------------------------
    |   0 | SELECT STATEMENT    |        | 1000 |  5000 |    3 | 00:00:01 |
    | * 1 |   TABLE ACCESS FULL | TEST01 | 1000 |  5000 |    3 | 00:00:01 |
    -----------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ------------------------------------------
    * 1 - filter("ID"=4)

    (四)總結

    在使用expdp/impdp進行導出/導入數據的時,統計信息是非常重要的,對於大部分統計信息,我們可以在導入結束之後收集獲得。但是對於列的直方圖統計信息,Oracle默認收集的方式是auto,即Oracle會根據用戶對列的使用情況進行判斷是否收集直方圖統計信息,然而數據剛遷移完成,在表還未使用的情況下收集統計信息,往往收集不到列的直方圖信息,這就造成了執行計劃異常,這種情況通常在下一次收集統計信息之後會有所改變。

    參考文檔:

    DBMS_STATS With METHOD_OPT =>’..SIZE auto’ May Not Collect Histograms (Doc ID 557594.1)

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

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

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

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

    網頁設計最專業,超強功能平台可客製化

    ※回頭車貨運收費標準

  • 02 . Zabbix配置監控項及聚合圖形

    02 . Zabbix配置監控項及聚合圖形

    安裝Zabbix Agent監控本機

    安裝agent軟件

    與server端不同,Agent只需安裝zabbix-agent包

    cat /etc/yum.repos.d/zabbix.repo 
    [zabbix]
    name=Zabbix Official Repository - $basearch
    baseurl=https://mirrors.aliyun.com/zabbix/zabbix/3.4/rhel/7/$basearch/
    enabled=1
    gpgcheck=1
    gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX-A14FE591
     
    [zabbix-non-supported]
    name=Zabbix Official Repository non-supported - $basearch
    baseurl=https://mirrors.aliyun.com/zabbix/non-supported/rhel/7/$basearch/
    enabled=1
    gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX
    gpgcheck=1
    
    
    curl https://mirrors.aliyun.com/zabbix/RPM-GPG-KEY-ZABBIX-A14FE591 -o /etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX-A14FE591
    curl https://mirrors.aliyun.com/zabbix/RPM-GPG-KEY-ZABBIX -o /etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX
    
    yum -y install zabbix-agent zabbix-get
    
    配置Agent並啟動
    vim /etc/zabbix/zabbix_agentd.conf
    Server=39.108.140.0                    # 被動模式 zabbix-server-ip
    ServerActive=39.108.140.0              # 主動模式 zabbix-server-ip
    Hostname=You-Men                       # Agent端主機名,最終显示在監控頁面上的名字
    UnsafeUserParameters=1                 # 是否限制用戶自定義keys使用特殊字符
    
    systemctl restart zabbix-agent
    netstat -antp|grep agent
    tcp        0      0 0.0.0.0:10050           0.0.0.0:*               LISTEN      3898/zabbix_agentd  
    tcp6       0      0 :::10050                :::*                    LISTEN      3898/zabbix_agentd
    
    配置snmp(可以不做)

    zabbix除了可以使用agent獲取數據之外,還可以通過snmp獲取數據,為了能夠讓zabbix監控更多的信息,將本機的snmp功能啟動起來.

    yum -y install net-snmp net-snmp-utils
    vim /etc/snmp/snmpd.conf
    com2sec notConfigUser 39.108.140.0 public
    access notConfigGroup "" any noauth exact all none none
    view all included .1 80
    systemctl restart snmpd && systemctl enabel snmpd
    ss -anup |grep snmp        # 161端口,udp協議
    # 測試snmp協議工作是否正常
    # snmpwalk -v 1 -c public 39.108.140.0   .1.3.6
    # 使用v1版本,共同體為public,來對192.168.0.1的.1.3.6分支進行walk。
    
    snmpwalk -v 2c -c public 39.108.140.0
    # 使用v2c版本,共同體為public,對39.108.140.0進行walk。
    # -v        显示當前SNMPWALK命令行版本.
    # -
    # 獲取cisco設備39.108.140.0的接口類型
    

    接下來我們到web界面上配置如何監控本地主機,我們看到接口上是127.0.0.1,但是我們配置文件寫的是39.108.140.0,我們讓這兩個IP一致.點擊3進去然後修改.

    更新完后,跳到下面頁面,稍等一會,重新載入一下頁面就是可用性為綠色了

    至此,監控本地主機就完成了,如果想看下監控本地主機的網卡流量就做下面圖2步驟.鼠標依次根據数字挨個點,如果想要監控項是中文的話,可以做Zabbix故障例一,但是4.4版本較以前版本有所改善,監控項不是亂碼,而是英文.

    如果想要將這種亂碼換成正常中文

    如果是windows在C盤搜索simkai.tff中文楷體,拷貝/上傳到服務器,然後cp到zabbix的字體目錄
    3.*版本:
    cp  /root/simkai.ttf   /usr/share/zabbix/fonts/
    
    # 不同的安裝方式,路徑會有所不同,所以可以直接find / -type d -type fonts找到類似的文件夾,那就是了
    # 注意字體權限問題
    vim /usr/share/zabbix/include/defines.inc.php
        define('ZBX_GRAPH_FONT_NAME',    'simkai');
        define('ZBX_FONT_NAME',            'simkai');
    

    Zabbix監控遠程主機

    如果遠程主機安裝不上zabbix-agent,可以通過裝的上的zabbix-agent的機器把包傳過去

    yum -y instlal yum-utils
    # 下載到指定目錄
    yum install zabbix-agent -y --downloadonly --downloaddir=/root
    1.安裝zabbix agent
        # 方法一(國外源zabbix好像下載不下來包了,用上面的源):
        # rpm -Uvh https://repo.zabbix.com/zabbix/4.4/rhel/7/x86_64/zabbix-release-4.4-1.el7.noarch.rpm
        yum clean all
        yum -y install zabbix-agent
        # 方法二:(使用別的機器傳過來的zabbix-agent包直接rpm安裝即可)
        rpm -ivh zabbix-agent-4.4.1-1.el7.x86_64.rpm
    
    # 修改zabbix-agent配置並啟動服務
    
        vim /etc/zabbix/zabbix_agentd.conf
        Server=192.168.244.144
        Server=192.168.244.144            //監控主機IP地址
        Hostname=agent1.zabbix.com        //被監控主機到監控主機的名字
        UnsafeUserParameters=1
    
        systemctl start zabbix-agent
        ss -antp |grep 10050
    # 接下來我們到web端進行操作
    # 為了服務方便管理和易於查看。
    # 監控系統中往往根據被監控的主機角色或其他屬性將同類主機劃分到同一個主機組中.
    

    如果等上一段時間,可用性哪裡沒有紅色警告,就說明這台主機被添加進來了,但是因為沒有掛載模板和創建監控項,所以我們接下來嘗試着掛載一下模板,然後再去創建監控項.

    我們到agent端裝一個nginx,然後去zabbix的web端找到此模板並掛載.

    yum -y install nginx
    systemctl start nginx
    

    測試監控主機

    接下來我們用瀏覽器或者elinks訪問一下nginx,產生一些數據,然後去zabbix上查看變化

    elinks --dump 116.196.83.113
    

    我們以後自定義Key監控項時,先看看最新數據有沒有數據過來,如果數據都不會過來,就別提圖形觸發器報警什麼了.

    至此,添加本地主機,遠程主機,創建主機組,掛載模板就已經完了

    Zabbix監控項

    監控項(Items)簡介

    監控項是Zabbix中獲得數據的基礎,沒有監控項,就沒有數據——因為一個主機只有監控項定義了單一的指標或者需要獲得的數據,監控項適用於採集數據的,多個同類的監控項可以定義成一個應用集,如,mysql增刪改查以及每秒鐘的讀表,寫錶速度可以寫成一個Mysql應用集.

    對於監控項的示例,需要輸入以下必要的信息

    名稱

    輸入CPU Load作為值,在列表中和其他地方,都會显示這個值作為監控項名稱.

    手動輸入system.cpu.load作為值,這是監控項的一個技術上的名稱,用於識別獲取信息的類型,這個特定值需要是Zabbix Agent預定義值的一種.
    https://www.zabbix.com/documentation/3.4/manual/config/items/itemtypes/zabbix_agent # 此網址就是zabbix官網的預定義值.

    信息類型

    在此處選擇Numeric(float),這個屬性定義了獲得數據的格式
    你也需要減少監控項歷史保留的天數,7或者14天,對於數據庫而言,最佳實踐是避免數據庫保留過多的歷史數據.
    我們選擇了數據類型后,暫時保持其他選項的默認值.
    1> 磁盤容量Units一般為B
    2> 網卡流量單位為bps
    3> Mysql每秒訪問量qps,例如MySQL每秒select,insert Mysql serlect

    點擊添加,新的監控項就出現在監控項列表中了

    查看數據

    當一個監控項定義完成后,你可能好奇他具體獲取了什麼值,前往監控首頁,點擊最新數據,選擇相應的主機.看數據能不能過來以及是不是自己想要的類型.

    圖表

    當監控項運行了一段時間后,可以查看可視化圖表,如果沒有可以自己創建一個,下面會有詳細介紹

    常用監控項

    1.服務器網絡接口進出流量和總流量
        net.if.in[if,<mode>]
        net.if.out[if,<mode>]
        net.if.total[if,<mode>]
    
    2.服務器啟動分區剩餘空間
        vfs.fs.size[fs,<mode>]
        vfs.fs.size[/boot,free]
    
    3.監控虛擬機內存
        vm.memory.size[<mode>]
        vm.memory.size[total
        vm.memory.size[free]
        vm.memory.size[wired]
    
    4.服務器服務狀態
        net.tcp.listen[port]
        net.tcp.port[<ip>,port]
        net.tcp.service[service,<ip>,<port>]
        net.tcp.service.perf[service,<ip>,<port>]
    
    5.服務器進程數量
            proc.num[<name>,<user>,<state>,<cmdine>]
            zabbix_get -s 39.108.140.0 -k proc.num
        121
        zabbix_get -s 39.108.140.0 -k proc.num[,,run]
        3
        zabbix_get -s 39.108.140.0 -k proc.num[,,sleep]
        118
    
    6.服務器CPU狀態(浮點型,無單位)    
        system.cpu.intr
        system.cpu.load[<cpu>,<mode>]
        system.cpu.num
        system.cpu.switches
        system.cpu.util[<cpu>,<type>,<mode>]
        zabbix_get -s 39.108.140.0 -k system.cpu.load[all,avg1]
        0.000000
        zabbix_get -s 39.108.140.0 -k system.cpu.load[,avg5]
        0.010000
    
    7.磁盤IO情況
        vfs.dev.read[device,<type>,<mode>]
        vfs.dev.write[device,<type>,<mode>]
    zabbix_get -s 39.108.140.0 -k vfs.dev.read[/dev/vda1]
    
    8.監控文件修改
        vfs.file.chsum[file]        # 如監控/etc/passwd ,/etc/group 文件從而知道是否有新用戶創建
        vfs.file.md5sum[file]
        vfs.file.size[file]        # 通常用來監控日誌
        vfs.fs.size[fs,<mode>]
    
    9.磁盤總和.
    
    監控網卡流量

    我們先創建一個應用集,這樣的話之後創建的網卡上傳,下載,總流量不會顯的很亂,都在一個Network應用集裏面,而且能導出成xml文件,放到其他的zabbxi主機上能直接用.

    我們此刻做的創建監控項是利用zabbix安裝好自帶的監控項,跟自定義Key差不多,都是寫一個監控腳本然後傳參,每一個鍵值相當於一個監控腳本

    接下來我們檢測---> 主機群組裡面去查看下最新數據,我們可以從下圖看到是有數據的

    下行寬帶和上行寬帶.

    下載就是in,下行寬帶,你發出去的就是out,作為一個服務器來說上行寬帶肯定要高,在家裡就是下行寬帶高,對服務器來說他需要接收很少的數據包,回復很多的數據包,而在家裡我們是發出去一個很小的數據包,返回來整個網頁.

    接下來我們再去創建一個網卡輸出流量,然後將他們做成一個圖標,以圖形化展示出來

    接下來我們再去監測裏面去查看最新數據,可以養成這個習慣,因為最新數據過來了才是說明當中數據流向沒有問題,如果數據都沒有過來你去創建圖形,圖表說沒有數據,你覺得得等一會,浪費時間影響效率

    可以看到,兩個監控項都是有數據的,接下來我們去創建圖形

    接下來我們去查看監測 ---> 圖形,選擇相應群組,相應的主機及創建的圖形

    這台主機可以裝一個nginx,然後上傳一張大一點圖片到網站根目錄,然後訪問,再查看網絡波動圖.

    或者我們直接上傳一個大點的rpm、tar包到其他主機.這樣看着明顯

    監控CPU

    跟剛才一樣,創建一個CPU應用集,方便管理歸納

    接下來我們創建應用集的監控項,cpuintr,cpu中斷數

    接下來我們創建一個cpu每隔一分鐘的負載監控項
    通過下圖,我們可以看到,每個監控項都是有數據過來的,接下來我們去創建圖形

    我們可以看到,數據是可以實時轉換成圖標的,接下來我們去做一個聚合圖形

    創建聚合圖形

    至此,我們第一個構造函數完成,另外一個構造函數同理,此處就不寫了,直接看結果圖.

    創建系統定義好的監控項,跟上面兩個都差不多,多做做自然就會了,如果不習慣使用官方定義好的key,我們可以根據公司環境自己寫腳本自定義key,此章完結.

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

    【其他文章推薦】

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

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

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

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

    ※回頭車貨運收費標準

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

  • 秒懂系列,超詳細Java枚舉教程!!!

    秒懂系列,超詳細Java枚舉教程!!!

    所有知識體系文章,GitHub已收錄,歡迎Star!再次感謝,願你早日進入大廠!

    GitHub地址: https://github.com/Ziphtracks/JavaLearningmanual

    深入理解Java枚舉

    一、什麼是枚舉

    1.1 什麼是枚舉?

    至於枚舉,我們先拿生活中的枚舉來入手,然後再引申Java中的枚舉,其實它們的意義很相似。

    談到生活中的枚舉,假如我們在玩擲骰子的遊戲,在我們手中有兩個骰子,要求擲出兩個骰子的點數和必須大於6的概率,那麼在此情此景,我們就需要使用枚舉法一一列舉出骰子點數的所有可能,然後根據列舉出來的可能,求出概率。

    可能有的小夥伴發現,這就是數學啊?這就是數學中的概率學和統計學。對,我們的枚舉法就是常用於概率統計中的。

    1.2 Java中的枚舉類

    Java 5.0引入了枚舉,枚舉限制變量只能是預先設定好的值。使用枚舉可以減少代碼中的 bug,方便很多場景使用。

    二、Java枚舉的語法

    枚舉類中的聲明

    1訪問修辭符 enum 枚舉名 {
    2    枚舉成員,
    3    枚舉成員,
    4    ...
    5};

    class類中枚舉的聲明

    1訪問修飾符 class 類名 {
    2    enum 枚舉名 {
    3        枚舉成員,
    4        枚舉成員,
    5        ...
    6    }
    7}

    三、Java枚舉類的使用規則和應用場景

    3.1 Java枚舉類的使用規則

    至於枚舉你也有所了解了,Java中的枚舉也是一樣的。而Java中枚舉類的使用,也有特定的規則和場景。如果你看了以下的規則不明白的話,沒有關係,繼續向下學你就會明白,因為我在下面都會有講解到這些規則。如下幾個規則:

    • 類的對象是確定的有限個數。
    • 當需要定義一組常量時,建議使用枚舉。
    • 如果枚舉類中只有一個對象,則可以作為單例模式的實現方法。
    • 枚舉類不能被繼承
    • 枚舉類不能被單獨的new創建對象
    • 枚舉類中的枚舉成員是用`,`隔開的,多個枚舉成員之間用`_`隔開
    • 如果枚舉類中的只有一個或多個枚舉成員,其他什麼都沒有,我們在用`,`隔開的同時。最後可以省略`;`結束符。

    注意: 如果關於枚舉單例設計模式不太了解的小夥伴可以參考深度學習單例設計模式一文,你肯定會有意想不到收穫,請相信我!

    3.2 Java枚舉類的應用場景

    根據Java中使用枚舉類的規則,有以下幾種場景適合來使用枚舉類,如下:

    • 星期: Monday(星期一)、Tuesday(星期二)、Wednesday(星期三)、Thursday(星期四)、Firday(星期五)、Saturday(星期六)、Sunday(星期日)
    • 性別: Man(男)、Woman(女)
    • 季節: Spring(春天)、Summer(夏天)、Autumn(秋天)、Winter(冬天)
    • 支付方式: Cash(現金)、WeChatPay(微信)、Alipay(支付寶)、BankCard(銀行卡)、CreditCard(信用卡)
    • 訂單狀態: Nonpayment(未付款)、Paid(已付款)、Fulfilled(已配貨)、Delivered(已發貨)、Return(退貨)、Checked(已確認)
    • 線程狀態: Establish(創建)、Ready(就緒)、Run(運行)、Obstruct(阻塞)、Die(死亡)
    • 等等……

    四、枚舉類的基本使用步驟解析

    那我們就解釋以下這兩個規則,我們在上述中已經了解了枚舉的作用。Java中枚舉也不例外,也是一一列舉出來方便我們拿出來一個或多個使用。這有點像我們的多選框,我們把需要用到的所有選項內容放在各個多選框後面,當我們在使用的時候只需要勾選自己需要的勾選框即可,這就代表了我們需要被選中多選框後面的內容。

    那麼,Java中的枚舉類是如何使用呢?

    這裏我們簡單的模擬一個場景,假設你的女朋友十分的喜歡喝點冷飲或熱奶茶之類的飲品,在生活中也有很多像蜜雪冰城等等這種類型的飲品店。當你為女朋友買她愛喝的珍珠奶茶時,服務員會問你,要大杯、中杯還是小杯的。當然,為了滿足女朋友,你通常會選擇大杯。這就意味着店內不允許顧客點規則外的飲品。

    注意: 如果你是初學者或是不了解枚舉類的使用,此基本使用不懂沒有關係,請繼續往下看即可!

    於是,我用Java代碼來實現一下,上述場景。

    首先,創建枚舉類。分別為珍珠奶茶添加大、中、小杯杯型。

     1package com.mylifes1110.java;
    2
    3/**
    4 * @ClassName PearlMilkTea
    5 * @Description 為珍珠奶茶添加三個杯型:大、中、小
    6 * @Author Ziph
    7 * @Date 2020/6/8
    8 * @Since 1.8
    9 */

    10public enum PearlMilkTea {
    11    //注意:這裏枚舉類中只有枚舉成員,我在此省略了;結束符
    12    SMALL, MEDIUM, LARGE
    13}

    其次,創建珍珠奶茶對象,再有方法來判斷枚舉類中的大、中、小杯。最後打印女朋友喝哪個杯型的珍珠奶茶!

     1package com.mylifes1110.test;
    2
    3import com.mylifes1110.java.PearlMilkTea;
    4
    5/**
    6 * @ClassName PearlMilkTeaTest
    7 * @Description 為女朋友買哪個杯型的珍珠奶茶(默認大杯)
    8 * @Author Ziph
    9 * @Date 2020/6/8
    10 * @Since 1.8
    11 */

    12public class PearlMilkTeaTest {
    13    public static void main(String[] args) {
    14        //創建大杯的珍珠奶茶對象
    15        PearlMilkTea pearlMilkTea = PearlMilkTea.LARGE;
    16        PearlMilkTeaTest.drinkSize(pearlMilkTea);
    17    }
    18
    19    //判斷為女朋友買哪個杯型的珍珠奶茶
    20    public static void drinkSize(PearlMilkTea pearlMilkTea) {
    21        if (pearlMilkTea == PearlMilkTea.LARGE) {
    22            System.out.println("我為女朋友買了一大杯珍珠奶茶!");
    23        } else if (pearlMilkTea == PearlMilkTea.MEDIUM) {
    24            System.out.println("我為女朋友買了一中杯珍珠奶茶!");
    25        } else {
    26            System.out.println("我為女朋友買了一小杯珍珠奶茶!");
    27        }
    28    }
    29}

    image-20200608151052517

    雖然,我們了解了枚舉類中的基本使用,但是我們在語法中還介紹了一種在類中定義的枚舉。正好,在此也演示一下。如下:

    1public class PearlMilkTea {
    2    enum DrinkSize {
    3        SMALL,
    4        MEDIUM, 
    5        LARGE
    6    }
    7}

    如果這樣創建就可以在class類中去創建enum枚舉類了。想想前面例子中的代碼其實並不合理,這是為什麼呢?因為我們寫代碼要遵循單一職責原則和見命知意的命名規範。所以,我寫的代碼是在珍珠奶茶的枚舉類中列舉的大、中、小的三種杯型枚舉成員。所以根據規範來講,我們珍珠奶茶中不能擁有杯型相關的枚舉,畢竟我們在生活中的這類飲品店中喝的所有飲品種類都有這三種杯型,因此我們的所有飲品種類中都需要寫一個枚舉類,顯然這是很不合理的。

    如果讓它變的更加合理化,我們就細分飲品種類來創建飲品枚舉類和杯型的枚舉類並分別兩兩適用即可。也許有小夥伴會問我為什麼我要說這些合理不合理呢?因為自我感覺這是對枚舉類應用的思想鋪墊,所以你品、你細品!

    五、自定義枚舉類

    5.1 自定義枚舉類步驟

    關於第四章枚舉類的基本使用,也許小夥伴們對枚舉的陌生,而並不知道為什麼這樣去創建枚舉對象。接下來,我來帶你使用常量來自定義枚舉類,試試是不是那個效果。

    既然,上述第三章我舉出了這麼多枚舉類的應用場景,那我們挑選一個比較經典的春夏秋冬來實現自定義枚舉類。

    首先,我們先創建一個季節類,分別提供屬性、私有構造器、春夏秋冬常量、Getter方法和toString方法,步驟如下:

     1package com.mylifes1110.java;
    2
    3/**
    4 * 自定義季節的枚舉類
    5 */

    6public class Season {
    7    //聲明Season對象的屬性,為private final修飾
    8    private final String seasonName;
    9
    10    //私有化構造器,併為對象賦值
    11    private Season(String seasonName) {
    12        this.seasonName = seasonName;
    13    }
    14
    15    //提供當前枚舉的多個對象,為public static final修飾
    16    public static final Season SPRING = new Season("春天");
    17    public static final Season SUMMER = new Season("夏天");
    18    public static final Season AUTUMN = new Season("秋天");
    19    public static final Season WINTER = new Season("冬天");
    20
    21    //提供外界通過getter方法來獲取枚舉對象的屬性
    22    public String getSeasonName() {
    23        return seasonName;
    24    }
    25
    26    //重寫toString方法,以便打印出枚舉結果
    27    @Override
    28    public String toString() {
    29        return "Season{" +
    30                "seasonName='" + seasonName + '\'' +
    31                '}';
    32    }
    33}

    其次,我們去創建一個測試類,來使用該自定義枚舉類創建對象。由此看來,我們就可以根據類名來句點出常量對象了!

     1package com.mylifes1110.test;
    2
    3import com.mylifes1110.java.Season;
    4
    5/**
    6 * 測試類
    7 */

    8public class SeasonTest {
    9    public static void main(String[] args) {
    10        Season spring = Season.SPRING;
    11        System.out.println(spring);
    12    }
    13}

    最後打印結果是春天的對象,由於我們覆蓋了toString方法,即可見對象內的內容。

    image-20200608160220000

    5.2 使用帶有參枚舉類

    如果你在第三章時Java枚舉類的基本使用不明白,估計看完自定義枚舉類也了解的大差不差了。但是你有沒有發現我們自定義枚舉類是使用的有參數的對象呢?那我們怎樣使用真正的枚舉類來實現有參數的枚舉類呢?繼續看吧那就!

    在這裏我將自定義枚舉類改裝了一下,改裝成了enum枚舉類實現的使用有參對象。如下:

     1package com.mylifes1110.java;
    2
    3public enum Season {
    4    SPRING("春天"),
    5    SUMMER("夏天"),
    6    AUTUMN("秋天"),
    7    WINTER("冬天");
    8
    9    private final String seasonName;
    10
    11    Season1(String seasonName) {
    12        this.seasonName = seasonName;
    13    }
    14
    15    public String getSeasonName() {
    16        return seasonName;
    17    }
    18}

    不知道你有沒有發現少了點什麼,少的部分其實就是我們創建常量對象的部分,而且在這個枚舉類中我也沒有去重寫toString方法,至於為什麼,下面就告訴你。

    注意: 枚舉對象之間用,隔開!

    其次,去創建了該枚舉類的測試類,我們測試以下,並看一下沒有重寫toString方法打印出來的結果。

     1package com.mylifes1110.test;
    2
    3import com.mylifes1110.java.Season;
    4
    5public class Seaso1Test {
    6    public static void main(String[] args) {
    7        Season1 spring = Season.SPRING;
    8        System.out.println(spring);                     //SPRING
    9        System.out.println(spring.getSeasonName());     //春天
    10    }
    11}

    這裏我將打印的結果放在了打印語句後面的註釋中。我們發現沒有重寫toString方法竟然打印出來的是SPRING,這是為什麼呢?這應該從我們的繼承關係中分析,如果繼承的是基類Object的話,沒有重寫toString方法會打印對象地址。那麼我們就可以斷定,enum枚舉類的父類不是Object。那它的父類是誰呢?我們可以藉助來對象來獲取其父類,如下:

    1System.out.println(Season.class.getSuperclass());        //class java.lang.Enum

    同樣,答案放在了代碼後面的註釋中。我們發現它默認繼承的是Enum類。那麼,我們稍後就來就看看這個類中到底寫了些什麼方法。

    六、Enum常用方法的使用

    6.1 Enum中的所有方法

    關於Enum類中的所有方法我以表格的方式列舉出來!

    返回值 方法 描述
    String name() 獲取枚舉成員的名稱
    static T valueOf(Class<T> enumType, String name) 獲取指定枚舉成員名稱和類型的枚舉成員
    String[] values() 獲取枚舉成員的所有值
    int compareTo(E o) 比較此枚舉與指定對象的順序
    int hashCode() 獲取枚舉成員的哈希值
    int ordinal() 獲取枚舉成員的序數(第一個枚舉成員位置為0)
    String toString() 返回枚舉成員名稱
    Class<E> getDeclaringClass() 獲取枚舉成員的類對象

    6.2 name和toString

    關於name方法和toString方法,其實很簡單。name()就是根據枚舉成員來獲取該枚舉成員的字符串名稱。而同String方法也是用來獲取枚舉成員的字符串名稱。雖然作用都是相同的,但是name方法是用final修飾的不能被重寫,而toString是可以被重寫的。這裏我們還使用季節的案例來演示,打印結果並放在了代碼後面的註釋中,如下:

    1System.out.println(Season.SUMMER.name());            //SUMMER
    2System.out.println(Season.SUMMER.toString());        //SUMMER

    6.3 valueOf

    此方法的作用是傳入一個字符串,然後將它轉換成對應的枚舉成員。這裏傳入的字符串必須與定義的枚舉成員的名稱一致,嚴格區分大小寫。如果傳入的字符串並沒有找到其對應的枚舉成員對象,就會拋出異常。如下:

    1System.out.println(Season.valueOf("WINTER"));            //WINTER
    2System.out.println(Season.valueOf("WIN"));                //java.lang.IllegalArgumentException

    image-20200608173858862

    6.4 values

    values方法的名字中就帶有一個s,再加上它的返回值是一個字符串數組。所以我們就可以得出它的作用是獲取枚舉成員的所有值,這些值並以數組的形式存儲。

    1Season[] seasons = Season.values();
    2for (Season season : seasons) {
    3    System.out.print(season + " ");
    4}

    結果為:

    1SPRING SUMMER AUTUMN WINTER 

    6.5 ordinal

    該方法是獲取枚舉成員的序數,其第一個枚舉成員位置為0。其實,為了好理解的話,可以把它看作數組中的索引。數組中的第一個元素位置同樣也是從0開始。那我們打印一下,看看結果如何,如下:

    1//獲取指定枚舉成員的次序
    2System.out.println(Season.SUMMER.ordinal());
    3
    4//獲取所有成員的次序
    5Season[] seasons = Season.values();
    6for (Season s : seasons) {
    7    System.out.println(s + " -> " + s.ordinal());
    8}

    結果為:

    image-20200608175529079

    其源碼就是返回了一個從0開始int類型的值,從源碼中也可以看出最大值是int取值範圍的最大值。如下:

    image-20200608180839568

    6.6 compareTo

    compareTo方法相信我們已經是很熟悉了。其作用就是用來比較的。但是在枚舉類中它比較的是什麼呢?實際上compareTo方法比較的是兩個枚舉成員的次序數,並返回次序相減后的結果。

    首先,我們要知道SUMMER的次序數為1,WINTER的次序數為3。當使用前者比較後者,打印的結果是前者與後者相減后的差值,即1-3=-2

    1System.out.println(Season.SUMMER.compareTo(Season.WINTER));            //-2

    它的源碼是怎麼做的呢?那我們進入查看一下。

    其中,前面的操作都是在判斷比較的雙方是否是一個枚舉類,如果不是的話就拋出異常。如果為枚舉類的話,就直接將次序數做了相減操作並返回。

    image-20200608180532795

    七、Java枚舉的高級特性

    7.1 常量

    我們知道,常量是用public static final修飾的。1.5之後有了枚舉,我們就可以把相關的常量放在一個枚舉容器中,而且使用枚舉的好處還在於枚舉為我們提供了很多便捷的的方法。

    示例:

    1public enum Season {
    2    SPRING, SUMMER, AUTUMN, WINTER
    3}

    7.2 switch語句

    你了解的switch語句都支持哪種類型呢?我這裏說一下,switch語句支持的類型有如下幾種:

    • 基本數據類型: byte、short、char、int
    • 包裝數據類型: Byte、Short、Character、Integer
    • 枚舉類型: Enum
    • 字符串類型: String(jdk7+ 開始支持)

    具體枚舉類與switch語句的使用是如何實現呢?枚舉又是如何為switch語句提供便利的呢?來看一下吧。

     1package com.mylifes1110.java;
    2
    3public class WeekTest {
    4    public static void main(String[] args) {
    5        Week week = Week.MONDAY;
    6        switch (week) {
    7            case MONDAY:
    8                System.out.println("星期一");
    9                break;
    10            case TUESDAY:
    11                System.out.println("星期二");
    12                break;
    13            case WEDNESDAY:
    14                System.out.println("星期三");
    15                break;
    16            case THURSDAY:
    17                System.out.println("星期四");
    18                break;
    19            case FRIDAY:
    20                System.out.println("星期五");
    21                break;
    22            case SATURDAY:
    23                System.out.println("星期六");
    24                break;
    25            case SUNDAY:
    26                System.out.println("星期日");
    27                break;
    28            default:
    29                System.out.println("null");
    30        }
    31    }
    32}
    33
    34enum Week {
    35    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
    36}

    7.3 枚舉中定義多個參數與方法

    有參枚舉在5.2中我已經做了詳細說明,我們在定義枚舉時不只是可以定義多個參數,還可以定義其他的普通方法來使用,而關於普通方法的使用是根據場景的,這裏我就不再做過多的贅述了。

     1package com.mylifes1110.java;
    2
    3public enum Season {
    4    SPRING("春天"),
    5    SUMMER("夏天"),
    6    AUTUMN("秋天"),
    7    WINTER("冬天");
    8
    9    private final String seasonName;
    10
    11    public static String getName(int index) {  
    12        for (Season s : Season.values()) {  
    13            if (c.getIndex() == index) {  
    14                return c.name;  
    15            }  
    16        }  
    17        return null;  
    18    }
    19
    20    Season1(String seasonName) {
    21        this.seasonName = seasonName;
    22    }
    23
    24    public String getSeasonName() {
    25        return seasonName;
    26    }
    27}

    7.4 枚舉類實現接口

    雖然枚舉類不能繼承,但是可以實現接口。以下是一個實現過程。

    首先,創建一個接口。

    1package com.mylifes1110.inter;
    2
    3public interface Show {
    4    void show();
    5}

    其次,讓我們的四季枚舉類實現該接口並重寫方法。

     1package com.mylifes1110.java;
    2
    3import com.mylifes1110.inter.Show;
    4
    5public enum Season implements Show {
    6    SPRING("春天"),
    7    SUMMER("夏天"),
    8    AUTUMN("秋天"),
    9    WINTER("冬天");
    10
    11    private final String seasonName;
    12
    13    Season1(String seasonName) {
    14        this.seasonName = seasonName;
    15    }
    16
    17    public String getSeasonName() {
    18        return seasonName;
    19    }
    20
    21    @Override
    22    public void show() {
    23        System.out.println("嚮往四季如春");
    24    }
    25}

    最後,當我們使用每一個枚舉類都可以調用show方法,而打印的結果也都是“嚮往四季如春”

    1Season.WINTER.show();                //嚮往四季如春

    聰明的你我相信發現了這個缺點,我們不管使用哪一個枚舉成員時,調用的show方法都是同一個。所以,我們在實現接口后,可以這樣重寫方法,如下:

     1package com.mylifes1110.java;
    2
    3import com.mylifes1110.inter.Show;
    4
    5public enum Season1 implements Show {
    6    SPRING("春天") {
    7        @Override
    8        public void show() {
    9            System.out.println("春天是個踏青的季節");
    10        }
    11    },
    12    SUMMER("夏天") {
    13        @Override
    14        public void show() {
    15            System.out.println("夏天是個炎熱的季節,我要吃冰棍");
    16        }
    17    },
    18    AUTUMN("秋天") {
    19        @Override
    20        public void show() {
    21            System.out.println("秋天還算是涼爽");
    22        }
    23    },
    24    WINTER("冬天") {
    25        @Override
    26        public void show() {
    27            System.out.println("冬天的雪還不錯,就是有點冷");
    28        }
    29    };
    30
    31    private final String seasonName;
    32
    33    Season1(String seasonName) {
    34        this.seasonName = seasonName;
    35    }
    36
    37    public String getSeasonName() {
    38        return seasonName;
    39    }
    40}

    我們在枚舉成員的後面加了{},而重寫的方法可以寫在各個枚舉成員中,這樣就接觸了上述所有的那個限制。這下,我們使用哪個枚舉成員對象調用show方法都是不同的。是不是非常NICE?

    7.5 使用接口對枚舉分類

    使用接口對枚舉分類,我們需要創建一個接口容器,裏面存放着此接口容器所存放的多個枚舉類,然後將各個枚舉類實現此接口,以這樣的方式可實現對枚舉分類。代碼如下,打印結果放在了代碼後面的註釋中:

     1package com.mylifes1110.inter;
    2
    3public interface Weeks {
    4    //工作日
    5    enum WorkingDay implements Weeks {
    6        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY
    7    }
    8
    9    //雙休日
    10    enum Weekend implements Weeks {
    11        SATURDAY, SUNDAY
    12    }
    13}
    14
    15class WeeksTest {
    16    public static void main(String[] args) {
    17        System.out.print("雙休日:");
    18        for (Weeks.Weekend weekend : Weeks.Weekend.values()) {
    19            System.out.print(weekend + " ");        //雙休日:SATURDAY SUNDAY
    20        }
    21
    22        //換行
    23        System.out.println();
    24
    25        System.out.print("工作日:");
    26        for (Weeks.WorkingDay workingDay : Weeks.WorkingDay.values()) {
    27            System.out.print(workingDay + " ");     //工作日:MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY
    28        }
    29
    30        //換行
    31        System.out.println();
    32
    33        Weeks.WorkingDay friday = Weeks.WorkingDay.FRIDAY;
    34        System.out.println("星期五:" + friday);      //星期五:FRIDAY
    35    }
    36}

    image-20200608194649335

    八 枚舉類集合

    8.1 EnumSet集合

    關於Set集合,我們知道其集合中的元素是不重複的。其中的方法有以下幾種:

    返回值 方法 描述
    static EnumSet<E> allOf(Class<E> elementType) 創建一個包含指定元素類型的所有元素的枚舉 set。
    EnumSet<E> clone() 返回一個set集合。
    static EnumSet<E> complementOf(EnumSet<E> s) 創建一個其元素類型與指定枚舉set相同的set集合(新集合中包含原集合所不包含的枚舉成員)
    static EnumSet<E> copyOf(EnumSet<E> s) 創建一個其元素類型與指定枚舉 set 相同的枚舉 set集合(新集合中包含與原集合相同的枚舉成員)
    static EnumSet<E> copyOf(Collection<E> s) 創建一個從指定 collection 初始化的枚舉 set
    static EnumSet<E> noneOf(Class<E> elementType) 創建一個具有指定元素類型的空枚舉 set
    static EnumSet<E> range(E from, E to) 創建一個最初包含由兩個指定端點所定義範圍內的所有元素的枚舉 set。
    static EnumSet<E> of 創建一個最初包含指定元素的枚舉 set。注意:可以指定多個元素,所以在這裏我沒有列舉參數
    8.1.1 allOf

    allOf方法需要我們傳入一個枚舉的類對象,它會根據傳入的枚舉類對象生成一個具有該類對象枚舉成員的Set集合。

    1//創建一個包含Week所有枚舉元素的Set集合
    2EnumSet<Week> weeks = EnumSet.allOf(Week.class);
    3System.out.println(weeks);              //[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
    4
    5//打印Set集合中的元素
    6for (Week week1 : weeks) {
    7    System.out.print(week1 + " ");      //MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY SUNDAY
    8}
    8.1.2 clone

    clone方法與直接打印枚舉的Set集合結果相同!

    1//返回一個Set集合
    2System.out.println(weeks.clone());      //[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
    8.1.3 range

    上面詳細講過枚舉是有序數的,而且枚舉類中的枚舉成員是秉承着從左向右的順序。所以我們可以使用range方法來創建指定枚舉成員端點的Set集合,也就是說我們需要傳入枚舉成員的起始與結束去創建一個該擁有該範圍枚舉成員的Set集合。如下:

    1//創建一個最初包含由兩個指定端點所定義範圍內的所有元素的枚舉 set。
    2System.out.println(EnumSet.range(Week.MONDAY, Week.FRIDAY));        //[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY]
    8.1.4 complementOf

    該方法有點特殊,它根據EnumSet去創建一個新Set集合。而新Set集合中枚舉成員相當於舊Set集合中枚舉成員的取反。

    我們用場景來模擬一下,當前Week枚舉類中有星期一到星期日7個枚舉成員。我們使用range方法創建一個從星期一到星期五的Set集合(s1),隨後我在將使用complementOf方法根據s1生成新的Set集合(s2),最後打印s2查看集合中的元素就只有星期六和星期日。

    注意: 如果我們的舊Set集合佔據了枚舉類中的所有枚舉成員,在使用complementOf方法生成的新Set集合,新集合中的元素打印後為空Set,即[]

    1//創建一個其元素類型與指定枚舉set相同的set集合(新集合中包含原集合所不包含的枚舉成員)
    2EnumSet<Week> weeks1 = EnumSet.complementOf(weeks);
    3System.out.println(weeks1);             //[]
    4
    5EnumSet<Week> range = EnumSet.range(Week.MONDAY, Week.FRIDAY);
    6EnumSet<Week> weeks3 = EnumSet.complementOf(range);
    7System.out.println(weeks3);                //[SATURDAY, SUNDAY]
    8.1.5 copyOf

    copyOf方法與complementOf相反,它創建一個新Set集合。而新Set集合中的枚舉成員與舊Set集合中的枚舉成員相同,這相當於就是Copy(複製功能)。如果你理解了complementOf方法,這個方法對你來說也是沒有挑戰。以下我使用copyOf方法複製了一份weeks,其枚舉成員一個不少。

    注意: copyOf方法還有一個可以複製connection集合來創建Set集合,其connection集合中必須存儲的是枚舉成員。

    1//創建一個其元素類型與指定枚舉 set 相同的枚舉 set集合(新集合中包含與原集合相同的枚舉成員)
    2EnumSet<Week> weeks2 = EnumSet.copyOf(weeks);
    3System.out.println(weeks2);             //[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
    1//複製存儲枚舉成員的HashSet集合
    2Set set = new HashSet();
    3set.add(Week.MONDAY);
    4EnumSet set1 = EnumSet.copyOf(set);
    5System.out.println(set1);        //[MONDAY]
    8.1.6 of

    of方法為我們提供了選擇性的便利,我們可以挑選任意枚舉成員成為Set集合的元素。

    1//創建一個最初包含指定元素的枚舉 set。
    2System.out.println(EnumSet.of(Week.MONDAY,Week.FRIDAY));            //[MONDAY, FRIDAY]
    8.1.7 noneOf

    傳入一個枚舉的類對象去創建一個空Set集合

    1EnumSet<Week> noneOf = EnumSet.noneOf(Week.class);
    2System.out.println(noneOf);                     //[]

    8.2 EnumMap集合

    8.2.1 EnumMap集合的方法列表

    關於Map集合,我們知道它是由鍵和值組成。EnumMap集合與HashMap集合的效率比較來說,EnumMap的效率高些。

    關於EnumMap集合的使用與HashMap是一致的,沒有什麼特殊的。至於EnumMap集合的方法,我這裏列舉一下。

    返回值 方法 描述
    void clear() 移除所有映射關係。
    EnumMap clone() 返回EnumMap集合。
    boolean containsKey(Object key) 包含此鍵,則返回true
    boolean containsValue(Object value) 包含一個或多個鍵映射到的該指定值,則返回true
    Set > entrySet() 返回映射鍵值關係的Set集合
    boolean equals(Object o) 比較對象與映射的相等關係
    V get(Object key) 獲取指定鍵映射的值,如果沒有,返回null
    Set<K> keySet() 返回所有鍵的Set集合
    V put(K key, V value) 將指定鍵值存儲在EnumMap集合中
    void putAll(Map m) 將所有鍵值對存儲在集合中
    V remove(Object key) 如果存在映射關係,則移除該映射關係
    int size() 返回存在映射關係的數量
    Collection<V> values() 返回此映射中所包含值的 Collection集合
    8.2.2 EnumMap集合的基本使用

    由於EnumMap集合與HashMap集合基本相似,這裏我就演示一下基本使用與HashMap不同的地方。

    EnumMap集合是我們new出來的對象,創建出來的對象需要傳入一個枚舉的類對象,才返回一個Map集合。Map集合是鍵值對形式存儲,所以我們在寫EnumMap集合的泛型時,根據需求來寫,如果需要鍵是某枚舉類型,我們泛型就寫它。如果有枚舉類是值的要求,那就泛型中的值寫枚舉類。鍵值對都要求是枚舉那也是OK的,我們寫泛型時都寫需求的枚舉類即可。除了創建對象和存儲對象需要指定枚舉類外,其他的與HashMap基本相同。

    如下,我在創建EnumMap集合時執行的Week枚舉類的類對象,泛型的鍵寫的是Week枚舉類,值寫的Integer,這就意味着我們在put(存儲鍵值對)的時候,鍵需要存儲Week枚舉類中的枚舉成員,值需要存儲Integer數值。

    1EnumMap<Week, Integer> map = new EnumMap<>(Week.class);
    2map.put(Week.MONDAY, 1);
    3map.put(Week.THURSDAY, 4);
    4System.out.println(map);            //{MONDAY=1, THURSDAY=4}

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

    【其他文章推薦】

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

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

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

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※回頭車貨運收費標準

    台中搬家公司費用怎麼算?

  • 20萬大空間/高顏值轎車 媳婦喜歡丈母娘滿意

    20萬大空間/高顏值轎車 媳婦喜歡丈母娘滿意

    國內消費者最關心的乘坐空間,2805mm軸距的起亞表現的很好,以175cm的體驗者為例,前後排可活的一拳的頭部空間,後排頭部空間超兩拳,提供了1。6T/2。0L/2。0T三種排量可供選擇,搭配6擋手自一體或者7擋雙離合變速箱,選擇性很強。

    國人買東西都有一個共同的特點

    就是大大大大大大大大大大大大

    手機必須上大屏的

    鑽戒要買大的

    那車子當然是越大越好

    前段時間一條火爆的廣告刷爆了朋友圈

    致25歲還一無是處的本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

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

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

  • 能兼顧到家用還開着爽 這幾款車只需要10萬起!

    能兼顧到家用還開着爽 這幾款車只需要10萬起!

    6L、1。8L以及2。0L三款發動機,傳動系統配備5擋手動以及大家最熟悉的CVT變速箱。CVT變速箱動力輸出十分平順,也非常適合在日常城市道路駕馭。硬朗的底盤以及韌性不錯的懸挂,加上ASC車身穩定系統,能給駕駛者足夠的信心。一汽-大眾-速騰指導價:13。

    前言

    對於80後部分朋友已成家立室,上有老下有小,買車必須要考慮到家用,而心中存在着一份激情,開着也能讓自己爽。所以對於這部分朋友來說,選擇操控好,還能兼顧到家用,也是他們首要的目標。

    長安馬自達-馬自達3 Axela昂克賽拉

    指導價:11.49-15.99萬

    昂克賽拉沿用了馬自達最新“魂動”家族化設計, 前臉造型如同獵豹,視覺感受更加富有攻擊性以及活力的風格,尾部設計較為簡潔,整體設計風格時尚年輕。

    內飾正是昂克賽拉的創新所在,雖然簡潔,但整體內飾氛圍更具運動感,車廂做工用料方面,比較厚道!

    昂克賽拉更有兩個版本可選,三廂以及兩廂車型,空間方面,能夠滿足日常出行,三廂版/兩廂版行李箱的容積為419L、350L,長途駕駛三廂版實用性更高。

    昂克賽拉搭載了1.5L以及2.0L兩款自然吸氣發動機,也是馬自達最新“創馳藍天”發動機,我相信大家也非常了解這款發動機,其優點就是能擁有相對低的油耗還能兼顧到充沛的動力輸出,傳動系統配備了6擋手動、6擋手自一體變速箱。底盤的質感高級,從容紮實,懸挂的調校偏運動,能有很好的操控性還能兼顧舒適性。

    東南汽車-翼神

    指導價:9.58-13.98萬

    翼神外觀採用了三菱經典的鯊魚嘴仿生學造型設計,黑色的大嘴結合成一體,尾部設計較為驚艷,有層次感。翼神整體的設計更加霸氣有力!

    翼神內飾主要以黑色為主,中控設計布局簡潔以及在物理按鍵上很到位,整體車廂氛圍很有戰鬥感。

    翼神車身長寬高為4570*1760*1490mm,軸距為2635mm,空間表現,寬敞也能輕鬆翹個二郎腿,座椅填充物軟硬適中,包裹性也很足。

    動力方面,翼神搭載了1.6L、1.8L以及2.0L三款發動機,傳動系統配備5擋手動以及大家最熟悉的CVT變速箱。CVT變速箱動力輸出十分平順,也非常適合在日常城市道路駕馭。硬朗的底盤以及韌性不錯的懸挂,加上ASC車身穩定系統,能給駕駛者足夠的信心。

    一汽-大眾-速騰

    指導價:13.18-21.88萬

    速騰外觀設計依然還是很大眾,家族式設計並沒有太多的亮點,圓滑的車身造型以及流暢動感線條,速騰依然不失時尚感。

    內飾設計依然還是大眾風格,雖然是大眾風格,但其整體設計風格很耐看,甚至會有那麼一絲高級感,做工用料上採用了些許硬質材料,視覺效果並不差,依然簡潔。

    速騰車身長寬高為4655*1780*1453mm,軸距為2651mm,空間表現,可以說是速騰最引以為傲的地方,蹺二郎腿毫無壓力。

    動力方面,速騰搭載了1.2T、1.4T.1.6T以及2.0T四款發動機,可供消費者選擇較多,傳動系統方面配備5擋手動、6擋手自一體、7擋雙離合變速箱,購買雙離合車型的消費者也無需擔心其的耐用性以及平順性,大眾也做了特別多的優化。底盤依然是大眾一貫的風格調校,紮實,速騰懸挂調校也偏向舒適。

    全文總結

    整體來說,這三款車都有運動基因的底子,能兼顧到家用以及在操控性方面也不錯,產品力也很強!加上現在優惠幅度也比較大,也值得入手。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※推薦評價好的iphone維修中心

    網頁設計最專業,超強功能平台可客製化

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

  • 都是六萬多,這三個漂亮的、配置高的SUV哪個是你的菜

    都是六萬多,這三個漂亮的、配置高的SUV哪個是你的菜

    如果車長真的達到4420mm,軸距也不會只有2510mm。雖然都是“3”,但是瑞虎3和DX3是截然不同的兩種設計風格。DX3走的完全是時尚路線,瑞虎3則成熟穩重許多。沒有改款前的內飾看起來比較過時,如今2016款的造型還說的過去。變得更加有層次感,看起來也年輕許多了。

    六萬多的裸車,想買一台不錯的車子,只有考慮自主品牌的車子了,因為這個價位合資車幾乎沒有什麼好的選擇,要麼就是丑到家,要麼就是配置很寒磣。

    但是如果選擇自主品牌的話,那麼路一下子就會變得特別寬,可以有很多不錯的選擇,尤其是對於自主品牌密集布局的SUV領域。所以小編選出了下面三台車子,看看哪台是你的菜。

    東南汽車-東南DX3

    指導價:6.79-10.09萬

    DX3的車身尺寸為4354*1840*1670mm,軸距為2610mm,因為DX7的外觀獲得了不少消費者的讚譽,所以這次DX3的設計還是由賓尼法利納設計中心完成。不得不說,DX3的外觀確實比較驚艷,犀利的前臉,凌厲的腰線,懸浮式車頂設計,這一切都讓DX3看起來非常時尚。

    內飾和外觀一樣,不僅好看,更重要的是原創性也非常高。據DX3的設計師設計透露,DX3的內飾設計風格為“永恆之美”,反正我也不知道什麼叫做永恆之美,只知道看起來挺時尚的。不過內飾的配色也很豐富,可以提供棕+黑、紅+黑兩種配色。

    DX3的空間表現很好,後排空間較為寬裕,在同級別中處於中等偏上的水準。它的動力系統為1.5L 120馬力+5擋手動,1.5T 156馬力+CVT變速箱。其實如果預算比較吃緊的話,1.5L的發動機就可以滿足日常家用了,即使是最低配,也會配備車身穩定系統、胎壓監測、上坡輔助、陡坡緩降、后視鏡電動調節。安全配置和實用配置都有,所以對於資金不充足的消費者來說,低配就夠了。

    奇瑞汽車-瑞虎3

    指導價:6.89-9.29萬

    瑞虎3的車身尺寸為4420*1760*1670mm,軸距為2510mm。不要被數據蒙蔽了,其實虎3的車長是加上那個外掛的備胎了。如果車長真的達到4420mm,軸距也不會只有2510mm。雖然都是“3”,但是瑞虎3和DX3是截然不同的兩種設計風格。DX3走的完全是時尚路線,瑞虎3則成熟穩重許多。

    沒有改款前的內飾看起來比較過時,如今2016款的造型還說的過去。變得更加有層次感,看起來也年輕許多了。尤其是中控大屏,可以立馬增加中控的檔次感。

    瑞虎3的動力系統為1.6L 126馬力+5擋手動/CVT變速箱。老款的瑞虎3沒有車身穩定系統,遭到了吐槽,現在瑞虎3終於標配了ESp,性價比提升了很多。同時奇瑞的瑞虎系列也有了一定的歷史了,質量還是挺讓人放心的,不管是手動擋還是CVT車型,質量方面都有着比較穩定的表現。

    凱翼汽車-凱翼X3

    指導價:6.66-9.69萬

    凱翼X3的車身尺寸為4335*1796*1665mm,軸距為2530mm。凱翼X3也是小型SUV,車身尺寸在同級別並不佔什麼優勢,雖然凱翼X3的外觀經過了重新的設計,變得年輕時尚許多,但是在車身側面和車尾,仍有一些瑞虎3的影子。

    中控內飾極其簡約,沒有什麼複雜的線條做映襯,可以看出來設計師是盡量追求簡介的設計風格,中控台的按鍵數目屈指可數。不過中控台的用料很不錯。部分區域採用了搪塑工藝,手感較好。

    凱翼X3的動力系統為1.6L 126馬力+5擋手動/CVT,動力表現和瑞虎3較為相近。其實就配置而言,凱翼X3的配置要低於DX3和瑞虎3的。不過凱翼X3也有着自己的特色,比如更加討人喜歡的外觀。

    總結:相對來說DX3和瑞虎3的性價比都很高,配置很實在,DX3的外觀看起來更年輕,瑞虎3則更適合比較穩重的消費者,至於凱翼X3,如果你覺得瑞虎3的外觀有點過於成熟的話,那麼可以看看凱翼X3,雖然它的性價比沒有瑞虎3那麼高。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

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

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

    ※超省錢租車方案

    ※回頭車貨運收費標準

  • 要是明年別克將這車國產 告訴我你買不買賬?

    要是明年別克將這車國產 告訴我你買不買賬?

    基於輕量化平台打造后,新君威不僅車重減輕,車身和軸距都相應加長了55mm和92mm,加上車尾採用的溜背設計,未來新君威的後排空間必定提升不少。新君威在未來也有望搭載1。5T/2。0T發動機,其中1。5T車型極有可能搭載7速雙離合變速箱。

    相信大家對別克君威都不陌生,這輛中級轎車從以前主打舒適穩重的風格向後期年輕化的運動范轉變。而最近它的姊妹車歐寶Insignia實車也亮相了,新一代君威的外觀基本會與其相同。

    可能很多人已經淡忘了歐寶這個品牌,實際上作為通用旗下的子品牌,歐寶、別克和沃克斯豪爾(Vaxuhall)都存在着換標車型。同一款車彼此換上不同的logo,在不同的國家地區發售。

    而歐寶Insignia所對應的車型分別是別克的君威和霍頓的Commodore(沒錯,霍頓同樣是通用的子品牌)。這輛未來的新君威走了略帶豪華的運動風格,不論是前臉還是腰線的設計都顯得非常運動,只不過到時進來國產的時候,外觀肯定會有一些調整,而這款Insignia則有可能在明年日內瓦車展發布,而新君威則有望在明年國產。

    基於輕量化平台打造后,新君威不僅車重減輕,車身和軸距都相應加長了55mm和92mm,加上車尾採用的溜背設計,未來新君威的後排空間必定提升不少。

    新君威在未來也有望搭載1.5T/2.0T發動機,其中1.5T車型極有可能搭載7速雙離合變速箱。只不過在國內眾多消費者對於別克的變速箱印象都很一般,未來國產車型還是得要多花點心思好好調整下了。

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

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

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

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

    網頁設計最專業,超強功能平台可客製化

    ※回頭車貨運收費標準

  • 看懂就是省錢!賣一輛車4S店可以賺你多少錢?

    看懂就是省錢!賣一輛車4S店可以賺你多少錢?

    汽車廠家整車利潤據網上資料显示,在國內,主流汽車廠家的整車利潤一般在10%左右,這個数字可能大家看了都不相信,因為在中國,很多人都認為汽車廠家是超賺錢的,當然,這10%其實不包括其中零散配件的價格利潤,現在是全球化時代,許多汽車廠家都會和許多知名的汽車配件,發動機和變速箱公司合作,其中這些核心部件在買賣當中究竟是有着多少利潤可言,關於這個問題,汽車廠家則以汽車技術不外露為理由,並沒有公開給我們廣大消費者。

    相信大家在買車的時候都相信一個道理,絕大多數的汽車,具有一個官方的廠商指導價,但一般我們在4S店裡購買的話,在這個基礎價格上,還能有着幾千上萬的優惠幅度,放眼全國,也存在着30萬的車降價7萬,20萬的車降價3萬元的情況,降價幅度如此之大,不由得讓人懷疑,這樣子4S店為何還能賺到錢?而且一輛車的成本是有多低?

    許多消費者在買車的時候,似乎不會去關注這個問題,認為只要車合適價格滿意,這就達成了購買的協議,但一輛車為何能夠降價這麼多,例如花了20萬元買了台雅閣,其中有多少錢真正屬於這輛車的?繼續往下看。

    買一輛車的錢,可以分為5個部分,分別是:稅費,技術轉讓費,汽車廠家整車利潤,4S店利潤,轎車成本。

    稅費

    廠家需要繳納的稅費:消費稅,增值稅

    消費者需要繳納是稅費:購置稅

    從網上相關數據的查閱中發現,每一輛車需要繳納的稅收佔著汽車價格的23%左右,消費者購車后繳納10%的車輛購置稅(雖然精準的購置稅算法為發票÷1.17×10%,但我們姑且算個大概),所以這麼一來,一輛車從生產到消費者手中需要繳納的稅收就約佔汽車價格的33%左右。

    除此之外,汽車廠家還要繳納教育附加的地方教育費,總費用合計在5%左右,這麼加起來,一輛車從生產出來直到消費者手中,其中所需要的稅費總的加起來就高達38%,稅費真的讓人防不勝防。

    技術轉讓費

    對於我們國內一些汽車合資品牌來說,每生產出一台合資車,都需要給外方支付10%的技術轉讓費,比如你買了一台20萬的轎車,其中技術轉讓費就高達2萬元,哦我的天,怪不得在同級轎車中,如果購買相同配置的話,合資車總要貴那麼幾萬塊,如此高的技術轉讓費,會導致國外一些價格較低的車型,很難引入國內,畢竟引入后,在價格戰上,始終斗不過寶駿310,或者長安奔奔的。

    汽車廠家整車利潤

    據網上資料显示,在國內,主流汽車廠家的整車利潤一般在10%左右,這個数字可能大家看了都不相信,因為在中國,很多人都認為汽車廠家是超賺錢的,當然,這10%其實不包括其中零散配件的價格利潤,現在是全球化時代,許多汽車廠家都會和許多知名的汽車配件,發動機和變速箱公司合作,其中這些核心部件在買賣當中究竟是有着多少利潤可言,關於這個問題,汽車廠家則以汽車技術不外露為理由,並沒有公開給我們廣大消費者。

    4S店利潤

    終於說到大家非常關心的問題了,據數據統計,在30萬元以下的車型,銷售一輛車的平均利潤為5%左右,賣一輛30萬的車4S店的利潤才1.5萬塊?怎麼可能!單純賣車賺的錢肯定不夠一整個4S店的開支(地租,人工費,設備耗損費,水電費),而且據了解,現在很多4S店由於資金不足,所以都把現車的合格證和主鑰匙抵押給銀行去貸款,等到這輛車賣出去了再去銀行贖回來,但也出現過賣車后無力贖回車輛的合格證的情況,導致車主無法按時上牌,所以這麼來說,許多4S店其實單車賣出來賺的利潤不多,主要還是靠拚命地賣車,在年底拿廠家返點。

    那麼賣一輛車,4S店如何通過其他方式賺到你的錢呢?

    保險費:對於一般人來說,在店裡買車,大多數人還是會在這裏把保險順便買了,方便以後出險的時候,這裏也有保險專員可以幫你跟進車輛的情況,更加方便,服務到位,大部分4S店都要求客戶買車的時候,保險也一定要在本店購買,更離譜的還要求必須買一定金額的保險,買個全險六七千,其中的利潤還是有一兩千的。

    上牌費:其實上牌費也就是500塊錢而已,但是在一二線城市例如廣州,上個牌需要收取3000塊錢手續費,且一定要在本店執行,客戶不能夠提裸車,而如果需要上比較偏遠城市的外地牌,手續費去到6000元也有。

    精品加裝費:對於一些熱銷車型來說,4S店肯定要大賺一筆,加價加價繼續加價,但是直接加價會影響市場,被稅局查到也有影響,所以都會利用一些其他方式來執行這種加價的“套路”,很簡單,通過一大堆高昂的精品加裝,價值就能夠上到一兩萬元,確實是炒高了價的“淘寶貨”啊。

    售後維修服務:雖然說4S店通過正兒八經的方式確實掙不到什麼錢,但是售後服務這個大蛋糕,確實一直讓他們吃得挺爽的,一般新車都規定3個月或者5000公里後進行首保,但是4S店一般會在3000公里的時候,就提醒客戶該過來首保了,雖然說免費,但送的就是服務,且在質保期內如果在其他地方維修出現什麼其他問題,4S店也不承認,所以很多人在質保期內,還是會乖乖地去4S店裡做一下保養,收費可都是外面的好幾倍。

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

    【其他文章推薦】

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

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

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

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

    ※回頭車貨運收費標準

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

  • 真的假的?10萬級的國產SUV已經擁有媲美合資的能力了!

    真的假的?10萬級的國產SUV已經擁有媲美合資的能力了!

    東風風神AX5新車指導價:8。97-12。87萬在外觀方面東風風神AX5擁有一個極高的原創外觀,採用了雙橫幅的鍍鉻前格柵,下部進氣口採用梯形設計,同時保險杠兩側還配備有類似進氣口造型的豎條狀LED日間行車燈,此外紅色卡鉗以及使用18寸風火輪式輪轂更能彰顯AX5的運動氣質,而AX5的離地間距達到190mm,處於中上游水準。

    合資水平是目前眾多國產車廠商一直追求境界。為什麼總有的人會說國產車追不上合資車型?在看來最主要的原因是個人的感受方面。

    國產品牌與合資到底差在哪兒?

    品牌影響力上的差距!“國產”“合資”看起來就覺得合資更高端,在這裏也存在一種崇洋媚外的心理。現在的消費者買車更看重面子!國產品牌在眾多人心中依舊是“山寨”“廉價”的代名詞,如果讓親戚朋友看到你開一台國產車,總是會覺得低人一等。在很多人看來,為什麼要買國產車?並不是因為愛過情懷,而是錢不夠。

    外觀內飾設計上的差距?認為設計是一種很主觀的東西,但凡有一些比較潮流的設計出現,喜歡的人會被吸引進來,不喜歡的人只會吐槽。所以在汽車設計方面越平庸的車型越受廣大消費者所喜愛,而且中國人更喜歡“大”這一個詞語,於是現在的新款車型在尺寸方面都越做越大。至於合資和國產是否存在差距?覺得現在國產車的設計已經相當不錯,除了那幾個不爭氣堅持模仿的廠商,大多都擁有了自己的一套家族式設計語言,而且原創度都相當高!

    行駛質感的差距!有的人說國產車型開上幾年底盤感覺就像要散掉一般,跟合資車型完全無法比較。決定汽車行駛整體的質感與汽車廠家的調校功力成正比關係,就像法系車能把扭力梁調得比你的獨立懸挂更牛逼一樣,幾乎所有的廠家將自己那套底盤數據當成國家機密一樣。而調校就是國產品牌的硬傷,有能力的國產品牌可以選擇直接向合資廠家買比較過時的底盤數據、或者直接給錢外包給國外的調校團隊,投入資金是相龐大的。說句難聽點的,有部分的國產廠商的車型在看來壓根就是跳過了“調校”這個步驟。

    國產品牌就這麼菜?

    其實並不是,相反10萬左右的購車預算,選擇國產車型性價比更高!首先是配置更高、而且車子比合資車型更為大氣!有的人說國產車質量不行,國產車型的故障率是稍高,但這是對於故障率極低的品牌而言(就像豐田這些),不是所有的合資品牌故障率都很低的!10年前的國產車都在路上跑着,也不要說現在的新車型了。

    外觀夠大氣、配置夠高、內飾檔次夠高、車型質量過關!是判斷一台國產車型能否擁有與合資車競爭的最主要因素!

    東風風神AX5

    新車指導價:8.97-12.87萬

    在外觀方面東風風神AX5擁有一個極高的原創外觀,採用了雙橫幅的鍍鉻前格柵,下部進氣口採用梯形設計,同時保險杠兩側還配備有類似進氣口造型的豎條狀LED日間行車燈,此外紅色卡鉗以及使用18寸風火輪式輪轂更能彰顯AX5的運動氣質,而AX5的離地間距達到190mm,處於中上游水準。

    中控台設計層次感挺高,採用了軟性材料覆蓋給到的質感也非常不錯,而且最讓喜歡的是它的實用性配置上,像副駕駛地下的鞋盒給愛穿高跟鞋的女生提供了放鞋的地方、配備了獨立的8英寸液晶屏和一鍵啟動等配置,內置的Windlink系統支持與安卓手機的連接。

    AX5提供1.4T渦輪增壓發動機,與之匹配的是一台5擋手動/6擋擋雙離合,在質量方面值得肯定,另外AX5的底盤平台源自東風與pSA集團共同研發的DF2平台,在行駛質感方面真心不錯。

    榮威RX5

    新車指導價:9.98-18.68萬

    榮威RX5在上市之初一直以“網紅”的形式登場,極高的顏值受到廣泛消費者的關注,作為榮威“律動設計”理念下的首款車型,“展翼格柵”的中控與矩陣式LED大燈相連,整體造型時尚大氣,原創度極高,逼格十足。

    採用黃金比例進行打造設計的中控台也是滿滿的逼格,大面積使用軟材料包裹,摸到哪兒都是軟的,中控10.4英寸的大显示屏更是成為了中控台的點睛之筆,整體給人的質感相當高檔。另外與阿里共同打造出來的車載互聯繫統更是將其“網紅”的定位推上了頂峰。

    在動力方面,1.5T的動力更適合家用、2.0T的動力能帶給你非常不俗的駕駛樂趣,在質量方面,榮威背後有着通用這個大集團的撐腰,無論在品控還是質量方面都值得肯定。

    合資的选手實力也不俗!

    雪鐵龍C3-XR

    新車指導價:10.88-17.18萬

    法系車一直不被人看好,車頭採用了更多平直的線條,彰顯飽滿風格。大燈簡單耐看,內部線條與前格柵相連接,整體感更強。而側面的線條緊湊動感,不規則的輪圈樣式也頗為個性。儘管它擁有着同級領先的底盤調校技術,但是配置搭配不合理是它主要的缺點,不過法系車也就有一個優點:那就是底盤紮實好開。

    現代ix25

    新車指導價:11.98-18.68萬

    韓國車一直以高顏值作為賣點,所以無論旗下哪款車型,無疑不是顏值極高的选手,當然韓系車另外一個賣點就是配置比同級別的合資車型配置更高,而且韓系車在終端優惠方面很大,所以在新車落地價上會比較便宜。也可以這麼說,現代ix25在實用性與家用兩方面是做得相當不錯的。

    國產車型配置高、價格更低,性價比十足。很多人對國產車存在偏見,但是這個困境將會隨着國產車型的日益發展得到改善,如果國產車的整體質量上來了,你還會覺得國產車比不上合資嗎?本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

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

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※回頭車貨運收費標準

    台中搬家公司費用怎麼算?