白米買回來吃不完長蟲了?教你用花椒防蟲,就不會浪費了

10{icon} {views}

家裡的米買回來短期內吃不完,放著放著就長蟲了。被蟲子糟蹋了的米就只好扔掉了。學會實用的小妙招,有效預防米生蟲,就不怕浪費了!

造成米生蟲的主要原因

一是糧食還在地裡時候本身就帶上了米蟲的卵,經過糧食工廠加工後仍然粘在一些大米邊上,蟲卵是非常小的,人的肉眼根本看不到。這種蟲卵在夏季高溫時容易孵化變成小蟲子,周圍有牠愛吃的大米,所以牠吃得多,繁殖的就快。

另一個原因呢,就是我們不注重米缸內部的衛生,只用肉眼看到它乾凈就可以了,但其實蟲卵容易隱藏在米缸底,米袋縫,人們很難將它清理出來。 當氣候適宜時,這些蟲卵又會孵化出來爬進米袋。

所以對於米生蟲的問題,我們還是要從根本解決問題。用這幾個小辦法,讓你家的米再也不生蟲。

1.瓶裝預防大米生蟲

買回裡的糧食取出來放到冰箱裡冷凍室裡,冷凍幾個小時,凍死蟲卵。將家中塑料的油桶洗乾凈晾乾,然後將糧食裝進桶中擰緊蓋子,放到陰涼處,保持大米乾燥就不會不生蟲了。

2.特殊氣味防蟲

日常生活中有很多物品,散發出特殊的氣味能夠防止米生蟲。大家可以把大蒜、柚子皮、橘子皮、海帶等單獨放入小布袋然後放在米中。但是,一定要注意不要放太多,以免串味,影響米的口感。

3.乾燥防蟲

買回來的米,拆開包裝後,在太陽底下曬一曬,徹底乾燥。然後再放到密封的容器中保存起來,就不容易生蟲子了。

4.白酒防蟲

將裝有50克的白酒,不蓋蓋,然後將裝有酒的瓶子埋在米中,瓶口要高於米,再將米袋封口。袋中的空氣越來越少,酒中揮發的乙醇有滅蟲、殺菌的作用,所以就可以防止生蟲了。

5.花椒防蟲

先在鍋裡放適量的水,再取花椒放在鍋裡煮。然後將布袋放入浸泡,撈出曬乾。將花椒放入小布袋中,分別放於米的下部和中部,紮緊布口袋,放於陰涼通風處,大米就不會生蟲了。

6.離牆離地

米放在離牆離地的地方,可以受到好的通風,從根本上避免受潮等因素,遠離生蟲。

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

【精選推薦文章】

※聽過「電子菸」嗎?想知道與一般傳統香菸有何不同嗎?

電子煙能幫助戒菸嗎?專家學者以健康觀點帶您來了解 !

※新手該如何選擇電子菸口味及濃度呢?

Java 數據持久化系列之JDBC

12{icon} {views}

前段時間小冰在工作中遇到了一系列關於數據持久化的問題,在排查問題時發現自己對 Java 後端的數據持久化框架的原理都不太了解,只有不斷試錯,因此走了很多彎路。於是下定決心,集中精力學習了持久化相關框架的原理和實現,總結出這個系列。

上圖是我根據相關源碼和網上資料總結的有關 Java 數據持久化的架構圖(只代表本人想法,如有問題,歡迎留言指出)。最下層就是今天要講的 JDBC,上一層是數據庫連接池層,包括 HikariCP 和 Druid等;再上一層是分庫分表中間件,比如說 ShardingJDBC;再向上是對象關係映射層,也就是 ORM,包括 Mybatis 和 JPA;最上邊是 Spring 的事務管理。

本系列的文章會依次講解圖中各個開源框架的基礎使用,然後描述其原理和代碼實現,最後會着重分析它們之間是如何相互集成和配合的。

廢話不多說,我們先來看 JDBC。

JDBC 定義

JDBC是Java Database Connectivity的簡稱,它定義了一套訪問數據庫的規範和接口。但它自身不參与數據庫訪問的實現。因此對於目前存在的數據庫(譬如Mysql、Oracle)來說,要麼數據庫製造商本身提供這些規範與接口的實現,要麼社區提供這些實現。

如上圖所示,Java 程序只依賴於 JDBC API,通過 DriverManager 來獲取驅動,並且針對不同的數據庫可以使用不同的驅動。這是典型的橋接的設計模式,把抽象 Abstraction 與行為實現Implementation 分離開來,從而可以保持各部分的獨立性以及應對他們的功能擴展。

JDBC 基礎代碼示例

單純使用 JDBC 的代碼邏輯十分簡單,我們就以最為常用的MySQL 為例,展示一下使用 JDBC 來建立數據庫連接、執行查詢語句和遍歷結果的過程。

public static void connectionTest(){

    Connection connection = null;
    Statement statement = null;
    ResultSet resultSet = null;

    try {
        // 1. 加載並註冊 MySQL 的驅動
        Class.forName("com.mysql.cj.jdbc.Driver").newInstance();

        // 2. 根據特定的數據庫連接URL,返回與此URL的所匹配的數據庫驅動對象
        Driver driver = DriverManager.getDriver(URL);
        // 3. 傳入參數,比如說用戶名和密碼
        Properties props = new Properties();
        props.put("user", USER_NAME);
        props.put("password", PASSWORD);

        // 4. 使用數據庫驅動創建數據庫連接 Connection
        connection = driver.connect(URL, props);

        // 5. 從數據庫連接 connection 中獲得 Statement 對象
        statement = connection.createStatement();
        // 6. 執行 sql 語句,返回結果
        resultSet = statement.executeQuery("select * from activity");
        // 7. 處理結果,取出數據
        while(resultSet.next())
        {
            System.out.println(resultSet.getString(2));
        }

        .....
    }finally{
        // 8.關閉鏈接,釋放資源  按照JDBC的規範,使用完成后管理鏈接,
        // 釋放資源,釋放順序應該是: ResultSet ->Statement ->Connection
        resultSet.close();
        statement.close();
        connection.close();
    }
}

代碼中有詳細的註釋描述每一步的過程,相信大家也都對這段代碼十分熟悉。

唯一要提醒的是使用完之後的資源釋放順序。按照 JDBC 規範,應該依次釋放 ResultSet,Statement 和 Connection。當然這隻是規範,很多開源框架都沒有嚴格的執行,但是 HikariCP卻嚴格准守了,它可以帶來很多優勢,這些會在之後的文章中講解。

上圖是 JDBC 中核心的 5 個類或者接口的關係,它們分別是 DriverManager、Driver、Connection、Statement 和 ResultSet。

DriverManager 負責管理數據庫驅動程序,根據 URL 獲取與之匹配的 Driver 具體實現。Driver 則負責處理與具體數據庫的通信細節,根據 URL 創建數據庫連接 Connection。

Connection 表示與數據庫的一個連接會話,可以和數據庫進行數據交互。Statement 是需要執行的 SQL 語句或者存儲過程語句對應的實體,可以執行對應的 SQL 語句。ResultSet 則是 Statement 執行后獲得的結果集對象,可以使用迭代器從中遍曆數據。

不同數據庫的驅動都會實現各自的 Driver、Connection、Statement 和 ResultSet。而更為重要的是,眾多數據庫連接池和分庫分表框架也都是實現了自己的 Connection、Statement 和 ResultSet,比如說 HikariCP、Druid 和 ShardingJDBC。我們接下來會經常看到它們的身影。

接下來,我們依次看一下這幾個類及其涉及的操作的原理和源碼實現。

載入 Driver 實現

可以直接使用 Class#forName的方式來載入驅動實現,或者在 JDBC 4.0 后則基於 SPI 機制來導入驅動實現,通過在 META-INF/services/java.sql.Driver 文件中指定實現類的方式來導入驅動實現,下面我們就來看一下兩種方式的實現原理。

Class#forName 作用是要求 JVM 查找並加載指定的類,如果在類中有靜態初始化器的話,JVM 會執行該類的靜態代碼段。加載具體 Driver 實現時,就會執行 Driver 中的靜態代碼段,將該 Driver 實現註冊到 DriverManager 中。我們來看一下 MySQL 對應 Driver 的具體代碼。它就是直接調用了 DriverManager的 registerDriver 方法將自己註冊到其維護的驅動列表中。

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        // 直接調用 DriverManager的 registerDriver 將自己註冊到其中
        DriverManager.registerDriver(new Driver());
    }
}

SPI 機制使用 ServiceLoader 類來提供服務發現機制,動態地為某個接口尋找服務實現。當服務的提供者提供了服務接口的一種實現之後,必須根據 SPI 約定在 META-INF/services 目錄下創建一個以服務接口命名的文件,在該文件中寫入實現該服務接口的具體實現類。當服務調用 ServiceLoader 的 load 方法的時候,ServiceLoader 能夠通過約定的目錄找到指定的文件,並裝載實例化,完成服務的發現。

DriverManager 中的 loadInitialDrivers 方法會使用 ServiceLoader 的 load 方法加載目前項目路徑下的所有 Driver 實現。

public class DriverManager {
    // 程序中已經註冊的Driver具體實現信息列表。registerDriver類就是將Driver加入到這個列表
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    // 使用ServiceLoader 加載具體的jdbc driver實現
    static {
        loadInitialDrivers();
    }
    private static void loadInitialDrivers() {
        // 省略了異常處理
        // 獲得系統屬性 jdbc.drivers 配置的值
        String drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                // 通過 ServiceLoader 獲取到Driver的具體實現類,然後加載這些類,會調用其靜態代碼塊
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
                return null;
            }
        });

        String[] driversList = drivers.split(":");
        // for 循環加載系統屬性中的Driver類。
        for (String aDriver : driversList) {
            println("DriverManager.Initialize: loading " + aDriver);
            Class.forName(aDriver, true,
                    ClassLoader.getSystemClassLoader());
        }
    }
}

比如說,項目引用了 MySQL 的 jar包 mysql-connector-java,在這個 jar 包的 META-INF/services 文件夾下有一個叫 java.sql.Driver 的文件,文件的內容為 com.mysql.cj.jdbc.Driver。而 ServiceLoader 的 load 方法找到這個文件夾下的文件,讀取文件的內容,然後加載出文件內容所指定的 Driver 實現。而正如之前所分析的,這個 Driver 類被加載時,會調用 DriverManager 的 registerDriver 方法,從而完成了驅動的加載。

Connection、Statement 和 ResultSet

當程序加載完具體驅動實現后,接下來就是建立與數據庫的連接,執行 SQL 語句並且處理返回結果了,其過程如下圖所示。

建立 Connection

創建 Connection 連接對象,可以使用 Driver 的 connect 方法,也可以使用 DriverManager 提供的 getConnection 方法,此方法通過 url 自動匹配對應的驅動 Driver 實例,然後還是調用對應的 connect 方法返回 Connection 對象實例。

建立 Connection 會涉及到與數據庫進行網絡請求等大量費時的操作,為了提升性能,往往都會引入數據庫連接池,也就是說復用 Connection,免去每次都創建 Connection 所消耗的時間和系統資源。

Connection 默認情況下,對於創建的 Statement 執行的 SQL 語句都是自動提交事務的,即在 Statement 語句執行完后,自動執行 commit 操作,將事務提交,結果影響到物理數據庫。為了滿足更好地事務控制需求,我們也可以手動地控制事務,手動地在Statement 的 SQL 語句執行後進行 commit 或者rollback。

connection = driver.connect(URL, props);
// 將自動提交關閉
connection.setAutoCommit(false);

statement = connection.createStatement();
statement.execute("INSERT INTO activity (activity_id, activity_name, product_id, start_time, end_time, total, status, sec_speed, buy_limit, buy_rate) VALUES (1, '香蕉大甩賣', 1, 530871061, 530872061, 20, 0, 1, 1, 0.20);");
// 執行後手動 commit
statement.getConnection().commit();

Statement

Statement 的功能在於根據傳入的 SQL 語句,將傳入 SQL 經過整理組合成數據庫能夠識別的執行語句(對於靜態的 SQL 語句,不需要整理組合;而對於預編譯SQL 語句和批量語句,則需要整理),然後傳遞 SQL 請求,之後會得到返回的結果。對於查詢 SQL,結果會以 ResultSet 的形式返回。

當你創建了一個 Statement 對象之後,你可以用它的三個執行方法的任一方法來執行 SQL 語句。

  • boolean execute(String SQL) : 如果 ResultSet 對象可以被檢索,則返回的布爾值為 true ,否則返回 false 。當你需要使用真正的動態 SQL 時,可以使用這個方法來執行 SQL DDL 語句。
  • int executeUpdate(String SQL) : 返回執行 SQL 語句影響的行的數目。使用該方法來執行 SQL 語句,是希望得到一些受影響的行的數目,例如,INSERT,UPDATE 或 DELETE 語句。
  • ResultSet executeQuery(String SQL) : 返回一個 ResultSet 對象。當你希望得到一個結果集時使用該方法,就像你使用一個 SELECT 語句。

對於不同類型的 SQL 語句,Statement 有不同的接口與其對應。

接口 | 介紹
-|-| Statement | 適合運行靜態 SQL 語句,不接受動態參數 PreparedStatement | 計劃多次使用並且預先編譯的 SQL 語句,接口需要傳入額外的參數 CallableStatement | 用於訪問數據庫存儲過程

Statement 主要用於執行靜態SQL語句,即內容固定不變的SQL語句。Statement每執行一次都要對傳入的SQL語句編譯一次,效率較差。而 PreparedStatement則解決了這個問題,它會對 SQL 進行預編譯,提高了執行效率。

PreparedStatement pstmt = null;
    try {
        String SQL = "Update activity SET activity_name = ? WHERE activity_id = ?";
        pstmt = connection.prepareStatement(SQL);
        pstmt.setString(1, "測試");
        pstmt.setInt(2, 1);
        pstmt.executeUpdate();
    }
    catch (SQLException e) {
    }
    finally {
        pstmt.close();
    }
}

除此之外, PreparedStatement 還可以預防 SQL 注入,因為 PreparedStatement 不允許在插入參數時改變 SQL 語句的邏輯結構。

PreparedStatement 傳入任何數據不會和原 SQL 語句發生匹配關係,無需對輸入的數據做過濾。如果用戶將”or 1 = 1”傳入賦值給佔位符,下述SQL 語句將無法執行:select * from t where username = ? and password = ?。

ResultSet

當 Statement 查詢 SQL 執行后,會得到 ResultSet 對象,ResultSet 對象是 SQL語句查詢的結果集合。ResultSet 對從數據庫返回的結果進行了封裝,使用迭代器的模式可以逐條取出結果集中的記錄。

while(resultSet.next()) {
    System.out.println(resultSet.getString(2));
}

ResultSet 一般也建議使用完畢直接 close 掉,但是需要注意的是關閉 ResultSet 對象不關閉其持有的 Blob、Clob 或 NClob 對象。 Blob、Clob 或 NClob 對象在它們被創建的的事務期間會一直持有效,除非其 free 函數被調用。

參考

    • https://blog.csdn.net/wl044090432/article/details/60768342
    • https://blog.csdn.net/luanlouis/article/details/29850811

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

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

平板收購,iphone手機收購,二手筆電回收,二手iphone收購-全台皆可收購

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

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

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

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

http協議詳細介紹

9{icon} {views}

1.1 HTTP協議簡介
我們日常生活中經常會使用瀏覽器訪問Web站點,但是大家有思考過在這個過程中到底發生了什麼嗎?為什麼我們在瀏覽器地址欄上面輸入要訪問的URL后就可以訪問到Web頁面呢?

1.1.1 瀏覽器背後的故事
當我們在瀏覽器地址欄上輸入要訪問的URL后,瀏覽器會分析出URL上面的域名,然後通過DNS服務器查詢出域名映射的IP地址,瀏覽器根據查詢到的IP地址與Web服務器進行通信,而通信的協議就是HTTP協議。

我們可以把這個過程類比成一個電話對話的過程。當我們要打電話給某個人,首先要知道對方的電話號碼,然後進行撥號。打通電話后我們會進行對話,當然要對話肯定需要共同的語言,如果一個人說國語,而另一個人說英語,那肯定不能進行溝通的。在本例中,電話號碼相當於上面的IP地址,而共同語言相當於HTTP協議。

我們通過一個簡單的圖來闡述這個過程:

瀏覽器與Web服務器使用HTTP協議進行通信,那麼什麼是HTTP協議呢?接下來我們會詳細介紹HTTP協議的相關知識。

1.1.2 TCP/IP協議
HTTP協議是構建在TCP/IP協議之上的,是TCP/IP協議的一個子集,所以要理解HTTP協議,有必要先了解下TCP/IP協議相關的知識。

由於TCP/IP協議族包含眾多的協議,在這裏我們無法一一討論。接下來,我們僅介紹理解HTTP協議需要掌握的TCP/IP協議族的一些相關知識點。如果想深入理解TCP/IP協議,可以參考經典書籍《TCP/IP詳解》。

TCP/IP協議族分層

TCP/IP協議族是由一個四層協議組成的系統,這四層分別為:應用層、傳輸層、網絡層和數據鏈路層。如圖所示

分層的好處是把各個相對獨立的功能解耦,層與層之間通過規定好的接口來通信。如果以後需要修改或者重寫某一個層的實現,只要接口保持不變也不會影響到其他層的功能。接下來,我們將會介紹各個層的主要作用。

1) 應用層

應用層一般是我們編寫的應用程序,其決定了向用戶提供的應用服務。應用層可以通過系統調用與傳輸層進行通信。

處於應用層的協議非常多,比如:FTP(File Transfer Protocol,文件傳輸協議)、DNS(Domain Name System,域名系統)和我們本章討論的HTTP(HyperText Transfer Protocol,超文本傳輸協議)等。

2) 傳輸層

傳輸層通過系統調用嚮應用層提供處於網絡連接中的兩台計算機之間的數據傳輸功能。

在傳輸層有兩個性質不同的協議:TCP(Transmission Control Protocol,傳輸控制協議)和UDP(User Data Protocol,用戶數據報協議)。

3) 網絡層

網絡層用來處理在網絡上流動的數據包,數據包是網絡傳輸的最小數據單位。該層規定了通過怎樣的路徑(傳輸路線)到達對方計算機,並把數據包傳輸給對方。

4) 鏈路層

鏈路層用來處理連接網絡的硬件部分,包括控制操作系統、硬件設備驅動、NIC(Network Interface Card,網絡適配器)以及光纖等物理可見部分。硬件上的範疇均在鏈路層的作用範圍之內。

數據包封裝

上層協議數據是如何轉變為下層協議數據的呢?這是通過封裝(encapsulate)來實現的。應用程序數據在發送到物理網絡之前,會沿着協議棧從上往下傳遞。每層協議都將在上層協議數據的基礎上加上自己的頭部信息(鏈路層還會加上尾部信息),以為實現該層功能提供必要的信息。如圖所示:

發送端發送數據時,數據會從上層傳輸到下層,且每經過一層都會被打上該層的頭部信息。而接收端接收數據時,數據會從下層傳輸到上層,傳輸前會把下層的頭部信息刪除。過程如圖所示:

數據傳輸過程

由於下層協議的頭部信息對上層協議是沒有實際的用途,所以在下層協議傳輸數據給上層協議的時候會把該層的頭部信息去掉,這個封裝過程對於上層協議來說是完全透明的。這樣做的好處是,應用層只需要關心應用服務的實現,而不用管底層的實現。

TCP三次握手

從上面的介紹可知,傳輸層協議主要有兩個:TCP協議和UDP協議。TCP協議相對於UDP協議的特點是:TCP協議提供面向連接、字節流和可靠的傳輸。

使用TCP協議進行通信的雙方必須先建立連接,然後才能開始傳輸數據。TCP連接是全雙工的,也就是說雙方的數據讀寫可以通過一個連接進行。為了確保連接雙方可靠性,在雙方建立連接時,TCP協議採用了三次握手(Three-way handshaking)策略。
過程如圖所示:

TCP協議三次握手的描述如下:

第一次握手:客戶端發送帶有SYN標誌的連接請求報文段,然後進入SYN_SEND狀態,等待服務端的確認。

第二次握手:服務端接收到客戶端的SYN報文段后,需要發送ACK信息對這個SYN報文段進行確認。同時,還要發送自己的SYN請求信息。服務端會將上述的信息放到一個報文段(SYN+ACK報文段)中,一併發送給客戶端,此時服務端將會進入SYN_RECV狀態。

第三次握手:客戶端接收到服務端的SYN+ACK報文段后,會想服務端發送ACK確認報文段,這個報文段發送完畢后,客戶端和服務端都進入ESTABLISHED狀態,完成TCP三次握手。

當三次握手完成后,TCP協議會為連接雙方維持連接狀態。為了保證數據傳輸成功,接收端在接收到數據包后必須發送ACK報文作為確認。如果在指定的時間內(這個時間稱為重新發送超時時間),發送端沒有接收到接收端的ACK報文,那麼就會重發超時的數據。

1.1.3 DNS服務
前面介紹了與HTTP協議有着密切關係的TCP/IP協議,接下來介紹的DNS服務也是與HTTP協議有着密不可分的關係。

通常我們訪問一個網站,使用的是主機名或者域名來進行訪問的。因為相對於IP地址(一組純数字),域名更容易讓人記住。但TCP/IP協議使用的是IP地址進行訪問的,所以必須有個機制或服務把域名轉換成IP地址。DNS服務就是用來解決這個問題的,它提供域名到IP地址之間的解析服務。

如下圖所示,展示了DNS服務把域名解析成IP地址的過程:

DNS服務是通過DNS協議進行通信的,而DNS協議跟HTTP協議一樣也是應用層協議。由於我們的重點是HTTP協議,所以這裏不打算對DNS協議進行詳細的分析,我們只需要知道可以通過DNS服務把域名解析成IP地址即可。

1.1.4 HTTP與TCP/IP、DNS的關係
到現在,我們介紹了與HTTP協議有密切關係的TCP/IP協議和DNS服務,接下來我們通過下圖來整理一下HTTP協議與它們之間的關係:

HTTP與TCP/IP、DNS的關係
從上圖中可以知道,當客戶端訪問Web站點時,首先會通過DNS服務查詢到域名的IP地址。然後瀏覽器生成HTTP請求,並通過TCP/IP協議發送給Web服務器。Web服務器接收到請求後會根據請求生成響應內容,並通過TCP/IP協議返回給客戶端。

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

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

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品在網路上成為最夯、最多人討論的話題?

※高價收購3C產品,價格不怕你比較

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

salesforce lightning零基礎學習(十五) 公用組件之 獲取表字段的Picklist(多語言),salesforce 零基礎學習(六十二)獲取sObject中類型為Picklist的field values(含record type)

8{icon} {views}

此篇參考:

 我們在lightning中在前台會經常碰到獲取picklist的values然後使用select option進行渲染成下拉列表,此篇用於實現針對指定的sObject以及fieldName(Picklist類型)獲取此字段對應的所有可用的values的公用組件。因為不同的record type可能設置不同的picklist values,所以還有另外的參數設置針對指定的record type developer name去獲取指定的record type對應的Picklist values.

一. PicklistService公用組件聲明實現

Common_PicklistController.cls:有三個形參,其中objectName以及fieldName是必填參數,recordTypeDeveloperName為可選參數。

 1 public without sharing class Common_PicklistController {
 2     
 3     @AuraEnabled(cacheable=true)
 4     public static List<Map<String,String>> getPicklistValues(String objectName, String fieldName,String recordTypeDeveloperName) {
 5         //1. use object and field name get DescribeFieldResult and also get all the picklist values
 6         List<Schema.PicklistEntry> allPicklistValuesByField;
 7         try {
 8             List<Schema.DescribeSobjectResult> objDescriptions = Schema.describeSObjects(new List<String>{objectName});
 9             Schema.SObjectField field = objDescriptions[0].fields.getMap().get(fieldName);
10             Schema.DescribeFieldResult fieldDescribeResult = field.getDescribe();
11             allPicklistValuesByField = fieldDescribeResult.getPicklistValues();
12         } catch (Exception e) {
13             throw new AuraHandledException('Failed to retrieve values : '+ objectName +'.'+ fieldName +': '+ e.getMessage());
14         }
15         
16         //2. get all active field name -> label map
17         List<Map<String,String>> activeOptionMapList = new List<Map<String,String>>();
18         Map<String,String> optionValue2LabelMap = new Map<String,String>();
19         List<String> optionValueList;
20         for(Schema.PicklistEntry entry : allPicklistValuesByField) {
21             if (entry.isActive()) {
22                 System.debug(LoggingLevel.INFO, '*** entry: ' + JSON.serialize(entry));
23                 optionValue2LabelMap.put(entry.getValue(), entry.getLabel());
24             }
25         }
26 
27         //3. generate list with option value(with/without record type)
28         if(String.isNotBlank(recordTypeDeveloperName)) {
29             optionValueList = PicklistDescriber.describe(objectName,recordTypeDeveloperName,fieldName);
30         } else {
31             optionValueList = new List<String>(optionValue2LabelMap.keySet());
32         }
33 
34         //4. generate and format result
35         if(optionValueList != null) {
36             for(String option : optionValueList) {
37                 String optionLabel = optionValue2LabelMap.get(option);
38                 Map<String,String> optionDataMap = new Map<String,String>();
39                 optionDataMap.put('value',option);
40                 optionDataMap.put('label', optionLabel);
41                 activeOptionMapList.add(optionDataMap);
42             }
43         }
44 
45         return activeOptionMapList;
46     }
47 }

 Common_PicklistService.cmp:聲明了getPicklistInfo方法,有以下三個主要參數.objectName對應sObject的API名字,fieldName對應的此sObject中的Picklist類型的字段,recordTypeDeveloperName對應這個sObject的record type的developer name

1 <aura:component access="global" controller="Common_PicklistController">
2     <aura:method access="global" name="getPicklistInfo"  description="Retrieve active picklist values and labels mapping with(without) record type" action="{!c.getPicklistInfoAction}">
3         <aura:attribute type="String" name="objectName" required="true" description="Object name"/>
4         <aura:attribute type="String" name="fieldName" required="true" description="Field name"/>
5         <aura:attribute type="String" name="recordTypeDeveloperName" description="record type developer name"/>
6         <aura:attribute type="Function" name="callback" required="true" description="Callback function that returns the picklist values and labels mapping as [{value: String, label: String}]"/>
7     </aura:method>
8 </aura:component>

 Common_PicklistServiceController.js: 獲取傳遞過來的參數,調用後台方法並對結果放在callback中。

 1 ({
 2     getPicklistInfoAction : function(component, event, helper) {
 3         const params = event.getParam('arguments');
 4         const action = component.get('c.getPicklistValueList');
 5         action.setParams({
 6             objectName : params.objectName,
 7             fieldName : params.fieldName,
 8             recordTypeDeveloperName : params.recordTypeDeveloperName
 9         });
10         action.setCallback(this, function(response) {
11             const state = response.getState();
12             if (state === 'SUCCESS') {
13                 params.callback(response.getReturnValue());
14             } else if (state === 'ERROR') {
15                 console.error('failed to retrieve picklist values for '+ params.objectName +'.'+ params.fieldName);
16                 const errors = response.getError();
17                 if (errors) {
18                     console.error(JSON.stringify(errors));
19                 } else {
20                     console.error('Unknown error');
21                 }
22             }
23         });
24 
25         $A.enqueueAction(action);
26     }
27 })

二. 公用組件調用

 上面介紹了公用組件以後,下面的demo是如何調用。

SimplePicklistDemo引入Common_PicklistService,設置aura:id用於後期獲取到此component,從而調用方法

 1 <aura:component implements="flexipage:availableForAllPageTypes">
 2     <!-- include common picklist service component -->
 3     <c:Common_PicklistService aura:id="service"/>
 4     <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
 5 
 6     <aura:attribute name="accountTypeList" type="List"/>
 7     <aura:attribute name="accountTypeListByRecordType" type="List"/>
 8 
 9     <lightning:layout verticalAlign="center" class="x-large">
10         <lightning:layoutItem flexibility="auto" padding="around-small">
11             <lightning:select label="account type">
12                 <aura:iteration items="{!v.accountTypeList}" var="type">
13                     <option value="{!type.value}" text="{!type.label}"></option>
14                 </aura:iteration>
15             </lightning:select>
16         </lightning:layoutItem>
17 
18         <lightning:layoutItem flexibility="auto" padding="around-small">
19             <lightning:select label="account type with record type">
20                 <aura:iteration items="{!v.accountTypeListByRecordType}" var="type">
21                     <option value="{!type.value}" text="{!type.label}"></option>
22                 </aura:iteration>
23             </lightning:select>
24         </lightning:layoutItem>
25     </lightning:layout>
26     
27 </aura:component>

SimplePicklistDemoController.js:初始化方法用於獲取到公用組件component然後獲取Account的type的values,第一個是獲取所有的values/labels,第二個是獲取指定record type的values/labels。

 1 ({
 2     doInit : function(component, event, helper) {
 3         const service = component.find('service');
 4         service.getPicklistInfo('Account','type','',function(result) {
 5             component.set('v.accountTypeList', result);
 6         });
 7 
 8         service.getPicklistInfo('Account','type','Business_Account',function(result) {
 9             component.set('v.accountTypeListByRecordType',result);
10         });
11     }
12 })

三.效果展示:

1. account type的展示方式

 

 2. account type with record type的展示方式。

 

 總結:篇中介紹了Picklist values針對with/without record type的公用組件的使用,感興趣的可以進行優化,篇中有錯誤的歡迎指出,有不懂的歡迎留言。

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

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

※高價3c回收,收購空拍機,收購鏡頭,收購 MACBOOK-更多收購平台討論專區

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

收購3c瘋!各款手機、筆電、相機、平板,歡迎來詢價!

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

如何使用偽類選擇器

6{icon} {views}

偽類選擇器介紹

  • 偽類選擇器就是用來給超級鏈接設置不同的狀態樣式。
  • 超級鏈接分為4種狀態如:正常狀態、訪問過後狀態、鼠標放上狀態、激活狀態。

偽類選擇器說明表

選擇器 描述
:link 向未被訪問的超級鏈接添加樣式,正常狀態。
:visited 向已經被訪問的超級鏈接添加樣式,訪問過後狀態。
:hover 當鼠標懸浮在超級鏈接上方時,向超級鏈接添加樣式,鼠標放上狀態。
:active 鼠標放在超級鏈接上並且點擊的一瞬間,向超級鏈接添加樣式,激活狀態。

偽類選擇器實踐

  • 讓我們進入偽類選擇器實踐,實踐內容將超級鏈接4種狀態進行演示,演示效果如:將向未被訪問的超級鏈接文本顏色設置為紅色、已經被訪問的超級鏈接文本顏色設置為綠色、當鼠標懸浮在超級鏈接上文本顏色設置為紫色、用鼠標點擊超級鏈接的一瞬間文本顏色設置為藍色

  • 代碼塊

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>偽類選擇器</title>
    <style>
        a:link{
            color:red;
        }
        a:visited{
            color: lime;
        }
        a:hover{
            color: purple;
        }
        a:active{
            color: blue;
        }
    </style>
</head>
  
<body>
    <a href="https://www.cnblogs.com/lq000122/">微笑是最初的信仰</a>
</body>
</html>
  • 正常狀態結果圖

  • 鼠標放上狀態結果圖

  • 激活狀態結果圖

  • 訪問過後狀態

總結

  • 超級鏈接的不同狀態他其實是由順序,也就是說偽類選擇器設置其實是順序的,如果按照偽類選擇器的順序,那麼設置的樣式就不會被渲染。
  • 順序:linkvisitedhoveractive

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

3c收購,鏡頭 收購有可能以全新價回收嗎?

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

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

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

賣IPHONE,iPhone回收,舊換新!教你怎麼賣才划算?

Clean Code 筆記 之 第四章 如何應用註釋

14{icon} {views}

繼上一篇筆記之後,今天我們討論一下 代碼中是存在註釋是否是一件好的事情。

 

在我們開發的過程中講究“名副其實,見名識意”,這也往往是很多公司的要求,但是有了這些要求是不是我們的代碼中如果存在註釋是不是意味着我們的 函數,變量,以及類 的命名就不符合了“名副其實,見名識意”。

我們先區分一下註釋的類別,註釋一般分為以下幾種:

  • 1, 單行註釋
  • 2, 多行註釋
  • 3, 文檔註釋
  • 4, #region 摺疊註釋,可以將 代碼摺疊

 註釋的類別

1, 單行註釋:

在以 “//” 開頭,用以說明一行代碼的作用放置位置 看習慣或者公司要求合理就行。常用於函數內部,在很多的開源代碼中文件的頭部我同樣見到很多人使用單行註釋進行說明,靈活就好。
體現形式如下:

 public List<string> getVipUserNameByUserType()
          {
            // Vip user name list
            var vipUserNames = new List<string>();

            foreach (var user in Users)
            {

                if (user.Type = "VIP")

                    vipUserNames.Add(user.Name);
            }
            return vipUserNames;

          }

View Code

2, 多行註釋:

以“/*”開頭 “*/” 結尾 常用於描述說明一段代碼以及類註釋或者說代碼塊常用於文件的頂部。說明作者信息,時間如果是開源的這包含開源的更多說明。
通常使用如下:

/*
    * 作者:〈版權〉
    * 描述:〈描述〉
    * 創建時間:YYYY-MM-DD
    * 作用:
*/

View Code

3, 文檔註釋:

也就是常用的XML 註釋:它的說明性更加的強烈,多用於類以及函數上,當然屬性上同樣可以使用:
如下所示:

        /// <summary>
        /// MyClass
        /// </summary>
        public class MyClass
        {
            /// <summary>
            /// MyProperty
            /// </summary>
            public int MyProperty { get; set; }
            /// <summary>
            /// MyMethod
            /// </summary>
            public void MyMethod(){  }
        }

View Code

以下是官方建議的文檔標記 點擊標籤會制動跳轉

 

4, #region : 摺疊註釋,常用於描述多個函數的基本作用

書中最喜歡的話

好的註釋不能美化糟糕的代碼,真正好的註釋是你想盡辦法不去謝的註釋。懷註釋都是糟糕代碼的支撐或借口,或者是對錯誤決策的修正。

下面看一個例子

       //Check to see if the employee is eligible for full benefits1)If((employee.flags & HOURLY_FLAG)&& (employee.age>65))

(2)If(employee.isEligibleForFullBenefits()))

  這兩個你更喜歡哪個

View Code

好的註釋的特徵:

1:表示法律信息(這樣的註釋一般出現在文檔頂部說明作用以及協議)

// Copyright (c) .NET Foundation. All rights reserved
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

View Code

2:提供信息的註釋(指無法通過命名提供信息如要 註釋輔助的)

public void ConfigureServices(IServiceCollection services)
{

// These two middleware are registered via an IStartupFilter in UseIISIntegration but you can configure them here.

services.Configure<IISOptions>(options =>
{});

}

View Code

3:對意圖的解釋

4: 警示:告知其他人會出現某種後果的註釋

5: TODO註釋: 主要描述應該做的目前還沒有做的事情。

6: 放大:提示不合理之物的重要性。

應避免的註釋

應該避免以下幾點:

1: 誤導性註釋

2: 日誌式註釋

3: 廢話註釋

4: 標記位置的註釋

5: 括號后的註釋

6: 歸屬與簽名

7: 註釋掉的代碼

8: Html 註釋

以上沒有一一舉例的原因是我的PPT是一份演示的PPT,裏面很多公司的代碼不便貼出,抱歉。

不足之處還請指出

 

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

※公開收購3c價格,不怕被賤賣!

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

PHP 的 self 關鍵字用法

4{icon} {views}

之前有人詢問 self 關鍵字的用法,答案是比較明顯的:靜態成員函數內不能用 this 調用非成員函數,但可以用 self 調用靜態成員函數/變量/常量;其他成員函數可以用 self 調用靜態成員函數以及非靜態成員函數。隨着討論的深入,發現 self 並沒有那麼簡單。鑒於此,本文先對幾個關鍵字做對比和區分,再總結 self 的用法。

parentstatic 以及 this 的區別

要想將徹底搞懂 self ,要與 parentstatic 以及 this 區分開。以下分別做對比。

parent

selfparent 的區分比較容易: parent 引用父類/基類被隱蓋的方法(或變量), self則引用自身方法(或變量)。例如構造函數中調用父類構造函數:

class Base {
    public function __construct() {
        echo "Base contructor!", PHP_EOL;
    }
}

class Child {
    public function __construct() {
        parent::__construct();
        echo "Child contructor!", PHP_EOL;
    }
}

new Child;
// 輸出:
// Base contructor!
// Child contructor!

 

static

static 常規用途是修飾函數或變量使其成為類函數和類變量,也可以修飾函數內變量延長其生命周期至整個應用程序的生命周期。但是其與 self 關聯上是PHP 5.3以來引入的新用途:靜態延遲綁定。

有了 static 的靜態延遲綁定功能,可以在運行時動態確定歸屬的類。例如:

class Base {
    public function __construct() {
        echo "Base constructor!", PHP_EOL;
    }

    public static function getSelf() {
        return new self();
    }

    public static function getInstance() {
        return new static();
    }

    public function selfFoo() {
        return self::foo();
    }

    public function staticFoo() {
        return static::foo();
    }

    public function thisFoo() {
        return $this->foo();
    }

    public function foo() {
        echo  "Base Foo!", PHP_EOL;
    }
}

class Child extends Base {
    public function __construct() {
        echo "Child constructor!", PHP_EOL;
    }

    public function foo() {
        echo "Child Foo!", PHP_EOL;
    }
}

$base = Child::getSelf();
$child = Child::getInstance();

$child->selfFoo();
$child->staticFoo();
$child->thisFoo();

 

程序輸出結果如下:

Base constructor!
Child constructor!
Base Foo!
Child Foo!
Child Foo!

 

在函數引用上, selfstatic 的區別是:對於靜態成員函數, self 指向代碼當前類, static 指向調用類;對於非靜態成員函數, self 抑制多態,指向當前類的成員函數, static 等同於 this ,動態指向調用類的函數。

parentselfstatic 三個關鍵字聯合在一起看挺有意思,分別指向父類、當前類、子類,有點“過去、現在、未來”的味道。

this

selfthis 是被討論最多,也是最容易引起誤用的組合。兩者的主要區別如下:

  1. this 不能用在靜態成員函數中, self 可以;
  2. 對靜態成員函數/變量的訪問, 建議 用 self ,不要用 $this::$this-> 的形式;
  3. 對非靜態成員變量的訪問,不能用 self ,只能用 this ;
  4. this 要在對象已經實例化的情況下使用, self 沒有此限制;
  5. 在非靜態成員函數內使用, self 抑制多態行為,引用當前類的函數;而 this 引用調用類的重寫(override)函數(如果有的話)。

self 的用途

看完與上述三個關鍵字的區別, self 的用途是不是呼之即出?一句話總結,那就是: self總是指向“當前類(及類實例)”。詳細說則是:

  1. 替代類名,引用當前類的靜態成員變量和靜態函數;
  2. 抑制多態行為,引用當前類的函數而非子類中覆蓋的實現;

槽點

  1. 這幾個關鍵字中,只有 this 要加 $ 符號且必須加,強迫症表示很難受;
  2. 靜態成員函數中不能通過 $this-> 調用非靜態成員函數,但是可以通過 self:: 調用,且在調用函數中未使用 $this-> 的情況下還能順暢運行。此行為貌似在不同PHP版本中表現不同,在當前的7.3中ok;
  3. 在靜態函數和非靜態函數中輸出 self ,猜猜結果是什麼?都是 string(4) "self" ,迷之輸出;
  4. return $this instanceof static::class; 會有語法錯誤,但是以下兩種寫法就正常:
    $class = static::class;
    return $this instanceof $class;
    // 或者這樣:
    return $this instanceof static;

 

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

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

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

※公開收購3c價格,不怕被賤賣!

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

高併發編程學習(1)——併發基礎

3{icon} {views}

為更良好的閱讀體驗,請訪問原文:

一、前言

當我們使用計算機時,可以同時做許多事情,例如一邊打遊戲一邊聽音樂。這是因為操作系統支持併發任務,從而使得這些工作得以同時進行。

  • 那麼提出一個問題:如果我們要實現一個程序能一邊聽音樂一邊玩遊戲怎麼實現呢?
public class Tester {

    public static void main(String[] args) {
        System.out.println("開始....");
        playGame();
        playMusic();
        System.out.println("結束....");
    }

    private static void playGame() {
        for (int i = 0; i < 50; i++) {
            System.out.println("玩遊戲" + i);
        }
    }

    private static void playMusic() {
        for (int i = 0; i < 50; i++) {
            System.out.println("播放音樂" + i);
        }
    }
}

我們使用了循環來模擬過程,因為播放音樂和打遊戲都是連續的,但是結果卻不盡人意,因為函數體總是要執行完之後才能返回。那麼到底怎麼解決這個問題?

并行與併發

并行性和併發性是既相似又有區別的兩個概念。

并行性是指兩個或多個事件在同一時刻發生。而併發性是指兩個或多個事件在同一時間間隔內發生。

在多道程序環境下,併發性是指在一段時間內宏觀上有多個程序在同時運行,但在單處理機環境下(一個處理器),每一時刻卻僅能有一道程序執行,故微觀上這些程序只能是分時地交替執行。例如,在 1 秒鐘時間內,0 – 15 ms 程序 A 運行;15 – 30 ms 程序 B 運行;30 – 45 ms 程序 C 運行;45 – 60 ms 程序 D 運行,因此可以說,在 1 秒鐘時間間隔內,宏觀上有四道程序在同時運行,但微觀上,程序 A、B、C、D 是分時地交替執行的。

如果在計算機系統中有多個處理機,這些可以併發執行的程序就可以被分配到多個處理機上,實現併發執行,即利用每個處理機愛處理一個可併發執行的程序。這樣,多個程序便可以同時執行。以此就能提高系統中的資源利用率,增加系統的吞吐量。

進程和線程

進程是指一個內存中運行的應用程序。一個應用程序可以同時啟動多個進程,那麼上面的問題就有了解決的思路:我們啟動兩個進程,一個用來打遊戲,一個用來播放音樂。這當然是一種解決方案,但是想象一下,如果一個應用程序需要執行的任務非常多,例如 LOL 遊戲吧,光是需要播放的音樂就有非常多,人物本身的語音,技能的音效,遊戲的背景音樂,塔攻擊的聲音等等等,還不用說遊戲本身,就光播放音樂就需要創建許多許多的進程,而進程本身是一種非常消耗資源的東西,這樣的設計顯然是不合理的。更何況大多數的操作系統都不需要一個進程訪問其他進程的內存空間,也就是說,進程之間的通信很不方便,此時我們就得引入“線程”這門技術,來解決這個問題。

線程是指進程中的一個執行任務(控制單元),一個進程可以同時併發運行多個線程。我們可以打開任務管理器,觀察到幾乎所有的進程都擁有着許多的「線程」(在 WINDOWS 中線程是默認隱藏的,需要在「查看」裏面點擊「選擇列」,有一個線程數的勾選項,找到並勾選就可以了)。

進程和線程的區別

進程:有獨立的內存空間,進程中的數據存放空間(堆空間和棧空間)是獨立的,至少有一個線程。

線程:堆空間是共享的,棧空間是獨立的,線程消耗的資源也比進程小,相互之間可以影響的,又稱為輕型進程或進程元。

因為一個進程中的多個線程是併發運行的,那麼從微觀角度上考慮也是有先後順序的,那麼哪個線程執行完全取決於 CPU 調度器(JVM 來調度),程序員是控制不了的。我們可以把多線程併發性看作是多個線程在瞬間搶 CPU 資源,誰搶到資源誰就運行,這也造就了多線程的隨機性。下面我們將看到更生動的例子。

Java 程序的進程(Java 的一個程序運行在系統中)里至少包含主線程和垃圾回收線程(後台線程),你可以簡單的這樣認為,但實際上有四個線程(了解就好):

  • [1] main——main 線程,用戶程序入口
  • [2] Reference Handler——清除 Reference 的線程
  • [3] Finalizer——調用對象 finalize 方法的線程
  • [4] Signal Dispatcher——分發處理髮送給 JVM 信號的線程

多線程和單線程的區別和聯繫?

  1. 單核 CPU 中,將 CPU 分為很小的時間片,在每一時刻只能有一個線程在執行,是一種微觀上輪流佔用 CPU 的機制。

  2. 多線程會存在線程上下文切換,會導致程序執行速度變慢,即採用一個擁有兩個線程的進程執行所需要的時間比一個線程的進程執行兩次所需要的時間要多一些。

結論:即採用多線程不會提高程序的執行速度,反而會降低速度,但是對於用戶來說,可以減少用戶的響應時間。

多線程的優勢

儘管面臨很多挑戰,多線程有一些優點仍然使得它一直被使用,而這些優點我們應該了解。

優勢一:資源利用率更好

想象一下,一個應用程序需要從本地文件系統中讀取和處理文件的情景。比方說,從磁盤讀取一個文件需要 5 秒,處理一個文件需要 2 秒。處理兩個文件則需要:

1| 5秒讀取文件A
2| 2秒處理文件A
3| 5秒讀取文件B
4| 2秒處理文件B
5| ---------------------
6| 總共需要14秒

從磁盤中讀取文件的時候,大部分的 CPU 時間用於等待磁盤去讀取數據。在這段時間里,CPU 非常的空閑。它可以做一些別的事情。通過改變操作的順序,就能夠更好的使用 CPU 資源。看下面的順序:

1| 5秒讀取文件A
2| 5秒讀取文件B + 2秒處理文件A
3| 2秒處理文件B
4| ---------------------
5| 總共需要12秒

CPU 等待第一個文件被讀取完。然後開始讀取第二個文件。當第二文件在被讀取的時候,CPU 會去處理第一個文件。記住,在等待磁盤讀取文件的時候,CPU 大部分時間是空閑的。

總的說來,CPU 能夠在等待 IO 的時候做一些其他的事情。這個不一定就是磁盤 IO。它也可以是網絡的 IO,或者用戶輸入。通常情況下,網絡和磁盤的 IO 比 CPU 和內存的 IO 慢的多。

優勢二:程序設計在某些情況下更簡單

在單線程應用程序中,如果你想編寫程序手動處理上面所提到的讀取和處理的順序,你必須記錄每個文件讀取和處理的狀態。相反,你可以啟動兩個線程,每個線程處理一個文件的讀取和操作。線程會在等待磁盤讀取文件的過程中被阻塞。在等待的時候,其他的線程能夠使用 CPU 去處理已經讀取完的文件。其結果就是,磁盤總是在繁忙地讀取不同的文件到內存中。這會帶來磁盤和 CPU 利用率的提升。而且每個線程只需要記錄一個文件,因此這種方式也很容易編程實現。

優勢三:程序響應更快

有時我們會編寫一些較為複雜的代碼(這裏的複雜不是說複雜的算法,而是複雜的業務邏輯),例如,一筆訂單的創建,它包括插入訂單數據、生成訂單趕快找、發送郵件通知賣家和記錄貨品銷售數量等。用戶從單擊“訂購”按鈕開始,就要等待這些操作全部完成才能看到訂購成功的結果。但是這麼多業務操作,如何能夠讓其更快地完成呢?

在上面的場景中,可以使用多線程技術,即將數據一致性不強的操作派發給其他線程處理(也可以使用消息隊列),如生成訂單快照、發送郵件等。這樣做的好處是響應用戶請求的線程能夠盡可能快地處理完成,縮短了響應時間,提升了用戶體驗。

其他優勢

多線程還有一些優勢也顯而易見:

  • 進程之前不能共享內存,而線程之間共享內存(堆內存)則很簡單。
  • 系統創建進程時需要為該進程重新分配系統資源,創建線程則代價小很多,因此實現多任務併發時,多線程效率更高.
  • Java 語言本身內置多線程功能的支持,而不是單純地作為底層系統的調度方式,從而簡化了多線程編程.

上下文切換

即使是單核處理器也支持多線程執行代碼,CPU 通過給每個線程分配 CPU 時間片來實現這個機制。時間片是 CPU 分配給各個線程的時間,因為時間片非常短,所以 CPU 通過不停地切換線程執行,讓我們感覺多個線程是同時執行的,時間片一般是幾十毫秒(ms)。

CPU 通過時間片分配算法來循環執行任務,當前任務執行一個時間片後會切換到下一個任務。但是,在切換前會保存上一個任務的狀態,以便下次切換回這個任務的時候,可以再加載這個任務的狀態。所以任務從保存到再加載的過程就是一次上下文切換。

這就像我們同時讀兩本書,當我們在讀一本英文的技術書時,發現某個單詞不認識,於是打開中英文字典,但是在放下英文技術書之前,大腦必須先記住這本書獨到了多少頁的多少行,等查完單詞之後,能夠繼續讀這本書。這樣的切換是會影響讀書效率的,同樣上下文切換也會影響多線程的執行速度。

二、創建線程的兩種方式

繼承 Thread 類

public class Tester {

    // 播放音樂的線程類
    static class PlayMusicThread extends Thread {

        // 播放時間,用循環來模擬播放的過程
        private int playTime = 50;

        public void run() {
            for (int i = 0; i < playTime; i++) {
                System.out.println("播放音樂" + i);
            }
        }
    }

    // 方式1:繼承 Thread 類
    public static void main(String[] args) {
        // 主線程:運行遊戲
        for (int i = 0; i < 50; i++) {
            System.out.println("打遊戲" + i);
            if (i == 10) {
                // 創建播放音樂線程
                PlayMusicThread musicThread = new PlayMusicThread();
                musicThread.start();
            }
        }
    }
}

運行結果發現打遊戲和播放音樂交替出現,說明已經成功了。

實現 Runnable 接口

public class Tester {

    // 播放音樂的線程類
    static class PlayMusicThread implements Runnable {

        // 播放時間,用循環來模擬播放的過程
        private int playTime = 50;

        public void run() {
            for (int i = 0; i < playTime; i++) {
                System.out.println("播放音樂" + i);
            }
        }
    }

    // 方式2:實現 Runnable 方法
    public static void main(String[] args) {
        // 主線程:運行遊戲
        for (int i = 0; i < 50; i++) {
            System.out.println("打遊戲" + i);
            if (i == 10) {
                // 創建播放音樂線程
                Thread musicThread = new Thread(new PlayMusicThread());
                musicThread.start();
            }
        }
    }
}

也能完成效果。

以上就是傳統的兩種創建線程的方式,事實上還有第三種,我們後邊再講。

多線程一定快嗎?

先來一段代碼,通過并行和串行來分別執行累加操作,分析:下面的代碼併發執行一定比串行執行快嗎?

import org.springframework.util.StopWatch;

// 比較并行和串行執行累加操作的速度
public class Tester {

    // 執行次數
    private static final long COUNT = 100000000;
    private static final StopWatch TIMER = new StopWatch();

    public static void main(String[] args) throws InterruptedException {
        concurrency();
        serial();
        // 打印比較測試結果
        System.out.println(TIMER.prettyPrint());
    }

    private static void serial() {
        TIMER.start("串行執行" + COUNT + "條數據");

        int a = 0;
        for (long i = 0; i < COUNT; i++) {
            a += 5;
        }
        // 串行執行
        int b = 0;
        for (long i = 0; i < COUNT; i++) {
            b--;
        }

        TIMER.stop();
    }

    private static void concurrency() throws InterruptedException {
        TIMER.start("并行執行" + COUNT + "條數據");

        // 通過匿名內部類來創建線程
        Thread thread = new Thread(() -> {
            int a = 0;
            for (long i = 0; i < COUNT; i++) {
                a += 5;
            }
        });
        thread.start();

        // 并行執行
        int b = 0;
        for (long i = 0; i < COUNT; i++) {
            b--;
        }
        // 等待線程結束
        thread.join();
        TIMER.stop();
    }
}

大家可以自己測試一下,每一台機器 CPU 不同測試結果可能也會不同,之前在 WINDOWS 本兒上測試的時候,多線程的優勢從 1 千萬數據的時候才開始體現出來,但是現在換了 MAC,1 億條數據時間也差不多,到 10 億的時候明顯串行就比并行快了… 總之,為什麼併發執行的速度會比串行慢呢?就是因為線程有創建和上下文切換的開銷。

繼承 Thread 類還是實現 Runnable 接口?

想象一個這樣的例子:給出一共 50 個蘋果,讓三個同學一起來吃,並且給蘋果編上號碼,讓他們吃的時候順便要說出蘋果的編號:

運行結果可以看到,使用繼承方式實現,每一個線程都吃了 50 個蘋果。這樣的結果顯而易見:是因為顯式地創建了三個不同的 Person 對象,而每個對象在堆空間中有獨立的區域來保存定義好的 50 個蘋果。

而使用實現方式則滿足要求,這是因為三個線程共享了同一個 Apple 對象,而對象中的 num 數量是一定的。

所以可以簡單總結出繼承方式和實現方式的區別:

繼承方式:

  1. Java 中類是單繼承的,如果繼承了 Thread 了,該類就不能再有其他的直接父類了;
  2. 從操作上分析,繼承方式更簡單,獲取線程名字也簡單..(操作上,更簡單)
  3. 從多線程共享同一個資源上分析,繼承方式不能做到…

實現方式:

  1. Java 中類可以實現多個接口,此時該類還可以繼承其他類,並且還可以實現其他接口(設計上,更優雅)..
  2. 從操作上分析,實現方式稍微複雜點,獲取線程名字也比較複雜,需要使用 Thread.currentThread() 來獲取當前線程的引用..
  3. 從多線程共享同一個資源上分析,實現方式可以做到..

在這裏,三個同學完成搶蘋果的例子,使用實現方式才是更合理的方式。

對於這兩種方式哪種好並沒有一個確定的答案,它們都能滿足要求。就我個人意見,我更傾向於實現 Runnable 接口這種方法。因為線程池可以有效的管理實現了 Runnable 接口的線程,如果線程池滿了,新的線程就會排隊等候執行,直到線程池空閑出來為止。而如果線程是通過實現 Thread 子類實現的,這將會複雜一些。

有時我們要同時融合實現 Runnable 接口和 Thread 子類兩種方式。例如,實現了 Thread 子類的實例可以執行多個實現了 Runnable 接口的線程。一個典型的應用就是線程池。

常見錯誤:調用 run() 方法而非 start() 方法

創建並運行一個線程所犯的常見錯誤是調用線程的 run() 方法而非 start() 方法,如下所示:

1| Thread newThread = new Thread(MyRunnable());
2| newThread.run();  //should be start();

起初你並不會感覺到有什麼不妥,因為 run() 方法的確如你所願的被調用了。但是,事實上,run() 方法並非是由剛創建的新線程所執行的,而是被創建新線程的當前線程所執行了。也就是被執行上面兩行代碼的線程所執行的。想要讓創建的新線程執行 run() 方法,必須調用新線程的 start() 方法。

三、線程的安全問題

吃蘋果遊戲的不安全問題

我們來考慮一下上面吃蘋果的例子,會有什麼問題?

儘管,Java 並不保證線程的順序執行,具有隨機性,但吃蘋果比賽的案例運行多次也並沒有發現什麼太大的問題。這並不是因為程序沒有問題,而只是問題出現的不夠明顯,為了讓問題更加明顯,我們使用 Thread.sleep() 方法(經常用來模擬網絡延遲)來讓線程休息 10 ms,讓其他線程去搶資源。(注意:在程序中並不是使用 Thread.sleep(10)之後,程序才出現問題,而是使用之後,問題更明顯.)

為什麼會出現這樣的錯誤呢?

先來分析第一種錯誤:為什麼會吃重複的蘋果呢?就拿 B 和 C 都吃了編號為 47 的蘋果為例吧:

  • A 線程拿到了編號為 48 的蘋果,打印輸出然後讓 num 減 1,睡眠 10 ms,此時 num 為 47。
  • 這時 B 和 C 同時都拿到了編號為 47 的蘋果,打印輸出,在其中一個線程作出了減一操作的時候,A 線程從睡眠中醒過來,拿到了編號為 46 的蘋果,然後輸出。在這期間並沒有任何操作不允許 B 和 C 線程不能拿到同一個編號的蘋果,之前沒有明顯的錯誤僅僅可能只是因為運行速度太快了。

再來分析第二種錯誤:照理來說只應該存在 1-50 編號的蘋果,可是 0 和-1 是怎麼出現的呢?

  • 當 num = 1 的時候,A,B,C 三個線程同時進入了 try 語句進行睡眠。
  • C 線程先醒過來,輸出了編號為 1 的蘋果,然後讓 num 減一,當 C 線程醒過來的時候發現 num 為 0 了。
  • A 線程醒過來一看,0 都沒有了,只有 -1 了。

歸根結底是因為沒有任何操作來限制線程來獲取相同的資源並對他們進行操作,這就造成了線程安全性問題。

如果我們把打印和減一的操作分成兩個步驟,會更加明顯:

ABC 三個線程同時打印了 50 的蘋果,然後同時做出減一操作。

像這樣的原子操作,是不允許分步驟進行的,必須保證同步進行,不然可能會引發不可設想的後果。

要解決上述多線程併發訪問一個資源的安全性問題,就需要引入線程同步的概念。

線程同步

多個執行線程共享一個資源的情景,是最常見的併發編程情景之一。為了解決訪問共享資源錯誤或數據不一致的問題,人們引入了臨界區的概念:用以訪問共享資源的代碼塊,這個代碼塊在同一時間內只允許一個線程執行。

為了幫助編程人員實現這個臨界區,Java(以及大多數編程語言)提供了同步機制,當一個線程試圖訪問一個臨界區時,它將使用一種同步機制來查看是不是已經有其他線程進入臨界區。如果沒有其他線程進入臨界區,他就可以進入臨界區。如果已經有線程進入了臨界區,它就被同步機制掛起,直到進入的線程離開這個臨界區。如果在等待進入臨界區的線程不止一個,JVM 會選擇其中的一個,其餘的將繼續等待。

synchronized 關鍵字

如果一個對象已用 synchronized 關鍵字聲明,那麼只有一個執行線程被允許訪問它。使用 synchronized 的好處顯而易見:保證了多線程併發訪問時的同步操作,避免線程的安全性問題。但是壞處是:使用 synchronized 的方法/代碼塊的性能比不用要低一些。所以好的做法是:盡量減小 synchronized 的作用域。

我們還是先來解決吃蘋果的問題,考慮一下 synchronized 關鍵字應該加在哪裡呢?

發現如果還再把 synchronized 關鍵字加在 if 裏面的話,0 和 -1 又會出來了。這其實是因為當 ABC 同是進入到 if 語句中,等待臨界區釋放的時,拿到 1 編號的線程已經又把 num 減一操作了,而此時最後一個等待臨界區的進程拿到的就會是 -1 了。

同步鎖 Lock

Lock 機制提供了比 synchronized 代碼塊和 synchronized 方法更廣泛的鎖定操作,同步代碼塊/ 同步方法具有的功能 Lock 都有,除此之外更強大,更體現面向對象。在併發包的類族中,Lock 是 JUC 包的頂層接口,它的實現邏輯並未用到 synchronized,而是利用了 volatile 的可見性。

使用 Lock 最典型的代碼如下:

class X {

    private final ReentrantLock lock = new ReentrantLock();

    public void m() {
        lock.lock();
        try {
            // ..... method body
        } finally {
            lock.unlock();
        }
    }
}

線程安全問題

線程安全問題只在多線程環境下才會出現,單線程串行執行不存在此類問題。保證高併發場景下的線程安全,可以從以下四個維度考量:

維度一:數據單線程可見

單線程總是安全的。通過限制數據僅在單線程內可見,可以避免數據被其他線程篡改。最典型的就是線程局部變量,它存儲在獨立虛擬機棧幀的局部變量表中,與其他線程毫無瓜葛。TreadLocal 就是採用這種方式來實現線程安全的。

維度二:只讀對象

只讀對象總是安全的。它的特性是允許複製、拒絕寫入。最典型的只讀對象有 String、Integer 等。一個對象想要拒絕任何寫入,必須要滿足以下條件:

  • 使用 final 關鍵字修飾類,避免被繼承;
  • 使用 private final 關鍵字避免屬性被中途修改;
  • 沒有任何更新方法;
  • 返回值不能為可變對象。

維度三:線程安全類

某些線程安全類的內部有非常明確的線程安全機制。比如 StringBuffer 就是一個線程安全類,它採用 synchronized 關鍵字來修飾相關方法。

維度四:同步與鎖機制

如果想要對某個對象進行併發更新操作,但又不屬於上述三類,需要開發工程師在代碼中實現安全的同步機制。雖然這個機制支持的併發場景很有價值,但非常複雜且容易出現問題。

處理線程安全的核心理念

要麼只讀,要麼加鎖。

合理利用好 JDK 提供的併發包,往往能化腐朽為神奇。Java 併發包(java.util.concurrent,JUC)中大多數類註釋都寫有:@author Doug Lea。如果說 Java 是一本史書,那麼 Doug Lea 絕對是開疆拓土的偉大人物。Doug Lea 在當大學老師時,專攻併發編程和併發數據結構設計,主導設計了 JUC 併發包,提高了 Java 併發編程的易用性,大大推進了 Java 的商用進程。

參考資料

  • 《Java 零基礎入門教程》 –
  • 《Java 併發編程的藝術》
  • 《Java 7 併發編程實戰手冊》
  • 《碼出高效 Java 開發手冊》 – 楊冠寶(孤盡) 高海慧(鳴莎)著

按照慣例黏一個尾巴:

歡迎轉載,轉載請註明出處!
獨立域名博客:wmyskxz.com
簡書 ID:
github:
歡迎關注公眾微信號:wmyskxz
分享自己的學習 & 學習資料 & 生活
想要交流的朋友也可以加 qq 群:3382693

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

※高價收購3C產品,價格不怕你比較

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

3c收購,鏡頭 收購有可能以全新價回收嗎?

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

可不可以 不要再“神化”Costco

6{icon} {views}

   上海群雄並起,Costco開市客想分羹並非易事。

  天貓旗艦店先行探路近5年後,全球第二大零售巨頭——Costco開市客中國大陸首店終於在今天(8月27日)正式開門迎客。

  從早前傳進入市場,到上海首店的落地,Costco開市客市場關注熱度只增不減,官方數據显示,自今年7月1日開放會員申請,開業前就已經積累了數萬名付費會員,“遠超預期”。

  開業首日消費者的熱情不斷刷新商場的經營現狀:

  •   Costco開市客上午9點正式開業門迎客,僅十餘分鐘,就已經採取限流進入措施;

  •   不到2小時,就已經提示消費者停車場全滿,需等待3小時;

  •   上午11時許,就有會員收到開市客推送的短信提醒,“目前購物高峰時段擁堵,建議錯峰出行前來購物”;

  •   隨後商場對外宣布下午將暫停營業

  開業不到3小時暫停營業,可見Costco開市客影響力驚人。

  Costco開市客亞洲區總裁張嗣漢也曾稱首店落戶上海,是基於過去5年其天貓線上訂單消費者分佈(主要集中在華東,上海最甚),但在上海的中高端超市領域,聚集了山姆會員店、City‘super、Ole’精品超市、久光超市、G-Super等強勁對手,開業火爆的Costco開市客能否撬動這塊“大蛋糕”?

  01

  現場實探:

  人氣爆滿不到3小時緊急暫停業

  Costco開市客大陸首店內究竟有哪些產品與服務?以低價著稱的它,優惠力度是否真如早前說的一樣?帶着好奇,贏商網小編今日上午跟隨人潮第一時間逛了這家新店。

  延續美國的門店風格,位於上海閔行區朱建路235號近聯友路的Costco開市客大陸首店,以標準的美式獨棟建築呈現。

  公開資料显示,該門店總建築面積2萬平方米,購物面積近1.4萬平方米,設有1200個停車位,是其全球門店中最大的停車場。

  與傳統商超不同,Costco開市客主打會員制,並且通過精簡商品種類、增大分量、減少過度包裝、全球聯合採購、簡單的裝修風格等方式,將平均毛利率做到僅10%,非食品類的百貨商品價格低於市場價的30%到60%,食品類則能低10%到20%。

  走進Costco開市客門店內,極簡的裝修風格及陳列方式,琳琅滿目的商品,以及熙熙攘攘的人流,是其給小編的第一印象。

  從產品內容上,涵蓋生鮮、熟食、珠寶、家用電器、洗護用品、零食、家居、鞋服、保健品……3400個SKU供消費者選購。

  首店中,超過一半的上架商品都來自國外知名品牌:貝德瑪、olay、桂格、飛利浦蘋果……

  除了將海外商品搬到眼前,Costco開市客門店內還有不少中國本土品牌,如五糧液、金門高粱酒……

  與此同時,Costco開市客自有品牌Kirkland柯克蘭同樣隨處可見,品類涵蓋保健品、冷凍混合蔬菜、咖啡、堅果…。。滿足平日各類生活所需,今天現場也引發消費者搶購。

  從現場消費者的購物車中物品來看,牛奶、水果、麵包等烘焙品類購買人數最多。

  價格方面,Costco開市客內部分品牌的確是有一定優勢,如一瓶500ml的貝德瑪卸妝水,天貓官方旗艦店平台售價約為158元,這裏4瓶只需449.9元;女士香水統一售價399.9元一件……

  此外,在收銀台周圍區域,則是分佈有會員部、輪胎部、光學眼鏡部,以及堪稱自助品質的試吃區域,不過現場人太多,小編沒有辦法穿越人海去體驗。

  值得注意的是,早前網上說Costco開市客需要憑會員卡方能入門,今天現場來看,並沒有對入場消費者做任何檢查,所有人均可入內

  02

  群雄並起想分羹並非易事

  Costco開市客超高的性價比,無疑是人群涌塞,造成賣場暫停營業的重要因素,畢竟早在開業前,品牌方便透露非食品類的百貨商品價格低於市場價的30%—60%,食品類則能低10%—20%。

  不過,Costco開市客門店內售賣奢侈品MCM、Burberry、愛馬仕、珠寶首飾、茅台酒等商品,某種程度上是在打高消費力群體的主意,加上選址偏遠、地鐵不能直達,直接忽略了距離較遠的無車一族。

  對此,上海市商業經濟學會會長齊曉齋認為Costco開市客的客群跨度很大,既兼顧了中高端消費者,也兼顧了一般的低端消費者,如果要可持續發展下來,還是要靠住在不遠處的有車一族。

  而在上海,瞄準高消費力群體的超市明顯不止Costco開市客一家。從15年前便亮相魔都靜安寺商圈的久光超市,到2010年湧現的City‘super、Ole’精品超市、山姆會員店,再到2014年開業的APITA雅品嘉、G-Super,直至今天正式開門的Costco開市客,這些超市無論是在產品構成上,還是服務細節方面,都在迎合高消費力群體的喜好。

  從這些超市的位置分佈來看:

  •   G-Super的門店最多,分佈區域最為廣泛,兼顧市區和遠郊區域

  •   兩大會員制超市山姆會員店、Costco開市客更傾向於偏遠地區

  •   較早進入上海市場的久光超市、Citysuper門店則牢牢佔據了市區核心商圈

  •   還有品牌至今只在上海開設一家店,側重於為所處區域的顧客服務,如APITA雅品嘉僅在金虹橋商場設有門店。

  值得一提的是,除山姆會員店、Costco開市客外,其他中高端超市均選擇在購物中心內開店。

  “首先,購物中心不會給予超市太大的面積,其次,停車位要與購物中心共享,Costco開市客、山姆會員店這類大賣場自然不會進駐購物中心”,對此,齊曉齋如是回答道。

  對比這些中高端超市不難發現,進口產品佔比較高為主要特色。其中:

  •   Citysuper:主要售賣外國進口食品、文具商品、化妝護理用品及家庭用品等,進口商品高達85%;

  •   Ole‘精品超市:根據經營品類定位分為Ole’ lifestyle、Ole‘ foodhall。據悉,Ole’ lifestyle的進口商品比例佔到70%,Ole foodhall內,60%為進口商品;

  •   G-Super:進口商品佔比為70%—80%;

  •   久光超市:以高端消費客戶為主,門店內有很多獨特的商品如松茸、甲魚蛋等,其進口商品佔比達到80%以上,此外,該超市內有許多日式食物,且成品和半成品一應俱全。

  在產品構成上,這些中高端超市還有着其他獨特優勢,如:

  •   G-Super擁有四大海外商品直采中心,自產、直采、直銷三合一

  •   山姆會員商店與京東合作“一小時達”服務,並在深圳、北京、上海市場中擴建雲倉(前置倉),該舉措還促使2018年山姆整體銷售同比增長8%。

  服務方面,Costco開市客的付費會員可享受輪胎修護、眼鏡和助聽器選配、西式餐飲,及免費驗光、免費眼鏡護理保養、免費輪胎氮氣填充、商品展示試吃等增值服務,一直被人們津津樂道。

  實際上,其他中高端超市也在不斷升級服務,以增強與顧客的黏性。今年1月,位於浦東的上海第一家山姆會員商店重裝升級開業,新增了品酒區、山姆廚房、活動區等互動區域,並開闢母嬰室,提供獨家禮品卡、高端眼鏡等高性價比商品,便是為了增強會員黏性,為全渠道發展夯實會員基礎。

  同樣提供個性化服務的還有City‘super。據了解,City’super除了提供數十種個性禮盒、禮籃及包裝服務,還獨創了superlife cultureclub生活藝會,定期舉辦由大廚主持的烹飪分享、品酒會等活動。

  由此來看,Costco開市客來到上海,面臨的不僅僅是同類型業態的比拼,更要直迎目標客群被中高端超市分流的挑戰。

  “上海這麼大的城市,Costco開市客想要持續下去肯定要開第二家店、第三家店,因為浦東的消費者不方便來該店購物”,齊曉齋說。

  不過,同樣走“付費制會員”的山姆會員商店,用了9年時間才在上海開出第二家店,Costco開市客想要快速覆蓋浦東的消費者,顯然並非一件易事。

  – 結語 –

  價格低、無理由退貨、享受多重會員服務,固然是Costco開市客吸引顧客接踵而至的重要原因,但低頻消費、批量購買與中國目前的消費特性顯然不夠一致

  如今,Costco開市客近處有在中國發展了20多年、布局26家門店的山姆會員商店,遠處有各大中高端超市牢牢鎖定高消費力的群體,輻射能力究竟如何,仍有待時間驗證。

(文章來源:贏商網)

(責任編輯:DF353)

【其他文章推薦】

地板施工造成其他裝潢有瑕疵該如何補救?

※想改變客廳裝潢風格嗎? 馬賽克拼貼打造溫馨鄉村風,教你如何運用馬賽克瓷磚自行DIY創作

※好評熱賣,復刻家具推薦首選口袋名單

※【找人才】舞台架設/燈光音響專業技術人員,福利佳,薪可議

※推薦竹東裝潢老師傅統包團隊,裝潢、修繕一次搞定!!

下沉市場 物流先行

11{icon} {views}

  電商為之痴迷的下沉市場,想要實現長線發展,自然要讓物流這項基礎設施“先行”。8月30日,京東物流針對低線城市城區、縣城以及周邊鄉鎮,發起“千縣萬鎮24小時達”時效提速計劃,計劃在2020年實現。京東物流提速完善低線市場的基礎設施時,同為自建物流的蘇寧正通過“百川計劃”加速裂變蘇寧倉儲網絡,阿里則借用投資併購方式直接入局。實際上,在三者上一輪的“刷牆運動”中,部分基礎設施已經搭建完成,如何能徹底解決“最後100米”的到戶問題,讓物流在低線市場也如同毛細血管一般存在,或許才是企業本輪制勝的關鍵。

  借速度爭奪縣鎮

  農產品上行、工業品下行時,搭建多年的物流尚難以構建“阡陌交通”的圖景。進入2019年便動作頻頻的京東物流,如今則想通過“24小時送達”的時速撬動縣鎮的物流市場。京東物流正計劃在2020年前實現“千縣萬鎮24小時達”時效提速計劃。

  據了解,此次“千縣萬鎮24小時達”時效提速計劃多措並舉,包括倉儲投入,將商品部署在距離鄉鎮客戶更近的地方,運用消費大數據精準備貨,提升商品滿足率,提升低線城市服務體驗;建設中轉場地以及投入智能設備,實現商品的高頻率快速集散;通過運力與配送班次的加密,將偏遠地區訂單升級為每日一送或每日兩送,提高當地配送時效,使24小時配送服務觸達更多人群。

  每日兩送、24小時時速……這些在一二線城市就能決定消費者選擇在哪家電商下單的“誘惑”性條件,同樣也能刺激縣鎮消費者的神經。意識到速度影響用戶對平台忠誠度的企業,除了京東,還有蘇寧,而兩者同為自建物流的企業,對各項標準與人員安排有着強把控力。

  在縣鎮市場,蘇寧物流以幫客縣鎮服務中心攻城略地。縣鎮用戶開始習慣基於終端物流搭建起來的送裝一體、半日裝、快修等核心服務。據了解,蘇寧幫客縣鎮服務中心實行“一縣一店”,全國已經建成1000家,預計2019年底將實現全國95%縣鎮的覆蓋。

  網絡尚需細化

  “農產品能不能賣出去、能不能在電商上買東西,往往要看是否有物流企業接收和配送,以及取件的快遞網點是否方便前往。”一位河南中牟的農戶對北京商報記者稱。在他看來,在電商下單並不是難事,取快遞才是難事。“有時候取快遞要到隔壁縣,無論是大件還是小件都極其不便。”

  電商平台雲聯鄉村河南首席執行官李權龍在接受北京商報記者採訪時曾表示,縣鎮的物流體系尚處於初級階段,物流集散較為薄弱,配送也同樣掣肘。多數情況下,快遞網點的密度要比時效更為重要;而當密度足夠支撐縣鎮居民的需求時,時效的重要性才會凸顯。

  此前,國家郵政局等18個部門近日聯合印發《關於認真落實習近平總書記重要指示推動郵政業高質量發展的實施意見》。到2022年,將基本實現郵政“村村直通郵”、快遞“鄉鄉有網點”,通過郵政、快遞渠道基本實現建制村電商配送全覆蓋。重點快遞企業國內重點城市間將實現48小時送達。

  京東物流有關負責人表示,京東物流在一二線及三線城市,“當日達”早已成為常態。此次“千縣萬鎮24小時達”時效提速計劃,重點針對低線城市城區、縣城以及周邊鄉鎮,目標就是讓24小時配送服務觸達更多人群。

  實際上,快遞企業想要做到48小時甚至是24小時送達,增加快遞網點數量,確保每個快遞網點覆蓋的範圍不再無限度被放大,而是服務特定的區域。2018年4月,阿里斥資45億元投資五星旗下匯通達,而匯通達主要業務便是服務整合鄉鎮零售網點。

  填補運力不足的空白

  在縣鎮甚至更下沉的市場,快遞費高於商品本身的價格通常是常態。一位在雲南騰衝從事鮮花餅零售的商家向北京商報記者舉例稱,快遞費會高出鮮花餅售價的5%-10%,甚至運費比鮮花餅還貴。該店主選擇與顧客各承擔一部分運費,或者直接漲價后再售賣。“快遞員取件不便讓快遞費居高不下的情況長期存在;多個縣鎮共用一個快遞網點,也耽誤商家的發貨時間。”

  上述商家進一步解釋稱:“現在的情況有所好轉,對家快遞企業爭奪訂單,增加了網點密度,快遞費用在明顯下降。”目前,縣鎮市場的物流雙向流通並不暢通。與城市相比,交通基礎設施較差,物流成本高、效率低。由於單位成本難以下降,讓物流成本居高不下。縣鎮市場的物流發展情況差異較大,有些偏遠地區基本為空白,電商配送存在不足情況。

  向低線市場滲透,對於開放的京東物流來講,或許是快速攤薄成本的方式。京東相關負責人解釋稱,隨着低線城市布局的完善,特別是開放物流后大量外部訂單進入,訂單密度大大提升,物流成本大幅下降。

(文章來源:北京商報)

(責任編輯:DF353)

【其他文章推薦】

柚木地板是什麼,其材料規格及適合施作環境?

※裝潢省錢妙招,家具訂製推薦達人,教您省下不必要的裝潢

※找尋專業規劃婚宴場所、尾牙活動、舞台背景等會場佈置專家在哪裡?

※住家安全首選「隱形鐵窗」,KO傳統老舊鐵窗!

※知名設計師專業沙發訂做,全省沙發工廠推薦一覽表