Hook踩坑記:React Hook react-unity-webgl

  自公司前後分離上手React以來,一個坑一個坑的踩,Class的全生命周期雲里霧裡,還么屢明白,就抱上了Hook的大腿不鬆手,確實爽到飛起。修改到Hook的過程基本比較順暢,直接少了三分之一的代碼,組件更容易封裝,調試更方便,諸多優點在此不再贅述,已有各路大佬紛紛評價,此處貼上中文官方地址:React-Hook文檔。這裏主要講講修改到一塊關於 Unity 3D模型加載的踩坑記。

  背景:React 加載 Unity 3D模型 ,使用到一個插件 react-unity-webgl,感興趣的盆友可以自行查閱。

  因為Class改Hook處理語法變動,邏輯代碼基本不用怎麼改動,所以基本沒有阻力,但是當我把這塊業務代碼改成Hook時,跟模型交互時通信失敗,無法驅動模型動作。百思不得其解,弄了倆測試頁面,test_hook、test_class,只能debugger,一步一步調,發現一些端倪。

  Class 有些初始化的代碼 都寫在了constructor(props){},這個大家都明白,第一次加載頁面的時候會走。hook呢,最外層是一個大方法,之前遷移的時候就寫在方法里最頂部了,也沒什麼問題。加載模型第一句是 const unityContent = new UnityContent(參數1,參數2);兩個頁面都能加載出來模型,但是跟斷點發現hook頁面的 unityContent 對象比class的缺少了一個重要的屬性:unityInstance,通信的方法就是靠它 Send() 的,而且發現同一個對象,屬性id一直在變,原來每次修改state時,都會走聲明的這段方法,導致每次都 new 一個新的對象,導致unityInstance屬性沒有正確掛在unityContent對象上。

  在知道大概原理的情況下,搞成全局變量,在加載時判斷是否已經初始化,問題就迎刃而解了(其實費了九牛二虎之力)。

  寫過hook的盆友第一反應會想到聲明寫到useEffect,然後 [] 只執行一次才是正確的寫法。

  之所以沒有呢,是因為模型加載跟其他的業務沒什麼關係,我並不需要渲染完整個DOM在來加載,並且加載模型很費時間,必須要剛加載頁面就同時加載模型,所以才有此次踩坑記。

  總結:Hook寫在useEffect之外的代碼會多次加載(包括刷新狀態),要做好判斷,否則很容易產生bug。更推薦(官方推薦)按業務按順序把初始化方法寫到useEffect。

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

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

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

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

源碼分析 | 手寫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

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

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

武漢肺炎讓日本人想起311 福島媽媽陷憂鬱:再一次面對「看不見的敵人」

文:宋瑞文(媽媽監督核電廠聯盟特約撰述)

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

※回頭車貨運收費標準

核災和武肺雙重考驗 福島人的「保養營隊」因疫情被迫中斷

文:宋瑞文(媽媽監督核電廠聯盟特約撰述)

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

法國費森翰核電廠吹熄燈號 將關閉第2反應爐

摘錄自2020年06月28日中央通訊社法國報導

法國費森翰(Fessenheim)核電廠在運轉40多年後,預定本月30日正式關閉。長期警告核污染的環保人士對費森翰除役自是大表歡迎,但另方面電廠關門也引發當地的經濟問題。

費森翰是法國最老的核電廠,於1977年開始運轉,原定屆40年除役,目前已逾3年。福島核災發生數月後,時任總統歐蘭德(Francois Hollande)承諾要關閉費森翰核電廠,但直到2018年,現任總統馬克宏(Emmanuel Macron)才批准費森翰除役。

費森翰核電廠由法國電力集團(EDF)經營,設有兩座反應爐,在今年2月已先停掉一部。第2座反應爐預定6月30日一早停爐卸載,但得花費數月時間,反應爐才足以冷卻下來,以移除核廢料,此一過程應在2023年前完成,但整廠全部拆卸最快也要到2040年。

能源議題
能源轉型
國際新聞
法國
核電廠
核能

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

※推薦台中搬家公司優質服務,可到府估價

日本7/1塑膠袋收費新制上路 業者推付費紙袋

摘錄自2020年06月28日中央通訊社日本報導

配合日本全國將從7月1日起實施塑膠購物袋收費新制,日本百貨公司等業者也將實施紙袋收費制度,希望促使消費者自行準備可重複利用的環保購物袋。

日本放送協會(NHK)報導,知名百貨業者表示,7月1日起將依序在直營的食品賣場取消提供塑膠製購物袋,取而代之是販售收費紙袋。收費紙袋較以往免費紙袋來得厚,提高耐用度,外層也做防潑水處理,每一個售價根據大小分成30日圓(約新台幣8元)跟50日圓。至於服裝及雜貨賣場,將繼續提供免費紙袋。

百貨業者表示,希望透過這樣的努力,可以提高消費者自配環保購物袋到三成左右。另外一家食品零售業者的450多家分店,已從4月起不再提供免費購物袋,取而代之的是販售每個15日圓的紙袋等,呼籲民眾自備環保購物袋。

公害污染
污染治理
國際新聞
日本
塑膠袋
紙袋
自備購物袋
廢棄物

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

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

月銷過萬的國民小車換裝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維修中心

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

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