降低產銷流程碳排 IKEA設二手家具店

摘錄自2020年11月10日公視新聞網報導

瑞典家具品牌IKEA,這個月開了第一間賣二手家具的示範店面,他們把回收而來的家具重新整修,希望能夠延長傢俱的使用壽命。

客戶在上千坪的店面參觀選購,看起來跟全球其他400多間IKEA店面沒有兩樣。不過在另一個角落,卻有幾位員工把舊傢俱堆放在一起,有的負責表面清潔,有的檢查各部位零件是否牢固,以及外表是否有髒污或刮痕。

標榜產品製程環保高標準的IKEA,為了在2030年之前,達成進一步降低產銷流程碳排放的目標,今年下半年展開一系列回收二手家具再降價轉賣的試辦計畫。首先是在9月間,位於英國愛丁堡格拉斯哥的分店,試辦接受消費者把以前購買,後來卻不敷所需的IKEA傢俱,賣回給業者。由於試辦相當成功,11月開始還要擴大到全英國,以及鄰近的愛爾蘭。

IKEA行銷經理夫亞爾說:「我們IKEA的目標,是(讓產銷成為)完整的循環,在2030年前達成兼顧永續跟獲利。從生產到如何做好售後服務,延長產品的使用壽命。」

氣候變遷
循環經濟
國際新聞
減碳
二手

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

【Spring註解開發】組件註冊-使用@Configuration和@Bean給容器中註冊組件

寫在前面

在之前的Spring版本中,我們只能通過寫XML配置文件來定義我們的Bean,XML配置不僅繁瑣,而且很容易出錯,稍有不慎就會導致編寫的應用程序各種報錯,排查半天,發現是XML文件配置不對!另外,每個項目編寫大量的XML文件來配置Spring,也大大增加了項目維護的複雜度,往往很多個項目的Spring XML文件的配置大部分是相同的,只有很少量的配置不同,這也造成了配置文件上的冗餘。

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

Spring IOC和DI

在Spring容器的底層,最重要的功能就是IOC和DI,也就是控制反轉和依賴注入。

IOC:控制反轉,將類的對象的創建交給Spring類管理創建。
DI:依賴注入,將類裏面的屬性在創建類的過程中給屬性賦值。
DI和IOC的關係:DI不能單獨存在,DI需要在IOC的基礎上來完成。

在Spring內部,所有的組件都會放到IOC容器中,組件之間的關係通過IOC容器來自動裝配,也就是我們所說的依賴注入。接下來,我們就使用註解的方式來完成容器組件的註冊、管理及依賴、注入等功能。

在介紹使用註解完成容器組件的註冊、管理及依賴、注入等功能之前,我們先來看看使用XML文件是如何注入Bean的。

通過XML文件注入JavaBean

首先,我們在工程的io.mykit.spring.bean包下創建Person類,作為測試的JavaBean,代碼如下所示。

package io.mykit.spring.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;

/**
 * @author binghe
 * @version 1.0.0
 * @description 測試實體類
 */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Person implements Serializable {
    private static final long serialVersionUID = 7387479910468805194L;
    private String name;
    private Integer age;
}

接下來,我們在工程的resources目錄下創建Spring的配置文件beans.xml,通過beans.xml文件將Person類注入到Spring的IOC容器中,配置如下所示。

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

    <bean id = "person" class="io.mykit.spring.bean.Person">
        <property name="name" value="binghe"></property>
        <property name="age" value="18"></property>
    </bean>
</beans>

到此,我們使用XML方式注入JavaBean就配置完成了。接下來,我們創建一個SpringBeanTest類來進行測試,這裏,我使用的是Junit進行測試,測試方法如下所示。

@Test
public void testXmlConfig(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    Person person = (Person) context.getBean("person");
    System.out.println(person);
}

運行testXmlConfig()方法,輸出的結果信息如下。

Person(name=binghe, age=18)

從輸出結果中,我們可以看出,Person類通過beans.xml文件的配置,已經注入到Spring的IOC容器中了。

通過註解注入JavaBean

通過XML文件,我們可以將JavaBean注入到Spring的IOC容器中。那使用註解又該如何實現呢?別急,其實使用註解比使用XML文件要簡單的多,我們在項目的io.mykit.spring.plugins.register.config包下創建PersonConfig類,並在PersonConfig類上添加@Configuration註解來標註PersonConfig類是一個Spring的配置類,通過@Bean註解將Person類注入到Spring的IOC容器中。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author binghe
 * @version 1.0.0
 * @description 以註解的形式來配置Person
 */
@Configuration
public class PersonConfig {
     @Bean
    public Person person(){
        return new Person("binghe001", 18);
    }
}

沒錯,通過PersonConfig類我們就能夠將Person類注入到Spring的IOC容器中,是不是很Nice!!主要我們在類上加上@Configuration註解,並在方法上加上@Bean註解,就能夠將方法中創建的JavaBean注入到Spring的IOC容器中。

接下來,我們在SpringBeanTest類中創建一個testAnnotationConfig()方法來測試通過註解注入的Person類,如下所示。

@Test
public void testAnnotationConfig(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
    Person person = context.getBean(Person.class);
    System.out.println(person);
}

運行testAnnotationConfig()方法,輸出的結果信息如下所示。

Person(name=binghe001, age=18)

可以看出,通過註解將Person類注入到了Spring的IOC容器中。

到這裏,我們已經明確,通過XML文件和註解兩種方式都可以將JavaBean注入到Spring的IOC容器中。那麼,使用註解將JavaBean注入到IOC容器中時,使用的bean的名稱是什麼呢? 我們可以在testAnnotationConfig()方法中添加如下代碼來獲取Person類型下的註解名稱。

//按照類型找到對應的bean名稱數組
String[] names = context.getBeanNamesForType(Person.class);
Arrays.stream(names).forEach(System.out::println);

完整的testAnnotationConfig()方法的代碼如下所示。

@Test
public void testAnnotationConfig(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
    Person person = context.getBean(Person.class);
    System.out.println(person);

    //按照類型找到對應的bean名稱數組
    String[] names = context.getBeanNamesForType(Person.class);
    Arrays.stream(names).forEach(System.out::println);
}

運行testAnnotationConfig()方法輸出的結果信息如下所示。

Person(name=binghe001, age=18)
person

那這裏的person是啥?我們修改下PersonConfig類中的person()方法,將person()方法修改成person01()方法,如下所示。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author binghe
 * @version 1.0.0
 * @description 以註解的形式來配置Person
 */
@Configuration
public class PersonConfig {

    @Bean
    public Person person01(){
        return new Person("binghe001", 18);
    }
}

此時,我們再次運行testAnnotationConfig()方法,輸出的結果信息如下所示。

Person(name=binghe001, age=18)
person01

看到這裏,大家應該有種豁然開朗的感覺了,沒錯!!使用註解注入Javabean時,bean在IOC中的名稱就是使用@Bean註解標註的方法名稱。我們可不可以為bean單獨指定名稱呢?那必須可以啊!只要在@Bean註解中明確指定名稱就可以了。比如下面的PersonConfig類的代碼,我們將person01()方法上的@Bean註解修改成@Bean(“person”)註解,如下所示。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author binghe
 * @version 1.0.0
 * @description 以註解的形式來配置Person
 */
@Configuration
public class PersonConfig {

    @Bean("person")
    public Person person01(){
        return new Person("binghe001", 18);
    }
}

此時,我們再次運行testAnnotationConfig()方法,輸出的結果信息如下所示。

Person(name=binghe001, age=18)
person

可以看到,此時,輸出的JavaBean的名稱為person。

結論:我們在使用註解方式向Spring的IOC容器中注入JavaBean時,如果沒有在@Bean註解中明確指定bean的名稱,就使用當前方法的名稱來作為bean的名稱;如果在@Bean註解中明確指定了bean的名稱,則使用@Bean註解中指定的名稱來作為bean的名稱。

好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

寫在最後

如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring註解驅動開發。公眾號回復“spring註解”關鍵字,領取Spring註解驅動開發核心知識圖,讓Spring註解驅動開發不再迷茫。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

“造輪運動”之 ORM框架系列(三)~ 乾貨呈上

   這一趴裏面,我就來正式介紹一下CoffeeSQL的乾貨。

    首先要給CoffeeSQL來個定位:最開始就是由於本人想要了解ORM框架內部的原理,所以就四處搜尋有關的博客與學習資料,就是在那個夏天,在博客園上看到了一位7tiny老哥的博客(https://www.cnblogs.com/7tiny/p/9575230.html),裏面基本上包含了我所想要了解的全套內容。幸得7tiny老哥的博客和代碼都寫的非常清晰,所以沒花多久時間就看完了源碼並洞悉其中奧妙,於是自己就有個想法:在7tiny的開源代碼的基礎上歸納自己的ORM框架。於是出於學習與自我使用的目的就開始了擴展功能的道路,到現在為止,自己已經在公司的一個項目中用上了,效果還不錯。在這裏也感謝7tiny老哥對我提出的一些問題及時的回復和指導,真心感謝。

一、框架模塊介紹

  根據CoffeeSQL的功能模塊組成來劃分,可以分為:數據庫連接管理、SQL命令執行入口、SQL命令生成器、SQL查詢引擎、ORM緩存機制、實體數據驗證 這六個部分,CoffeeSQL的操作入口與其他的ORM框架一樣,都是以數據庫上下文(DBContext)的方式進行操作。整體結構圖如下:

 

下面就大致地介紹一下每一個模塊的具體功能與實現的思路:

1、數據庫連接管理(DBConnectionManagement)

   數據庫連接的管理實際上就是對數據庫連接字符串與其對應的數據庫連接對象的管理機制,它可以保證在進行一主多從的數據庫部署時ORM幫助我們自動地切換連接的數據庫,而且還支持 <最小使用>與 <輪詢>兩種數據庫連接切換策略。

 

2、SQL命令執行入口(QueryExecute)

   QueryExecute是CoffeeSQL生成的所有sql語句執行的入口,執行sql語句並返回結果,貫穿整個CoffeeSQL最核心的功能就是映射sql查詢結果到實體,這裏採用的是構建表達式樹的技術,性能大大優於反射獲取實體的方式,具體的兩者速度對比的實驗在7tiny的博客中有詳細介紹,大家可以移步觀看(https://www.cnblogs.com/7tiny/p/9861166.html),在我的博客(https://www.cnblogs.com/MaMaNongNong/p/12173620.html)中我使用表達式樹的技術造了個簡練版的OOM框架。

   這裏貼出核心代碼,方便查看:

   

  1     /// <summary>
  2     /// Auto Fill Adapter
  3     /// => Fill DataRow to Entity
  4     /// </summary>
  5     public class EntityFillAdapter<Entity>
  6     {
  7         private static readonly Func<DataRow, Entity> funcCache = GetFactory();
  8 
  9         public static Entity AutoFill(DataRow row)
 10         {
 11             return funcCache(row);
 12         }
 13 
 14         private static Func<DataRow, Entity> GetFactory()
 15         {
 16             #region get Info through Reflection
 17             var entityType = typeof(Entity);
 18             var rowType = typeof(DataRow);
 19             var convertType = typeof(Convert);
 20             var typeType = typeof(Type);
 21             var columnCollectionType = typeof(DataColumnCollection);
 22             var getTypeMethod = typeType.GetMethod("GetType", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string) }, null);
 23             var changeTypeMethod = convertType.GetMethod("ChangeType", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(object), typeof(Type) }, null);
 24             var containsMethod = columnCollectionType.GetMethod("Contains");
 25             var rowIndexerGetMethod = rowType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, new[] { new ParameterModifier(1) });
 26             var columnCollectionIndexerGetMethod = columnCollectionType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(int) }, new[] { new ParameterModifier(1) });
 27             var entityIndexerSetMethod = entityType.GetMethod("set_Item", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(string), typeof(object) }, null);
 28             var properties = entityType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
 29             #endregion
 30 
 31             #region some Expression class that can be repeat used
 32             //DataRow row
 33             var rowDeclare = Expression.Parameter(rowType, "row");
 34             //Student entity
 35             var entityDeclare = Expression.Parameter(entityType, "entity");
 36             //Type propertyType
 37             var propertyTypeDeclare = Expression.Parameter(typeof(Type), "propertyType");
 38             //new Student()
 39             var newEntityExpression = Expression.New(entityType);
 40             //row == null
 41             var rowEqualnullExpression = Expression.Equal(rowDeclare, Expression.Constant(null));
 42             //row.Table.Columns
 43             var rowTableColumns = Expression.Property(Expression.Property(rowDeclare, "Table"), "Columns");
 44             //int loopIndex
 45             var loopIndexDeclare = Expression.Parameter(typeof(int), "loopIndex");
 46             //row.Table.Columns[loopIndex].ColumnName
 47             var columnNameExpression = Expression.Property(Expression.Call(rowTableColumns, columnCollectionIndexerGetMethod, loopIndexDeclare), "ColumnName");
 48             //break;
 49             LabelTarget labelBreak = Expression.Label();
 50             //default(Student)
 51             var defaultEntityValue = Expression.Default(entityType);
 52             #endregion
 53 
 54             var setRowNotNullBlockExpressions = new List<Expression>();
 55                         
 56             #region entity = new Student();loopIndex = 0;
 57             setRowNotNullBlockExpressions.Add(Expression.Assign(entityDeclare, newEntityExpression));
 58             setRowNotNullBlockExpressions.Add(Expression.Assign(loopIndexDeclare, Expression.Constant(0)));
 59 
 60             #endregion
 61 
 62             #region loop Fill DataRow's field to Entity Indexer
 63             /*
 64              * while (true)
 65              * {
 66              *     if (loopIndex < row.Table.Columns.Count)
 67              *     {
 68              *         entity[row.Table.Columns[loopIndex].ColumnName] = row[row.Table.Columns[loopIndex].ColumnName];
 69              *         loopIndex++;
 70              *     }
 71              *     else break;
 72              * } 
 73              */
 74 
 75             setRowNotNullBlockExpressions.Add(
 76 
 77                 Expression.Loop(
 78                     Expression.IfThenElse(
 79                         Expression.LessThan(loopIndexDeclare, Expression.Property(rowTableColumns, "Count")),
 80                         Expression.Block(
 81                             Expression.Call(entityDeclare, entityIndexerSetMethod, columnNameExpression, Expression.Call(rowDeclare, rowIndexerGetMethod, columnNameExpression)),
 82                             Expression.PostIncrementAssign(loopIndexDeclare)
 83                         ),
 84                         Expression.Break(labelBreak)
 85                     ),
 86                     labelBreak
 87                 )
 88             );
 89             #endregion
 90 
 91             #region assign for Entity property
 92             foreach (var propertyInfo in properties)
 93             {
 94                 var columnAttr = propertyInfo.GetCustomAttribute(typeof(ColumnAttribute), true) as ColumnAttribute;
 95 
 96                 // no column , no translation
 97                 if (null == columnAttr) continue;
 98 
 99                 if (propertyInfo.CanWrite)
100                 {
101                     var columnName = Expression.Constant(columnAttr.GetName(propertyInfo.Name), typeof(string));
102 
103                     //entity.Id
104                     var propertyExpression = Expression.Property(entityDeclare, propertyInfo);
105                     //row["Id"]
106                     var value = Expression.Call(rowDeclare, rowIndexerGetMethod, columnName);
107                     //default(string)
108                     var defaultValue = Expression.Default(propertyInfo.PropertyType);
109                     //row.Table.Columns.Contains("Id")
110                     var checkIfContainsColumn = Expression.Call(rowTableColumns, containsMethod, columnName);
111                     //!row["Id"].Equals(DBNull.Value)
112                     var checkDBNull = Expression.NotEqual(value, Expression.Constant(System.DBNull.Value));
113                     
114                     var propertyTypeName = Expression.Constant(propertyInfo.PropertyType.ToString(), typeof(string));
115 
116                     /*
117                      * if (row.Table.Columns.Contains("Id") && !row["Id"].Equals(DBNull.Value))
118                      * {
119                      *     propertyType = Type.GetType("System.String");
120                      *     entity.Id = (string)Convert.ChangeType(row["Id"], propertyType);
121                      * }
122                      * else
123                      *     entity.Id = default(string);
124                      */
125                     setRowNotNullBlockExpressions.Add(
126 
127                         Expression.IfThenElse(
128                             Expression.AndAlso(checkIfContainsColumn, checkDBNull),
129                             Expression.Block(
130                                 Expression.Assign(propertyTypeDeclare, Expression.Call(getTypeMethod, propertyTypeName)),
131                                 Expression.Assign(propertyExpression, Expression.Convert(Expression.Call(changeTypeMethod, value, propertyTypeDeclare), propertyInfo.PropertyType))
132                             ),
133                             Expression.Assign(propertyExpression, defaultValue)
134                         )
135                     );
136                 }
137             }
138 
139             #endregion
140 
141             var checkIfRowIsNull = Expression.IfThenElse(
142                 rowEqualnullExpression,
143                 Expression.Assign(entityDeclare, defaultEntityValue),
144                 Expression.Block(setRowNotNullBlockExpressions)
145             );
146 
147             var body = Expression.Block(
148 
149                 new[] { entityDeclare, loopIndexDeclare, propertyTypeDeclare },
150                 checkIfRowIsNull,
151                 entityDeclare   //return Student;
152             );
153 
154             return Expression.Lambda<Func<DataRow, Entity>>(body, rowDeclare).Compile();
155         }
156     }
157 
158     #region
159     //public class Student : EntityDesign.EntityBase
160     //{
161     //    [Column]
162     //    public string Id { get; set; }
163 
164     //    [Column("StudentName")]
165     //    public string Name { get; set; }
166     //}
167     ////this is the template of "GetFactory()" created.
168     //public static Student StudentFillAdapter(DataRow row)
169     //{
170     //    Student entity;
171     //    int loopIndex;
172     //    Type propertyType;
173 
174     //    if (row == null)
175     //        entity = default(Student);
176     //    else
177     //    {
178     //        entity = new Student();
179     //        loopIndex = 0;
180 
181     //        while (true)
182     //        {
183     //            if (loopIndex < row.Table.Columns.Count)
184     //            {
185     //                entity[row.Table.Columns[loopIndex].ColumnName] = row[row.Table.Columns[loopIndex].ColumnName];
186     //                loopIndex++;
187     //            }
188     //            else break;
189     //        }
190 
191     //        if (row.Table.Columns.Contains("Id") && !row["Id"].Equals(DBNull.Value))
192     //        {
193     //            propertyType = Type.GetType("System.String");
194     //            entity.Id = (string)Convert.ChangeType(row["Id"], propertyType);
195     //        }
196     //        else
197     //            entity.Id = default(string);
198 
199     //        if (row.Table.Columns.Contains("StudentName") && !row["StudentName"].Equals(DBNull.Value))
200     //        {
201     //            propertyType = Type.GetType("System.String");
202     //            entity.Name = (string)Convert.ChangeType(row["StudentName"], propertyType);
203     //        }
204     //        else
205     //            entity.Name = default(string);
206     //    }
207 
208     //    return entity;
209     //}
210     #endregion

EntityFillAdapter(表達式樹技術)

 

3、SQL查詢引擎(QueryEngine)

  SQL查詢引擎的功能主要就是以函數的形式來構建查詢SQL的結構。將sql語句使用高級語言的函數來進行構建能大大減輕程序員必須一絲不苟編寫sql語句的壓力。特別是在使用強類型查詢引擎時以Lambda表達式的方式編寫程序,相當舒適的體驗;對於稍微複雜的sql,建議使用弱類型查詢引擎來構建sql查詢語句,同時也提供方便的分頁功能,用法與Dapper類似;再複雜一點的數據庫查詢邏輯可能你就要考慮使用存儲過程查詢引擎了,總之,有了這三個查詢引擎,所有的查詢需求都能滿足了。最後一個是update的執行引擎,它被用來構建update的語句。

 

4、實體數據驗證(EntityValidation)

  實體數據驗證是完全獨立的一部分,主要用來檢驗實體類中字段值的合法性,相當於在高級語言層面對即將持久化到數據庫表中的數據進行預先的字段合法性校驗,避免在持久化過程中發生不必要的字段格式不合法的錯誤。

 

5、ORM緩存機制(ORMCache)

  這裏的ORM緩存主要分為兩級緩存,一級緩存為以sql語句為緩存鍵的緩存,緩存的內容就是當前執行的sql語句的執行結果;而二級緩存則是以表名為緩存鍵的表緩存,就是會把一整個表的數據全部存入緩存中,所以表緩存最適合那些數據量不大且查詢頻繁的表

 

6、SQL命令生成器【強類型】(CommandTextGenerator)

  在使用諸如強類型查詢引擎、Update執行引擎等進行了強類型的SQL語句構造后,相應的sql構造信息都要通過SQL命令生成器來生成最終可由數據庫執行的sql語句。SQL命令生成器扮演的就是類似於翻譯官的角色,將高級語言中的語句轉化為數據庫中的sql語句。在實際的應用場景中還可以根據不同的數據庫類型將SQL命令生成器擴展成諸如Mysql-SQL命令生成器或者Oracle-SQL命令生成器以符合不同類型數據庫的不同sql語法。

 

7、數據庫上下文(DBContext)

  作為整個CoffeeSQL的操作入口,DBContext類涵蓋了各種配置參数字段與增刪改查的API調用函數。其中在事務處理中,由於寫操作都是通過對主庫的操作,所以在事務處理中是以主庫作為事務處理的對象。

二、使用方式

  下載CoffeeSql源碼進行編譯,你會得到 CoffeeSql.Core.dll、CoffeeSql.Oracle.dll、CoffeeSql.Mysql.dll 三個dll文件,其中CoffeeSql.Core.dll為必選,然後根據你的數據庫類型選擇是CoffeeSql.Oracle.dll或者CoffeeSql.Mysql.dll,目前還只支持這兩種數據庫,後續會支持更多數據庫。

 

 

三、展望

  路漫漫其修遠兮,吾將上下而求索,對比市面上火熱的ORM框架,CoffeeSQL還是缺少了一些實用的功能,對這個ORM框架的展望中我會考慮以下一些功能:

    1、CodeFirst、DbFirst功能的支持,可以快捷方便地進行實體類與數據庫建表sql的生成;

    2、批量插入操作的實現,可以提高批量插入數據的性能;

    3、對多表聯合查詢的lambda語法支持;

  

  介紹的再多都不如讀一遍源碼來的實在,有想深入了解orm原理的小夥伴可以閱讀一下源碼,真的SO EASY!

   源碼地址:https://gitee.com/xiaosen123/CoffeeSqlORM

   本文為作者原創,轉載請註明出處:https://www.cnblogs.com/MaMaNongNong/p/12896787.html

 

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

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

Alink漫談(六) : TF-IDF算法的實現

Alink漫談(六) : TF-IDF算法的實現

目錄

  • Alink漫談(六) : TF-IDF算法的實現
    • 0x00 摘要
    • 0x01 TF-IDF
      • 1.1 原理
      • 1.2 計算方法
    • 0x02 Alink示例代碼
      • 2.1 示例代碼
      • 2.2 TF-IDF模型
      • 2.3 TF-IDF預測
    • 0x03 分詞 Segment
      • 3.1 結巴分詞
      • 3.2 分詞過程
    • 0x04 訓練
      • 4.1 計算IDF
      • 4.2 排序
        • 4.2.1 SortUtils.pSort
          • 採樣SampleSplitPoint
          • 歸併 SplitPointReducer
          • SplitData把真實數據IDF插入
          • reduceGroup計算同類型單詞數目
        • 4.2.2 localSort
      • 4.3 過濾
    • 0x05 生成模型
      • 5.1 DocCountVectorizerModelData
      • 5.2 BuildDocCountModel
    • 0x06 預測
    • 0x07 參考

0x00 摘要

Alink 是阿里巴巴基於實時計算引擎 Flink 研發的新一代機器學習算法平台,是業界首個同時支持批式算法、流式算法的機器學習平台。TF-IDF(term frequency–inverse document frequency)是一種用於信息檢索與數據挖掘的常用加權技術。本文將為大家展現Alink如何實現TF-IDF。

0x01 TF-IDF

TF-IDF(term frequency–inverse document frequency)是一種統計方法,一種用於信息檢索與數據挖掘的常用加權技術。

TF是詞頻(Term Frequency),IDF是逆文本頻率指數(Inverse Document Frequency)。

為什麼要用TF-IDF?因為計算機只能識別数字,對於一個一個的單詞,計算機是看不懂的,更別說是一句話,或是一篇文章。而TF-IDF就是用來將文本轉換成計算機看得懂的語言,或者說是機器學習或深度學習模型能夠進行學習訓練的數據集

1.1 原理

TF-IDF用以評估一個詞對於一個文件集或一個語料庫中的其中一份文件的重要程度。字詞的重要性隨着它在文件中出現的次數成正比增加,但同時會隨着它在語料庫中出現的頻率成反比下降。

TF-IDF的主要思想是:如果某個詞或短語在一篇文章中出現的頻率TF高,並且在其他文章中很少出現,則認為此詞或者短語具有很好的類別區分能力,適合用來分類。

TF-IDF實際上是:TF * IDF,TF詞頻(Term Frequency),IDF逆向文件頻率(Inverse Document Frequency)。

詞頻(term frequency,TF)指的是某一個給定的詞語在該文件中出現的頻率。這個数字是對詞數(term count)的歸一化,以防止它偏向長的文件(同一個詞語在長文件里可能會比短文件有更高的詞數,而不管該詞語重要與否)。

IDF逆向文件頻率 (inverse document frequency, IDF)反應了一個詞在所有文本(整個文檔)中出現的頻率,如果一個詞在很多的文本中出現,那麼它的IDF值應該低。而反過來如果一個詞在比較少的文本中出現,那麼它的IDF值應該高。比如一些專業的名詞如“Machine Learning”。這樣的詞IDF值應該高。一個極端的情況,如果一個詞在所有的文本中都出現,那麼它的IDF值應該為0。

如果單單以TF或者IDF來計算一個詞的重要程度都是片面的,因此TF-IDF綜合了TF和IDF兩者的優點,用以評估一字詞對於一個文件集或一個語料庫中的其中一份文件的重要程度。字詞的重要性隨着它在文件中出現的次數成正比增加,但同時會隨着它在語料庫中出現的頻率成反比下降。上述引用總結就是:一個詞語在一篇文章中出現次數越多, 同時在所有文檔中出現次數越少, 越能夠代表該文章,越能與其它文章區分開來。

1.2 計算方法

TF的計算公式如下:

\[TF_w = \frac {N_w}{N} \]

其中 N_w 是在某一文本中詞條w出現的次數,N 是該文本總詞條數。

IDF的計算公式如下:

\[IDF_w = log(\frac {Y}{Y_w + 1}) \]

其中 Y 是語料庫的文檔總數,Y_w 是包含詞條w的文檔數,分母加一是為了避免w 未出現在任何文檔中從而導致分母為0 的情況。

TF-IDF 就是將TF和IDF相乘 :

\[TF-IDF_w = TF_w * IDF_w \]

從以上計算公式便可以看出,某一特定文件內的高詞語頻率,以及該詞語在整個文件集合中的低文件頻率,可以產生出高權重的TF-IDF。因此,TF-IDF傾向於過濾掉常見的詞語,保留重要的詞語。

0x02 Alink示例代碼

2.1 示例代碼

首先我們給出示例代碼,下文是通過一些語料來訓練出一個模型,然後用這個模型來做預測:

public class DocCountVectorizerExample {

    AlgoOperator getData(boolean isBatch) {
        Row[] rows = new Row[]{
                Row.of(0, "二手舊書:醫學電磁成像"),
                Row.of(1, "二手美國文學選讀( 下冊 )李宜燮南開大學出版社 9787310003969"),
                Row.of(2, "二手正版圖解象棋入門/謝恩思主編/華齡出版社"),
                Row.of(3, "二手中國糖尿病文獻索引"),
                Row.of(4, "二手郁達夫文集( 國內版 )全十二冊館藏書")
        };

        String[] schema = new String[]{"id", "text"};

        if (isBatch) {
            return new MemSourceBatchOp(rows, schema);
        } else {
            return new MemSourceStreamOp(rows, schema);
        }
    }

    public static void main(String[] args) throws Exception {
        DocCountVectorizerExample test = new DocCountVectorizerExample();
        BatchOperator batchData = (BatchOperator) test.getData(true);

         // 分詞
        SegmentBatchOp segment = new SegmentBatchOp() 
                                                .setSelectedCol("text")
                                                .linkFrom(batchData);
        // TF-IDF訓練
        DocCountVectorizerTrainBatchOp model = new DocCountVectorizerTrainBatchOp()
                                                .setSelectedCol("text")
                                                .linkFrom(segment);
        // TF-IDF預測
        DocCountVectorizerPredictBatchOp predictBatch = new 
            																		DocCountVectorizerPredictBatchOp()
                                                .setSelectedCol("text")
                                                .linkFrom(model, segment);
        model.print();
        predictBatch.print();
    }
}

2.2 TF-IDF模型

TF-IDF模型打印出來如下:

model_id|model_info
--------|----------
0|{"minTF":"1.0","featureType":"\"WORD_COUNT\""}
1048576|{"f0":"二手","f1":0.0,"f2":0}
2097152|{"f0":"/","f1":1.0986122886681098,"f2":1}
3145728|{"f0":"出版社","f1":0.6931471805599453,"f2":2}
4194304|{"f0":")","f1":0.6931471805599453,"f2":3}
5242880|{"f0":"(","f1":0.6931471805599453,"f2":4}
6291456|{"f0":"入門","f1":1.0986122886681098,"f2":5}
......
36700160|{"f0":"美國","f1":1.0986122886681098,"f2":34}
37748736|{"f0":"謝恩","f1":1.0986122886681098,"f2":35}
38797312|{"f0":"象棋","f1":1.0986122886681098,"f2":36}

2.3 TF-IDF預測

TF-IDF預測結果如下:

id|text
--|----
0|$37$0:1.0 6:1.0 10:1.0 25:1.0 26:1.0 28:1.0
1|$37$0:1.0 1:1.0 2:1.0 4:1.0 11:1.0 15:1.0 16:1.0 19:1.0 20:1.0 32:1.0 34:1.0
2|$37$0:1.0 3:2.0 4:1.0 5:1.0 8:1.0 22:1.0 23:1.0 24:1.0 29:1.0 35:1.0 36:1.0
3|$37$0:1.0 12:1.0 27:1.0 31:1.0 33:1.0
4|$37$0:1.0 1:1.0 2:1.0 7:1.0 9:1.0 13:1.0 14:1.0 17:1.0 18:1.0 21:1.0 30:1.0

0x03 分詞 Segment

中文分詞(Chinese Word Segmentation) 指的是將一個漢字序列切分成一個一個單獨的詞。分詞就是將連續的字序列按照一定的規範重新組合成詞序列的過程。

示例代碼中,分詞部分如下:

    SegmentBatchOp segment = new SegmentBatchOp() 
                                            .setSelectedCol("text")
                                            .linkFrom(batchData);

分詞主要是如下兩個類,其作用就是把中文文檔分割成單詞。

public final class SegmentBatchOp extends MapBatchOp <SegmentBatchOp>
	implements SegmentParams <SegmentBatchOp> {

	public SegmentBatchOp(Params params) {
		super(SegmentMapper::new, params);
	}
}

public class SegmentMapper extends SISOMapper {
	private JiebaSegmenter segmentor;
}

3.1 結巴分詞

有經驗的同學看到這裏就會露出微笑:結巴分詞。

jieba分詞是國內使用人數最多的中文分詞工具https://github.com/fxsjy/jieba。jieba分詞支持四種分詞模式:

  • 精確模式,試圖將句子最精確地切開,適合文本分析;
  • 全模式,把句子中所有的可以成詞的詞語都掃描出來, 速度非常快,但是不能解決歧義;
  • 搜索引擎模式,在精確模式的基礎上,對長詞再次切分,提高召回率,適合用於搜索引擎分詞。
  • paddle模式,利用PaddlePaddle深度學習框架,訓練序列標註(雙向GRU)網絡模型實現分詞。

Alink使用了com.alibaba.alink.operator.common.nlp.jiebasegment.viterbi.FinalSeg;來 完成分詞。具體是在https://github.com/huaban/jieba-analysis的基礎上稍微做了調整。

public class JiebaSegmenter implements Serializable {
    private static FinalSeg finalSeg = FinalSeg.getInstance();
    private WordDictionary wordDict;
    ......
    private Map<Integer, List<Integer>> createDAG(String sentence) 
}

從Alink代碼中看,實現了索引分詞和查詢分詞兩種模式,應該是有分詞粒度粗細之分。

createDAG函數的作用是 :在處理句子過程中,基於前綴詞典實現高效的詞圖掃描,生成句子中漢字所有可能成詞情況所構成的有向無環圖 (DAG)。

結巴分詞對於未登錄詞,採用了基於漢字成詞能力的 HMM 模型,使用了 Viterbi 算法。

3.2 分詞過程

分詞過程主要是在SegmentMapper.mapColumn函數中完成的,當輸入是 “二手舊書:醫學電磁成像”,結巴分詞將這個句子分成了六個單詞。具體參見如下:

input = "二手舊書:醫學電磁成像"
tokens = {ArrayList@9619}  size = 6
 0 = {SegToken@9630} "[二手, 0, 2]"
 1 = {SegToken@9631} "[舊書, 2, 4]"
 2 = {SegToken@9632} "[:, 4, 5]"
 3 = {SegToken@9633} "[醫學, 5, 7]"
 4 = {SegToken@9634} "[電磁, 7, 9]"
 5 = {SegToken@9635} "[成像, 9, 11]"
 
mapColumn:44, SegmentMapper (com.alibaba.alink.operator.common.nlp)
apply:-1, 35206803 (com.alibaba.alink.common.mapper.SISOMapper$$Lambda$646)
handleMap:75, SISOColsHelper (com.alibaba.alink.common.mapper)
map:52, SISOMapper (com.alibaba.alink.common.mapper)
map:21, MapperAdapter (com.alibaba.alink.common.mapper)
map:11, MapperAdapter (com.alibaba.alink.common.mapper)
collect:79, ChainedMapDriver (org.apache.flink.runtime.operators.chaining)
collect:35, CountingCollector (org.apache.flink.runtime.operators.util.metrics)
invoke:196, DataSourceTask (org.apache.flink.runtime.operators)

0x04 訓練

訓練是在DocCountVectorizerTrainBatchOp類完成的,其通過linkFrom完成了模型的構建。其實計算TF IDF相對 簡單,複雜之處在於之後的大規模排序。

public DocCountVectorizerTrainBatchOp linkFrom(BatchOperator<?>... inputs) {
        BatchOperator<?> in = checkAndGetFirst(inputs);
  
        DataSet<DocCountVectorizerModelData> resDocCountModel = generateDocCountModel(getParams(), in);

        DataSet<Row> res = resDocCountModel.mapPartition(new MapPartitionFunction<DocCountVectorizerModelData, Row>() {
            @Override
            public void mapPartition(Iterable<DocCountVectorizerModelData> modelDataList, Collector<Row> collector) {
                new DocCountVectorizerModelDataConverter().save(modelDataList.iterator().next(), collector);
            }
        });
        this.setOutput(res, new DocCountVectorizerModelDataConverter().getModelSchema());
        return this;
}

4.1 計算IDF

計算 IDF 的工作是在generateDocCountModel完成的,具體步驟如下:

第一步 通過DocWordSplitCount和UDTF的混合使用得到了文檔中的單詞數目docWordCnt

BatchOperator<?> docWordCnt = in.udtf(
        params.get(SELECTED_COL),
        new String[] {WORD_COL_NAME, DOC_WORD_COUNT_COL_NAME},
        new DocWordSplitCount(NLPConstant.WORD_DELIMITER),
        new String[] {});

DocWordSplitCount.eval的輸入是已經分詞的句子,然後按照空格分詞,按照單詞計數。其結果是:

map = {HashMap@9816}  size = 6
 "醫學" -> {Long@9833} 1
 "電磁" -> {Long@9833} 1
 ":" -> {Long@9833} 1
 "成像" -> {Long@9833} 1
 "舊書" -> {Long@9833} 1
 "二手" -> {Long@9833} 1

第二步 得到了文檔數目docCnt

BatchOperator docCnt = in.select("COUNT(1) AS " + DOC_COUNT_COL_NAME);

這個數目會廣播出去 .withBroadcastSet(docCnt.getDataSet(), "docCnt");,後面的CalcIdf會繼續使用,進行 行數統計。

第三步 會通過CalcIdf計算出每一個單詞的DF和IDF

open時候會獲取docCnt。然後reduce會計算IDF,具體計算如下:

double idf = Math.log((1.0 + docCnt) / (1.0 + df));
collector.collect(Row.of(featureName, -wordCount, idf));

具體得到如下

df = 1.0
wordCount = 1.0
featureName = "中國"
idf = 1.0986122886681098
docCnt = 5

這裏一個重點是:返回值中,是 -wordCount,因為單詞越多權重越小,為了比較所以取負

4.2 排序

得到所有單詞的IDF之後,就得到了一個IDF字典,這時候需要對字典按照權重進行排序。排序具體分為兩步。

4.2.1 SortUtils.pSort

第一步是SortUtils.pSort,大規模并行抽樣排序。

Tuple2<DataSet<Tuple2<Integer, Row>>, DataSet<Tuple2<Integer, Long>>> partitioned = SortUtils.pSort(sortInput, 1);

這步非常複雜,Alink參考了論文,如果有興趣的兄弟可以深入了解下。

* reference: Yang, X. (2014). Chong gou da shu ju tong ji (1st ed., pp. 25-29).
* Note: This algorithm is improved on the base of the parallel sorting by regular sampling(PSRS).

pSort返回值是:

* @return f0: dataset which is indexed by partition id, f1: dataset which has partition id and count.

pSort中又分如下幾步

採樣SampleSplitPoint

SortUtils.SampleSplitPoint.mapPartition這裏完成了採樣。

DataSet <Tuple2 <Object, Integer>> splitPoints = input
   .mapPartition(new SampleSplitPoint(index))
   .reduceGroup(new SplitPointReducer());

這裏的輸入row就是上文IDF的返回數值。

用allValues記錄了本task目前處理的句子有多少個單詞。

用splitPoints做了採樣。如何選擇呢,通過genSampleIndex函數。

public static Long genSampleIndex(Long splitPointIdx, Long count, Long splitPointSize) {
   splitPointIdx++;
   splitPointSize++;

   Long div = count / splitPointSize;
   Long mod = count % splitPointSize;

   return div * splitPointIdx + ((mod > splitPointIdx) ? splitPointIdx : mod) - 1;
}

後續操作也使用同樣的genSampleIndex函數來做選擇,這樣保證在操作所有序列上可以選取同樣的採樣點。

allValues = {ArrayList@10264}  size = 8  //本task有多少單詞
 0 = {Double@10266} -2.0
 1 = {Double@10271} -1.0
 2 = {Double@10272} -1.0
 3 = {Double@10273} -1.0
 4 = {Double@10274} -1.0
 5 = {Double@10275} -1.0
 6 = {Double@10276} -1.0
 7 = {Double@10277} -1.0
 
splitPoints = {ArrayList@10265}  size = 7 //採樣了7個
 0 = {Double@10266} -2.0
 1 = {Double@10271} -1.0
 2 = {Double@10272} -1.0
 3 = {Double@10273} -1.0
 4 = {Double@10274} -1.0
 5 = {Double@10275} -1.0
 6 = {Double@10276} -1.0

最後返回採樣數據,返回時候附帶當前taskIDnew Tuple2 <Object, Integer>(obj,taskId)

這裡有一個trick點

  for (Object obj : splitPoints) {
     Tuple2 <Object, Integer> cur
        = new Tuple2 <Object, Integer>(
        obj,
        taskId); //這裏返回的是類似 (-5.0,2) :其中2就是task id,-5.0是-wordcount。
     out.collect(cur);
  }

  out.collect(new Tuple2(
     getRuntimeContext().getNumberOfParallelSubtasks(),
     -taskId - 1));//這裏返回的是一個特殊元素,類似(4,-2) :其中4是本應用中并行task數目,-2是當前-taskId - 1。這個task數目後續就會用到。

具體數據參見如下:

row = {Row@10211} "中國,-1.0,1.0986122886681098"
 fields = {Object[3]@10214} 
 
cur = {Tuple2@10286} "(-5.0,2)" // 返回採樣數據,返回時候附帶當前taskID
 f0 = {Double@10285} -5.0 // -wordcount。
 f1 = {Integer@10300} 2 // 當前taskID
歸併 SplitPointReducer

歸併所有task生成的sample。然後再次sample,把sample數據組成一個數據塊,這個數據塊選擇的原則是:每個task都盡量選擇若干sample

這裏其實是有一個轉換,就是從正常單詞的抽樣 轉換到 某一類單詞的抽樣,這某一類的意思舉例是:出現次數為一,或者出現次數為五 這種單詞

這裏all是所有採樣數據,其中一個元素內容舉例 (-5.0,2) :其中2就是task id,-5.0是-wordcount。

這裏用 Collections.sort(all, new PairComparator()); 來對所有採樣數據做排序。排序基準是首先對 -wordcount,然後對task ID。

SplitPointReducer的返回採樣數值就作為廣播變量存儲起來:.withBroadcastSet(splitPoints, "splitPoints");

這裏的trick點是:

for (Tuple2 <Object, Integer> value : values) {
   if (value.f1 < 0) { 
      instanceCount = (int) value.f0;  // 特殊數據,類似(4,-2) :其中4是本應用中task數目,這個就是後續選擇哪些taskid的基準
      continue;
   }
   all.add(new Tuple2 <>(value.f0, value.f1)); // (-5.0,2) 正常數據
}

選擇sample index splitPoints.add(allValues.get(index));也使用了同樣的genSampleIndex。

計算中具體數據如下:

for (int i = 0; i < splitPointSize; ++i) {
		int index = genSampleIndex(
					Long.valueOf(i),
					Long.valueOf(count),
					Long.valueOf(splitPointSize))
					.intValue();
		spliters.add(all.get(index));
}
for (Tuple2 <Object, Integer> spliter : spliters) {
		out.collect(spliter);
}

count = 33
all = {ArrayList@10245}  size = 33 // 所有採樣數據,
0 = {Tuple2@10256} "(-5.0,2)"// 2就是task id,-5.0是-wordcount。
1 = {Tuple2@10285} "(-2.0,0)"
......
6 = {Tuple2@10239} "(-1.0,0)"
7 = {Tuple2@10240} "(-1.0,0)"
8 = {Tuple2@10241} "(-1.0,0)"
9 = {Tuple2@10242} "(-1.0,0)"
10 = {Tuple2@10243} "(-1.0,0)"
11 = {Tuple2@10244} "(-1.0,1)"
......
16 = {Tuple2@10278} "(-1.0,1)"
......
24 = {Tuple2@10279} "(-1.0,2)"
......
32 = {Tuple2@10313} "(-1.0,3)"
  
// spliters是返回結果,這裏分別選取了all中index為8,16,24這個三個record。每個task都選擇了一個元素。
spliters = {HashSet@10246}  size = 3
 0 = {Tuple2@10249} "(-1.0,0)" // task 0 被選擇。就是說,這裏從task 0中選擇了一個count是1的元素,具體選擇哪個單詞其實不重要,就是為了選擇count是1的這種即可。
 1 = {Tuple2@10250} "(-1.0,1)" // task 1 被選擇。具體同上。
 2 = {Tuple2@10251} "(-1.0,2)" // task 2 被選擇。具體同上。
SplitData把真實數據IDF插入

use binary search to partition data into sorted subsets。前面函數給出的是詞的count,但是沒有IDF。這裏將用二分法查找 找到IDF,然後把IDF插入到partition data中。

首先要注意一點:splitData的輸入就是原始輸入input, 和splitPoints的輸入是一樣 的

DataSet <Tuple2 <Integer, Row>> splitData = input
   .mapPartition(new SplitData(index))
   .withBroadcastSet(splitPoints, "splitPoints");

open函數中會取出廣播變量 splitPoints。

splitPoints = {ArrayList@10248}  size = 3
 0 = {Tuple2@10257} "(-1.0,0)"
 1 = {Tuple2@10258} "(-1.0,1)"
 2 = {Tuple2@10259} "(-1.0,2)"

本函數的輸入舉例

row = {Row@10232} "入門,-1.0,1.0986122886681098"

會在splitPoints中二分法查找,得到splits中每一個 sample 對應的真實IDF。然後發送出去。

這裏需要特殊說明下,這個二分法查找查找的是IDF數值,比如count為1的這種單詞對應的IDF數值,可能很多單詞都是count為1,所以找到一個這樣單詞的IDF即可

splitPoints = {ArrayList@10223}  size = 3
 0 = {Tuple2@10229} "(-1.0,0)"
 1 = {Tuple2@10230} "(-1.0,1)"
 2 = {Tuple2@10231} "(-1.0,2)"
curTuple.f0 = {Double@10224} -1.0
  
int bsIndex = Collections.binarySearch(splitPoints, curTuple, new PairComparator());

		int curIndex;
		if (bsIndex >= 0) {
			curIndex = bsIndex;
		} else {
			curIndex = -bsIndex - 1;
		}

// 假設單詞是 "入門",則發送的是 "入門" 這類單詞在本partition的index,和 "入門" 的單詞本身
// 其實,從調試過程看,是否發送單詞信息本身並不重要,因為接下來的那一步操作中,並沒有用到單詞本身信息
out.collect(new Tuple2 <>(curIndex, row)); 
reduceGroup計算同類型單詞數目

這裡是計算在某一partition中,某一種類單詞的數目。比如count為1的單詞,這種單詞總共有多少個

後續會把new Tuple2 <>(id, count)作為partitionCnt廣播變量存起來。

id就是這類單詞在這partition中間的index,我們暫時稱之為partition index。count就是這類單詞在本partition的數目。

// 輸入舉例
value = {Tuple2@10312} "(0,入門,-1.0,1.0986122886681098)"
 f0 = {Integer@10313} 0
 
// 計算數目
for (Tuple2 <Integer, Row> value : values) {
		id = value.f0;
		count++;
}

out.collect(new Tuple2 <>(id, count));  
  
// 輸出舉例,假如是序號為0的這類單詞,其總體數目是12。這個序號0就是這類單詞在某一partition中的序號。就是上面的 curIndex。
id = {Integer@10313} 0
count = {Long@10338} 12

4.2.2 localSort

第二步是localSort。Sort a partitioned dataset. 最終排序並且會返回最終數值,比如 (29, “主編,-1.0,1.0986122886681098″), 29就是”主編” 這個單詞在 IDF字典中的序號。

DataSet<Tuple2<Long, Row>> ordered = localSort(partitioned.f0, partitioned.f1, 1);

open函數中會獲取partitionCnt。然後計算出某一種類單詞,其在本partition之前所有partition中,這類單詞數目。

public void open(Configuration parameters) throws Exception {
		List <Tuple2 <Integer, Long>> bc = getRuntimeContext().getBroadcastVariable("partitionCnt");
		startIdx = 0L;
		int taskId = getRuntimeContext().getIndexOfThisSubtask();
		for (Tuple2 <Integer, Long> pcnt : bc) {
			if (pcnt.f0 < taskId) {
					startIdx += pcnt.f1;
			}
		}
}

bc = {ArrayList@10303}  size = 4
 0 = {Tuple2@10309} "(0,12)"  // 就是task0裏面,這種單詞有12個
 1 = {Tuple2@10310} "(2,9)"// 就是task1裏面,這種單詞有2個
 2 = {Tuple2@10311} "(1,7)"// 就是task2裏面,這種單詞有1個
 3 = {Tuple2@10312} "(3,9)"// 就是task3裏面,這種單詞有3個
// 如果本task id是4,則其startIdx為30。就是所有partition之中,它前面index所有單詞的和。  

然後進行排序。Collections.sort(valuesList, new RowComparator(field));

valuesList = {ArrayList@10405}  size = 9
 0 = {Row@10421} ":,-1.0,1.0986122886681098"
 1 = {Row@10422} "主編,-1.0,1.0986122886681098"
 2 = {Row@10423} "國內,-1.0,1.0986122886681098"
 3 = {Row@10424} "文獻,-1.0,1.0986122886681098"
 4 = {Row@10425} "李宜燮,-1.0,1.0986122886681098"
 5 = {Row@10426} "糖尿病,-1.0,1.0986122886681098"
 6 = {Row@10427} "美國,-1.0,1.0986122886681098"
 7 = {Row@10428} "謝恩,-1.0,1.0986122886681098"
 8 = {Row@10429} "象棋,-1.0,1.0986122886681098"
  
  
// 最後返回時候,就是  (29, "主編,-1.0,1.0986122886681098"),29就是“主編”這個單詞在最終字典中的序號。
// 這個序號是startIdx + cnt,startIdx是某一種類單詞,其在本partition之前所有partition中,這類單詞數目。比如在本partition之前,這類單詞有28個,則本partition中,從29開始計數。就是最終序列號
	for (Row row : valuesList) {
		out.collect(Tuple2.of(startIdx + cnt, row));
		cnt++; // 這裏就是在某一類單詞中,單調遞增,然後賦值一個字典序列而已
	}  
cnt = 1
row = {Row@10336} "主編,-1.0,1.0986122886681098"
 fields = {Object[3]@10339} 
startIdx = 28

4.3 過濾

最後還要進行過濾,如果文字個數超出了字典大小,就拋棄多餘文字。

ordered.filter(new FilterFunction<Tuple2<Long, Row>>() {
    @Override
    public boolean filter(Tuple2<Long, Row> value) {
        return value.f0 < vocabSize;
    }
})

0x05 生成模型

具體生成模型代碼如下。

DataSet<DocCountVectorizerModelData> resDocCountModel = ordered.filter(new FilterFunction<Tuple2<Long, Row>>() {
    @Override
    public boolean filter(Tuple2<Long, Row> value) {
        return value.f0 < vocabSize;
    }
}).mapPartition(new BuildDocCountModel(params)).setParallelism(1);
return resDocCountModel;

其中關鍵類是 DocCountVectorizerModelData 和 BuildDocCountModel。

5.1 DocCountVectorizerModelData

這是向量信息。

/**
 * Save the data for DocHashIDFVectorizer.
 *
 * Save a HashMap: index(MurMurHash3 value of the word), value(Inverse document frequency of the word).
 */
public class DocCountVectorizerModelData {
    public List<String> list;
    public String featureType;
    public double minTF;
}

5.2 BuildDocCountModel

最終生成的模型信息如下,這個也就是之前樣例代碼給出的輸出。

modelData = {DocCountVectorizerModelData@10411} 
 list = {ArrayList@10409}  size = 37
  0 = "{"f0":"9787310003969","f1":1.0986122886681098,"f2":19}"
  1 = "{"f0":"下冊","f1":1.0986122886681098,"f2":20}"
  2 = "{"f0":"全","f1":1.0986122886681098,"f2":21}"
  3 = "{"f0":"華齡","f1":1.0986122886681098,"f2":22}"
  4 = "{"f0":"圖解","f1":1.0986122886681098,"f2":23}"
  5 = "{"f0":"思","f1":1.0986122886681098,"f2":24}"
  6 = "{"f0":"成像","f1":1.0986122886681098,"f2":25}"
  7 = "{"f0":"舊書","f1":1.0986122886681098,"f2":26}"
  8 = "{"f0":"索引","f1":1.0986122886681098,"f2":27}"
  9 = "{"f0":":","f1":1.0986122886681098,"f2":28}"
  10 = "{"f0":"主編","f1":1.0986122886681098,"f2":29}"
  11 = "{"f0":"國內","f1":1.0986122886681098,"f2":30}"
  12 = "{"f0":"文獻","f1":1.0986122886681098,"f2":31}"
  13 = "{"f0":"李宜燮","f1":1.0986122886681098,"f2":32}"
  14 = "{"f0":"糖尿病","f1":1.0986122886681098,"f2":33}"
  15 = "{"f0":"美國","f1":1.0986122886681098,"f2":34}"
  16 = "{"f0":"謝恩","f1":1.0986122886681098,"f2":35}"
  17 = "{"f0":"象棋","f1":1.0986122886681098,"f2":36}"
  18 = "{"f0":"二手","f1":0.0,"f2":0}"
  19 = "{"f0":")","f1":0.6931471805599453,"f2":1}"
  20 = "{"f0":"/","f1":1.0986122886681098,"f2":2}"
  21 = "{"f0":"出版社","f1":0.6931471805599453,"f2":3}"
  22 = "{"f0":"(","f1":0.6931471805599453,"f2":4}"
  23 = "{"f0":"入門","f1":1.0986122886681098,"f2":5}"
  24 = "{"f0":"醫學","f1":1.0986122886681098,"f2":6}"
  25 = "{"f0":"文集","f1":1.0986122886681098,"f2":7}"
  26 = "{"f0":"正版","f1":1.0986122886681098,"f2":8}"
  27 = "{"f0":"版","f1":1.0986122886681098,"f2":9}"
  28 = "{"f0":"電磁","f1":1.0986122886681098,"f2":10}"
  29 = "{"f0":"選讀","f1":1.0986122886681098,"f2":11}"
  30 = "{"f0":"中國","f1":1.0986122886681098,"f2":12}"
  31 = "{"f0":"書","f1":1.0986122886681098,"f2":13}"
  32 = "{"f0":"十二冊","f1":1.0986122886681098,"f2":14}"
  33 = "{"f0":"南開大學","f1":1.0986122886681098,"f2":15}"
  34 = "{"f0":"文學","f1":1.0986122886681098,"f2":16}"
  35 = "{"f0":"郁達夫","f1":1.0986122886681098,"f2":17}"
  36 = "{"f0":"館藏","f1":1.0986122886681098,"f2":18}"
 featureType = "WORD_COUNT"
 minTF = 1.0

0x06 預測

預測業務邏輯是DocCountVectorizerModelMapper

首先我們可以看到 FeatureType,這個可以用來配置輸出哪種信息。比如可以輸出以下若干種:

public enum FeatureType implements Serializable {
    /**
     * IDF type, the output value is inverse document frequency.
     */
    IDF(
        (idf, termFrequency, tokenRatio) -> idf
    ),
    /**
     * WORD_COUNT type, the output value is the word count.
     */
    WORD_COUNT(
        (idf, termFrequency, tokenRatio) -> termFrequency
    ),
    /**
     * TF_IDF type, the output value is term frequency * inverse document frequency.
     */
    TF_IDF(
        (idf, termFrequency, tokenRatio) -> idf * termFrequency * tokenRatio
    ),
    /**
     * BINARY type, the output value is 1.0.
     */
    BINARY(
        (idf, termFrequency, tokenRatio) -> 1.0
    ),
    /**
     * TF type, the output value is term frequency.
     */
    TF(
        (idf, termFrequency, tokenRatio) -> termFrequency * tokenRatio
    );
}

其次,在open函數中,會加載模型,比如:

wordIdWeight = {HashMap@10838}  size = 37
 "醫學" -> {Tuple2@10954} "(6,1.0986122886681098)"
 "選讀" -> {Tuple2@10956} "(11,1.0986122886681098)"
 "十二冊" -> {Tuple2@10958} "(14,1.0986122886681098)"
...
 "華齡" -> {Tuple2@11022} "(22,1.0986122886681098)"
 "索引" -> {Tuple2@11024} "(27,1.0986122886681098)"
featureType = {DocCountVectorizerModelMapper$FeatureType@10834} "WORD_COUNT"

最後,預測時候調用predictSparseVector函數,會針對輸入 二手 舊書 : 醫學 電磁 成像來進行匹配。生成稀疏向量SparseVector。

0|$37$0:1.0 6:1.0 10:1.0 25:1.0 26:1.0 28:1.0

以上表示那幾個單詞 分別對應0 6 10 25 26 28 這幾個字典中對應序號的單詞,其在本句對應的出現數目都是一個。

0x07 參考

Tf-Idf詳解及應用

https://github.com/fxsjy/jieba

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

kubernetes pod內抓包,telnet檢查網絡連接的幾種方式

背景

在日常kubernetes的運維中,經常遇到pod的網絡問題,如pod間網絡不通,或者端口不通,更複雜的,需要在容器裏面抓包分析才能定位。而kubertnets的場景,pod使用的鏡像一般都是盡量精簡,很多都是基於alpine基礎鏡像製作的,因而pod內沒有ping,telnet,nc,curl命令,更別說tcpdump這種複雜的工具了。除了在容器或者鏡像內直接安裝這些工具這種最原始的法子,我們探討下其他法子。

實現

kubectl debug插件方式

項目地址 kubect debug,https://github.com/aylei/kubectl-debug

kubectl-debug 是一個簡單的 kubectl 插件,能夠幫助你便捷地進行 Kubernetes 上的 Pod 排障診斷。背後做的事情很簡單: 在運行中的 Pod 上額外起一個新容器,並將新容器加入到目標容器的 pid, network, user 以及 ipc namespace 中,這時我們就可以在新容器中直接用 netstat, tcpdump 這些熟悉的工具來解決問題了, 而舊容器可以保持最小化,不需要預裝任何額外的排障工具。操作流程可以參見官方項目地址文檔。

一條 kubectl debug命令背後是這樣的

步驟分別是:

  1. 插件查詢 ApiServer:demo-pod 是否存在,所在節點是什麼
  2. ApiServer 返回 demo-pod 所在所在節點
  3. 插件請求在目標節點上創建 Debug Agent Pod
  4. Kubelet 創建 Debug Agent Pod
  5. 插件發現 Debug Agent 已經 Ready,發起 debug 請求(長連接)
  6. Debug Agent 收到 debug 請求,創建 Debug 容器並加入目標容器的各個 Namespace 中,創建完成后,與 Debug 容器的 tty 建立連接

接下來,客戶端就可以開始通過 5,6 這兩個連接開始 debug 操作。操作結束后,Debug Agent 清理 Debug 容器,插件清理 Debug Agent,一次 Debug 完成。

直接進入容器net ns方式

有2種進入pod 所在net ns的方式,前提都是需要登錄到pod所在宿主機,且需要找出pod對應的容器ID或者名字。

ip netns方式

  • 獲取pod對應容器的ID或者name

    pid="$(docker inspect -f '{{.State.Pid}}' <container_name | uuid>)" #替換為環境實際的容器名字或者uuid
    
  • 創建容器對應netns

    ip netns會到/var/run/netns目錄下尋找network namespace,把容器進程中netns連接到這個目錄中后,ip netns才會感知到

    $ sudo mkdir -p /var/run/netns
    
    #docker默認不會創建這個鏈接,需要手動創建,這時候執行ip netns,就應當看到鏈接過來的network namespace
    $ sudo ln -sf /proc/$pid/ns/net "/var/run/netns/<container_name|uuid>" 
    
  • 執行ip netns <<container_name|uuid > bash,進入容器ns

    ip netns exec <container_name|uuid>  bash
    
  • 執行telnet,tcpdump等命令,此時執行ip a或者ifconfig,只能看到容器本身的IP

如下圖,執行ifconfig,只看到容器本身的IP,此時執行telnet,tcpdump等於直接在容器內操作

nsenter方式

nsenter為util-linux裏面的一個工具,除了進入容器net ns,還支持其他很多操作,可以查看官方文檔。

pid="$(docker inspect -f '{{.State.Pid}}' <container_name | uuid>)"
nsenter -t $pid -n /bin/bash
tcpdump -i eth0 -nn  #此時利用宿主機的tcpdump執行抓包操作,等於在容器內抓包

總結

  1. kubectl debug方式功能更強大,缺點是需要附加鏡像,要在目標pod創建debug agent的容器,比較笨重,但是優點是能使用的工具更多,不需要ssh到pod所在節點,除了netstat,tcpdump工具,還能使用htop,iostat等其他高級工具,不僅能對網絡進行debug,還能對IO等其他場景進行診斷,適用更複雜的debug場景。
  2. 直接進入容器net ns方式相對比較輕量,復用pod所在宿主機工具,但魚和熊掌不可兼得,缺點是只能進行網絡方面的debug,且需要ssh登錄到pod所在節點操作。

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

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

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

廣州車展 這些“見所未見”的轎跑/SUV絕不能錯過

渾身肌肉一臉野性的“美式壯man”近一個世紀以來,雪佛蘭皮卡不斷演化——讓人們體驗到更加豐富而時尚的生活樂趣。本次索羅德Silverado、庫羅德Colorado的展示,也讓中國的消費者真正的認識以及觸碰到皮卡,其像無畏的騎士,是一種血性的代表,彷彿每時每刻都想衝出家門去越野。

雪佛蘭誕生於賽道之上,但卻回歸到群眾之中。雪佛蘭給我們的印象是一個普及化的品牌,產品覆蓋面很廣,服務於各種人群,但有很多人不知道的是,第一輛雪佛蘭汽車其實是一輛高性能的高端車型,Classic six不僅在性能上超越當時市面上的車型,搭載的發動機在技術上也是領先同行,雖然售價偏高,但是一炮打響了雪佛蘭的金字招牌,就如創始人路易·雪佛蘭的賽車手熱誠一般,在人們心中留下深深的激情勇敢、創新突破的印跡。

縱然雪佛蘭在那之後調整了方向,更加側重於製造服務廣大受眾的汽車,但這樣的DNA已深入雪佛蘭汽車的骨髓,雪佛蘭的產品一直是高品質駕駛樂趣的代表,歷經百年後,如今的雪佛蘭已經是一種生活態度的體現。在本次的廣州車展上,這個百年金領結更是給大家展示了一道“見所未見”的風景線,年輕的轎跑,硬漢的皮卡,極致的跑車,你想要的這裏都有。

從變形金剛穿越的燃爆大黃蜂

沒錯,如今展示在廣州車展舞台的就是《變形金剛》電影里鼎鼎大名的“大黃蜂”—— 全新第六代科邁羅。雖身處展台,但從前臉的進氣格柵,經典肌肉線條,LED扁平前大燈,都能讓人感受到科邁羅馭風疾馳的賽道氣息。

自誕生以來,這輛有着“非凡魅力“的大黃蜂,其轟鳴便橫掃了國際各大賽事。這也得益於創始人路易·雪佛蘭,在雪佛蘭公司成立之前,他就是一名賽車手,每天夢想的就是生產高品質的跑車,直至今天的通用汽車總裁丹·阿曼先生都是賽車的好手。很顯然,這個金領結早在誕生時,就與運動一直捆綁着。

硬派氣質秒殺同級的探界者Equinox

身為SUV陣營“主導者”——探界者Equinox,在碰撞展檯燈光之間,顯露了十足的美式力量。同時也在用一種無聲的力量詮釋着探界者的英文名Equinox所表達的含義:晝夜均分的春分與秋分時節,蓄含突破與改變現狀的力量,不受界限與束縛地探索各種可能。

另外,更值得一提的是,在小排量渦輪發動機成為主流的當下,誠然各大廠商的技術已不存在明顯的“代差”,但毋庸置疑通用的發動機一直處於一線。目前在Equinox車上配備了9AT變速箱,從机械結構上來看,沒有採用絞牙式的離合器,但體積沒有增大,平順性會有更好的保證。

渾身肌肉一臉野性的“美式壯man”

近一個世紀以來,雪佛蘭皮卡不斷演化——讓人們體驗到更加豐富而時尚的生活樂趣。本次索羅德Silverado、庫羅德Colorado的展示,也讓中國的消費者真正的認識以及觸碰到皮卡,其像無畏的騎士,是一種血性的代表,彷彿每時每刻都想衝出家門去越野。

這兩款皮卡,跟我們通常認知的皮卡可以說不是一種產物,雪佛蘭的皮卡絕對稱得上高級好開。它們的加入,也將會讓許久沒有全新車型加入的中國高端皮卡市場競爭更加激烈,很是值得期待。

索羅德Silverado

庫羅德Colorado

此次車展,雪佛蘭帶給消費者的不單是一場視覺上的盛宴,更是一個關於夢的兌現。時光荏苒,這個金領結已經走過百年,從K系列與福特T型車比肩開始,成功度過大蕭條,不斷推出新產品,甚至還一定程度上為現在汽車製造工業奠定了基礎。

雪佛蘭已橫跨140多個國家與地區,馳騁了無數世界超級賽事,征服了全球2.3億車主,同時,在品牌建設上的突破,也取得耀眼的成績——作為上海迪斯尼官方度假區的官方汽車合作品牌,獨家贊助了極具標誌性景點的“創極速光輪”,還與曼聯聯合為廣大球迷帶來眾多精彩賽事,贊助紅粉筆計劃為鄉村支教事業出一份力。

(快閃視頻)

說了這麼多,“棒球、熱狗、蘋果派,不能少了雪佛蘭”。雪佛蘭已經完成了美國夢,也終將成為與可口可樂、蘋果一樣的icon,從美國夢成就世界夢。你我的生活也可能都逃不過一輛雪佛蘭。而本次車展無論你聚焦於以上任何一款產品,它都能代表雪佛蘭。

未來雪佛蘭還將會繼續研發與進步,夢想不止,夢創未來。無論是新能源,車聯網,自動駕駛等等有關未來出行的一切,雪佛蘭都有着充足的技術儲備,期待雪佛蘭將持續以一種高品質的姿態,帶給你我更多“見所未見“的產品。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

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

排量小動力猛!10.9萬起緊湊型家用車更貴了反而更值得買?

2T發動機最大馬力116匹,峰值扭矩185牛米,與之匹配的是一台模擬八個檔位的S-CVT無級變速箱,由於定位依舊是一款家用轎車,在轉向手感建立和油門響應的調校方式上還是保留了雷凌以往較為輕盈的特性,駕駛依舊平易近人。除了發動機,它貴在哪。

雷凌1.2T

自從卡羅拉推出了1.2T版本以後,作為與其同平台的雷凌也很快的裝配上了同樣的渦輪增壓發動機,由於雷凌的設計會顯得更加的運動激進化一些,這也是很多年輕人的首次購車選擇,但是在很多人眼裡,1.2T的雷凌上市以後比以往要貴了幾千塊,這車到底還值得購買嗎?

廣汽豐田雷凌1.2T

指導價格:10.98-13.38萬

外觀並無變化

廣汽豐田雷凌的新款1.2T渦輪增壓車型畢竟只是一款中期改款車型,在外觀上並沒有什麼過多的變化,僅僅是在頂配車型當中增加了一套LED光源的日間行車燈以及中網採用了鋼琴黑騎的裝飾,值得一提的是,廣汽豐田雷凌1.2T新增了一種名為琥珀棕的車身配色。

運動化的內飾是為亮點

內飾設計其實也沒多少改動,只是在原有的基礎上增添了紅色的裝飾,添置於中控台和門板上,增加了內飾視覺感官上的精緻度,並且將前排座椅改動成為帶兩側護翼的運動型座椅設計,縫線工藝所綉出來的LEVIN字樣彰顯着身份,也讓雷凌內飾看上去更加動感。

小排量渦輪增壓才是重點

我們可以注意到1.2T雷凌的尾標上標註的是D-4T的標識,意為:Dirct-injuction 4 stroke gasoline engine with Turbo.翻譯過來就是:四衝程缸內直噴渦輪增壓發動機。與市面上多數“少了排量就少了缸數”的小排量三缸渦輪增壓機不同,豐田這款1.2T發動機依然使用了直列四缸的布局,更多的缸數也意味着這款發動機在運轉過程中可以保持優良的平順性。

由於使用了豐田雙VVT-iW可變氣門正時技術、以及使用缸內直噴的噴油方式,這款發動機的升功率也會相應增高,而且這款發動機的內燃機熱力循環方式可以在奧拓循環和阿特金森循環之間切換,所以在保證了發動機工作效率的同時,也保證了燃油經濟性。

雷凌1.2T發動機最大馬力116匹,峰值扭矩185牛米,與之匹配的是一台模擬八個檔位的S-CVT無級變速箱,由於定位依舊是一款家用轎車,在轉向手感建立和油門響應的調校方式上還是保留了雷凌以往較為輕盈的特性,駕駛依舊平易近人。

除了發動機,它貴在哪?

很多人會覺得,既然換了渦輪增壓發動機肯定在技術成本上就變得更加昂貴了,但其實我們可以對比一下,作為以往雷凌的主力車型1.6L自然吸氣版本指導價格為10.78-13.08萬,而雷凌1.2T的指導價格為10.98-13.38萬,但雷凌1.2T的配置卻是對得起它的售價。

雷凌1.2T車型標配了發動機啟停系統,並且增加了車身穩定系統,牽引力控制系統,以及上坡輔助系統,這些主動安全配置的搭載在以往的1.6L車型中並沒有裝配,如此看來這兩三千的定價換來的是更多的安全配置,這波交易並不虧。

全文總結:與雙胞胎車型卡羅拉不同的是,雷凌1.2T的上市是作為全面取代1.6L自然吸氣版本的角色存在,所以從主動安全配置上看雷凌的性價比有所提高,而1.2T渦輪增壓的搭載動力表現也比原來作為主力的1.6L車型更好,所以從品控、定位以及車型本身的綜合產品力來說,雷凌仍舊是一款不錯的值得購買的合資緊湊型家用轎車。至於究竟是否值得購買,那就要看你對於日系車,對於豐田品牌的接受程度有多高了,本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

這台800萬的自主車 馬化騰 劉強東 雷軍人手一台

全球最快電動車發布 蔚來 Ep9動力方面搭載了4台高性能電機以及4個獨立變速箱,最終能夠輸出1,360匹馬力,0到200公里加速7。1秒,極速313公里。Ep9採用彈匣式可換電池系統,快充模式下充滿電僅需45分鐘,續航里程可達427公里。搭載的DRS可調擾流控制系統,包括三種可調模式的動態尾翼系統,和全尺寸底盤擴散器等空氣動力裝置,使得Ep9在每小時240公里的速度下能夠獲得高達24,000牛的下壓力。

思域1.0T正式發布

本次只有手動/自動各一個車型,價格分別是11.59萬和12.79萬元。相比1.5T的豪華型,前排側氣囊、前後排頭部氣簾、無鑰匙進入/啟動、電動天窗、后駐車雷達、全液晶儀錶盤、後排杯架、後排中央扶手、中控屏、前霧燈、后視鏡加熱、自動空調統統都沒有了,3缸發動機還簡配成這樣,大致意思就是說這已經是最低價了,所以 1.5T的車型該加價的加價,該等車的等車繼續等吧。

樂視 lucid motors

官方表明,從造電池到造車,Atieva算是進行了一個大的跨越,而其核心競爭力則是其獨特的電池冷卻和能量管理技術,電池的能量密度比其他競爭對手普遍高出20%,續航里程可以輕鬆超過480km,它的最大馬力可以達到1200匹,最後為了安全起見被設置在900匹。儘管如此,它的百公里加速時間也可以達到2.69秒,還沒看到實車在路上跑之前,我們就看看咯。

全球最快電動車發布 蔚來 Ep9

動力方面搭載了4台高性能電機以及4個獨立變速箱,最終能夠輸出1,360匹馬力,0到200公里加速7.1秒,極速313公里。Ep9採用彈匣式可換電池系統,快充模式下充滿電僅需45分鐘,續航里程可達427公里。

搭載的DRS可調擾流控制系統,包括三種可調模式的動態尾翼系統,和全尺寸底盤擴散器等空氣動力裝置,使得Ep9在每小時240公里的速度下能夠獲得高達24,000牛的下壓力。

紀錄片显示Ep9在10月12日德國紐博格林北環賽道進行的測試中,創造了7分05秒的最快電動汽車圈速,公司創始投資人包括雷軍,馬化騰,奶茶妹夫劉強東,整個發布會過程中透露Ep9的造價達到了120萬美元,據之前消息稱未來量產的首款車型將會是一款20萬左右的SUV車型,大家覺得怎麼樣?

新能源汽車騙補貼披露 金龍汽車罰款近8億

本月財政部向工信部抄送了《財政部行政處罰事項告知書》,確認蘇州金龍公司申報2015年度中央財政補貼資金的新能源汽車中,有1683輛車截至2015年底仍未完工,但在2015年卻提前辦理了機動車行駛證。涉及補助資金5.19億元。根據相關法規,工信部將責令蘇州金龍公司停止生產和銷售問題車型,暫停蘇州金龍公司申報新能源汽車推廣應用推薦車型資質,並將問題車型從《新能源汽車推廣應用推薦車型目錄》予以剔除,進行為期6個月整改,整改完成后,工信部將對整改情況進行驗收。

最終追回罰款達7.78億,其實不止金龍汽車,現在國內93家新能源汽車企業中有72家存在有騙補貼的行為,這一次的強力打壓會不會對以後的新能源電動車發展有所改進呢?本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

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

Redmi K40 系列正式發表: K40/K40 Pro/K40 Pro+ 三旗艦全系列搭載高通 8 系列旗艦處理器,售價約 8,645 元起

從去年底小米11 發表後,今年一月初小米的「友商」紅米 Redmi 也開始預告新一代 Redmi K40 系列即將推出的消息。當時在發表會前直接提前預先公佈售價為人民幣 2,999 元已經引起許多討論,不過在今(25)日晚間的 Redmi K40 系列發表會則帶來兩款高通 8 系列旗艦處理器共三款旗艦新機,性價比再次帶來更多驚喜。

Redmi K40 系列正式發表: K40/K40 Pro/K40 Pro+ 三旗艦全系列搭載高通 8 系列旗艦處理器,售價約 8,645 元起

在今日發表會之前, Redmi 官方就已經預告這次「K40 系列」雙旗艦發表會將帶來兩款旗艦級處理器的新機,不過最終一次發表了共三款旗艦機,分別為搭載高通 Snapdragon 870 處理器的 Redmi K40 以及搭載高通 Snapdragon 888 處理器的 Redmi K40 Pro 、 Redmi K40 Pro+ 。

Redmi K40

首先看到目前 Redmi K40 系列三款機型中最「入門」的 Redmi K40 標準版,以往 Redmi K 系列手機在標準版其實都算是中高階的定位,不過這次在 Redmi K40 仍然是定位為真旗艦的手機。

硬體規格方面, Redmi K40 為首批搭載 Qualcomm Snapdragon 870 處理器,安兔兔實測跑分達到 691,459 分。另外, Redmi K40 也配備最高 12GB LPDDR5 RAM 、256GB UFS 3.1 ROM 、Wi-Fi 6:

螢幕方面, Redmi K40 系列搭載 6.67 吋 FHD+(2400×1080)解析度的 E4 AMOLED 居中挖孔全螢幕,5,000,000:1 對比度、 1300nit、支持 DCI P-3 廣色域、HDR10+ 顯示、MEMC 動態補償等:

值得一提的是, Redmi K40 系列採用前置鏡頭挖孔直徑僅  2.76mm  也是目前全球最小:

Redmi K40 全系列正反面皆採用康寧第五代大猩猩玻璃保護:

另外, Redmi K40 系列也首次搭載 True Tone 顯示,能根據環境色溫自動調整顯示色溫:

Redmi K40 系列搭載環境光感應器,能自動感應亮度和色溫進行調節:

Redmi K40 系列也支持 120Hz 螢幕更新率:

這次 Redmi K40 系列則將觸控採樣率提升至 360Hz ,支持三指同時 360Hz 觸控採樣率: 

相機方面, Redmi K40 配備 4800 萬像素三鏡頭主相機,分別為 4800 萬像素主鏡頭(SONY IMX582 感光元件)、 800 萬像素 119° 超廣角鏡頭、 500 萬像素長焦微距鏡頭:

前相機部份 Redmi K40 則配備 2000 萬像素 AI 自然美顏相機,支持各項 AI 美顏功能:

電量方面, Redmi K40 內建 4520mAh 大容量電池並支持 33W 快速充電:

Redmi K40 系列機身重量僅 196g 、機身厚度也只有 7.8mm 相當輕薄:

對比目前市場上主流輕薄的手機, K40 系列的機身厚度真的相當輕薄:

另外, Redmi K40 全系列也採用側邊指紋辨識,可同時支持多功能智慧按鍵設定:

音效方面, Redmi K40 全系列配備立體聲雙揚聲器,通過 Hi-Res Audio、 Hi-Res Audio Wireless 認證,而且 Redmi K40 系列為 Redmi 首次支持 Dolby Atmos 杜比全景聲的機型:

其他方面,多功能 NFC、雙頻 GNSS 定位、紅外線遙控、IP53 等級生活防潑水、 X 軸線性馬達:

售價方面, Redmi K40 建議售價為人民幣 1,999 元(約合新台幣 8,645 元)起:

Redmi K40 共推出四種版本, 6GB+128GB 版本售價人民幣 1,999 元(約合新台幣 8,645 元)、8GB+128GB 版本售價人民幣 2,199 元(約合新台幣 9,510 元)、8GB+256GB 版本售價人民幣 2,499 元(約合新台幣 10,808 元)、12GB+256GB 售價人民幣 2699 元(約合新台幣 11,673 元): 

Redmi K40 Pro

Redmi K40 Pro 則是當初那款提前公佈起售價人民幣 2,999 元的高通 Snapdragon 888 旗艦手機,不過最終 Redmi 也在發表會帶來售價上的「驚喜」:

性能方面, Redmi K40 Pro 搭載 Qualcomm Snapdragon 888 處理器、配備最高 8GB LPDDR5 RAM 、 256GB ROM ,安兔兔性能跑分達到 744,877 分:

螢幕方面, Redmi K40 Pro 和 K40 標準版相同,皆搭載 6.67 吋 FHD+ 解析度 E4 AMOLED 螢幕,同樣支持 120Hz 螢幕更新率、360Hz 觸控採樣率、 DCI-P3 廣色域、5,000,000:1 和 TrueTone 環境色溫等:

Redmi K40 Pro 螢幕也通過 DisplayMate 最高 A+ 等級評價,不過 Redmi 品牌總經理盧偉冰表示 Redmi K40 標準版採用的螢幕也完全相同,只是差在 Redmi K40 Pro 有進行送測程序:

相機部份, Redmi K40 Pro 搭載 6400 萬像素三硬頭主相機,分別為 6400 萬像素主鏡頭、 800 萬像素 119° 超廣角鏡頭、 500 萬像素微距長焦鏡頭,前鏡頭則一樣配備 2000 萬像素 AI 美顏相機。 

電量方面, Redmi K40 Pro 一樣內建 4520mAh 大容量電池並支持 33W 快速充電:

網路連接方面, Redmi K40 Pro 系列則升級為  WiFi6e 規格:

Redmi K40 Pro 人民幣 2,799 元(約合新台幣 12,105 元)起:

Redmi K40 Pro 共推出三種規格配置, 6GB+128GB 版本售價人民幣 2,799 元(約合新台幣 12,105 元)、 8GB+128GB 版本售價人民幣 2,999 元(約合新台幣 12,970 元)、8GB+256GB 版本售價人民幣 3,299 元(約合新台幣 14,268 元):

Redmi K40 Pro+

除了 Redmi K40 和 Redmi K40 Pro ,今天也同步推出 Redmi K40 Pro+ 。不過 Redmi K40 Pro+ 在處理器、螢幕、電量、快充等規格幾乎和 Redmi K40 Pro 相同。

Redmi K40 Pro+ 在相機方面搭載 1.08 億像素三鏡頭主相機,分別為 1.08 億像素主鏡頭、800 萬像素 119° 超廣角鏡頭、 500 萬像素微距長焦鏡頭,前鏡頭則一樣配備 2000 萬像素 AI 美顏相機。 

主鏡頭也是 Redmi K40 Pro 與 K40 Pro + 規格上唯一的差異:

採用 1/1.52″ 的 Samsung ISOCELL HM2 感光元件:

Redmi K40 Pro+ 則推出 12GB+256GB 單一規格,建議售價人民幣 3,699 元(約合新台幣 15,999 元)。

配件方面, Redmi K40 全系列標配充電器、充電線、手機殼和出廠即貼上螢幕保護貼:

配件

在今日 Redmi K40 系列發表會也推出兩款專為遊戲玩家而生的遊戲週邊配件。首先是「大魔王肩鍵」能安裝在 Redmi K40 系列機身兩端延伸遊戲操控,售價為人民幣 59 元(約合新台幣 255 元):

另外,也推出一款「冰封電競背夾」,能在 1 分鐘降低 15°C 核心溫度,售價為人民幣 99 元(約合新台幣 428 元):

延伸閱讀:
Redmi MAX 86 超大螢幕電視發表:86 吋超大螢幕 4KHDR ,售價只要約 34,596 元

Redmi AirDots 3 真無線耳機全新繽紛配色驚喜登場:30小時超長續航、藍牙 5.2 、升級圈鐵雙單元提升音質表現

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

Redmi AirDots 3 真無線耳機全新繽紛配色驚喜登場:30小時超長續航、藍牙 5.2 、升級圈鐵雙單元提升音質表現

近年真無線藍牙耳機已經逐漸成為民眾聆聽音樂的選擇,在 Redmi K40 系列旗艦新機發表會除了 RedmiBook Pro 筆電和 Redmi MAX 86″ 超大螢幕電視,也推出全新一代的 Redmi AirDots 3 真無線藍牙耳機。外觀方面, Redmi AirDots 3 配色更加繽紛,擁有 30 小時最長續航、系列首次採用圈鐵雙單元大幅提升音質表現,藍牙 5.2 改善畫面不同步、卡頓等干擾問題讓連線更穩定。

Redmi AirDots 3 真無線耳機全新繽紛配色驚喜登場:30小時超長續航、藍牙 5.2 、升級圈鐵雙單元提升音質表現

過去幾代 Redmi AirDots 系列真無線藍牙耳機(台灣命名為小米 EarBuds 超值版),以平易近人的價格、超高的 CP 值,成為許多預算考量消費者在真無線耳機的首選之一。在 Redmi K40 系列新機發表會的最後,也悄悄推出了全新一代的新品 Redmi AirDots 3  。

外觀方面, Redmi AirDots 3 推出藍色、粉色和白色三款全新繽紛配色:

Redmi AirDots 3 也是 AirDots 系列首次搭載圈鐵雙單元(高頻動鐵+低拼動圈)的耳機,兼具低音聲場及低音細節,高中低頻都能還原均衡且飽滿的音色:

搭載全新 Qualcomm 3040 晶片,同時升級為藍牙 5.2 技術,有效改善過去影音不同步、卡頓干擾等問題,讓聲音傳輸更快、更穩。另外, Redmi AirDots 3 也支持 aptX Adaptive 音頻解碼技術,兼具高解析度音質與低延遲的優勢,無論追劇聽歌、娛樂遊戲都帶來更流暢的體驗:

續航方面, Redmi AirDots 3 單只耳機充滿電可使用約 7 小時,搭配內建 600mAh 充電盒最長續航可達 30 小時:

Redmi AirDots 3 支持智慧感應檢測,採用內建紅外線光學感應器可即時幾冊耳機佩戴狀態,入耳瞬間即可感應,摘下耳機自動暫停播放。

Redmi AirDots 3 在體驗上也有明顯升級,此次將過去實體按鍵操控改為觸控操作,解決耳機外觀按鈕設計的突兀、減緩案件壓迫感。使用者可透過輕觸右側耳機兩下暫停/播放音樂、輕觸左耳兩下喚醒語音助理,輕觸多功能鍵接聽/掛斷電話:

Redmi AirDots 3 支持語音助理,可透過輕觸耳機喚醒:

搭配支持 MIUI 螢幕彈窗連接功能的智慧型手機,只要打開耳機盒蓋在手機端螢幕即可彈出連接畫面進行連接,同時也會在手機畫面顯示耳機、充電盒的電量資訊:

其他機身細節部分, Redmi AirDots 3 採用 USB Type-C 充電接口,充電盒具備一鍵連接按鈕:

在 Redmi AirDots 3 充電盒外配備電量指示燈,用戶能根據亮燈情況瞭解充電盒剩餘電量,耳機本體也支持 IPX4 等級生活防潑水:

售價方面, Redmi AirDots 3 真無線藍牙耳機建議售價為人民幣 199 元(約合新台幣 860 元)。

圖片/消息來源:小米官網(中國)

延伸閱讀:
Redmi K40 系列正式發表: K40/K40 Pro/K40 Pro+ 三旗艦全系列搭載高通 8 系列旗艦處理器,售價約 8,645 元起

Redmi MAX 86 超大螢幕電視發表:86 吋超大螢幕 4KHDR ,售價只要約 34,596 元

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

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