源碼分析 | 手寫mybait-spring核心功能(乾貨好文一次學會工廠bean、類代理、bean註冊的使用)

作者:小傅哥
博客:https://bugstack.cn – 匯總系列原創專題文章

沉澱、分享、成長,讓自己和他人都能有所收穫!

一、前言介紹

一個知識點的學習過程基本分為;運行helloworld、熟練使用api、源碼分析、核心專家。在分析mybaits以及mybatis-spring源碼之前,我也只是簡單的使用,因為它好用。但是他是怎麼做的多半是憑自己的經驗去分析,但始終覺得這樣的感覺缺少點什麼,在幾次夙興夜寐,靡有朝矣之後決定徹底的研究一下,之後在去仿照着寫一版核心功能。依次來補全自己的技術棧的空缺。在現在技術知識像爆炸一樣迸發,而我們多半又忙於工作業務開發。就像一個不會修車的老司機,只能一腳油門,一腳剎車的奔波。車速很快,但經不起壞,累覺不愛。好!為了解決這樣問題,也為了錢程似錦(形容錢多的想家裡的棉布一樣),努力!

開動之前先慶祝下我的iPhone4s又活了,還是那麼好用(嗯!有點卡);

二、以往章節

關於mybaits & spring 源碼分析以及demo功能的章節匯總,可以通過下列內容進行系統的學習,同時以下章節會有部分內容涉及到demo版本的mybaits;

  • 源碼分析 | Mybatis接口沒有實現類為什麼可以執行增刪改查
  • 源碼分析 | 像盜墓一樣分析Spring是怎麼初始化xml並註冊bean的
  • 源碼分析 | 基於jdbc實現一個Demo版的Mybatis

三、一碟小菜類代理

往往從最簡單的內容才有抓手。先看一個接口到實現類的使用,在將這部分內容轉換為代理類。

1. 定義一個 IUserDao 接口並實現這個接口類

public interface IUserDao {

    String queryUserInfo();

}

public class UserDao implements IUserDao {

    @Override
    public String queryUserInfo() {
        return "實現類";
    }

}

2. new() 方式實例化

IUserDao userDao = new UserDao();
userDao.queryUserInfo();

這是最簡單的也是最常用的使用方式,new 個對象。

3. proxy 方式實例化

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?>[] classes = {IUserDao.class};
InvocationHandler handler = (proxy, method, args) -> "你被代理了 " + method.getName();

IUserDao userDao = (IUserDao) Proxy.newProxyInstance(classLoader, classes, handler);

String res = userDao.queryUserInfo();
logger.info("測試結果:{}", res);
  • Proxy.newProxyInstance 代理類實例化方式,對應傳入類的參數即可
  • ClassLoader,是這個類加載器,我們可以獲取當前線程的類加載器
  • InvocationHandler 是代理后實際操作方法執行的內容,在這裏可以添加自己業務場景需要的邏輯,在這裏我們只返回方法名

測試結果:

23:20:18.841 [main] INFO  org.itstack.demo.test.ApiTest - 測試結果:你被代理了 queryUserInfo

Process finished with exit code 0

四、盛宴來自Bean工廠

在使用Spring的時候,我們會採用註冊或配置文件的方式,將我們的類交給Spring管理。例如;

<bean id="userDao" class="org.itstack.demo.UserDao" scope="singleton"/>

UserDao是接口IUserDao的實現類,通過上面配置,就可以實例化一個類供我們使用,但如果IUserDao沒有實現類或者我們希望去動態改變他的實現類比如掛載到別的地方(像mybaits一樣),並且是由spring bean工廠管理的,該怎麼做呢?

1. FactoryBean的使用

FactoryBean 在spring起到着二當家的地位,它將近有70多個小弟(實現它的接口定義),那麼它有三個方法;

  • T getObject() throws Exception; 返回bean實例對象
  • Class<?> getObjectType(); 返回實例類類型
  • boolean isSingleton(); 判斷是否單例,單例會放到Spring容器中單實例緩存池中

那麼我們現在就將上面用到的代理類交給spring的FactoryBean進行管理,代碼如下;

ProxyBeanFactory.java & bean工廠實現類

public class ProxyBeanFactory implements FactoryBean<IUserDao> {

    @Override
    public IUserDao getObject() throws Exception {

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class<?>[] classes = {IUserDao.class};
        InvocationHandler handler = (proxy, method, args) -> "你被代理了 " + method.getName();

        return (IUserDao) Proxy.newProxyInstance(classLoader, classes, handler);
    }

    @Override
    public Class<?> getObjectType() {
        return IUserDao.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

}

spring-config.xml & 配置bean類信息

<bean id="userDao" class="org.itstack.demo.bean.ProxyBeanFactory"/>

ApiTest.test_IUserDao() & 單元測試

@Test
public void test_IUserDao() {
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
    IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class);
    String res = userDao.queryUserInfo();
    logger.info("測試結果:{}", res);
}

測試結果:

一月 20, 2020 23:43:35 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [spring-config.xml]
23:43:35.440 [main] INFO  org.itstack.demo.test.ApiTest - 測試結果:你被代理了 queryUserInfo

Process finished with exit code 0

咋樣,神奇不!你的接口都不需要實現類,就被安排的明明白白的。記住這個方法FactoryBean和動態代理。

2. BeanDefinitionRegistryPostProcessor 類註冊

你是否有懷疑過你媳婦把你錢沒收了之後都存放到哪去了,為啥你每次get都那麼費勁,像垃圾回收了一樣,不可達。

好嘞,媳婦那就別想了,研究下你的bean都被註冊到哪了就可以了。在spring的bean管理中,所有的bean最終都會被註冊到類DefaultListableBeanFactory中,接下來我們就主動註冊一個被我們代理了的bean。

RegisterBeanFactory.java & 註冊bean的實現類

public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(ProxyBeanFactory.class);

        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, "userDao");
        registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // left intentionally blank
    }

}
  • 這裏包含4塊主要內容,分別是;
    • 實現BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry方法,獲取bean註冊對象
    • 定義bean,GenericBeanDefinition,這裏主要設置了我們的代理類工廠。我們已經測試過他獲取一個代理類
    • 創建bean定義處理類,BeanDefinitionHolder,這裏需要的主要參數;定義bean、bean名稱
    • 最後將我們自己的bean註冊到spring容器中去,registry.registerBeanDefinition()

spring-config.xml & 配置bean類信息

<bean id="userDao" class="org.itstack.demo.bean.RegisterBeanFactory"/>

ApiTest.test_IUserDao() & 單元測試

@Test
public void test_IUserDao() {
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
    IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class);
    String res = userDao.queryUserInfo();
    logger.info("測試結果:{}", res);
}

測試結果:

信息: Loading XML bean definitions from class path resource [spring-config.xml]
一月 20, 2020 23:42:29 上午 org.springframework.beans.factory.support.DefaultListableBeanFactory registerBeanDefinition
信息: Overriding bean definition for bean 'userDao' with a different definition: replacing [Generic bean: class [org.itstack.demo.bean.RegisterBeanFactory]; scope=; abstract=false; lazyInit=false; autowireMode=1; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [spring-config.xml]] with [Generic bean: class [org.itstack.demo.bean.ProxyBeanFactory]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null]
23:42:29.754 [main] INFO  org.itstack.demo.test.ApiTest - 測試結果:你被代理了 queryUserInfo

Process finished with exit code 0

納尼?是不有一種滿腦子都是騷操作的感覺,自己註冊的bean自己知道在哪了,咋回事了。

五、老闆郎上主食呀(mybaits-spring)

如果通過上面的知識點;代理類、bean工廠、bean註冊,將我們一個沒有實現類的接口安排的明明白白,讓他執行啥就執行啥,那麼你是否可以想到,這個沒有實現類的接口,可以通過我們的折騰,去調用到我們的mybaits呢!

如下圖,通過mybatis使用的配置,我們可以看到數據源DataSource交給SqlSessionFactoryBean,SqlSessionFactoryBean實例化出的SqlSessionFactory,再交給MapperScannerConfigurer。而我們要實現的就是MapperScannerConfigurer這部分;

1. 需要實現哪些核心類

為了更易理解也更易於對照,我們將實現mybatis-spring中的流程核心類,如下;

  • MapperFactoryBean {給每一個沒有實現類的接口都代理一個這樣的類,用於操作數據庫執行crud}
  • MapperScannerConfigurer {掃描包下接口類,免去配置。這樣是上圖中核心配置類}
  • SimpleMetadataReader {這個類完全和mybaits-spring中的類一樣,為了解析class文件。如果你對類加載處理很好奇,可以閱讀我的《用java實現jvm虛擬機》}
  • SqlSessionFactoryBean {這個類核心內容就一件事,將我們寫的demo版的mybaits結合進來}

在分析之前先看下我們實現主食是怎麼食用的,如下;

<bean id="sqlSessionFactory" class="org.itstack.demo.like.spring.SqlSessionFactoryBean">
    <property name="resource" value="spring/mybatis-config-datasource.xml"/>
</bean>

<bean class="org.itstack.demo.like.spring.MapperScannerConfigurer">
    <!-- 注入sqlSessionFactory -->
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    <!-- 給出需要掃描Dao接口包 -->
    <property name="basePackage" value="org.itstack.demo.dao"/>
</bean>

2. (類介紹)SqlSessionFactoryBean

這類本身比較簡單,主要實現了FactoryBean , InitializingBean用於幫我們處理mybaits核心流程類的加載處理。(關於demo版的mybaits已經在上文中提供學習鏈接)

SqlSessionFactoryBean.java

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean {

    private String resource;
    private SqlSessionFactory sqlSessionFactory;

    @Override
    public void afterPropertiesSet() throws Exception {
        try (Reader reader = Resources.getResourceAsReader(resource)) {
            this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public SqlSessionFactory getObject() throws Exception {
        return sqlSessionFactory;
    }

    @Override
    public Class<?> getObjectType() {
        return sqlSessionFactory.getClass();
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    public void setResource(String resource) {
        this.resource = resource;
    }

}
  • 實現InitializingBean主要用於加載mybatis相關內容;解析xml、構造SqlSession、鏈接數據庫等
  • FactoryBean,這個類我們介紹過,主要三個方法;getObject()、getObjectType()、isSingleton()

3. (類介紹)MapperScannerConfigurer

這類的內容看上去可能有點多,但是核心事情也就是將我們的dao層接口掃描、註冊

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor {

    private String basePackage;
    private SqlSessionFactory sqlSessionFactory;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        try {
            // classpath*:org/itstack/demo/dao/**/*.class
            String packageSearchPath = "classpath*:" + basePackage.replace('.', '/') + "/**/*.class";

            ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
            Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);

            for (Resource resource : resources) {
                MetadataReader metadataReader = new SimpleMetadataReader(resource, ClassUtils.getDefaultClassLoader());

                ScannedGenericBeanDefinition beanDefinition = new ScannedGenericBeanDefinition(metadataReader);
                String beanName = Introspector.decapitalize(ClassUtils.getShortName(beanDefinition.getBeanClassName()));
                
                beanDefinition.setResource(resource);
                beanDefinition.setSource(resource);
                beanDefinition.setScope("singleton");
                beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
                beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(sqlSessionFactory);
                beanDefinition.setBeanClass(MapperFactoryBean.class);

                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);
                registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // left intentionally blank
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }
}

  • 類的掃描註冊,classpath:org/itstack/demo/dao/**/.class,解析calss文件獲取資源信息;Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
  • 遍歷Resource,這裏就你的class信息,用於註冊bean。ScannedGenericBeanDefinition
  • 這裡有一點,bean的定義設置時候,是把beanDefinition.setBeanClass(MapperFactoryBean.class);設置進去的。同時在前面給他設置了構造參數。(細細品味)
  • 最後執行註冊registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

4. (類介紹)MapperFactoryBean

這個類就非常有意思了,因為你所有的dao接口類,實際就是他。他這裏幫你執行你對sql的所有操作的分發處理。為了更加簡化清晰,目前這裏只實現了查詢部分,在mybatis-spring源碼中分別對select、update、insert、delete、其他等做了操作。

public class MapperFactoryBean<T> implements FactoryBean<T> {

    private Class<T> mapperInterface;
    private SqlSessionFactory sqlSessionFactory;

    public MapperFactoryBean(Class<T> mapperInterface, SqlSessionFactory sqlSessionFactory) {
        this.mapperInterface = mapperInterface;
        this.sqlSessionFactory = sqlSessionFactory;
    }

    @Override
    public T getObject() throws Exception {
        InvocationHandler handler = (proxy, method, args) -> {
            System.out.println("你被代理了,執行SQL操作!" + method.getName());
            try {
                SqlSession session = sqlSessionFactory.openSession();
                try {
                    return session.selectOne(mapperInterface.getName() + "." + method.getName(), args[0]);
                } finally {
                    session.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            return method.getReturnType().newInstance();
        };
        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{mapperInterface}, handler);
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterface;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

}
  • T getObject(),中是一個java代理類的實現,這個代理類對象會被掛到你的注入中。真正調用方法內容時會執行到代理類的實現部分,也就是“你被代理了,執行SQL操作!”

  • InvocationHandler,代理類的實現部分非常簡單,主要開啟SqlSession,並通過固定的key;“org.itstack.demo.dao.IUserDao.queryUserInfoById”執行SQL操作;

    session.selectOne(mapperInterface.getName() + “.” + method.getName(), args[0]);

    <mapper namespace="org.itstack.demo.dao.IUserDao">
    
    	<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="org.itstack.demo.po.User">
    		SELECT id, name, age, createTime, updateTime
    		FROM user
    		where id = #{id}
    	</select>
    	
    </mapper>
    
  • 最終返回了執行結果,關於查詢到結果信息會反射操作成對象類,這部分內容可以遇到demo版本的mybatis

六、倒滿走一個

好!到這一切開發內容就完成了,測試走一個。

mybatis-config-datasource.xml & 數據源配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/itstack_demo_ddd?useUnicode=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/User_Mapper.xml"/>
        <mapper resource="mapper/School_Mapper.xml"/>
    </mappers>

</configuration>

test-config.xml & 配置xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
       default-autowire="byName">
    <context:component-scan base-package="org.itstack"/>

    <aop:aspectj-autoproxy/>

    <bean id="sqlSessionFactory" class="org.itstack.demo.like.spring.SqlSessionFactoryBean">
        <property name="resource" value="spring/mybatis-config-datasource.xml"/>
    </bean>

    <bean class="org.itstack.demo.like.spring.MapperScannerConfigurer">
        <!-- 注入sqlSessionFactory -->
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
        <!-- 給出需要掃描Dao接口包 -->
        <property name="basePackage" value="org.itstack.demo.dao"/>
    </bean>

</beans>

SpringTest.java & 單元測試

public class SpringTest {

    private Logger logger = LoggerFactory.getLogger(SpringTest.class);

    @Test
    public void test_ClassPathXmlApplicationContext() {
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("test-config.xml");
        IUserDao userDao = beanFactory.getBean("IUserDao", IUserDao.class);
        User user = userDao.queryUserInfoById(1L);
        logger.info("測試結果:{}", JSON.toJSONString(user));
    }

}

測試結果;

一月 20, 2020 23:51:43 上午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@30b8a058: startup date [Mon Jan 20 23:51:43 CST 2020]; root of context hierarchy
一月 20, 2020 23:51:43 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [test-config.xml]
你被代理了,執行SQL操作!queryUserInfoById
2020-01-20 23:51:45.592 [main] INFO  org.itstack.demo.SpringTest[26] - 測試結果:{"age":18,"createTime":1576944000000,"id":1,"name":"水水","updateTime":1576944000000}

Process finished with exit code 0

酒乾熱火笑紅塵,春秋幾載年輪,不問。回首皆是Spring!Gun!變心!你被代理了!

七、綜上總結

  • 通過這些核心關鍵類的實現;SqlSessionFactoryBean、MapperScannerConfigurer、SqlSessionFactoryBean,我們將spring與mybaits集合起來使用,解決了沒有實現類的接口怎麼處理數據庫CRUD操作
  • 那麼這個知識點可以用到哪裡,不要只想着面試!在我們業務開發中是不會有很多其他數據源操作,比如ES、Hadoop、數據中心等等,包括自建。那麼我們就可以做成一套統一數據源處理服務,以優化服務開發效率
  • 由於這次工程類是在itstack-demo-code-mybatis中繼續開發,如果需要獲取源碼可以關注公眾號:bugstack蟲洞棧,回復:源碼分析

八、推薦閱讀

  • 這麼折騰學習畢業進大廠不是問題
  • 工作兩年簡歷寫的差教你優化
  • 講一下我自己的學習路線,給你一些參考
  • 基於Springboot的中間件開發,了解一下

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

【其他文章推薦】

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

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

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

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

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

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

※回頭車貨運收費標準

變分(圖)自編碼器不能直接應用於下游任務(GAE, VGAE, AE, VAE and SAE)

    自編碼器是無監督學習領域中一個非常重要的工具。最近由於圖神經網絡的興起,圖自編碼器得到了廣泛的關注。筆者最近在做相關的工作,對科研工作中經常遇到的:自編碼器(AE),變分自編碼器(VAE),圖自編碼器(GAE)和圖變分自編碼器(VGAE)進行了總結。如有不對之處,請多多指正。
    另外,我必須要強調的一點是:很多文章在比較中將自編碼器和變分自編碼器視為一類,我個人認為,這二者的思想完全不同。自編碼器的目的不是為了得到latent representation(中間層),而是為了生成新的樣本。我自己的實驗得出的結論是,變分自編碼器和變分圖自編碼器生成的中間層不能直接用來做下游任務(聚類、分類等),這是一個坑。

自編碼器(AE)

    在解釋圖自編碼器之前,首先理解下什麼是自編碼器。自編碼器的思路來源於傳統的PCA,其目的可以理解為非線性降維。我們知道在傳統的PCA中,學習器學得一個子空間矩陣,將原始數據投影到一個低維子空間,從未達到數據降維的目的。自編碼器則是利用神經網絡將數據逐層降維,每層神經網絡之間的激活函數就起到了將”線性”轉化為”非線性”的作用。自編碼器的網絡結構可以是對稱的也可以是非對稱的。我們下面以一個簡單的四層對稱的自編碼器為例,全文代碼見最後。
   (嚴格的自編碼器是只有一個隱藏層,但是我在這裏做了個拓展,其最大的區別就是隱藏層以及神經元數量的多少,理解一個,其它的都就理解了。)

圖自編碼器(GAE)

    圖自編碼器和自編碼器最大的區別有兩點:一是圖自編碼器在encoder過程中使用了一個 \(n*n\) 的卷積核;另一個是圖自編碼器沒有數據解碼部分,轉而代之的是圖解碼(graph decoder),具體實現是前後鄰接矩陣的變化做loss。
   圖自編碼器可以像自編碼器那樣用來生成隱向量,也可以用來做鏈路預測(應用於推薦任務)。

變分自編碼器(VAE)

   變分自編碼是讓中間層Z服從一個分佈。這樣我們想要生成一個新的樣本的時候,就可以直接在特定分佈中隨機抽取一個樣本。另外,我初學時遇到的疑惑,就是中間層是怎麼符合分佈的。我的理解是:
      輸入樣本:\(\mathbf{X \in \mathcal{R}^{n * d}}\)
      中間層 :\(\mathbf{Z \in \mathcal{R}^{n * m}}\)
   所謂的正態分佈是讓\(Z\)的每一行\(z_i\)符合正態分佈,這樣才能隨機從正態分佈中抽一個新的\(z_i\)出來。但是正是這個原因,我認為\(Z\)不能直接用來處理下游任務(分類、聚類),我自己的實驗確實效果不好。

變分圖自編碼器(VGAE)

    如果你理解了變分比編碼器和圖自編碼器,那麼變分圖自編碼器你也就能理解了。第一個改動就是在VAE的基礎上把encoder過程換成了GCN的卷積過程,另一個改動就是把decoder過程換成了圖decoder過程。同樣生成的中間層隱向量不能直接應用下游任務。
   數據集和下游任務的代碼見: https://github.com/zyx423/GAE-and-VGAE.git

全文代碼如下:

class myAE(torch.nn.Module):
     
    def __init__(self, d_0, d_1, d_2, d_3, d_4):
        super(myAE, self).__init__()
        // 這裏的d0, d_1, d_2, d_3, d_4對應四層神經網絡的維度
    
        self.conv1 = torch.nn.Sequential(
            torch.nn.Linear(d_0, d_1, bias=False),
            torch.nn.ReLU(inplace=True)
        )

        self.conv2 = torch.nn.Sequential(
            torch.nn.Linear(d_1, d_2, bias=False),
            torch.nn.ReLU(inplace=True)
        )

        self.conv3 = torch.nn.Sequential(
            torch.nn.Linear(d_2, d_3, bias=False),
            torch.nn.ReLU(inplace=True)
        )

        self.conv4 = torch.nn.Sequential(
            torch.nn.Linear(d_3, d_4, bias=False),
            torch.nn.Sigmoid()
        )
    def Encoder(self, H_0):
        H_1 = self.conv1(H_0)
        H_2 = self.conv2(H_1)
        return H_2

    def Decoder(self, H_2):
        H_3 = self.conv3(H_2)
        H_4 = self.conv4(H_3)
        return H_4

    def forward(self, H_0):
        Latent_Representation = self.Encoder(H_0)
        Features_Reconstrction = self.Decoder(Latent_Representation)
        return Latent_Representation, Features_Reconstrction

class myGAE(torch.nn.Module):
    def __init__(self, d_0, d_1, d_2):
        super(myGAE, self).__init__()

        self.gconv1 = torch.nn.Sequential(
            torch.nn.Linear(d_0, d_1, bias=False),
            torch.nn.ReLU(inplace=True)
        )
        self.gconv1[0].weight.data = get_weight_initial(d_1, d_0)

        self.gconv2 = torch.nn.Sequential(
            torch.nn.Linear(d_1, d_2, bias=False)
        )
        self.gconv2[0].weight.data = get_weight_initial(d_2, d_1)

    def Encoder(self, Adjacency_Modified, H_0):
        H_1 = self.gconv1(torch.matmul(Adjacency_Modified, H_0))
        H_2 = self.gconv2(torch.matmul(Adjacency_Modified, H_1))
        return H_2

    def Graph_Decoder(self, H_2):
        graph_re = Graph_Construction(H_2)
        Graph_Reconstruction = graph_re.Middle()
        return Graph_Reconstruction


    def forward(self, Adjacency_Modified, H_0):
        Latent_Representation = self.Encoder(Adjacency_Modified, H_0)
        Graph_Reconstruction = self.Graph_Decoder(Latent_Representation)
        return Graph_Reconstruction, Latent_Representation

class myVAE(torch.nn.Module):
    def __init__(self, d_0, d_1, d_2, d_3, d_4, bias=False):
        super(myVAE, self).__init__()

        self.conv1 = torch.nn.Sequential\
        (
            torch.nn.Linear(d_0, d_1, bias= False),
            torch.nn.ReLU(inplace=True)
        )

        # VAE有兩個encoder,一個用來學均值,一個用來學方差
        self.conv2_mean = torch.nn.Sequential(
            torch.nn.Linear(d_1, d_2, bias=False)

        )
        self.conv2_std = torch.nn.Sequential(
            torch.nn.Linear(d_1, d_2, bias=False)
        )
        self.conv3 = torch.nn.Sequential(
            torch.nn.Linear(d_2, d_3, bias=False),
            torch.nn.ReLU(inplace=False)
        )
        self.conv4 = torch.nn.Sequential(
            torch.nn.Linear(d_3, d_4, bias=False),
            torch.nn.Sigmoid()
        )

    def Encoder(self, H_0):
        H_1 = self.conv1(H_0)
        H_2_mean = self.conv2_mean(H_1)
        H_2_std = self.conv2_std(H_1)
        return H_2_mean, H_2_std

    def Reparametrization(self, H_2_mean, H_2_std):
        # sigma = 0.5*exp(log(sigma^2))= 0.5*exp(log(var))
        std = 0.5 * torch.exp(H_2_std)
        # N(mu, std^2) = N(0, 1) * std + mu。
        # 數理統計中的正態分佈方差,剛學過, std是方差。
        # torch.randn 生成正態分佈
        Latent_Representation = torch.randn(std.size()) * std + H_2_mean
        return Latent_Representation

    # 解碼隱變量
    def Decoder(self, Latent_Representation):
        H_3 = self.conv3(Latent_Representation)
        Features_Reconstruction = self.conv4(H_3)
        return Features_Reconstruction

    # 計算重構值和隱變量z的分佈參數
    def forward(self, H_0):
        H_2_mean, H_2_std = self.Encoder(H_0)
        Latent_Representation = self.Reparametrization(H_2_mean, H_2_std)
        Features_Reconstruction = self.Decoder(Latent_Representation)
        return Latent_Representation, Features_Reconstruction, H_2_mean, H_2_std

class myVGAE(torch.nn.Module):
    def __init__(self, d_0, d_1, d_2):
        super(myVGAE, self).__init__()

        self.gconv1 = torch.nn.Sequential(
            torch.nn.Linear(d_0, d_1, bias=False),
            torch.nn.ReLU(inplace=True)
        )
        # self.gconv1[0].weight.data = get_weight_initial(d_1, d_0)

        self.gconv2_mean = torch.nn.Sequential(
            torch.nn.Linear(d_1, d_2, bias=False)
        )
        # self.gconv2_mean[0].weight.data = get_weight_initial(d_2, d_1)

        self.gconv2_std = torch.nn.Sequential(
            torch.nn.Linear(d_1, d_2, bias=False)
        )
        # self.gconv2_std[0].weight.data = get_weight_initial(d_2, d_1)

    def Encoder(self, Adjacency_Modified, H_0):
        H_1 = self.gconv1(torch.matmul(Adjacency_Modified, H_0))
        H_2_mean = self.gconv2_mean(torch.matmul(Adjacency_Modified, H_1))
        H_2_std = self.gconv2_std(torch.matmul(Adjacency_Modified, H_1))
        return H_2_mean, H_2_std

    def Reparametrization(self, H_2_mean, H_2_std):

        # sigma = 0.5*exp(log(sigma^2))= 0.5*exp(log(var))
        std = 0.5 * torch.exp(H_2_std)
        # N(mu, std^2) = N(0, 1) * std + mu。
        # 數理統計中的正態分佈方差,剛學過, std是方差。
        # torch.randn 生成正態分佈
        Latent_Representation = torch.randn(std.size()) * std + H_2_mean
        return Latent_Representation

    # 解碼隱變量
    def Graph_Decoder(self, Latent_Representation):
        graph_re = Graph_Construction(Latent_Representation)
        Graph_Reconstruction = graph_re.Middle()
        return Graph_Reconstruction

    def forward(self, Adjacency_Modified, H_0):
        H_2_mean, H_2_std = self.Encoder(Adjacency_Modified, H_0)
        Latent_Representation = self.Reparametrization(H_2_mean, H_2_std)
        Graph_Reconstruction = self.Graph_Decoder(Latent_Representation)
        return Latent_Representation, Graph_Reconstruction, H_2_mean, H_2_std

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

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

Python3 源碼閱讀-深入了解Python GIL

今日得到: 三人行,必有我師焉,擇其善者而從之,其不善者而改之。

現在已經是2020年了,而在2010年的時候,大佬David Beazley就做了講座講解Python GIL的設計相關問題,10年間相信也在不斷改善和優化,但是並沒有將GIL從CPython中移除,可想而知,GIL已經深入CPython,難以移除。就目前來看,工作中常用的還是協程,多線程來處理高併發的I/O密集型任務。CPU密集型的大型計算可以用其他語言來實現。

1. GIL

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.) —– Global Interpreter Lock

為了防止多線程共享內存出現競態問題,設置的防止多線程併發執行機器碼的一個Mutex。

2. python32 之前-基於opcode數量的調度方式

在python3.2版本之前,定義了一個tick計數器,表示當前線程在釋放gil之前連續執行的多少個字節碼(實際上有部分執行較快的字節碼並不會被計入計數器)。如果當前的線程正在執行一個 CPU 密集型的任務, 它會在 tick 計數器到達 100 之後就釋放 gil, 給其他線程一個獲得 gil 的機會。

(圖片來自 Understanding the Python GIL(youtube))

以opcode個數為基準來計數,如果有些opcode代碼複雜耗時較長,一些耗時較短,會導致同樣的100個tick,一些線程的執行時間總是執行的比另一些長。是不公平的調度策略。

(圖片來自Understanding-the-python-gil)

如果當前的線程正在執行一個 IO密集型的 的任務, 你執行 sleep/recv/send(...etc) 這些會阻塞的系統調用時, 即使 tick 計數器的值還沒到 100, gil 也會被主動地釋放。至於下次該執行哪一個線程這個是操作系統層面的,線程調度算法優先級調度,開發者沒辦法控制。

在多核機器上, 如果兩個線程都在執行 CPU 密集型的任務, 操作系統有可能讓這兩個線程在不同的核心上運行, 也許會出現以下的情況, 當一個擁有了 gil 的線程在一個核心上執行 100 次 tick 的過程中, 在另一個核心上運行的線程頻繁的進行搶佔 gil, 搶佔失敗的循環, 導致 CPU 瞎忙影響性能。 如下圖:綠色部分表示該線程在運行,且在執行有用的計算,紅色部分為線程被調度喚醒,但是無法獲取GIL導致無法進行有效運算等待的時間。

由圖可見,GIL的存在導致多線程無法很好的利用多核CPU的併發處理能力。

3. python3.2 之後-基於時間片的切換

由於在多核機器下可能導致性能下降, gil的實現在python3.2之後做了一些優化 。python在初始化解釋器的時候就會初始化一個gil,並設置一個DEFAULT_INTERVAL=5000, 單位是微妙,即0.005秒(在 C 裏面是用 微秒 為單位存儲, 在 python 解釋器中以秒來表示)這個間隔就是GIL切換的標誌。

// Python\ceval_gil.h
#define DEFAULT_INTERVAL 5000

static void _gil_initialize(struct _gil_runtime_state *gil)
{
    _Py_atomic_int uninitialized = {-1};
    gil->locked = uninitialized;
    gil->interval = DEFAULT_INTERVAL;
}

python中查看gil切換的時間

In [7]: import sys
In [8]: sys.getswitchinterval()
Out[8]: 0.005

如果當前有不止一個線程, 當前等待 gil 的線程在超過一定時間的等待后, 會把全局變量 gil_drop_request 的值設置為 1, 之後繼續等待相同的時間, 這時擁有 gil 的線程看到了 gil_drop_request 變為 1, 就會主動釋放 gil 並通過 condition variable 通知到在等待中的線程, 第一個被喚醒的等待中的線程會搶到 gil 並執行相應的任務, 將gil_drop_request設置為1的線程不一定能搶到gil

4 condition variable相關字段

  1. locked : locked 的類型是_Py_atomic_int, 值-1表示還未初始化,0表示當前的gil處於釋放狀態,1表示某個線程已經佔用了gil,這個值的類型設置為原子類型之後在 ceval.c 就可以不加鎖的對這個值進行讀取。
  2. interval:是線程在設置gil_drop_request這個變量之前需要等待的時長,默認是5000毫秒
  3. last_holder:存放了最後一個持有 gil 的線程的 C 中對應的 PyThreadState 結構的指針地址, 通過這個值我們可以知道當前線程釋放了 gil 后, 是否有其他線程獲得了 gil(可以採取措施避免被自己重新獲得)
  4. switch_number: 是一個計數器, 表示從解釋器運行到現在, gil 總共被釋放獲得多少次
  5. mutex:是一把互斥鎖, 用來保護 locked, last_holder, switch_number 還有 _gil_runtime_state 中的其他變量
  6. cond:是一個 condition variable, 和 mutex 結合起來一起使用, 當前線程釋放 gil 時用來給其他等待中的線程發送信號
  7. ** switch_cond and switch_mutex**

switch_cond 是另一個 condition variable, 和 switch_mutex 結合起來可以用來保證釋放后重新獲得 gil 的線程不是同一個前面釋放 gil 的線程, 避免 gil 切換時線程未切換浪費 cpu 時間

這個功能如果編譯時未定義 FORCE_SWITCHING 則不開啟

static void
drop_gil(struct _ceval_runtime_state *ceval, PyThreadState *tstate)
{
    ...

#ifdef FORCE_SWITCHING
    if (_Py_atomic_load_relaxed(&ceval->gil_drop_request) && tstate != NULL) {
        MUTEX_LOCK(gil->switch_mutex);
        /* Not switched yet => wait */
        if (((PyThreadState*)_Py_atomic_load_relaxed(&gil->last_holder)) == tstate)
        {   
            /* 如果 last_holder 是當前線程, 釋放 switch_mutex 這把互斥鎖, 等待 switch_cond 這個條件變量的信號 */
            RESET_GIL_DROP_REQUEST(ceval);
            /* NOTE: if COND_WAIT does not atomically start waiting when
               releasing the mutex, another thread can run through, take
               the GIL and drop it again, and reset the condition
               before we even had a chance to wait for it. */
            /* 注意, 如果 COND_WAIT 不在互斥鎖釋放后原子的啟動,
                另一個線程有可能會在這中間拿到 gil 並釋放,
            '並且重置這個條件變量, 這個過程發生在了 COND_WAIT 之前 */
            COND_WAIT(gil->switch_cond, gil->switch_mutex);
        }
        MUTEX_UNLOCK(gil->switch_mutex);
    }
#endif
}

4. gil在main_loop中的體現

//
main_loop:
for (;;) {
    /* 如果 gil_drop_request 被其他線程設置為 1 */
    /* 給其他線程一個獲得 gil 的機會 */
    if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) {
    /* Give another thread a chance */
    if (_PyThreadState_Swap(&runtime->gilstate, NULL) != tstate) {
        Py_FatalError("ceval: tstate mix-up");
    }
    drop_gil(ceval, tstate);

    /* Other threads may run now */

    take_gil(ceval, tstate);

    /* Check if we should make a quick exit. */
    exit_thread_if_finalizing(runtime, tstate);

    if (_PyThreadState_Swap(&runtime->gilstate, tstate) != NULL) {
        Py_FatalError("ceval: orphan tstate");
        }
    }
    /* Check for asynchronous exceptions. */
    /* 忽略 */
    fast_next_opcode:
    switch (opcode) {
        case TARGET(NOP): {
            FAST_DISPATCH();
        }
        /* 忽略 */
        case TARGET(UNARY_POSITIVE): {
            PyObject *value = TOP();
            PyObject *res = PyNumber_Positive(value);
            Py_DECREF(value);
            SET_TOP(res);
            if (res == NULL)
                goto error;
            DISPATCH();
        }
    	/* 忽略 */
    }
    /* 忽略 */
}

這個很大的 for loop 會按順序逐個的加載 opcode, 並委派給中間很大的 switch statement 去進行執行, switch statement 會根據不同的 opcode 跳轉到不同的位置執行

for loop在開始位置會檢查 gil_drop_request變量, 必要的時候會釋放 gil

不是所有的 opcode 執行之前都會檢查 gil_drop_request 的, 有一些 opcode 結束時的代碼為 FAST_DISPATCH(), 這部分 opcode 會直接跳轉到下一個 opcode 對應的代碼的部分進行執行

而另一些 DISPATCH() 結尾的作用和 continue 類似, 會跳轉到 for loop 頂端, 重新檢測 gil_drop_request, 必要時釋放 gil

5 如何解決GIL

GIL只會對CPU密集型的程序產生影響,規避GIL限制主要有兩種常用策略:一是使用多進程,二是使用C語言擴展,把計算密集型的任務轉移到C語言中,使其獨立於Python,在C代碼中釋放GIL。當然也可以使用其他語言編譯的解釋器如 JpythonPyPy

6.總結

  1. Python語言和GIL沒有半毛錢關係,僅僅是由於歷史原因在CPython解釋器中難以移除GIL
  2. GIL:全局解釋器鎖,每個線程在執行的過程都需要先獲取GIL,確保同一時刻僅有一個線程執行代碼,所以python的線程無法利用多核。
  3. 線程在I/O操作等可能引起阻塞的system call之前,可以暫時釋放GIL,執行完畢后重新獲取GIL,python3.2以後使用時間片來切換線程,時間閾值是0.005秒,而python3.2之前是使用opcode執行的數量(tick=100)來切換的。
  4. Python的多線程在多核CPU上,只對於IO密集型計算產生正面效果;而當有至少有一個CPU密集型線程存在,那麼多線程效率會由於GIL而大幅下降

參考

Cpython-gil講解-zpoint

Python的GIL是什麼鬼-盧鈞軼(cenalulu)

Youtube-Understanding the Python GIL

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

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

月銷過萬的國民小車換裝6AT 能否再一次引發爆款?

競品車型從定價上看,長安逸動的競品車型有非常多,畢竟這個價位區間的自主品牌緊湊型轎車特別多。帝豪GL指導價格:7。88-11。38萬艾瑞澤5指導價格:5。89-9。79萬海馬福美來指導價格:6。18-11。89萬長安逸動的優勢在於擁有兩廂版本與三廂版本兩種選擇餘地,可以滿足不同消費訴求的潛在買家,而兩廂版本的逸動XT說真的還是蠻多年輕人喜歡的類型。



有那麼一款車,在已經過去的十一個月平均銷量達到1.3萬台,它有着緊湊簡潔的外觀設計,也有着三廂版本與兩廂車型的區分,以此滿足不同胃口消費者的需求,它的動力總成表現平平,但平實好用,它叫長安逸動。

如今,長安逸動的下線產量已經突破300萬輛大關,而這個傲人的数字就由換裝愛信6AT變速箱的新款長安逸動來完成。搭載了全新傳動系統的逸動,將有怎樣的表現?

長安逸動

指導價格:8.99-10.39萬(1.6L 6AT版本)

由於僅僅是傳動系統的升級,長安逸動的外觀和內飾設計上並沒有什麼變化,包括車身三圍尺寸都於現款車型一模一樣,依舊維持了一種簡潔時尚的外觀造型。

長安逸動高銷量的原因也來自於較為用心質感出色的內飾裝配工藝,大量的軟質搪塑材料包裹,而且觸感較為細膩,整車內飾也會顯得高檔感比較出色。

作為最大的改動部分,愛信6AT的注入無疑是長安逸動新車上市的重頭戲,相較於之前較老式的4AT變速箱,6AT的變速箱理論上會更加省油而且也將汽車性能提升一個層級。發動機的參數沒有變化,依舊是最大馬力128匹,峰值扭矩168牛米的1.6L直噴發動機。

競品車型

從定價上看,長安逸動的競品車型有非常多,畢竟這個價位區間的自主品牌緊湊型轎車特別多。

帝豪GL

指導價格:7.88-11.38萬

艾瑞澤5

指導價格:5.89-9.79萬

海馬福美來

指導價格:6.18-11.89萬

長安逸動的優勢在於擁有兩廂版本與三廂版本兩種選擇餘地,可以滿足不同消費訴求的潛在買家,而兩廂版本的逸動XT說真的還是蠻多年輕人喜歡的類型。

以往的逸動只有手動和4AT的版本選擇,在市場反應中並不會顯得優勢明顯,而這次長安十分聰明的避開了雙離合的選擇而採用6AT,或許會讓一部分對於雙離合變速箱的潛在車主感到放心。

對於該車的購買建議是,可以考慮直接上到最頂配,10.39萬的售價並不會顯得太貴,長安的品控和做工對得起這個價格,而且在配置上,頂配逸動在無鑰匙進入、一鍵啟動、天窗、真皮多功能方向盤,自動泊車、EpS电子助力轉向、ESC車身穩定系統等科技配置裝配程度比較齊全。以上配置當然其他細分車型也有一部分搭載,但是頂配是絕對配置完善的版本。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

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

這幾台賣斷貨的家轎/SUV真值得買買買嗎

99萬11銷量:12375輛科沃茲與310也是在9月份上市,自上市以來,科沃茲銷量為35952輛。得益於其售價較低配置表現也並不單薄,自動擋車型配有ESp車身穩定系統,售價在8。89萬,這樣的售價相比同級別車型來說更有誘惑力,且底盤功底也延續了通用一貫特性紮實穩重。

前言

對於新車的上市,銷售好不好也有很多因素,例如配置以及性價比都能直接影響到一台車的銷量,而有些新車上市,因為競爭力大以及自身綜合表現一般銷量過於慘淡,甚至幾個月只賣出幾千台。我們今天就來聊聊幾款新車自從上市以來銷量都沒差過,而這幾款車真的有這麼好嗎?咱們就一探究竟。

寶駿310

指導價:3.68-4.98萬

11月銷量:15006輛

310可以說是寶駿一款重磅利器,自9月份上市以來,銷量已經突破了三萬大關,與市場上老口碑不錯的車型相比更有實力。價格上,310選擇市場的低端價位,因為310是屬於兩廂車型,它也具備了兩廂車所擁有大空間的優勢。而310也主要針對三四線城市,以家用車代步為市場,隨着三四線城市對汽車需求量的增加,310也找對了方向。

310搭載1.2L自然吸氣發動機傳動系統配備5擋手動,也有不少用戶吐槽其動力,但以日常駕駛代步動力還是夠用。但還是希望310後期能推出1.5L車型。

因為售價低,而必須降低成本,所以310全系都沒配備ESp車身穩定系統。但人性化配置還是做的很到位,電動天窗、定速巡航等,而後排能純平放倒,增加實用性。2550mm的軸距讓310在空間表現不輸同級別三廂車型。

科沃茲

指導價:7.99-10.99萬

11銷量:12375輛

科沃茲與310也是在9月份上市,自上市以來,科沃茲銷量為35952輛。得益於其售價較低配置表現也並不單薄,自動擋車型配有ESp車身穩定系統,售價在8.89萬,這樣的售價相比同級別車型來說更有誘惑力,且底盤功底也延續了通用一貫特性紮實穩重。

外觀設計,科沃茲沿用了最新家族式風格設計,其針對年輕消費者市場,不少剛畢業出來工作,家庭條件相對來說較為富裕,給子女配台車對於現在社會來說,在正常不過了。

科沃茲搭載1.5L自然吸氣發動機,傳動系統配備5擋手動以及6擋手自一體變速箱,動力表現上,中規中矩,但只要捨得給油,動力的輸出還是和可觀,而年輕消費者購車也會注重油耗方面,而科沃茲也擁有相對經濟的油耗,綜合油耗為7L/100km。

博越

指導價:9.88-15.78萬

11月銷量:18531輛

博越自上市以來,銷量為88832銷量,銷量增長也是非常穩定,也並不會有很大的突破,這也關於廠家的產能問題。對於博越為什麼能有這麼穩定的銷量,這也取決於消費者對其品質的認可以及自主品牌在質量方面有很大的提升。

外觀設計,博越也是非常能討好外觀控,其整體設計都非常時尚以及有力量感。

動力方面,博越搭載1.8T和2.0L兩款發動機,傳動系統配備6擋手動與6擋手自一體。在油耗表現上,博越還是偏高。有不少消費者都吐糟訂車以及等車問題,就算訂車都有等三到四個月,如果博越在油耗表現上能有所降低,還是非常值得等待。選擇博越大部分消費也也取決於內飾的質感以及在安全配置的提升。

全文總結

對於小白來說,可能對於選車問題都會跟隨大眾,認為銷量好這款車就一定好,這些東西都不一定,但重要是適合自己用車需求,所以買車也需要謹慎多參考幾款車型,因為總有一款會適合自己!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

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

想要成家的90后,12萬元究竟能買到什麼好車?

以上車型都是適合年輕人的,即使是使用貸款的方式購買也不會造成太大的壓力,關鍵的一點是這些車型基本可以陪伴你度過初初成家、孩子長大的過程。

以上車型都是適合年輕人的,即使是使用貸款的方式購買也不會造成太大的壓力,關鍵的一點是這些車型基本可以陪伴你度過初初成家、孩子長大的過程。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

榮威i6這款車一推出 速騰英朗都緊張了

空間榮威i6的長寬高為4671*1835*1464mm,軸距為2715mm,實際體驗過後,也確實有中型車的空間表現,特別是後排空間非常寬敞,座墊也夠長,乘坐舒適性好。動力動力方面將提供兩款發動機可供選擇,1。0T三缸發動機,還有就是1。5T的四缸發動機,最大功率為169馬力,匹配手動變速箱和雙離合變速箱,其他參數尚未公布,懸挂方面將採用多連桿的獨立懸挂,相比於同級別車型來說更有優勢。

前言

繼互聯網汽車榮威RX5的成功之路后,上汽集團趁熱打鐵,準備入侵轎車市場,發布了一款基於Vision-R概念車打造而來的全新轎車-榮威i6,該車正式上市后將取代榮威550,從它身上看到了不少榮威RX5的影子,套娃式的設計保證旗下車型視覺上整齊劃一,一起來看一下這輛“准中型車”的實力如何。

上汽集團-榮威i6

售價:待定

外觀

第一眼望去,前臉的整體造型和RX5較為相似,中網與大燈的銜接過渡很順暢,整體感很強,給人很成熟大氣的感覺,側面的車身比例也很協調,線條柔和簡約,採用LED光源的尾燈造型銳利,點亮效果很炫,微微上翹的小鴨尾尾箱造型甚至有些轎跑的味道。

內飾

家族化的內飾設計看上去很順眼,但中控台上使用的硬塑料有點掉檔次,幸好中間蒙皮挽救了不少分數,最具亮點的就是10.4英寸的中控大屏了,自帶YunOS系統,日常使用非常的方便,儀錶盤的樣式也是採用了RX5高配車型的,中間液晶屏兩邊指針式的設計方案,視覺效果很出色。

空間

榮威i6的長寬高為4671*1835*1464mm,軸距為2715mm,實際體驗過後,也確實有中型車的空間表現,特別是後排空間非常寬敞,座墊也夠長,乘坐舒適性好。

動力

動力方面將提供兩款發動機可供選擇,1.0T三缸發動機,還有就是1.5T的四缸發動機,最大功率為169馬力,匹配手動變速箱和雙離合變速箱,其他參數尚未公布,懸挂方面將採用多連桿的獨立懸挂,相比於同級別車型來說更有優勢。

總結:榮威自從推出了RX5取得成功后,受關注度是越來越高了,這也讓我們看到了這個品牌的實力,i6的後排空間和YunOS系統相信會是最大的吸引點,配置上相信也會是比較豐富的,認清市場定位得出合適的售價后,覺得是又一爆款車型。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

※回頭車貨運收費標準

12.8萬起的大氣中型車 真的好開性價比高?

方向盤的採用電動助力,它回饋力度線性,而且指向性比起以往的起亞車型更加精準。轉動起來也更有信心。底盤採用前麥弗遜、后多連桿獨立懸架結構,它整體性不錯,高速過彎時對於車身支撐性充足,對於小震動的過濾也比較徹底。

韓系車一直主打超高的性價比,大家對於它們的印象或多或少都停留在韓系車追隨日系車的觀念上!但是韓系車企也具備自我研發實力,我說的研發實力可不光指外觀設計方面!

就像現代和起亞的新車型都採用渦輪增壓發動機+雙離合變速箱的動力組合。而前不久,編者去到4S店和朋友就試駕了一下1.6T的K4(主要是朋友想買這車)。到底這款車開起來怎樣?

東風悅達起亞-K4

指導價:12.88-18.88萬

編者此次試駕的是起亞K4的1.6T+7擋雙離合車型,該動力總成車型的售價為:14.98-17.98萬。市場優惠不少,就像在廣東地區的優惠約為2萬元,性價比不低。

中控設計簡約、時尚,方向盤採用了平底的設計,營造出一定的運動氛圍。

部分車型還帶有全景天窗!加上棕色的內飾顯得有質感。讓第一次接觸K4的人,對它的好感度提升不少。

動力總成:

它採用1.6T渦輪增壓發動機,最大功率175馬力,採用了缸內直噴技術,最大扭矩265牛米/1500-4500轉。這款發動機的動力參數十分漂亮。

搭配一款7擋雙離合變速箱,這樣的動力總成是緊貼着如今科技發展的潮流。

駕駛感受如何:

1.6T的起亞K4初段加速動力輸出比較沉穩,動力線性平順,不會有一踩就“竄”的感覺,而將轉速提升至2000轉以後,動力得到爆發,中段加速能力比較強勁,所以提速超車不成問題。

方向盤的採用電動助力,它回饋力度線性,而且指向性比起以往的起亞車型更加精準!轉動起來也更有信心。

底盤採用前麥弗遜、后多連桿獨立懸架結構,它整體性不錯,高速過彎時對於車身支撐性充足,對於小震動的過濾也比較徹底。而面對大幅度震動時稍有一些多餘動作。

1.6T發動機的動力輸出比較強勁,所以1.6T的K4百公里加速時間為8.24秒!比起自吸車型快了不少。這也是更多年輕人想要購買1.6T車型的原因之一。

油耗表現如何:

起亞K4 1.6T自動擋車型車主口碑油耗:8.4L/100km。

由於7擋雙離合的加入,K4的油耗表現也不算高,畢竟動力表現不錯。

競爭對手:

上汽通用雪佛蘭-邁銳寶

指導價:16.49-19.99萬

1.6T的起亞K4要面對同樣採用小排量增壓發動機的中型車,換裝1.5T發動機的邁銳寶就是其中一個。而邁銳寶的動力同樣表現強勁。

編者語:

起亞K4車身設計大氣、運動,而且配置也很高,加上一般有着不錯的優惠。所以性價比自然是不錯的,只是變速箱的調校比較溫和一些,如果這種駕駛風格適合你的話,那它真是個不錯的選擇。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

月銷量突破3W!顏值高低油耗買了這款車的車主怎麼說

0T,發現2。0T動力非常強勁以及換擋邏輯平順性相比1。5T車型做的要更好。內飾豪華感氛圍很容易打動人,從內飾的用料、裝配、做工都彰顯豪華感,後排地台做到全平,為後排中間乘客提高舒適性。唯一不滿足就是副駕駛沒配備電動調節,略失檔次。

前言

昂科威在中型SUV市場已爭得其應有的一席之地,時尚外觀以及較高配置都要同比同級別對手更為豐富!11月銷量昂科威脫穎而出,11月銷量就已突破3萬輛大關!這對其自身實力也不用懷疑。今天我們就看看已經提車的朋友們,對它的評價怎麼樣。

別克-昂科威

指導價:20.99-34.99萬

車主一:Dawson_道森

購買車型:昂科威 2017款 20T 兩驅豪華版

裸車價格:22.59萬

車主點評:當時第一眼看昂科威科技性能以及酷炫霸氣的外觀設計,就已被它深深吸引,相比其它同級別車型,昂科威兩驅更有駕駛樂趣,油耗有點偏高,但能接受。昂科威這款車性價比高與它動力性能非常突出,即使小玩越野昂科威性能優勢還是非常不錯。昂科威空間以及乘坐感受,都能給滿分,前後排空間非常有優勢,沒有選擇錯!

目前行駛里程:昂科威目前行駛4850公里,剛過首保綜合油耗在7-8L/100km,還是相當滿意。

車主二:半夏

購買車型:昂科威 2017款 28T 四驅精英型

裸車價格:25.39萬

車主點評:原本要選擇1.5T車型,通過試駕2.0T,發現2.0T動力非常強勁以及換擋邏輯平順性相比1.5T車型做的要更好!內飾豪華感氛圍很容易打動人,從內飾的用料、裝配、做工都彰顯豪華感,後排地台做到全平,為後排中間乘客提高舒適性。唯一不滿足就是副駕駛沒配備電動調節,略失檔次。

目前行駛里程:目前行駛2800公里,市區油耗10.3L/100km,高速油耗則能做到6.6L/100km,相當省油!

車主三:眼鏡男阿銳

購買車型:昂科威 2017款 28T 四驅豪華型

裸車價格:27.99萬

車主點評:主要看中昂科威配置豐富,例如氙氣大燈、無鑰匙進入/啟動系統、定速巡航、全景天等實用配置都配備了。操控方面,方向盤轉向輕盈,力度適中以及真皮方向盤摸起來質感非常舒服,靜音方面效比同級別日系車做的更好,即使高速行駛120km,依然沒什麼風噪聲。唯一就是受不了內飾套娃設計風格,沒有眼前一亮的感覺。

目前行駛里程:目前行駛6500公里,綜合油耗為10L/100km,油耗可以接受

全文總結

昂科威是一款綜合性不錯的車型,駕駛起來非常輕鬆,有一定的運動基因,配置方面較為豐富,性價比高,對一台中型SUV來說能擁有優秀燃油經濟性,真的是讓人相當滿意。這也證明其銷量為什麼能有這麼大的成功!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

國產車你們真是夠了…逼得洋品牌無路可走

這是一個悲傷的故事合資品牌:國產車。你真夠了為什麼你們的設計越來越漂亮。家族式設計語言已經遍及各個車企在顏值方面雖說比較主觀但國產車的顏值普遍很高為什麼你們價低配置還比我們高。高性價比是國產車所主打的更能吸引消費者的關注為什麼你們動力越來越強了。

“國產車,你夠了”

相信這是眾多合資車型的心聲

很多年以前

我們買車的選擇很少!

合資車型來來去去

消費者都偏向於選擇那幾輛神車

像緊湊型轎車:捷達、卡羅拉

中型車:帕薩特、雅閣、凱美瑞

SUV:CRV、途觀等一系列的車型

直到今天

雖然合資車型的選擇很多

但是…

消費者選擇的車型

從每月銷量榜單就能看出來

賣得好的來來去去都是那幾輛

賣不好的車型基本都在底層掙扎

時代在進步,社會在發展

這一個囧況已經發生了改變

如同雨後竹筍那般

近幾年國產品牌集體發力

毛主席說過

星星之火可以燎原

於是…

這是一個悲傷的故事

合資品牌:國產車!你真夠了

為什麼你們的設計越來越漂亮!

家族式設計語言已經遍及各個車企

在顏值方面雖說比較主觀

但國產車的顏值普遍很高

為什麼你們價低配置還比我們高!

高性價比是國產車所主打的

更能吸引消費者的關注

為什麼你們動力越來越強了!

越來越多的自主發動機現身市場

在同排量的發動機下

自主發動機已經向合資發動機靠齊

但依舊存在一定的差距

發動機與變速箱的匹配依舊要加把勁

為什麼你們的質量越來越好了!

在整體水平上

近幾年國產車有了較大的飛躍

這包括質量上的進步

當然相比合資車型來說

國產車型的故障率會高一點

另外國產與合資的差距

主要表現在售後方面

這是國產品牌4S店急需改進的地方

國產車做得越來越好

對於我們也是好處多多!

國產車能打破合資車的垄斷

首先我們再也不需要總買合資品牌了

車型可選性越來越多

日後車型的配置越來越豐富

汽車的價格越做越便宜

這是眾多消費者最希望看到的東西本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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