Spring Boot2 系列教程(二十三)理解 Spring Data Jpa

有很多讀者留言希望松哥能好好聊聊 Spring Data Jpa! 其實這個話題松哥以前零零散散的介紹過,在我的書里也有介紹過,但是在公眾號中還沒和大夥聊過,因此本文就和大家來仔細聊聊 Spring Data 和 Jpa!

本文大綱:

1. 故事的主角

1.1 Jpa

1.1.1 JPA 是什麼

  1. Java Persistence API:用於對象持久化的 API
  2. Java EE 5.0 平台標準的 ORM 規範,使得應用程序以統一的方式訪問持久層

1.1.2 JPA 和 Hibernate 的關係

  1. JPA 是 Hibernate 的一個抽象(就像 JDBC 和 JDBC 驅動的關係);
  2. JPA 是規範:JPA 本質上就是一種 ORM 規範,不是 ORM 框架,這是因為 JPA 並未提供 ORM 實現,它只是制訂了一些規範,提供了一些編程的 API 接口,但具體實現則由 ORM 廠商提供實現;
  3. Hibernate 是實現:Hibernate 除了作為 ORM 框架之外,它也是一種 JPA 實現
  4. 從功能上來說, JPA 是 Hibernate 功能的一個子集

1.1.3 JPA 的供應商

JPA 的目標之一是制定一個可以由很多供應商實現的 API,Hibernate 3.2+、TopLink 10.1+ 以及 OpenJPA 都提供了 JPA 的實現,Jpa 供應商有很多,常見的有如下四種:

  1. Hibernate:JPA 的始作俑者就是 Hibernate 的作者,Hibernate 從 3.2 開始兼容 JPA。
  2. OpenJPA:OpenJPA 是 Apache 組織提供的開源項目。
  3. TopLink:TopLink 以前需要收費,如今開源了。
  4. EclipseLink

1.1.4 JPA 的優勢

  1. 標準化: 提供相同的 API,這保證了基於 JPA 開發的企業應用能夠經過少量的修改就能夠在不同的 JPA 框架下運行。
  2. 簡單易用,集成方便: JPA 的主要目標之一就是提供更加簡單的編程模型,在 JPA 框架下創建實體和創建 Java 類一樣簡單,只需要使用 javax.persistence.Entity 進行註解;JPA 的框架和接口也都非常簡單。
  3. 可媲美JDBC的查詢能力: JPA的查詢語言是面向對象的,JPA 定義了獨特的JPQL,而且能夠支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能夠提供的高級查詢特性,甚至還能夠支持子查詢。
  4. 支持面向對象的高級特性: JPA 中能夠支持面向對象的高級特性,如類之間的繼承、多態和類之間的複雜關係,最大限度的使用面向對象的模型

1.1.5 JPA 包含的技術

  1. ORM 映射元數據:JPA 支持 XML 和 JDK 5.0 註解兩種元數據的形式,元數據描述對象和表之間的映射關係,框架據此將實體對象持久化到數據庫表中。
  2. JPA 的 API:用來操作實體對象,執行CRUD操作,框架在後台完成所有的事情,開發者從繁瑣的 JDBC 和 SQL 代碼中解脫出來。
  3. 查詢語言(JPQL):這是持久化操作中很重要的一個方面,通過面向對象而非面向數據庫的查詢語言查詢數據,避免程序和具體的 SQL 緊密耦合。

1.2 Spring Data

Spring Data 是 Spring 的一個子項目。用於簡化數據庫訪問,支持NoSQL 和 關係數據存儲。其主要目標是使數據庫的訪問變得方便快捷。Spring Data 具有如下特點:

  • SpringData 項目支持 NoSQL 存儲:
    1. MongoDB (文檔數據庫)
    2. Neo4j(圖形數據庫)
    3. Redis(鍵/值存儲)
    4. Hbase(列族數據庫)
  • SpringData 項目所支持的關係數據存儲技術:
    1. JDBC
    2. JPA
  • Spring Data Jpa 致力於減少數據訪問層 (DAO) 的開發量. 開發者唯一要做的,就是聲明持久層的接口,其他都交給 Spring Data JPA 來幫你完成
  • 框架怎麼可能代替開發者實現業務邏輯呢?比如:當有一個UserDao.findUserById() 這樣一個方法聲明,大致應該能判斷出這是根據給定條件的 ID 查詢出滿足條件的 User 對象。Spring Data JPA 做的便是規範方法的名字,根據符合規範的名字來確定方法需要實現什麼樣的邏輯。

2. 主角的故事

2.1 Jpa 的故事

為了讓大夥徹底把這兩個東西學會,這裏我就先來介紹單純的 Jpa 使用,然後我們再結合 Spring Data 來看 Jpa 如何使用。

整體步驟如下:

  • 1.使用 IntelliJ IDEA 創建項目,創建時選擇 JavaEE Persistence ,如下:
  • 2.創建成功后,添加依賴 jar,由於 Jpa 只是一個規範,因此我們說用 Jpa 實際上必然是用 Jpa 的某一種實現,那麼是哪一種實現呢?當然就是 Hibernate 了,所以添加的 jar,實際上來自 Hibernate,如下:
  • 3.添加實體類

接下來在項目中添加實體類,如下:

@Entity(name = "t_book")
public class Book {
    private Long id;
    private String name;
    private String author;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long getId() {
        return id;
    }
    // 省略其他getter/setter
}

首先 @Entity 註解表示這是一個實體類,那麼在項目啟動時會自動針對該類生成一張表,默認的表名為類名,@Entity 註解的 name 屬性表示自定義生成的表名。@Id 註解表示這個字段是一個 id,@GeneratedValue 註解表示主鍵的自增長策略,對於類中的其他屬性,默認都會根據屬性名在表中生成相應的字段,字段名和屬性名相同,如果開發者想要對字段進行定製,可以使用 @Column 註解,去配置字段的名稱,長度,是否為空等等。

  • 4.創建 persistence.xml 文件

JPA 規範要求在類路徑的 META-INF 目錄下放置 persistence.xml,文件的名稱是固定的

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
    <persistence-unit name="NewPersistenceUnit" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <class>org.javaboy.Book</class>
        <properties>
            <property name="hibernate.connection.url"
                      value="jdbc:mysql:///jpa01?useUnicode=true&amp;characterEncoding=UTF-8"/>
            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
            <property name="hibernate.connection.username" value="root"/>
            <property name="hibernate.connection.password" value="123"/>
            <property name="hibernate.archive.autodetection" value="class"/>
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
        </properties>
    </persistence-unit>
</persistence>

注意:

  1. persistence-unit 的 name 屬性用於定義持久化單元的名字, 必填。
  2. transaction-type:指定 JPA 的事務處理策略。RESOURCE_LOCAL:默認值,數據庫級別的事務,只能針對一種數據庫,不支持分佈式事務。如果需要支持分佈式事務,使用JTA:transaction-type=”JTA”
  3. class 節點表示顯式的列出實體類
  4. properties 中的配置分為兩部分:數據庫連接信息以及Hibernate信息
  • 5.執行持久化操作
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("NewPersistenceUnit");
EntityManager manager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
Book book = new Book();
book.setAuthor("羅貫中");
book.setName("三國演義");
manager.persist(book);
transaction.commit();
manager.close();
entityManagerFactory.close();

這裏首先根據配置文件創建出來一個 EntityManagerFactory ,然後再根據 EntityManagerFactory 的實例創建出來一個 EntityManager ,然後再開啟事務,調用 EntityManager 中的 persist 方法執行一次持久化操作,最後提交事務,執行完這些操作后,數據庫中舊多出來一個 t_book 表,並且表中有一條數據。

2.1.1 關於 JPQL

  1. JPQL 語言,即 Java Persistence Query Language 的簡稱。JPQL 是一種和 SQL 非常類似的中間性和對象化查詢語言,它最終會被編譯成針對不同底層數據庫的 SQL 查詢,從而屏蔽不同數據庫的差異。JPQL語言的語句可以是 select 語句、update 語句或 delete 語句,它們都通過 Query 接口封裝執行。
  2. Query接口封裝了執行數據庫查詢的相關方法。調用 EntityManager 的 createQuery、create NamedQuery 及 createNativeQuery 方法可以獲得查詢對象,進而可調用 Query 接口的相關方法來執行查詢操作。
  3. Query接口的主要方法如下:
  • int executeUpdate(); | 用於執行update或delete語句。
  • List getResultList(); | 用於執行select語句並返回結果集實體列表。
  • Object getSingleResult(); | 用於執行只返回單個結果實體的select語句。
  • Query setFirstResult(int startPosition); | 用於設置從哪個實體記錄開始返回查詢結果。
  • Query setMaxResults(int maxResult); | 用於設置返回結果實體的最大數。與setFirstResult結合使用可實現分頁查詢。
  • Query setFlushMode(FlushModeType flushMode); | 設置查詢對象的Flush模式。參數可以取2個枚舉值:FlushModeType.AUTO 為自動更新數據庫記錄,FlushMode Type.COMMIT 為直到提交事務時才更新數據庫記錄。
  • setHint(String hintName, Object value); | 設置與查詢對象相關的特定供應商參數或提示信息。參數名及其取值需要參考特定 JPA 實現庫提供商的文檔。如果第二個參數無效將拋出IllegalArgumentException異常。
  • setParameter(int position, Object value); | 為查詢語句的指定位置參數賦值。Position 指定參數序號,value 為賦給參數的值。
  • setParameter(int position, Date d, TemporalType type); | 為查詢語句的指定位置參數賦 Date 值。Position 指定參數序號,value 為賦給參數的值,temporalType 取 TemporalType 的枚舉常量,包括 DATE、TIME 及 TIMESTAMP 三個,,用於將 Java 的 Date 型值臨時轉換為數據庫支持的日期時間類型(java.sql.Date、java.sql.Time及java.sql.Timestamp)。
  • setParameter(int position, Calendar c, TemporalType type); | 為查詢語句的指定位置參數賦 Calenda r值。position 指定參數序號,value 為賦給參數的值,temporalType 的含義及取捨同前。
  • setParameter(String name, Object value); | 為查詢語句的指定名稱參數賦值。
  • setParameter(String name, Date d, TemporalType type); | 為查詢語句的指定名稱參數賦 Date 值,用法同前。
  • setParameter(String name, Calendar c, TemporalType type); | 為查詢語句的指定名稱參數設置Calendar值。name為參數名,其它同前。該方法調用時如果參數位置或參數名不正確,或者所賦的參數值類型不匹配,將拋出 IllegalArgumentException 異常。

2.1.2 JPQL 舉例

和在 SQL 中一樣,JPQL 中的 select 語句用於執行查詢。其語法可表示為:

select_clause form_clause [where_clause] [groupby_clause] [having_clause] [orderby_clause]

其中:

  1. from 子句是查詢語句的必選子句。
  2. select 用來指定查詢返回的結果實體或實體的某些屬性。
  3. from 子句聲明查詢源實體類,並指定標識符變量(相當於SQL表的別名)。
  4. 如果不希望返回重複實體,可使用關鍵字 distinct 修飾。select、from 都是 JPQL 的關鍵字,通常全大寫或全小寫,建議不要大小寫混用。

在 JPQL 中,查詢所有實體的 JPQL 查詢語句很簡單,如下:

select o from Order o

select o from Order as o

這裏關鍵字 as 可以省去,標識符變量的命名規範與 Java 標識符相同,且區分大小寫,調用 EntityManager 的 createQuery() 方法可創建查詢對象,接着調用 Query 接口的 getResultList() 方法就可獲得查詢結果集,如下:

Query query = entityManager.createQuery( "select o from Order o"); 
List orders = query.getResultList();
Iterator iterator = orders.iterator();
while(iterator.hasNext() ) {
  // 處理Order
}

其他方法的與此類似,這裏不再贅述。

2.2 Spring Data 的故事

在 Spring Boot 中,Spring Data Jpa 官方封裝了太多東西了,導致很多人用的時候不知道底層到底是怎麼配置的,本文就和大夥來看看在手工的 Spring 環境下,Spring Data Jpa 要怎麼配置,配置完成后,用法和 Spring Boot 中的用法是一致的。

2.2.1 基本環境搭建

首先創建一個普通的 Maven 工程,並添加如下依賴:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-oxm</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.27</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-expression</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>5.2.12.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-jpamodelgen</artifactId>
        <version>5.2.12.Final</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.29</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-jpa</artifactId>
        <version>1.11.3.RELEASE</version>
    </dependency>
</dependencies>

這裏除了 Jpa 的依賴之外,就是 Spring Data Jpa 的依賴了。

接下來創建一個 User 實體類,創建方式參考 Jpa 中實體類的創建方式,這裏不再贅述。

接下來在 resources 目錄下創建一個 applicationContext.xml 文件,並配置Spring 和 Jpa,如下:

<context:property-placeholder location="classpath:db.properties"/>
<context:component-scan base-package="org.javaboy"/>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
    <property name="driverClassName" value="${db.driver}"/>
    <property name="url" value="${db.url}"/>
    <property name="username" value="${db.username}"/>
    <property name="password" value="${db.password}"/>
</bean>
<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
    <property name="dataSource" ref="dataSource"/>
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
    </property>
    <property name="packagesToScan" value="org.javaboy.model"/>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.format_sql">true</prop>
            <prop key="hibernate.hbm2ddl.auto">update</prop>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQL57Dialect</prop>
        </props>
    </property>
</bean>
<bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 配置jpa -->
<jpa:repositories base-package="org.javaboy.dao"
                  entity-manager-factory-ref="entityManagerFactory"/>

這裏和 Jpa 相關的配置主要是三個:

  • 一個是 entityManagerFactory
  • 一個是 Jpa 的事務
  • 一個是配置 dao 的位置

配置完成后,就可以在 org.javaboy.dao 包下創建相應的 Repository 了,如下:

public interface UserDao extends Repository<User, Long> {
    User getUserById(Long id);
}

getUserById 表示根據 id 去查詢 User 對象,只要我們的方法名稱符合類似的規範,就不需要寫 SQL,具體的規範一會來說。好了,接下來,創建 Service 和 Controller 來調用這個方法,如下:

@Service
@Transactional
public class UserService {
    @Resource
    UserDao userDao;

    public User getUserById(Long id) {
        return userDao.getUserById(id);
    }
}
public void test1() {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = ctx.getBean(UserService.class);
    User user = userService.getUserById(1L);
    System.out.println(user);
}

這樣,就可以查詢到 id 為 1 的用戶了。

2.2.2 Repository

上文我們自定義的 UserDao 實現了 Repository 接口,這個 Repository 接口是什麼來頭呢?

首先來看 Repository 的一個繼承關係圖:

可以看到,實現類不少。那麼到底如何理解 Repository 呢?

  1. Repository 接口是 Spring Data 的一個核心接口,它不提供任何方法,開發者需要在自己定義的接口中聲明需要的方法 public interface Repository<T, ID extends Serializable> { }
  2. 若我們定義的接口繼承了 Repository, 則該接口會被 IOC 容器識別為一個 Repository Bean,進而納入到 IOC 容器中,進而可以在該接口中定義滿足一定規範的方法。
  3. Spring Data可以讓我們只定義接口,只要遵循 Spring Data 的規範,就無需寫實現類。
  4. 與繼承 Repository 等價的一種方式,就是在持久層接口上使用 @RepositoryDefinition 註解,併為其指定 domainClass 和 idClass 屬性。像下面這樣:
@RepositoryDefinition(domainClass = User.class, idClass = Long.class)
public interface UserDao
{
    User findById(Long id);
    List<User> findAll();
}

基礎的 Repository 提供了最基本的數據訪問功能,其幾個子接口則擴展了一些功能,它的幾個常用的實現類如下:

  • CrudRepository: 繼承 Repository,實現了一組 CRUD 相關的方法
  • PagingAndSortingRepository: 繼承 CrudRepository,實現了一組分頁排序相關的方法
  • JpaRepository: 繼承 PagingAndSortingRepository,實現一組 JPA 規範相關的方法
  • 自定義的 XxxxRepository 需要繼承 JpaRepository,這樣的 XxxxRepository 接口就具備了通用的數據訪問控制層的能力。
  • JpaSpecificationExecutor: 不屬於Repository 體系,實現一組 JPA Criteria 查詢相關的方法

2.2.3 方法定義規範

2.2.3.1 簡單條件查詢

  • 按照 Spring Data 的規範,查詢方法以 find | read | get 開頭
  • 涉及條件查詢時,條件的屬性用條件關鍵字連接,要注意的是:條件屬性以首字母大寫

例如:定義一個 Entity 實體類:

class User{ 
   private String firstName; 
   private String lastName; 
}

使用 And 條件連接時,條件的屬性名稱與個數要與參數的位置與個數一一對應,如下:

findByLastNameAndFirstName(String lastName,String firstName);
  • 支持屬性的級聯查詢. 若當前類有符合條件的屬性, 則優先使用, 而不使用級聯屬性. 若需要使用級聯屬性, 則屬性之間使用 _ 進行連接.

查詢舉例:

  • 按照 id 查詢
User getUserById(Long id);
User getById(Long id);
  • 查詢所有年齡小於 90 歲的人
List<User> findByAgeLessThan(Long age);
  • 查詢所有姓趙的人
List<User> findByUsernameStartingWith(String u);
  • 查詢所有姓趙的、並且 id 大於 50 的人
List<User> findByUsernameStartingWithAndIdGreaterThan(String name, Long id);
  • 查詢所有姓名中包含”上”字的人
List<User> findByUsernameContaining(String name);
  • 查詢所有姓趙的或者年齡大於 90 歲的
List<User> findByUsernameStartingWithOrAgeGreaterThan(String name, Long age);
  • 查詢所有角色為 1 的用戶
List<User> findByRole_Id(Long id);

2.2.3.2 支持的關鍵字

支持的查詢關鍵字如下圖:

2.2.3.3 查詢方法流程解析

為什麼寫上方法名,JPA就知道你想幹嘛了呢?假如創建如下的查詢:findByUserDepUuid(),框架在解析該方法時,首先剔除 findBy,然後對剩下的屬性進行解析,假設查詢實體為Doc:

  1. 先判斷 userDepUuid (根據 POJO 規範,首字母變為小寫)是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,繼續第二步;
  2. 從右往左截取第一個大寫字母開頭的字符串(此處為 Uuid),然後檢查剩下的字符串是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,則重複第二步,繼續從右往左截取;最後假設 user 為查詢實體的一個屬性;
  3. 接着處理剩下部分(DepUuid),先判斷 user 所對應的類型是否有depUuid屬性,如果有,則表示該方法最終是根據 “ Doc.user.depUuid” 的取值進行查詢;否則繼續按照步驟 2 的規則從右往左截取,最終表示根據 “Doc.user.dep.uuid” 的值進行查詢。
  4. 可能會存在一種特殊情況,比如 Doc 包含一個 user 的屬性,也有一個 userDep 屬性,此時會存在混淆。可以明確在屬性之間加上 “_” 以顯式表達意圖,比如 “findByUser_DepUuid()” 或者 “findByUserDep_uuid()”
  5. 還有一些特殊的參數:例如分頁或排序的參數:
Page<UserModel> findByName(String name, Pageable pageable);
List<UserModel> findByName(String name, Sort sort);

2.2.3.4 @Query 註解

有的時候,這裏提供的查詢關鍵字並不能滿足我們的查詢需求,這個時候就可以使用 @Query 關鍵字,來自定義查詢 SQL,例如查詢 Id 最大的 User:

@Query("select u from t_user u where id=(select max(id) from t_user)")
User getMaxIdUser();

如果查詢有參數的話,參數有兩種不同的傳遞方式,

  • 利用下標索引傳參,索引參數如下所示,索引值從1開始,查詢中 ”?X” 個數需要與方法定義的參數個數相一致,並且順序也要一致:
@Query("select u from t_user u where id>?1 and username like ?2")
List<User> selectUserByParam(Long id, String name);
  • 命名參數(推薦):這種方式可以定義好參數名,賦值時採用@Param(“參數名”),而不用管順序:
@Query("select u from t_user u where id>:id and username like :name")
List<User> selectUserByParam2(@Param("name") String name, @Param("id") Long id);

查詢時候,也可以是使用原生的 SQL 查詢,如下:

@Query(value = "select * from t_user",nativeQuery = true)
List<User> selectAll();

2.2.3.5 @Modifying 註解

涉及到數據修改操作,可以使用 @Modifying 註解,@Query 與 @Modifying 這兩個 annotation 一起聲明,可定義個性化更新操作,例如涉及某些字段更新時最為常用,示例如下:

@Modifying
@Query("update t_user set age=:age where id>:id")
int updateUserById(@Param("age") Long age, @Param("id") Long id);

注意:

  1. 可以通過自定義的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支持使用 INSERT
  2. 方法的返回值應該是 int,表示更新語句所影響的行數
  3. 在調用的地方必須加事務,沒有事務不能正常執行
  4. 默認情況下, Spring Data 的每個方法上有事務, 但都是一個只讀事務. 他們不能完成修改操作

說到這裏,再來順便說說 Spring Data 中的事務問題:

  1. Spring Data 提供了默認的事務處理方式,即所有的查詢均聲明為只讀事務。
  2. 對於自定義的方法,如需改變 Spring Data 提供的事務默認方式,可以在方法上添加 @Transactional 註解。
  3. 進行多個 Repository 操作時,也應該使它們在同一個事務中處理,按照分層架構的思想,這部分屬於業務邏輯層,因此,需要在Service 層實現對多個 Repository 的調用,並在相應的方法上聲明事務。

好了,關於Spring Data Jpa 本文就先說這麼多。

關注公眾號【江南一點雨】,專註於 Spring Boot+微服務以及前後端分離等全棧技術,定期視頻教程分享,關注后回復 Java ,領取松哥為你精心準備的 Java 乾貨!

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

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

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

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

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

德國龐巴迪研發出200-400千瓦無線充電系統 未來將引入中國市場

龐巴迪2009年開始研發無線充電系統PRIMOVE,於2013年將該系統成功應用在純電動巴士無線充電中。截至2015年底已在德國曼海姆、柏林、布勞恩斯魏克市開通了商業運行線路,累計運行里程已超過12.5萬公里。目前,龐巴迪已研發生產出200-400千瓦無線充電系統PRIMOVE。未來,龐巴迪將把這個無線充電技術引入中國市場。

目前,中國電動巴士使用的無線充電系統是單組30千瓦或使用兩組並聯達到60千瓦充電功率。龐巴迪的這套大功率無線充電系統的充電速度遠遠超過了目前中國純電動巴士廣泛使用的80千瓦有線充電系統,使得無線充電不僅可以在城市微循環線路中使用,也同樣適應於骨幹公交線路 (例如BRT) 以及有軌電車等軌道交通。

為了將這項技術產業化並推向市場,擁有專利技術的地埋式初級線圈預製模組已經經過了長達一年的一百萬次的13噸軸重碾壓試驗,以此保證模組至少20年的使用壽命。該系統所有電力電子設備全部採取地埋的特點,使城市核心區充電基礎設施零占地成為可能。所有電力電子設備達到最高防護等級IP68,並採用德國專業的排水箱體設計,即使在暴雨情況下依然保證安全運行,成為這套無線充電系統的優勢所在。

獨特的線圈設計將電磁場定向傳輸,使巴士車內幾乎檢測不到電磁場,車外電磁場強度也只有國際標準ICNIRP2010 的四分之一。目前,PRIMOVE無線充電技術處於國際電動車用無線充電標準IEC61980中的最大功率等級。
 

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

【其他文章推薦】

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

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

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

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

電動汽車分時租賃入駐上海 2017年底前投放6000輛

國網上海市電力公司2月3日與上海國際汽車城(集團)有限公司簽署戰略合作協定。今後,雙方將在現有國網充電樁點位基礎上再增加EVCARD共用車位;同時在充電業務領域開展合作,積極共建充電樁和共用充電資源,從而推動申城新能源車分時租賃點的有效擴容,實現城市公共資源的合理利用。

作為合作計畫的首批現實成果,當天,三個合作網站正式啟用。

EVCARD是中國首個電動汽車分時租賃品牌。它于2013年進行開發,並於2015年1月在上海嘉定率先投入市場。截至2016年1月份,EVCARD在上海的用戶數達到38940人,訂單數量也達到25765個。

自從有了電動車分時租賃,同濟大學嘉定校區的學生王蓓出門時多了一重選擇,她來到附近的“大學裡社區”EVCARD電動汽車租賃網點,熟練地刷卡取車,隨後便開著這輛租來的榮威E50純電動車上路。辦完事,她開車返回取車網點,停好車輛,刷卡還車。借車總耗時107分鐘,每分鐘0.5元人民幣,計時收費,共花費53.5元,這甚至比她出門坐計程車還要便宜幾十元。

業內人士測算指出,目前電動車租賃行業之所以不盈利,首先在於網點不夠,取還車、充電都不太方便,當下的任務是結合城市交通需求來進行新增網點規劃;另一個原因在於電動汽車價格太高,這就導致了租車的價格下不來,解決這個問題,要靠技術發展,也需要時間。

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

【其他文章推薦】

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

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

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

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

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

為什麼 HTTPS 比 HTTP 安全

HTTP(超文本傳輸協議)是目前互聯網應用最廣泛的協議,伴隨着人們網絡安全意識的加強,HTTPS 被越來越多地採納。不論是訪問一些購物網站,或是登錄一些博客、論壇等,我們都被 HTTPS 保護着,甚至 Google Chrome、Firefox 等主流瀏覽器已經將所有基於 HTTP 的站點都標記為不安全。

為什麼 HTTPS 比 HTTP 安全?在回答這個問題之前,首先我們得了解 HTTP 和 HTTPS 是什麼。

HTTP 和 HTTPS 的訪問過程

從互聯網發展至今,HTTP 一直擔任互聯網傳輸信息的標準協議。傳輸的信息可以是互聯網內計算機之間的文檔,文件,圖像,視頻等。

HTTP 請求過程中,客戶端與服務器之間沒有任何身份確認的過程,數據全部明文傳輸,“裸奔”在互聯網上,所以很容易遭到黑客的攻擊。

從上圖中可以看到,客戶端發出的請求很容易被黑客截獲,如果此時黑客冒充服務器,則其可返回任意信息給客戶端,而不被客戶端察覺,所以我們經常會聽到一詞“劫持”。

而 HTTPS 實際上是帶有 SSL 的 HTTP(HTTP + SSL=HTTPS)。當您在瀏覽器的地址欄中看到 HTTPS 時,這就意味着與該網站的所有通信都將被加密,整個訪問過程更加安全。

為什麼 HTTPS 比 HTTP 安全

HTTPS 的安全性往往體現在三個方面:

  • 服務器身份驗證,通過服務器身份驗證,用戶可以明確當前它正在與對應的服務器進行通信。

  • 數據機密性,其他方無法理解發送的數據內容,因為提交的數據是加密的。

  • 數據完整性,傳輸會攜帶 Message Authentication Code(MAC)用於驗證,因此傳輸的數據不會被另一方更改。
    可以舉個例子來比較下。一個 HTTP 請求,其組成則是多個遵循 HTTP 協議的文本行,例如下面的 GET 請求:

GET /helloupyun.txt HTTP/1.1

User-Agent: curl/7.73.0 libcurl/7.73.0 OpenSSL/1.1.l zlib/1.2.11

Host: www.upyun.com

Accept-Language: en

請求會以明文的形式直接發送,既然是明文的形式,對於協議命令和語法有基本了解的人,只要監控了請求發送的過程,就能獲取並讀懂請求的意義。因此用 HTTP 的方式發送密碼一類的數據時,安全性極低。

相對的,HTTPS 使用了 SSL(或 TLS)來加密 HTTP 請求和響應,因此在上面的示例中,監控請求的人將會看到一串隨機的数字,而不是可讀性的文本。

GsERHg9YDMpYk0VVDiRvw1H5miNieJeJ/FNUjgH0BmVAWII6+T4MnDwmCMZUI/orxP3HGwYCSIvyzS3MpmmSe4iaWKCOHH==

其中加密過程採用的 SSL(安全套接字層)這一標準的安全技術,涵蓋了非對稱密鑰和對稱密鑰。

對稱加密
對稱加密是指加密與解密使用同一個密鑰的加密算法。

目前常見的加密算法有:DES、AES、IDEA 等

非對稱加密
非對稱加密使用的是兩個密鑰,公鑰與私鑰,我們會使用公鑰對網站賬號密碼等數據進行加密,再用私鑰對數據進行解密。這個公鑰會發給查看網站的所有人,而私鑰是只有網站服務器自己擁有的。

目前常見非對稱加密算法:RSA,DSA,DH 等。

而常用的套件,例如 ChaCha20-Poly1305 加密套件就使用了這兩種算法,其中 Chacha20 是指對稱加密算法,而Poly1305 是指身份認證算法。

參考 RFC 文檔,我們可以了解 ChaCha20 提供了 256 位的加密強度,這作為對稱加密算法來保障 HTTPS 安全性是足夠了。

而 Poly1305 作為身份認證算法提供身份驗證,可以防止攻擊者在 TLS 握手過程中,將虛假信息插入到安全的數據流中,Poly1305 算法提供了大約 100 位的安全性加密強度,足以阻止這類攻擊。

總的來看,HTTPS 相比 HTTP ,它作為一種加密手段不僅加密了數據,還給了網站一張安全可信賴的身份證。

聊聊 HTTPS 的一些優缺點

整體來看 HTTPS 有以下五個優點:

  • 最大限度地提高 Web 上數據和事務的安全性;

  • 加密用戶敏感或者機密信息;

  • 提高搜索引擎中的排名

  • 避免在瀏覽器中出現“不安全”的提示;

  • 提升用戶對網站的信賴。

相對的,缺點也是必不可少的:

  • HTTPS 協議在握手階段耗時相對較大,會影響頁面整體加載速度;

  • 在瀏覽器和服務器上會更多的 CPU 周期來加密/解密數據;

  • SSL 證書一般都需要支付一定費用來獲取,並且費用往往不低;

  • 並不是絕對意義上的安全,在網站遭受攻擊,服務器被劫持時,HTTPS 基本起不到任何安全防護作用。

將 HTTP 升級成 HTTPS

如何將網站從 HTTP 升級成 HTTPS 呢?相比起常規的升級步驟,又拍雲提供一套更為簡潔明了的流程,從 SSL 證書的申購、管理到部署,三步即可完成。同時,又拍雲與國際頂級 CA 機構合作,證書類型豐富,操作流程簡單方便。

推薦閱讀

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

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

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

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

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

如何構建自己的 react hooks

我們組的前端妹子在組內分享時談到了 react 的鈎子,趁此機會我也對我所理解的內容進行下總結,方便更多的同學了解。在 React 的 v16.8.0 版本里添加了 hooks 的這種新的 API,我們非常有必要了解下他的使用方法,並能夠結合我們的業務編寫幾個自定義的 hooks。

1. 常用的一個 hooks

官方中提供了幾個內置的鈎子,我們簡單了解下他們的用法。

1.1 useState: 狀態鈎子

需要更新頁面狀態的數據,我們可以把他放到 useState 的鈎子里。例如點擊按鈕一下,數據加 1 的操作:

const [count, setCount] = useState(0);

return (<>
    <p>{ count}</p>
    <button onClick = {
        () => setCount(count + 1)
    }> add 1 </button>
    </>
);

在 typescript 的體系中,count 的類型,默認就是當前初始值的類型,例如上面例子中的變量就是 number 類型。如果我們想自定義這個變量的類型,可以在 useState 後面進行定義:

const [count, setCount] = useState<number | null>(null); // 變量count為number類型或者null類型

同時,使用 useState 改變狀態時,是整個把 state 替換掉的,因此,若狀態變量是個 object 類型的數據,我只想修改其中的某個字段,在之前 class 組件內調用 setState 時,他內部會自動合併數據。

class Home extends React.Component {
    state = {
        name: 'wenzi',
        age: 20,
        score: 89
    };

    update() {
        this.setState({
            score: 98
        }); // 內部自動合併
    }
}

但在 function 組件內使用 useState 時,需要自己先合併數據,然後再調用方法,否則會造成字段的丟失。

const [person, setPerson] = useState({
    name: 'wenzi',
    age: 20,
    score: 89
});

setPerson({
    ...person,
    {
        score: 98
    }
}); // 先合併數據 { name: 'wenzi', age: 20, score: 98 }
setPerson({
    score: 98
}); // 僅傳入要修改的字段,后name和age字段丟失

1.2 useEffect: 副作用鈎子

useEffect 可以看做是 componentDidMount,componentDidUpdate 和 componentWillUnmount 這三個函數的組合。

useEffect 鈎子在組件初始化完畢時,一定會執行一次,在組件重新渲染的過程中,是否還要 update,還要看傳入的第 2 個參數。

  1. 當只有回調函數這一個參數時,組件的每次更新,回調都會執行;
  2. 當有 2 個參數時,只有第 2 參數里的數據發生變化時,回調才執行;
  3. 只想在組件初始化完畢時只執行一次,第 2 個參數可以傳入一個空的數組;

我們可以看下這個例子,無論點擊 add按鈕 還是 settime按鈕 ,useEffect 的回調都會執行:

const Home = () => {
    const [count, setCount] = useState(0);
    const [nowtime, setNowtime] = useState(0);

    useEffect(() => {
        console.log('count', count);
        console.log('nowtime', nowtime);
    });

    return ( <>
        <p>count: {count} </p>
        <p>nowtime: {nowtime} </p>
        <button onClick = {() => setCount(count + 1)}> add 1 </button>
        <button onClick = {() => setNowtime(Date.now())} > set now time </button>
    </>);
};

若改成下面的這樣,回調僅會在 count 發生變化時才會在控制台輸出,僅修改 nowtime 的值時沒有輸出:

useEffect(() => {
    console.log('count', count);
    console.log('nowtime', nowtime);
}, [count]);

useEffect 的回調函數還可以返回一個函數,這個函數在 effect 生命周期結束之前調用。為防止內存泄漏,清除函數會在組件卸載前執行。另外,如果組件多次渲染,則在執行下一個 effect 之前,上一個 effect 就已被清除

基於上面的代碼,我們稍微修改一下:

useEffect(() => {
    console.log('count', count);
    console.log('nowtime', nowtime);

    return () => console.log('effect callback will be cleared');
}, [count]);

基於這個機制,在一些存在添加綁定和取消綁定的案例上特別合適,例如監聽頁面的窗口大小變化、設置定時器、與後端的 websocket 接口建立連接和斷開連接等,都可以預計 useEffect 進行二次的封裝,形成自定義的 hook。關於自定義 hook,下面我們會講到。

1.3 useMemo 和 useCallback

function 組件中定義的變量和方法,在組件重新渲染時,都會重新重新進行計算,例如下面的這個例子:

const Home = () => {
    const [count, setCount] = useState(0);
    const [nowtime, setNowtime] = useState(0);

    const getSum = () => {
        const sum = ((1 + count) * count) / 2;
        return sum + ' , ' + Math.random(); // 這個random是為了看到區別
    };

    return ( <>
        <p> count: {count}< /p>
        <p> sum: {getSum()}</p>
        <p> nowtime: {nowtime}</p>
        <button onClick = {() => setCount(count + 1)} > add 1 </button>
        <button onClick = {() => setNowtime(Date.now())}> set now time </button>
    </>);
};

這裡有 2 個按鈕,一個是 count+1,一個設置當前的時間戳, getSun() 方法是計算從 1 到 count 的和,我們每次點擊 add 按鈕后,sum 方法都會重新計算和。可是當我們點擊 settime 按鈕時,getSum 方法也會重新計算,這是沒有必要的。

這裏我們可以使用 useMemo 來修改下:

const sum = useMemo(() => ((1 + count) * count) / 2 + ' , ' + Math.random(), [count]);

<p> {sum} </p>;

修改后就可以看到,sum 的值只有在 count 發生變化的時候才重新計算,當點擊 settime 按鈕的時候,sum 並沒有重新計算。這要得益於 useMemo 鈎子的特性:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useMemo 返回回調里 return 的值,而且 memoizedValue 它僅會在某個依賴項改變時才重新計算。這種優化有助於避免在每次渲染時都進行高開銷的計算。如果沒有提供依賴項數組,useMemo 在每次渲染時都會計算新的值。

在上面的例子里,只有 count 變量發生變化時,才重新計算 sum,否則 sum 的值保持不變。

useCallback 與 useMemo 類型,只不過 useCallback 返回的是一個函數,例如:

const fn = useCallback(() => {
    return ((1 + count) * count) / 2 + ' , ' + nowtime;
}, [count]);

2. 實現幾個自定義的 hook

在官方文檔里,實現了好友的在線與離線功能。這裏我們自己也學着實現幾個 hook。

2.1 獲取窗口變化的寬高

我們通過監聽resize事件來獲取實時獲取window窗口的寬高,對這個方法進行封裝后可以在生命周期結束前能自動解綁resize事件:

const useWinResize = () => {
    const [size, setSize] = useState({
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
    });
    const resize = useCallback(() => {
        setSize({
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
    })
    }, [])
    useEffect(() => {
        window.addEventListener('resize', resize);
        return () => window.removeEventListener('resize', resize);
    }, []);
    return size;
}

使用起來也非常方便:

const Home = () => {
    const {width, height} = useWinResize();

    return <div>
        <p>width: {width}</p>
        <p>height: {height}</p>
    </div>;
};

點擊鏈接可以查看demo演示。

2.2 定時器 useInterval

在前端中使用定時器時,通常要在組件生命周期結束前清除定時器,如果定時器的周期發生變化了,還要先清除定時器再重新按照新的周期來啟動。這種最常用的場景就是九宮格抽獎,用戶點擊開始抽獎后,先緩慢啟動,然後逐漸變快,接口返回中獎結果后,再開始減速,最後停止。

我們很容易想到用 useEffect 來實現這樣的一個 hook:

const useInterval = (callback, delay) => {
    useEffect(() => {
        if (delay !== null) {
            let id = setInterval(callback, delay);
            return () => clearInterval(id);
        }
    }, [delay]);
};

我們把這段代碼用到項目中試試:

const Home = () => {
    const [count, setCount] = useState(0);

    useInterval(() => {
        console.log(count);
        setCount(count + 1);
    }, 500);

    return <div > {
        count
    } < /div>;
};

可是這段運行后很奇怪,頁面從 0 到 1 后,就再也不變了, console.log(count) 的輸出表明代碼並沒有卡死,那麼問題出在哪兒了?

React 組件中的 props 和 state 是可以改變的, React 會重渲染它們且「丟棄」任何關於上一次渲染的結果,它們之間不再有相關性。

useEffect() Hook 也「丟棄」上一次渲染結果,它會清除上一次 effect 再建立下一個 effect,下一個 effect 鎖住新的 props 和 state,這也是我們第一次嘗試簡單示例可以正確工作的原因。

但 setInterval 不會「丟棄」。 它會一直引用老的 props 和 state 直到你把它換掉 —— 不重置時間你是無法做到的。

這裏就要用到這個 hook 了,我們把 callback 存儲到 ref 中,當 callback 更新時去更新 ref.current 的值:

const useInterval = (callback, delay) => {
    const saveCallback = useRef();

    useEffect(() => {
        // 每次渲染后,保存新的回調到我們的 ref 里
        saveCallback.current = callback;
    });

    useEffect(() => {
        function tick() {
            saveCallback.current();
        }
        if (delay !== null) {
            let id = setInterval(tick, delay);
            return () => clearInterval(id);
        }
    }, [delay]);
};

當我們使用新的 useInterval 時,發現就可以自增了,點擊查看樣例。

這裏我們使用一個變量來控制增加的速度:

const [count, setCount] = useState(0);
const [diff, setDiff] = useState(500);

useInterval(() => {
    setCount(count + 1);
}, diff);

return ( <div>
    <p> count: {count} </p>
    <p> diff: {diff}ms </p> 
    <p>
        <button onClick = {() => setDiff(diff - 50)}> 加快50ms </button> 
        <button onClick = {() => setDiff(diff + 50)} > 減慢50ms </button>
    </p>
</div>);

分別點擊兩個按鈕,可以調整count增加的速度。

3. 總結

使用react hook可以做很多有意思的事情,這裏我們也僅僅是舉幾個簡單的例子,後續我們也會更加深入了解hook的原理。

▼我是來騰訊的小小前端開發工程師,
長按識別二維碼關注,與大家共同學習、討論▼

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

【其他文章推薦】

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

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

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

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

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

PHP讀取Excel內的圖片

今天接到了一個從Excel內讀取圖片的需求,在網上查找了一些資料,基本實現了自己的需求,不過由於查到的一些代碼比較久遠,裏面一些庫方法已經被移除不存在了,所以不能直接移植到自己的項目里,需要稍加改動一下。

這裏介紹一下分別使用phpspreadsheet和PHPExcel擴展庫來實現讀取Excel內圖片的功能:

PHPSpreadsheet

首先安裝phpspreadsheet,由於線上服務器PHP版本是PHP5.6,所以需要安裝兼容PHP5.6的版本,這裏安裝1.8.2版本

composer require phpoffice/phpspreadsheet=1.8.2

然後就可以在項目里使用了

use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\IOFactory;

$imageFilePath = './uploads/imgs/'; //圖片本地存儲的路徑
if (!file_exists($imageFilePath)) { //如果目錄不存在則遞歸創建
    mkdir($imageFilePath, 0777, true);
}

try {
    $inputFileName = './files/1.xlsx';  //包含圖片的Excel文件
    $objRead = IOFactory::createReader('Xlsx');
    $objSpreadsheet = $objRead->load($inputFileName);
    $objWorksheet = $objSpreadsheet->getSheet(0);
    $data = $objWorksheet->toArray();

    foreach ($objWorksheet->getDrawingCollection() as $drawing) {
        list($startColumn, $startRow) = Coordinate::coordinateFromString($drawing->getCoordinates());
        $imageFileName = $drawing->getCoordinates() . mt_rand(1000, 9999);

        switch ($drawing->getExtension()) {
            case 'jpg':
            case 'jpeg':
                $imageFileName .= '.jpg';
                $source = imagecreatefromjpeg($drawing->getPath());
                imagejpeg($source, $imageFilePath . $imageFileName);
                break;
            case 'gif':
                $imageFileName .= '.gif';
                $source = imagecreatefromgif($drawing->getPath());
                imagegif($source, $imageFilePath . $imageFileName);
                break;
            case 'png':
                $imageFileName .= '.png';
                $source = imagecreatefrompng($drawing->getPath());
                imagepng($source, $imageFilePath, $imageFileName);
                break;
        }
        $startColumn = ABC2decimal($startColumn);
        $data[$startRow-1][$startColumn] = $imageFilePath . $imageFileName;
    }
    dump($data);die();
} catch (\Exception $e) {
    throw $e;
}

public function ABC2decimal($abc)
{
    $ten = 0;
    $len = strlen($abc);
    for($i=1;$i<=$len;$i++){
        $char = substr($abc,0-$i,1);//反向獲取單個字符

        $int = ord($char);
        $ten += ($int-65)*pow(26,$i-1);
    }
    return $ten;
}

可以看到,圖片被讀取並存到了本地服務器中

PHPExcel

PHPExcel實現從Excel文件里讀取內容的方法和phpspreadsheet幾乎一樣,畢竟phpspreadsheet就是在PHPExcel基礎上寫的,不過PHPExcel由於已經被廢棄了,所以建議優先使用phpspreadsheet,如果原來項目里一直使用了PHPExcel也可以繼續使用PHPExcel的方法

use PHPExcel_IOFactory;
use PHPExcel_Cell;

try {
    $inputFileName = './files/1.xlsx';
    $inputFileType = PHPExcel_IOFactory::identify($inputFileName);
    $objReader = PHPExcel_IOFactory::createReader($inputFileType);
    $objPHPExcel = $objReader->load($inputFileName);
} catch (\Exception $e) {
    die('加載文件發生錯誤:"'.pathinfo($inputFileName,PATHINFO_BASENAME).'": '.$e->getMessage());
}

$sheet = $objPHPExcel->getSheet(0);
$data = $sheet->toArray(); //該方法讀取不到圖片,圖片需單獨處理
$imageFilePath = './uploads/imgs/'; //圖片本地存儲的路徑
if (!file_exists($imageFilePath)) {
    mkdir($imageFilePath, 0777, true);
}

//處理圖片
foreach ($sheet->getDrawingCollection() as $img) {
    list($startColumn, $startRow) = PHPExcel_Cell::coordinateFromString($img->getCoordinates()); //獲取圖片所在行和列
    $imageFileName = $img->getCoordinates() . mt_rand(1000, 9999);
    switch($img->getExtension()) {
        case 'jpg':
        case 'jpeg':
            $imageFileName .= '.jpeg';
            $source = imagecreatefromjpeg($img->getPath());
            imagejpeg($source, $imageFilePath.$imageFileName);
            break;
        case 'gif':
            $imageFileName .= '.gif';
            $source = imagecreatefromgif($img->getPath());
            imagejpeg($source, $imageFilePath.$imageFileName);
            break;
        case 'png':
            $imageFileName .= '.png';
            $source = imagecreatefrompng($img->getPath());
            imagejpeg($source, $imageFilePath.$imageFileName);
            break;
    }
    $startColumn = ABC2decimal($startColumn);
    $data[$startRow-1][$startColumn] = $imageFilePath . $imageFileName;

}
var_dump($data);

public function ABC2decimal($abc)
{
    $ten = 0;
    $len = strlen($abc);
    for($i=1;$i<=$len;$i++){
        $char = substr($abc,0-$i,1);//反向獲取單個字符

        $int = ord($char);
        $ten += ($int-65)*pow(26,$i-1);
    }
    return $ten;
}

參考文章:

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

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

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

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

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

Non-local Neural Networks 原理詳解及自注意力機制思考

Paper:
 

Author:Xiaolong Wang, Ross Girshick, Abhinav Gupta, Kaiming He (CMU, FAIR)

1 創新點

這篇文章非常重要,個人認為應該算是cv領域裏面的自注意力機制的核心文章,語義分割裏面引入的各種自注意力機制其實都可以認為是本文的特殊化例子。分析本文的意義不僅僅是熟悉本文,而是了解其泛化思想。

不管是cv還是NLP任務,都需要捕獲長範圍依賴。在時序任務中,RNN操作是一種主要的捕獲長範圍依賴手段,而在CNN中是通過堆疊多個卷積模塊來形成大感受野。目前的卷積和循環算子都是在空間和時間上的局部操作,長範圍依賴捕獲是通過重複堆疊,並且反向傳播得到,存在3個不足:

(1) 捕獲長範圍依賴的效率太低;

(2) 由於網絡很深,需要小心的設計模塊和梯度;

(3) 當需要在比較遠位置之間來回傳遞消息時,這是局部操作是困難的.

故作者基於圖片濾波領域的非局部均值濾波操作思想,提出了一個泛化、簡單、可直接嵌入到當前網絡的非局部操作算子,可以捕獲時間(一維時序信號)、空間(圖片)和時空(視頻序列)的長範圍依賴。這樣設計的好處是:

(1) 相比較於不斷堆疊卷積和RNN算子,非局部操作直接計算兩個位置(可以是時間位置、空間位置和時空位置)之間的關係即可快速捕獲長範圍依賴,但是會忽略其歐式距離,這種計算方法其實就是求自相關矩陣,只不過是泛化的自相關矩陣

(2) 非局部操作計算效率很高,要達到同等效果,只需要更少的堆疊層

(3) 非局部操作可以保證輸入尺度和輸出尺度不變,這種設計可以很容易嵌入到目前的網絡架構中。

2 核心思想

由於我主要做2d圖片的CV需求,故本文的大部分分析都是針對圖片而言,而不是時間序列或者視頻序列。

本文的非局部操作算子是基於非局部均值操作而提出的,故很有必要解釋下非局部均值操作。我們在CNN或者傳統圖片濾波算子中涉及的都是局部操作,例如Sobel算子,均值濾波算子等等,其計算示意圖如下:

  圖片來源:吳恩達深度學習課程

可以看出每個位置的輸出值都是kernel和輸入的局部卷積計算得到的,而非局部均值濾波操作是: computes a weighted mean of all pixels in an image,非常簡單。核心思想是在計算每個像素位置輸出時候,不再只和鄰域計算,而是和圖像中所有位置計算相關性,然後將相關性作為一個權重表徵其他位置和當前待計算位置的相似度。可以簡單認為採用了一個和原圖一樣大的kernel進行卷積計算。下圖表示了高斯濾波,雙邊濾波和非局部均值處理過程:

 

可以看出對於待計算的中心紅色點,前兩種局部操作都是在鄰域計算,而非局部均值是和整個圖片進行計算的。但是實際上如果採用逐點計算方式,不僅計算速度非常慢,而且抗干擾能力不太好,故非局部均值操作是採用Block的思想,計算block和block之間的相關性。

 

可以看出,待計算的像素位置是p,故先構造block,然後計算其他位置block和當前block的相關性,可以看出q1和q2區域和q非常相似,故計算時候給予一個大權重,而q3給予一個小的權重。這樣的做法可以突出共性(關心的區域),消除差異(通常是噪聲)。

 
上圖可以看出非局部操作的優點,每一個例子中左圖是待計算像素點的位置,右圖是基於NL均值操作計算出來的權重分布圖,看(c)可以非常明顯看出,由於待計算點位置是在邊緣處,通過非局部操作后突出了全部邊緣。

上面的所有分析都是基於非局部操作來講的,但是實際上在深度學習時代,可以歸為自注意力機制Self-attention。在機器翻譯中,自我注意模塊通過關注所有位置並在嵌入空間中取其加權平均值來計算序列(例如,句子)中的位置處的響應,在CV中那就是通過關注圖片中(可以是特徵圖)所有位置並在嵌入空間中取其加權平均值來表示圖片中某位置處的響應。嵌入空間可以認為是一個更抽象的圖片空間表達,目的是匯聚更多的信息,提高計算效率。聽起來非常高級的樣子,到後面可以看出,是非常簡單的。

3 網絡結構

下面開始給出非局部操作的具體公式。首先在深度學習中非局部操作可以表達為:

 

 

 

i是輸出特徵圖的其中一個位置,通用來說這個位置可以是時間、空間和時空。j是所有可能位置的索引,x是輸入信號,可以是圖像、序列和視頻,通常是特徵圖。y是和x尺度一樣的輸出圖,f是配對計算函數,計算第i個位置和其他所有位置的相關性,g是一元輸入函數,目的是進行信息變換,C(x)是歸一化函數,保證變換前後整體信息不變。以上是一個非常泛化的公式,具體細節見下面。在局部卷積算子中,一般的  

由於f和g都是通式,故結合神經網絡特定,需要考慮其具體形式。
首先g由於是一元輸出,比較簡單,我可以採用1×1卷積,代表線性嵌入,其形式為:

 

對於f,前面我們說過其實就是計算兩個位置的相關性,那麼第一個非常自然的函數是Gaussian。

(1) Gaussian

 

對兩個位置進行點乘,然後通過指數映射,放大差異。

(2) Embedded Gaussian

 

前面的gaussian形式是直接在當前空間計算,而(2)更加通用,在嵌入空間中計算高斯距離。這裏:

   

 

 

前面兩個:  

 

 

仔細觀察,如果把C(x)考慮進去,那麼  

其實就是softmax形式,完整考慮是:

 

這個就是目前常用的位置注意力機制的表達式,所以說語義分割中大部分通道注意力機制都是本文的特殊化。

(3) Dot product

考慮一種最簡單的非局部操作形式:

 

其中C(x)=N,像素個數。可以看出(2) (3)的主要區別是是否含有激活函數softmax。

(4) Concatenation

參考 Relation Networks可以提出:

 

 

 

前面是基本的非局部操作算子,利用這些算子,下面開始構造成模塊。  

可以看出,上面構造成了殘差形式。上面的做法的好處是可以隨意嵌入到任何一個預訓練好的網絡中,因為只要設置W_z初始化為0,那麼就沒有任何影響,然後在遷移學習中學習新的權重。這樣就不會因為引入了新的模塊而導致預訓練權重無法使用

下面結合具體實例分析:

 

由於我們考慮的是圖片,故可以直接設置T=1,或者說不存在。首先網絡輸入是X= (batch, h, w, 1024) ,經過Embedded Gaussian中的兩個嵌入權重變換 , 得到(batch, h, w, 512), (batch, h, w, 512), 其實這裏的目的是降低通道數,減少計算量;然後分別對這兩個輸出進行reshape操作,變成(batch, hw, 512),后對這兩個輸出進行矩陣乘(其中一個要轉置),計算相似性,得到(batch, hw, hw),
然後在第2個維度即最後一個維度上進行softmax操作,得到(batch, hw, hw), 意這樣做就是通道注意力,相當於找到了當前圖片或特徵圖中每個像素與其他所有位置像素的歸一化相關性;然後將g也採用一樣的操作,先通道降維,然後reshape;然後和 (batch, hw, hw)進行矩陣乘,得到(batch, h, w, 512), 即將通道注意力機制應用到了所有通道的每張特徵圖對應位置上,本質就是輸出的每個位置值都是其他所有位置的加權平均值,通過softmax操作可以進一步突出共性。最後經過一個1×1卷積恢復輸出通道,保證輸入輸出尺度完全相同。

4 核心代碼實現

 

拷貝的代碼來源:

可以看出,具體實現非常簡單,就不細說了。

5 擴展

通讀全文,你會發現思路非常清晰,模塊也非常簡單。其背後的思想其實是自注意力機制的泛化表達,準確來說本文只提到了位置注意力機制(要計算位置和位置之間的相關性,辦法非常多)。

個人認為:如果這些自注意模塊的計算開銷優化的很小,那麼應該會成為CNN的基礎模塊。既然位置和位置直接的相關性那麼重要,那我是不是可以認為graph CNN才是未來?因為圖卷積網絡是基於像素點和像素點之間建模,兩者之間的權重是學習到的,性能肯定比這種自監督方式更好,後面我會寫文章分析。

本文設計的模塊依然存在以下的不足:

(1) 只涉及到了位置注意力模塊,而沒有涉及常用的通道注意力機制

(2) 可以看出如果特徵圖較大,那麼兩個(batch,hxw,512)矩陣乘是非常耗內存和計算量的,也就是說當輸入特徵圖很大存在效率底下問題,雖然有其他辦法解決例如縮放尺度,但是這樣會損失信息,不是最佳處理辦法。

 

 

6 實驗

Non-local Blocks的高效策略。我們設置Wg,,的channel的數目為x的channel數目的一半,這樣就形成了一個bottleneck,能夠減少一半的計算量。Wz再重新放大到x的channel數目,保證輸入輸出維度一致。 
還有一個subsampling的trick可以進一步使用,就是將(1)式變為:yi=1C(x^)∑∀jf(xi,x^j)g(x^j),其中x^x下採樣得到的(比如通過pooling),我們將這個方式在空間域上使用,可以減小1/4的pairwise function的計算量。這個trick並不會改變non-local的行為,而是使計算更加稀疏了。這個可以通過在圖2中的ϕg後面增加一個max pooling層實現。 
我們在本文中的所有non-local模塊中都使用了上述的高效策略。

6.1. 視頻分類模型

為了理解non-local networks的操作,我們在視頻分類任務上進行了一系列的ablation experiments。 
2D ConvNet baseline (C2D)。為了獨立開non-local nets中時間維度的影響vs 3D ConvNets,我們構造了一個簡單的2D baseline結構。 
Table 1給出了ResNet-50 C2D backbone。輸入的video clip是32幀,大小為224*224。Table 1中的所有卷積都是用的2D的kernel,即逐幀對輸入視頻進行計算。唯一和temporal有關的計算就是pooling,也就是說這個baseline模型簡單地在時間維度上做了一個聚合的操作。 

Inflated 3D ConvNet (I3D)。 Table 1中的C2D模型可以通過inflate的操作轉換成一個3D卷積的結構。具體地,一個2D k*k大小的kernel可以inflate成3D t*k*k大小的kernel,只要將其權重重複t次,再縮小t倍即可。

我們討論2種inflate的方式。一種是將residual block中的3*3的kernel inflate成3*3*3的,另一種是將residual block中的1*1的kernel inflate成3*1*1的。這兩種形式我們分別用I3D333I3D311表示。因為3D conv的計算量很大,我們只對每2個residual blocks中的1個kernel做inflate。對更多的kernel做inflate發現效果反而變差了。另外conv1層我們inflate成5*7*7。

Non-local network。 我們將non-local block插入到C2D或I3D中,就得到了non-local nets。我們研究了插入1,5,10個non-local blocks的情況,實現細節將在後面給出。

6.2 Non-local Network實現細節

Training。 我們的模型是在ImageNet上pretrain的,沒有特殊說明的話我們使用32幀的輸入。32幀是通過從原始長度的視頻中隨機選擇1個位置取出64個連續幀,然後每隔1幀取1幀得到的最終的32幀。spatial size是224*224大小,是將原始視頻rescale到短邊為[256,320]區間的隨機值,然後再random crop 224*224大小。我們在8卡GPU上進行訓練,每卡上有8 clips(也就是說總的batchsize是64 clips)。我們一共迭代了400k iterations,初始lr為0.01,然後每150k iterations lr下降1/10。momentum設為0.9,weight decay設為0.0001。dropout在global pooling層後面使用,dropout ratio設為0.5。 
我們finetune模型的時候 BN是打開的,這和常見的finetune ResNet的操作不同,它們通常是frozen BN。我們發現在我們的實驗中enable BN有利於減少過擬合。 
在最後一個1*1*1 conv層(表示Wz)的後面我們加了一個BN層,其他位置我們沒有增加BN。這個BN層的scale參數初始化為0,這是為了保證整個non-local block的初始狀態相當於一個identity mapping,這樣插入到任何預訓練網絡中在一開始都能保持其原來的表現。

Inference。 推理時,在我們將視頻rescale到短邊256進行推理。時域上我們從整個視頻中平均採樣10個clip,然後分別計算他們的softmax scores,最後做平均得到整個視頻的score。

6.3 實驗

關於視頻分類的實驗,我們在Kinetics上進行全面的實驗,另外也給出了Charades上的實驗結果,显示出我們的模型的泛化性。這裏只給出Kinetics上的結果,更多的請看原文。 
Table 2給出了ablation results。 

f的表現形式的影響。表2a比較了不同的non-local block的形式插入到C2D得到的結果(插入位置在res4的最後一個residual block之前)。發現即使只加一個non-local block都能得到~1%的提高。 
有意思的是不同的non-local block的形式效果差不多,說明是non-local block的結構在起作用,而對具體的表達方式不敏感。本文後面都採用embedded Gaussian進行實驗,因為這個版本有softmax,可以直接給出[0,1]之間的scores。

哪個階段加入non-local blocks?表2b比較了一個non-local block加在resnet的不同stage的效果,具體加在不同stage的最後一個residual block之前。發現在res2,res3,res4層上加non-local block效果類似,加在res5上效果稍差。這個的可能原因是res5的spatial size比較小,只有7*7,可能無法提供精確的spatial信息了。

加入更多的non-local blocks。表2c給出了加入更多non-local block的結果,我們在resnet-50上加1 block(在res4),加5 block(3個在res4,2個在res3,每隔1個residual block加1個non-local block),加10 block(在res3和res4每個residual block都加non-local block)。在resnet101的相同位置加block。發現更多non-local block通常有更好的結果。我們認為這是因為更多的non-local block能夠捕獲長距離多次轉接的依賴。信息可以在時空域上距離較遠的位置上進行來回傳遞,這是通過local models無法實現的。 
另外需要提到的是增加non-local block得到的性能提升並不只是因為它給base model增加了深度。為了說明這一點,表2c中resnet50 5blocks能夠達到73.8的acc,而resnet101 baseline是73.1,同時resnet50 5block只有resnet101的約70%的參數量和80%的FLOPs。說明non-local block得到的性能提升並不只是因為它增加了深度。

時空域上做non-local。我們的方法也可以處理時空域的信息,這一特性非常好,在視頻中相關的物體可能出現在較遠的空間和較長的時間,它們的相關性也可以被我們的模型捕獲。表2d給出了在時間維度,空間維度和時空維度分別做non-local的結果。僅在空間維度上做就相當於non-local的依賴僅在單幀圖像內部發生,也就是說在式(1)上僅對index i的相同幀的index j做累加。僅在時間維度上做也類似。表2d显示只做時間維度或者只做空間維度的non-local,都比C2D baseline要好,但是沒有同時做時空維度的效果好。

Non-local net vs. 3D ConvNet。表2e比較了我們的non-local C2D版本和inflated 3D ConvNets的性能。Non-local的操作和3D conv的操作可以看成是將C2D推廣到時間維度的兩種方式。 
表2e也比較了param的數量,FLOPs等。我們的non-local C2D模型比I3D更加精確(75.1 vs 74.4),並且有更小的FLOPs(1.2x vs 1.5x)。說明單獨使用時non-local比3D conv更高效。

Non-local 3D ConvNet. 不管上面的比較,其實non-local操作和3D conv各有各的優點:3D conv可以對局部依賴進行建模。表2f給出了在I3D311上插入5個non-local blocks的結果。發現NL I3D都能夠在I3D的基礎上提升1.6個點的acc,說明了non-local和3D conv是可以相互補充的。

更長的輸入序列。 最後我們也實驗了更長輸入序列的情況下模型的泛化性。輸入clip包含128幀連續幀,沒有做下採樣,是一般情況下取的32幀的4倍長度。為了將這個模型放入顯存中,每個GPU上只能放下2 clips。因為這麼小的batchsize的原因,我們freeze所有的BN層。我們從32幀訓練得到的模型作為初始化模型,然後用128幀進行finetune,使用相同的iterations數目(雖然batchsize減小了),初始lr為0.0025,其他設置和之前保持一致。 
表2g給出了128幀的實驗結果,和表2f的32幀的結果相比,所有模型都表現得更好,說明我們的模型在長序列上的效果也很好。

和state-of-the-art的比較。表3給出了Kinetics上各個方法的結果。 

 

 

來源: 

       https://www.jianshu.com/p/a9771abedf50

       https://blog.csdn.net/u010158659/article/details/78635219

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

【其他文章推薦】

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

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

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

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

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

高性能Web動畫和渲染原理系列(5)合成層的生成條件和陷阱

目錄

示例代碼託管在:

博客園地址:

華為雲社區地址:

一. 硬件加速相關的幾個概念

之前介紹到了RenderLayer渲染層的概念,在涉及到硬件加速的話題時,出現了很多新的概念,參考《Webkit技術內幕》一書的介紹總結如下:

Webkit決定將哪些RenderLayer對象組合在一起,形成一個有後端存儲的新層,這一新層不久後會用於合成,這裏稱之為合成層CompositingLayer)。每一個合成層都會對應一個或多個後端存儲,由RenderLayerBacking類進行統一管理,後端存儲空間使用GraphicsLayer來表示,也就是說RenderLayerBacking管理着一個或多個與對應的合成層有關的GraphicsLayer

筆者旁白:對於渲染過程來說,只需要理解這裏形成了新的CompositingLayer合成層就可以了,其他的層概念基本都是用於實現對CompositingLayer功能支持的,概念數量太多對於理解宏觀流程是一大障礙。

二. 合成層的生成條件

顯式提升

合成層的處理是依賴於硬件加速的,但是GPU的存儲空間有限最好不要濫用,過多的合成層有可能還會造成相反的效果,所以瀏覽器只會將滿足下列任意條件的RenderLayer提升為CompositingLayer

  • 具有CSS3D屬性或CSS透視效果
  • 包含的RenderObject節點表示的是使用硬件加速的視頻解碼技術的HTML5video元素
  • 包含的RenderObject節點包含使用了硬件加速的Canvas2DWebGL技術
  • 使用了CSS透明效果或CSS變形動畫
  • 使用了硬件加速的CSS Filters技術(有的文獻中表示filters屬性並沒有提升為合成層的效果,推測只有一部分filters濾鏡效果需要使用硬件加速,並非所有)
  • 使用了剪裁Clip或者反射Reflection,並且它的後代中包含一個合成層
  • 擁有一個Z坐標比自己小的兄弟節點,且該節點是一個合成層。

上面的規則里我們最熟悉的可能就是transform:translateZ(0)或者在關鍵幀動畫的定義中改變transformopacity屬性。當然,隨着技術的演進,上面的規則並不一定全面Chromium官網提供的開發者演講PPT中也對提升的理由進行了相關的描述:

你可以在Chrome調試面板的【Layers】功能中對分層相關的結果進行檢視,查看哪些層進行了提升以及被提升的具體原因,避免出現與自己意圖相悖的層提升:

隱式提升

RenderLayer滿足特殊條件時被提升為CompositingLayer對開發者而言是比較可控的。但除此之外,在瀏覽器的合成階段,還存在隱式合成的狀況,一些特定的場景中出現的合成層並不是開發者主觀期望的。

隱式合成主要發生在元素出現重疊時,層級較低的元素如果被提升為合成層后,最終合成的結果就可能出現在原來比自己層級更高的元素之上,從而出現錯誤的堆疊關係,為了糾正這種關係,只能讓原本層級高(但是並不用提升為合成層的元素)發生提升也成為合成層。例如下面的代碼:

<div style="position:absolute;height:200px;width:200px;background-color: #DA5961;"></div>
<div style="position:absolute;left:30px;top:50px;height:200px;width:200px;background-color: #3498db;"></div>
<div style="position:absolute;left:60px;top:100px;height:200px;width:200px;background-color: #1abc9c;"></div>

三個div盒子堆疊在一起,可以看到它們都繪製在同一個層上(這裏的層並不與RenderLayer對應,畢竟它只是一个中間態的樹結構):

此時如果為最底下的紅色矩形添加transform:translateZ(0)屬性將其提升為合成層后,為了保證正確的堆疊關係,藍色和綠色的矩形就會被提升為合成層,代碼如下:

<div style="transform:translateZ(0);position:absolute;height:200px;width:200px;background-color: #DA5961;"></div>
<div style="position:absolute;left:30px;top:50px;height:200px;width:200px;background-color: #3498db;"></div>
<div style="position:absolute;left:60px;top:100px;height:200px;width:200px;background-color: #1abc9c;"></div>

藍色和綠色的矩形並沒有形成獨立的合成層,而是被壓縮在同一個合成層中:

從上圖中的細節信息中可以看到,提升的原因是layerFotSquashingContent,也就是為了保證堆疊順序的正確,用一個單獨的合成層來將受到影響的元素收集在一起,既保證堆疊順序,也避免在期望之外生成過多的合成層。如果調整綠色矩形的位置,就可以看到,當視覺上不存在覆蓋時,它就不需要提升了:

BUT!!!還沒完,最坑的部分來了,如果此時給藍色的div加上一點動畫,你會發現綠色div又被提升到了獨立的合成層上,儘管他們之間並沒有重疊區,但還是被提升了:

從圖中的合成原因可以看到:它可能和一個相鄰的合成層元素髮生交疊,所以被提升了。沒錯,就是“可能”。Fouber這篇中的示例更加詳細,子元素引發父元素提升,父元素又引發兄弟元素提升。

三. 硬件加速的權衡

所有的技術方案都是有代價的,這是亘古不變的道理,合成層的好處很明顯,GPUCPU的處理速度快很多,觸發repaint重繪時,只需要重繪獨立的層,然後重新合成即可,不需要重繪整個畫面。但它也存在一些弊端:首先是數據傳輸的問題,CPUGPU的關係就好比客戶端和服務端一樣,它們的協作是需要傳輸數據的,當層的數量達到一定量級后,傳輸的速度就會影響到整體的處理效率,進而導致在一些低中端設備上出現閃爍等現象;另外,每個合成層都具會佔據額外的內存,這個數量通常比開發者以為的要大的多,尤其是在移動端這種硬件資源受限制的場景中,過量的內存使用分分鐘就會讓應用崩潰。

四. 動畫實現的一些建議

  1. 使用transform實現動畫

    這可能是我們編寫動畫時聽到最多的建議了。例如使用lefttop來實現位置動畫時,絕對定位的元素會形成RenderLayer,但是卻不符合提升為CompositingLayer的條件,所以動畫元素就會和Document處在同一個合成層里,持續進行的動畫就會導致Document這一層(通常是正常文檔流這一層,包含了大量的流式布局的元素)不斷重繪,從而影響渲染效率,如果能夠讓動畫的節點放到單獨的合成層里,就可以避免這種大規模重繪,並藉助GPU加速合成的能力加速整個渲染流程。

  2. 排查被動提升的情形

    被動提升主要是指“兄弟元素相對層級低於自己但卻是一個合成層”的情形以及“發生堆疊遮擋的幾個元素中層級較低的元素被提升為合成層”的狀況。一般的解決方案是主動提升動畫元素的z-index值或者調整文檔結構中節點的先後順序,當然所有的結果都還需要通過測試來確認。

  3. 考慮合成層的空間佔用

    合成層的後端存儲是渲染后的像素點數據,它的體積可能會非常大,在使用大屏圖片時需要盡可能將其壓縮至視覺可接受的範圍而不能一味追求高清,對於純色的元素,可以使用較小的尺寸並藉助transform:scale來放大至需要的尺寸。

  4. 實測為王

    任何方案都只是一種思路,必須通過在真實環境測試驗證才能確認其有效性。

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

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

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

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

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

高通將研發動態無線充電技術 有望實現邊行駛邊充電

高通近日表示他們正在研發一種動態無線充電技術,可以緩解電動汽車行駛一半沒電的這種困擾,實現邊行駛汽車邊充電。

2014年的時候高通在首屆電動汽車方程式錦標賽中推出了Halo車用無線充電系統(靜態無線充電技術)。這個名為Halo的車用無線充電系統總共包含四個部分:供電組件、充電板、車載接收器和車載控制器。而它的原理是利用磁共振效應實現地面充電板與電動車充電板之間的電量傳輸,從而實現對純電動汽車或是混合型動力汽車的動力電池組進行非接觸式充電。

在利用Halo車用無線充電系統給汽車進行充電時的操作也相對簡單, Halo車用無線充電系統也成為靜態車用無線充電系統,高通表示在充電時只需將車輛停放在充電裝置上即可實現充電任務, Halo 車用無線充電系統的充電效率可以高達90%,堪稱絕對的高效,遠遠超過現階段的線纜充電裝備的充電效率。據悉Halo車用無線充電系統的功率可達20kW,一個配備85kWh電池組的特斯拉 MODEL S P85利用Halo車用無線充電系統充電整個過程也只需要不到五個小時的時間即可完成。

在談到Halo車用無線充電系統時,高通總裁Aberle表示這種技術將在未來的兩到三年即可問世,應用到當下的電動汽車中。目前高通表示他們正在和汽車製造商戴姆勒-克萊斯勒合作,準備將這種靜態無線充電系統應用到他們的汽車中。

而未來要推出的這種動態無線充電技術屬於Halo車用無線充電系統即靜態無線充電技術的升級版,為了實現這種邊行駛邊充電的目標,未來他們將會吧無線充電裝置安裝在路面地下,當電動汽車行駛經過含有充電裝置的路段時即可實現邊行駛邊充電。但由於這種技術需要對路面進行徹底的翻修重置,需要高昂的專案費用,因而短時間內還是不能實現邊行駛邊充電的目標。

其實高通並不是第一個因為這種原因拉長專案進程的公司,之前因特爾曾計畫在機場、火車站、餐館等公共場所為筆記本等智慧設備修建無線充電系統,但因為對基礎設施改造的工程量巨大和所需的項目費昂貴,項目始終進展緩慢,暫時毫無進展。

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

【其他文章推薦】

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

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

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

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

德國政府斥資20億歐元發力電動汽車

德國總理默克爾日前召見了幾位德國主要車企高管,討論電動汽車補貼政策問題。在此之前,德國經濟部長(西格馬•加布里爾)也承諾要拿出20億歐元來鼓勵消費者購買電動汽車,這些資金將主要用於消費者購買補貼、充電網路建設及鼓勵政府用車等方面。

具體政策,德國政府還要和汽車製造商繼續對話,最快也要等到3月才會制定完成。

儘管默克爾與汽車行業老闆們的會談還未達成一致,但對電動汽車補助政策的制定還是能起到極強的推動作用。根據路透社最新報導,“默克爾團隊考慮推行一項高達5000歐元的補助給電動車車主,但德國財長朔伊布勒表示反對。”

其實,從2009年以來,德國就一直通過鼓勵措施來推動新能源汽車的發展。2009年,德國內閣通過了具有重要戰略意義的電動汽車發展綱領性檔《國家電動汽車發展計畫》,該計畫確定了發展電動汽車的主要技術路線,以及德國發展電動汽車的目標;2011年,德國政府發佈了電動汽車政府行動計畫,提出將2013年年底前投入電動汽車研發的政府資助由之前規劃的10億歐元提高到20億歐元,並將推動建設3—4個國家級大規模示範專案;更在2014年通過了一項關於電動汽車的新法律,旨在提高德國全境電動汽車的使用率,該項法律從經濟補貼、擁有進入限制區域的權利、停車優惠、特殊標誌車牌、車道使用等多方面極大的保障了新能源汽車消費者的利益。而今年德國又將斥20億歐元出臺補貼政策,則更能看出德國政府對新能源汽車產業的重視。

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

【其他文章推薦】

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

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

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

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?