分類
發燒車訊

類加載器 – ClassLoader詳解

獲得ClassLoader的途徑

  • 獲得當前類的ClassLoader
    • clazz.getClassLoader()
  • 獲得當前線程上下文的ClassLoader
    • Thread.currentThread().getContextClassLoader();
  • 獲得系統的ClassLoader
    • ClassLoader.getSystemClassLoader()
  • 獲得調用者的ClassLoader
    • DriverManager.getCallerClassLoader

ClassLoader源碼解析

概述

類加載器是用於加載類的對象,ClassLoader是一個抽象類。如果我們給定了一個類的二進制名稱,類加載器應嘗試去定位或生成構成定義類的數據。一種典型的策略是將給定的二進制名稱轉換為文件名,然後去文件系統中讀取這個文件名所對應的class文件。

每個Class對象都會包含一個定義它的ClassLoader的一個引用。

數組類的Class對象,不是由類加載器去創建的,而是在Java運行期JVM根據需要自動創建的。對於數組類的類加載器來說,是通過Class.getClassLoader()返回的,與數組當中元素類型的類加載器是一樣的;如果數組當中的元素類型是一個原生類型,數組類是沒有類加載器的【代碼一】。

應用實現了ClassLoader的子類是為了擴展JVM動態加載類的方式。

類加載器典型情況下時可以被安全管理器所使用去標識安全域問題。

ClassLoader類使用了委託模型來尋找類和資源,ClassLoader的每一個實例都會有一個與之關聯的父ClassLoader,當ClassLoader被要求尋找一個類或者資源的時候,ClassLoader實例在自身嘗試尋找類或者資源之前會委託它的父類加載器去完成。虛擬機內建的類加載器,稱之為啟動類加載器,是沒有父加載器的,但是可以作為一個類加載器的父類加載器【雙親委託機制】。

支持併發類加載的類加載器叫做并行類加載器,要求在初始化期間通過ClassLoader.registerAsParallelCapable 方法註冊自身,ClassLoader類默認被註冊為可以并行,但是如果它的子類也是并行加載的話需要單獨去註冊子類。

在委託模型不是嚴格的層次化的環境下,類加載器需要并行,否則類加載會導致死鎖,因為加載器的鎖在類加載過程中是一直被持有的。

通常情況下,Java虛擬機以平台相關的形式從本地的文件系統中加載類,比如在UNIX系統,虛擬機從CLASSPATH環境所定義的目錄加載類。
然而,有些類並不是來自於文件;它們是從其它來源得到的,比如網絡,或者是由應用本身構建【動態代理】。定義類(defineClass )方法會將字節數組轉換為Class的實例,這個新定義類的實例可以由Class.newInstance創建。

由類加載器創建的對象的方法和構造方法可能引用其它的類,為了確定被引用的類,Java虛擬機會調用最初創建類的類加載器的loadClass方法。

二進制名稱:以字符串參數的形式向CalssLoader提供的任意一個類名,必須是一個二進制的名稱,包含以下四種情況

  • “java.lang.String” 正常類
  • “javax.swing.JSpinner$DefaultEditor” 內部類
  • “java.security.KeyStore\(Builder\)FileBuilder$1″ KeyStore的內部類Builder的內部類FileBuilder的第一個匿名內部類
  • “java.net.URLClassLoader$3$1” URLClassLoader類的第三個匿名內部類的第一個匿名內部類

代碼一:

public class Test12 {
    public static void main(String[] args) {
        String[] strings = new String[6];
        System.out.println(strings.getClass().getClassLoader());
        // 運行結果:null

        Test12[] test12s = new Test12[1];
        System.out.println(test12s.getClass().getClassLoader());
        // 運行結果:sun.misc.Launcher$AppClassLoader@18b4aac2

        int[] ints = new int[2];
        System.out.println(ints.getClass().getClassLoader());
        // 運行結果:null
    }
}

loadClass方法

loadClass的源碼如下, loadClass方法加載擁有指定的二進制名稱的Class,默認按照如下順序尋找類:

  • 調用findLoadedClass(String)檢查這個類是否被加載
  • 調用父類加載器的loadClass方法,如果父類加載器為null,就會調用啟動類加載器
  • 調用findClass(String)方法尋找

使用上述步驟如果類被找到且resolve為true,就會去調用resolveClass(Class)方法

protected Class<?> loadClass(String name, boolean resolve)
  throws ClassNotFoundException
{
  synchronized (getClassLoadingLock(name)) {
      // First, check if the class has already been loaded
      Class<?> c = findLoadedClass(name);
      if (c == null) {
          long t0 = System.nanoTime();
          try {
              if (parent != null) {
                  c = parent.loadClass(name, false);
              } else {
                  c = findBootstrapClassOrNull(name);
              }
          } catch (ClassNotFoundException e) {
              // ClassNotFoundException thrown if class not found
              // from the non-null parent class loader
          }

          if (c == null) {
              // If still not found, then invoke findClass in order
              // to find the class.
              long t1 = System.nanoTime();
              c = findClass(name);

              // this is the defining class loader; record the stats
              sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
              sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
              sun.misc.PerfCounter.getFindClasses().increment();
          }
      }
      if (resolve) {
          resolveClass(c);
      }
      return c;
  }
}

findClass方法

findClass的源碼如下,findClass尋找擁有指定二進制名稱的類,JVM鼓勵我們重寫此方法,需要自定義加載器遵循雙親委託機制,該方法會在檢查完父類加載器之後被loadClass方法調用,默認返回ClassNotFoundException異常。

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

defineClass方法

defineClass的源碼如下,defineClass方法將一個字節數組轉換為Class的實例。

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                     ProtectionDomain protectionDomain)
    throws ClassFormatError
{
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}

自定義類加載器

/**
 * 繼承了ClassLoader,這是一個自定義的類加載器
 * @author 夜的那種黑丶
 */
public class ClassLoaderTest extends ClassLoader {
    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
       Class<?> clazz = loader.loadClass("classloader.Test01");
        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
    //------------------------------以上為測試代碼---------------------------------

    /**
     * 類加載器名稱,標識作用
     */
    private String classLoaderName;

    /**
     * 從磁盤讀物字節碼文件的擴展名
     */
    private String fileExtension = ".class";

    /**
     * 創建一個類加載器對象,將系統類加載器當做該類加載器的父加載器
     * @param classLoaderName 類加載器名稱
     */
    private ClassLoaderTest(String classLoaderName) {
        // 將系統類加載器當做該類加載器的父加載器
        super();
        this.classLoaderName = classLoaderName;
    }

    /**
     * 創建一個類加載器對象,显示指定該類加載器的父加載器
     * 前提是需要有一個類加載器作為父加載器
     * @param parent 父加載器
     * @param classLoaderName 類加載器名稱
     */
    private ClassLoaderTest(ClassLoader parent, String classLoaderName) {
        // 显示指定該類加載器的父加載器
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    /**
     * 尋找擁有指定二進制名稱的類,重寫ClassLoader類的同名方法,需要自定義加載器遵循雙親委託機制
     * 該方法會在檢查完父類加載器之後被loadClass方法調用
     * 默認返回ClassNotFoundException異常
     * @param className 類名
     * @return Class的實例
     * @throws ClassNotFoundException 如果類不能被找到,拋出此異常
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        byte[] data = this.loadClassData(className);
        /*
         * 通過defineClass方法將字節數組轉換為Class
         * defineClass:將一個字節數組轉換為Class的實例,在使用這個Class之前必須要被解析
         */
        return this.defineClass(className, data, 0 , data.length);
    }

    /**
     * io操作,根據類名找到對應文件,返回class文件的二進制信息
     * @param className 類名
     * @return class文件的二進制信息
     * @throws ClassNotFoundException 如果類不能被找到,拋出此異常
     */
    private byte[] loadClassData(String className) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data;
        ByteArrayOutputStream byteArrayOutputStream = null;

        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            inputStream = new FileInputStream(new File(className + this.fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();

            int ch;
            while (-1 != (ch = inputStream.read())) {
                byteArrayOutputStream.write(ch);
            }

            data = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }
}

以上是一段自定義類加載器的代碼,我們執行這段代碼

classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2

可以看見,這段代碼中進行類加載的類加載器還是系統類加載器(AppClassLoader)。這是因為jvm的雙親委託機製造成的,private ClassLoaderTest(String classLoaderName)將系統類加載器當做我們自定義類加載器的父加載器,jvm的雙親委託機制使自定義類加載器委託系統類加載器完成加載。

改造以下代碼,添加一個path屬性用來指定類加載位置:

public class ClassLoaderTest extends ClassLoader {
    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
        loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
        Class<?> clazz = loader.loadClass("classloader.Test01");
        System.out.println("class:" + clazz);

        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
    //------------------------------以上為測試代碼---------------------------------

    /**
     * 從指定路徑加載
     */
    private String path;

    ......
    
    /**
     * io操作,根據類名找到對應文件,返回class文件的二進制信息
     * @param className 類名
     * @return class文件的二進制信息
     * @throws ClassNotFoundException 如果類不能被找到,拋出此異常
     */
    private byte[] loadClassData(String className) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data;
        ByteArrayOutputStream byteArrayOutputStream = null;

        className = className.replace(".", "/");

        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            inputStream = new FileInputStream(new File(this.path + className + this.fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();

            int ch;
            while (-1 != (ch = inputStream.read())) {
                byteArrayOutputStream.write(ch);
            }

            data = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public void setPath(String path) {
        this.path = path;
    }
}

運行一下

class:class classloader.Test01
classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2

修改一下測試代碼,並刪除工程下的Test01.class文件

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
   loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz);

    Object object = clazz.newInstance();
    System.out.println(object);
    System.out.println(object.getClass().getClassLoader());
}

運行一下

class:class classloader.Test01
classloader.Test01@135fbaa4
classloader.ClassLoaderTest@7f31245a

分析

改造后的兩塊代碼,第一塊代碼中加載類的是系統類加載器AppClassLoader,第二塊代碼中加載類的是自定義類加載器ClassLoaderTest。是因為ClassLoaderTest會委託他的父加載器AppClassLoader加載class,第一塊代碼的path直接是工程下,AppClassLoader可以加載到,而第二塊代碼的path在桌面目錄下,所以AppClassLoader無法加載到,然後ClassLoaderTest自身嘗試加載並成功加載到。如果第二塊代碼工程目錄下的Test01.class文件沒有被刪除,那麼依然是AppClassLoader加載。

再來測試一塊代碼

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest("loader");
    loader2.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

結果顯而易見,類由系統類加載器加載,並且clazz和clazz2是相同的。

class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2
class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2

在改造一下

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest("loader2");
    loader2.setPath("/home/fanxuan/桌面/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

運行結果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:621009875
classloader.ClassLoaderTest@45ee12a7

ClassLoaderTest是顯而易見,但是clazz和clazz2是不同的,這是因為類加載器的命名空間的原因。

我們可以通過設置父類加載器來讓loader和loader2處於同一命名空間

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest(loader, "loader2");
    loader2.setPath("/home/fanxuan/桌面/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

運行結果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:325040804
classloader.ClassLoaderTest@7f31245a

擴展:命名空間

  • 每個類加載器都有自己的命名空間,命名空間由該加載器及所有的父加載器所加載的類組成
  • 在同一命名空間中,不會出現類的完整名字(包括類的包名)相同的兩個類
  • 在不同的命名空間中,有可能會出現類的完整名字(包括類的包名)相同的兩個類

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

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

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

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

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

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

分類
發燒車訊

多線程編程(3)——synchronized原理以及使用

一、對象頭

  通常在java中一個對象主要包含三部分:

  • 對象頭 主要包含GC的狀態、、類型、類的模板信息(地址)、synchronization狀態等,在後面介紹。

  • 實例數據:程序代碼中定義的各種類型的字段內容。

  • 對齊數據:對象的大小必須是 8 字節的整數倍,此項根據情況而定,若對象頭和實例數據大小正好是8的倍數,則不需要對齊數據,否則大小就是8的差數。

先看下面的實例、程序的輸出以及解釋。

/*需提前引入jar包
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core 解析java對象布局 -->
        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>
​
*/
//Java對象以8個字節對其,不夠則使用對其數據
public class Student {
    private int id;       // 4字節
    private boolean sex;  // 1字節
    public Student(int id, boolean sex){
        this.id = id;
        this.sex = sex;
    }
}
public class Test01 {
    public static void main(String[] args) {
        Student stu = new Student(6, true);
        //計算對象hash,底層是C++實現,不需要java去獲取,如果此處不調用,則後面的hash值不會去計算
        System.out.println("hashcode: " + stu.hashCode());  
        System.out.println(ClassLayout.parseInstance(stu).toPrintable());
    }
}
/* output
hashcode: 523429237
com.thread.synchronizeDemo.Student object internals:
OFFSET SIZE TYPE DESCRIPTION        VALUE
 0     4    (object header)    01 75 e5 32 (00000001 01110101 11100101 00110010) (853898497)
4      4     (object header)    1f 00 00 00 (00011111 00000000 00000000 00000000) (31)
8      4     (object header)    43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12     4       int Student.id                                6
16     1   boolean Student.sex                               true
17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
​
備註:上述代碼在64位的機器上運行,此時
對象頭占  (4+4+4)*8 = 96 位(bit)
實例數據  (4+1)*8 = 40 位(bit)
對齊數據  7*8 = 56 位(bit) 因為Java對象以8個字節對其的方式,需補7byte去對齊
*/

   下面主要陳述對對象頭的解釋,內容從hotspot官網摘抄下來的信息:

object header

  Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object’s layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format.

mark word

  The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits.

klass pointer

  The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original objec

  由此可知,對象頭主要包含GC的狀態(用4位表示——表示範圍0-15,用來記錄GC年齡,這也就是為什麼對象在survivor中從from區到to區來迴轉換15次後轉入到老年代tenured區)、類型、類的模板信息(地址)、synchronization 狀態等,由兩個字組成mark word和klass pointer(類元素據信息地址,具體數據通常在堆的方法區中,即8字節,但有時候會有一些優化設置,會開啟指針壓縮,將代表klass pointer的8字節變成4字節大小,這也是為什麼在上述代碼中對象頭大小是(8+4)byte,而不是16byte。)。本節最主要介紹對象頭的mark word這部分。關於對象頭中每部分bit所代表的意義可以查看hotspot源碼中代碼的注,這段註釋是從openjdk中拷貝的。

JVM和hotspot、openjdk的區別

JVM是一種產品的規範定義,hotspot(Oracle公司)是對該規範實現的產品,還有遵循這些規範的其他產品,比如J9(IBM開發的一個高度模塊化的JVM)、Zing VM等。

openjdk是一個hotspot項目的大部分源代碼(可以通過編譯后變成.exe文件),hotspot小部分代碼Oracle並未公布

// openjdk-8-src-b132-03_mar_2014\openjdk\hotspot\src\share\vm\oops\markOop.hpp
/*
Bit-format of an object header (most significant first, big endian layout below):
​
32 bits:
--------
        hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
        JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
        size:32 ------------------------------------------>| (CMS free block)
        PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
​
64 bits:
--------
unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
size:64 ----------------------------------------------------->| (CMS free block)
​
*/

可以看到在32位機器和64位機器中,對象的布局的差異還是很大的,本文主要 敘述64位機器下的布局,其實兩者無非是位數不同而已,大同小異。在64位機器用64位(8byte)表示Mark Word,首先前25位(0-25)是未被使用,接下來31位表示hash值,然後是對象分代年齡大小,最後Synchronized的鎖信息,分為兩部分,共3bit,如下錶,鎖的嚴格性依次是鎖、偏向鎖、輕量級鎖、重量級鎖。

 

關於鎖的一些解釋

無鎖

  無鎖沒有對資源進行鎖定,所有的線程都能訪問並修改同一個資源,但同時只有一個線程能修改成功。

偏向鎖

  引入偏向鎖是為了在無多線程競爭的情況下,一段同步代碼一直被一個線程所訪問因為輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令,當由另外的線程所訪問,偏向鎖就會升級為輕量級鎖。

輕量級鎖 

  當鎖是偏向鎖的時候,被另外的線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,從而提高性能。輕量級鎖所適應的場景是線程交替執行同步塊的情況,如果存在同一時間訪問同一鎖的情況,就會導致輕量級鎖膨脹為重量級鎖。

重量級鎖

  依賴於操作系統Mutex Lock所實現的鎖,JDK中對Synchronized做的種種優化,其核心都是為了減少這種重量級鎖的使用。JDK1.6以後,為了減少獲得鎖和釋放鎖所帶來的性能消耗,提高性能,引入了“輕量級鎖”和“偏向鎖”。  

GC

  這並不是鎖的狀態,而是GC標誌,等待GC回收。

現在開始從程序層面分析前面程序的對象頭的布局信息,在此之前需要知道的是,在windows中對於數據的存儲採用的是小端存儲,所以要反過來讀

大端模式——是指數據的高字節保存在內存的低地址中,而數據的低字節保存在內存的高地址中,這樣的存儲模式有點兒類似於把數據當作字符串順序處理:地址由小向大增加,而數據從高位往低位放;這和我們的閱讀習慣一致。

小端模式——是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內存的低地址中,這種存儲模式將地址的高低和數據位權有效地結合起來,高地址部分權值高,低地址部分權值低。 一般在網絡中用的大端;本地用的小端;

運行程序如下,可以看到對應的hashcode值被打印出來:

public static void main(String[] args) {
     Student stu = new Student(6, true);
    //Integer.toHexString()此方法返回的字符串表示的無符號整數參數所表示的值以十六進制
     System.out.println("hashcode: " + Integer.toHexString(stu.hashCode()));
     System.out.println(ClassLayout.parseInstance(stu).toPrintable());
}
/*
hashcode: 1f32e575
com.thread.synchronizeDemo.Student object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 75 e5 32 (00000001 01110101 11100101 00110010) (853898497)
      4     4           (object header)                           1f 00 00 00 (00011111 00000000 00000000 00000000) (31)
      8     4           (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4       int Student.id                                6
     16     1   boolean Student.sex                               true
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

//前8個字節反過來看,可以看出對象頭的hash是1f32e575,同時是無鎖的狀態00000001
*/

 二、Monitor

       可以把它理解為一個同步工具(數據結構),也可以描述為一種同步機制,通常被描述為一個對象。每個對象都存在着一個 monitor 與之關聯,對象與其 monitor 之間的關係有存在多種實現方式,如monitor可以與對象一起創建銷毀或當線程試圖獲取對象鎖時自動生成,但當一個 monitor 被某個線程持有后,它便處於鎖定狀態(每一個線程都有一個可用 monitor record 列表)[具體可以看參考資料5]。需要注意的是這種監視器鎖是發生在對象的內部鎖已經變成重量級鎖的時候。

/*  openjdk-8-src-b132-03_mar_2014\openjdk\hotspot\src\share\vm\runtime\ObjectMonitor.hpp
// initialize the monitor, exception the semaphore, all other fields // are simple integers or pointers ObjectMonitor() { _header = NULL; _count = 0; //記錄個數 _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; _WaitSet = NULL; //處於wait狀態的線程,會被加入到_WaitSet _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; //處於等待鎖block狀態的線程,會被加入到該列表 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; _previous_owner_tid = 0; } */

  Monitor的實現主要藉助三個結構去完成多線程的併發操作——_owner、_WaitSet 、_EntryList。當多個線程同時訪問由synchronized修飾的對象、類或一段同步代碼時,首先會進入_EntryList 集合,如果某個線程取得了_owner的所有權,該線程就可以去執行,如果該線程調用了wait()方法,就會放棄_owner的所有權,進入等待狀態,等下一次喚醒。如下圖(圖片摘自參考資料5)。

 三、synchronized的用法

     synchronized修飾方法和修飾一個代碼塊類似,只是作用範圍不一樣,修飾代碼塊是大括號括起來的範圍,而修飾方法範圍是整個函數。其中synchronized(this) 與synchronized(class) 之間的區別有以下五點要注意:

    1、對於靜態方法,由於此時對象還未生成,所以只能採用類鎖;

    2、只要採用類鎖,就會攔截所有線程,只能讓一個線程訪問。

    3、對於對象鎖(this),如果是同一個實例,就會按順序訪問,但是如果是不同實例,就可以同時訪問。

   4、如果對象鎖跟訪問的對象沒有關係,那麼就會都同時訪問。

   5、當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。

當然,Synchronized也可修飾一個靜態方法,而靜態方法是屬於類的而不屬於對象的,所以synchronized修飾的靜態方法鎖定的是這個類的所有對象。關於如下synchronized的用法,我們經常會碰到的案例:

public class Thread5 implements Runnable {
    private static int count = 0;
    public synchronized static void add() {
        count++;
    }
    @Override
    public void run() {
        for (int i = 0; i < 1000000; i++) {
            synchronized (Thread5.class){
                count++;
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 20; i++) {
            es.execute(new Thread5());
        }
        es.shutdown();
        es.awaitTermination(6, TimeUnit.SECONDS);
        System.out.println(count);
    }
}
/* 類鎖
  20000000
  */

而一旦換成對象鎖,不同實例,就可以同時訪問。則會出錯:

public void run() {
        for (int i = 0; i < 1000000; i++) {
            synchronized (this){
                count++;
            }
        }
}
/* 對象鎖
 10746948
*/

這是因為靜態變量並不屬於某個實例對象,而是屬於類所有,所以對某個實例加鎖,並不會改變count變量臟讀和臟寫的情況,還是造成結果不正確。

 

參考資料

  1. 對象布局的各部分介紹——

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

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

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

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

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

分類
發燒車訊

[學習筆記] 在Eclipse中使用Hibernate,並創建第一個Demo工程,數據庫為Oracle XE

前文參考:

在Eclipse中使用Hibernate

安裝 Hibernate Tools 插件

https://tools.jboss.org/downloads/

Add the following URL to your Eclipse 4.13 (2019-09) installation, via:

Help > Install New Software… > Work with:

http://download.jboss.org/jbosstools/photon/stable/updates/

Then select the individual features that you want to install:

點擊Next

點擊Next
同意相關協議,點擊Finish .

則會開始下載安裝。

視網絡速度,可能需要幾分鐘到十幾分鐘的時間才能完成安裝。

最後會提示重啟Eclipse才能生效。

在Eclipse中新建Hibernate應用

File->New -> Java Project

點擊Finish

項目結構圖

在Eclipse中新建用戶庫

此時下面显示了已經建立的用戶庫列表

我們要添加Hibernate的依賴庫,因此點擊用戶庫

Hibernate_4.3.5_final

選擇jar文件

項目結構圖

繼續配置Hibernate

最後自動形成 如下的文件內容:[本例使用oracle數據庫]

Oracle 11g xe 在windows安裝請看如下鏈接:

hibernate.cfg.xml

 <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
        <property name="hibernate.connection.password">123456</property>
        <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:1521:xe</property>
        <property name="hibernate.connection.username">test</property>
        <property name="hibernate.default_schema">test</property>
        <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
    </session-factory>
</hibernate-configuration>

再增加幾個屬性

配置文件更新后的內容如下: 注意要去掉name屬性 更改 為

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
                                         "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
 <session-factory >
  <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
  <property name="hibernate.connection.password">123456</property>
  <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:xe:orcl</property>
  <property name="hibernate.connection.username">test</property>
  <property name="hibernate.default_schema">test</property>
  <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
  <property name="hibernate.show_sql">true</property>
  <!-- 第一次加載hibernate時根據model類會自動建立起表的結構(前提是先建立好數據庫),以後加載hibernate時根據 model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。要注意的是當部署到服務器后,表結構是不會被馬上建立起來的,是要等 應用第一次運行起來后才會。 -->
  <property name="hibernate.hbm2ddl.auto">update</property> 
  <property name="hibernate.format_sql">true</property>
  <property name="hibernate.default_entity_mode">pojo</property>
 </session-factory>
</hibernate-configuration>

繼續完善工程Hibernate_demo_001

新建一個包:mytest001.demo

在包mytest001.demo之下新建一個PO類: Emp

package mytest001.demo;

public class Emp {
    // 員工的標識屬性
    private Integer id;
    // 姓名
    private String name;
    // 年齡
    private Integer age;
    // 工資 (分)
    private Integer salary;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getSalary() {
        return salary;
    }

    public void setSalary(Integer salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Emp [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + "]";
    }
}

此刻此PO Emp.java 尚不具備持久化能力。下面為其添加註解。

@Entity 註解聲明該類是一個Hibernate持久化類
@Table 指定該類映射的表,對應的數據庫表名是T_EMP
@Id 指定該類的標識屬性,映射到數據庫的主鍵列
@GeneratedValue(strategy=GenerationType.SEQUENCE) 指定了主鍵生成策略,由於本文使用Oracle Database, 因此指定了使用 SEQUENCE

在hibernate.cfg.xml中增加持久化映射類名

增加一個新類:EmpManager,用於管理員工。

package mytest001.demo;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.Service;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;


public class EmpManager {

    public static void main(String[] args)  throws Exception  {
         
    //實例化配置
    Configuration configuration  = new Configuration()
            //不帶參數則默認加載hibernate.cfg.xml
            .configure();
    
    ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
            .applySettings(configuration.getProperties()).build();
    SessionFactory sFactory = configuration.buildSessionFactory(serviceRegistry);
    
    //創建session 
    Session session = sFactory.openSession();
    
    //開始事務
    Transaction tx = session.beginTransaction();
    //創建員工對象
    Emp emp = new Emp();
    // 設置員工信息
    emp.setAge(28);
    emp.setName("scott");
    emp.setSalary(10000);
    session.save(emp);
    // 提交事務
    tx.commit();
    session.close();
    sFactory.close();
            
    
    }

}

最後配置文件的內容:hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
                                         "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
 <session-factory >
  <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
  <property name="hibernate.connection.password">123456</property>
  <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:1521:xe</property>
  <property name="hibernate.connection.username">test</property>
  <property name="hibernate.default_schema">test</property>
  <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
  <property name="hibernate.show_sql">true</property>
  <!-- 第一次加載hibernate時根據model類會自動建立起表的結構(前提是先建立好數據庫),以後加載hibernate時根據 model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。要注意的是當部署到服務器后,表結構是不會被馬上建立起來的,是要等 應用第一次運行起來后才會。 -->
  <property name="hibernate.hbm2ddl.auto">update</property> 
  <property name="hibernate.format_sql">true</property>
  <property name="hibernate.default_entity_mode">pojo</property>
  <mapping class="mytest001.demo.Emp"/>
 </session-factory>
</hibernate-configuration>

Emp.java

package mytest001.demo;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="T_EMP")
public class Emp {
    // 員工的標識屬性
    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE)
    private Integer id;
    // 姓名
    private String name;
    // 年齡
    private Integer age;
    // 工資 (分)
    private Integer salary;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getSalary() {
        return salary;
    }

    public void setSalary(Integer salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Emp [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + "]";
    }
}

最後的工程結構如下:

運行EmpManger

十一月 23, 2019 8:50:47 上午 org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.4.Final}
十一月 23, 2019 8:50:47 上午 org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.3.5.Final}
十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Configuration configure
INFO: HHH000043: Configuring from resource: /hibernate.cfg.xml
十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Configuration getConfigurationInputStream
INFO: HHH000040: Configuration resource: /hibernate.cfg.xml
十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Configuration doConfigure
INFO: HHH000041: Configured SessionFactory: null
十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH000402: Using Hibernate built-in connection pool (not for production use!)
十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH000401: using driver [oracle.jdbc.driver.OracleDriver] at URL [jdbc:oracle:thin:@localhost:1521:xe]
十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH000046: Connection properties: {user=test, password=****}
十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH000006: Autocommit mode: false
十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
十一月 23, 2019 8:50:48 上午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.Oracle10gDialect
十一月 23, 2019 8:50:48 上午 org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
INFO: HHH000399: Using default transaction strategy (direct JDBC transactions)
十一月 23, 2019 8:50:48 上午 org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000228: Running hbm2ddl schema update
十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000102: Fetching database metadata
十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000396: Updating schema
十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: T_EMP
十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: T_EMP
十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: T_EMP
十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000232: Schema update complete
Hibernate: 
    select
        test.hibernate_sequence.nextval 
    from
        dual
Hibernate: 
    insert 
    into
        test.T_EMP
        (age, name, salary, id) 
    values
        (?, ?, ?, ?)
十一月 23, 2019 8:50:48 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH000030: Cleaning up connection pool [jdbc:oracle:thin:@localhost:1521:xe]

到數據庫中查詢表:(這個表會被自動創建)
select * from t_emp;

如果再次運行會增加新的記錄。

至此,本文完成。

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

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

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

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

分類
發燒車訊

Spring Boot2 系列教程(二十六)Spring Boot 整合 Redis

在 Redis 出現之前,我們的緩存框架各種各樣,有了 Redis ,緩存方案基本上都統一了,關於 Redis,松哥之前有一個系列教程,尚不了解 Redis 的小夥伴可以參考這個教程:

使用 Java 操作 Redis 的方案很多,Jedis 是目前較為流行的一種方案,除了 Jedis ,還有很多其他解決方案,如下:

除了這些方案之外,還有一個使用也相當多的方案,就是 Spring Data Redis。

在傳統的 SSM 中,需要開發者自己來配置 Spring Data Redis ,這個配置比較繁瑣,主要配置 3 個東西:連接池、連接器信息以及 key 和 value 的序列化方案。

在 Spring Boot 中,默認集成的 Redis 就是 Spring Data Redis,默認底層的連接池使用了 lettuce ,開發者可以自行修改為自己的熟悉的,例如 Jedis。

Spring Data Redis 針對 Redis 提供了非常方便的操作模板 RedisTemplate 。這是 Spring Data 擅長的事情,那麼接下來我們就來看看 Spring Boot 中 Spring Data Redis 的具體用法。

方案一:Spring Data Redis

創建工程

創建工程,引入 Redis 依賴:

創建成功后,還需要手動引入 commos-pool2 的依賴,因此最終完整的 pom.xml 依賴如下:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
</dependencies>

這裏主要就是引入了 Spring Data Redis + 連接池。

配置 Redis 信息

接下來配置 Redis 的信息,信息包含兩方面,一方面是 Redis 的基本信息,另一方面則是連接池信息:

spring.redis.database=0
spring.redis.password=123
spring.redis.port=6379
spring.redis.host=192.168.66.128
spring.redis.lettuce.pool.min-idle=5
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=1ms
spring.redis.lettuce.shutdown-timeout=100ms

自動配置

當開發者在項目中引入了 Spring Data Redis ,並且配置了 Redis 的基本信息,此時,自動化配置就會生效。

我們從 Spring Boot 中 Redis 的自動化配置類中就可以看出端倪:

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
                    RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
            RedisTemplate<Object, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
    }
    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(
                    RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
            StringRedisTemplate template = new StringRedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
    }
}

這個自動化配置類很好理解:

  1. 首先標記這個是一個配置類,同時該配置在 RedisOperations 存在的情況下才會生效(即項目中引入了 Spring Data Redis)
  2. 然後導入在 application.properties 中配置的屬性
  3. 然後再導入連接池信息(如果存在的話)
  4. 最後,提供了兩個 Bean ,RedisTemplate 和 StringRedisTemplate ,其中 StringRedisTemplate 是 RedisTemplate 的子類,兩個的方法基本一致,不同之處主要體現在操作的數據類型不同,RedisTemplate 中的兩個泛型都是 Object ,意味者存儲的 key 和 value 都可以是一個對象,而 StringRedisTemplate 的 兩個泛型都是 String ,意味者 StringRedisTemplate 的 key 和 value 都只能是字符串。如果開發者沒有提供相關的 Bean ,這兩個配置就會生效,否則不會生效。

使用

接下來,可以直接在 Service 中注入 StringRedisTemplate 或者 RedisTemplate 來使用:

@Service
public class HelloService {
    @Autowired
    RedisTemplate redisTemplate;
    public void hello() {
        ValueOperations ops = redisTemplate.opsForValue();
        ops.set("k1", "v1");
        Object k1 = ops.get("k1");
        System.out.println(k1);
    }
}

Redis 中的數據操作,大體上來說,可以分為兩種:

  1. 針對 key 的操作,相關的方法就在 RedisTemplate 中
  2. 針對具體數據類型的操作,相關的方法需要首先獲取對應的數據類型,獲取相應數據類型的操作方法是 opsForXXX

調用該方法就可以將數據存儲到 Redis 中去了,如下:

k1 前面的字符是由於使用了 RedisTemplate 導致的,RedisTemplate 對 key 進行序列化之後的結果。

RedisTemplate 中,key 默認的序列化方案是 JdkSerializationRedisSerializer 。

而在 StringRedisTemplate 中,key 默認的序列化方案是 StringRedisSerializer ,因此,如果使用 StringRedisTemplate ,默認情況下 key 前面不會有前綴。

不過開發者也可以自行修改 RedisTemplate 中的序列化方案,如下:

@Service
public class HelloService {
    @Autowired
    RedisTemplate redisTemplate;
    public void hello() {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        ValueOperations ops = redisTemplate.opsForValue();
        ops.set("k1", "v1");
        Object k1 = ops.get("k1");
        System.out.println(k1);
    }
}

當然也可以直接使用 StringRedisTemplate:

@Service
public class HelloService {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    public void hello2() {
        ValueOperations ops = stringRedisTemplate.opsForValue();
        ops.set("k2", "v2");
        Object k1 = ops.get("k2");
        System.out.println(k1);
    }
}

另外需要注意 ,Spring Boot 的自動化配置,只能配置單機的 Redis ,如果是 Redis 集群,則所有的東西都需要自己手動配置,關於如何操作 Redis 集群,松哥以後再來和大家分享。

方案二:Spring Cache

通過 Spring Cache 的形式來操作 Redis,Spring Cache 統一了緩存江湖的門面,這種方案,松哥之前有過一篇專門的文章介紹,小夥伴可以移步這裏:。

方案三:回歸原始時代

第三種方案,就是直接使用 Jedis 或者 其他的客戶端工具來操作 Redis ,這種方案在 Spring Boot 中也是支持的,雖然操作麻煩,但是支持,這種操作松哥之前也有介紹的文章,因此這裏就不再贅述了,可以參考 。

總結

Spring Boot 中,Redis 的操作,這裏松哥給大家總結了三種方案,實際上前兩個使用廣泛一些,直接使用 Jedis 還是比較少,基本上 Spring Boot 中沒見過有人直接這麼搞。

好了,本文就說到這裏,有問題歡迎留言討論。

相關案例已經上傳到 GitHub,歡迎小夥伴們們下載:

掃碼關注松哥,公眾號後台回復 2TB,獲取松哥獨家 超2TB 免費 Java 學習乾貨

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

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

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

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

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

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

分類
發燒車訊

javascript閉包詳解

閉包(closure)是Javascript語言的一個難點,也是它的特色,很多高級應用都要依靠閉包實現。

下面就是我的學習筆記,對於Javascript初學者應該是很有用的。

一、變量的作用域

要理解閉包,首先必須理解Javascript特殊的變量作用域。

變量的作用域無非就是兩種:全局變量和局部變量。

Javascript語言的特殊之處,就在於函數內部可以直接讀取全局變量。

  

var n=999;

  function f1(){
    alert(n);
  }

  f1(); // 999

  

另一方面,在函數外部自然無法讀取函數內的局部變量。

 

 function f1(){
    var n=999;
  }

  alert(n); // error

  

這裡有一個地方需要注意,函數內部聲明變量的時候,一定要使用var命令。如果不用的話,你實際上聲明了一個全局變量!

 

 function f1(){
    n=999;
  }

  f1();

  alert(n); // 999

  

二、如何從外部讀取局部變量?

出於種種原因,我們有時候需要得到函數內的局部變量。但是,前面已經說過了,正常情況下,這是辦不到的,只有通過變通方法才能實現。

那就是在函數的內部,再定義一個函數。

  

function f1(){

    var n=999;

    function f2(){
      alert(n); // 999
    }

  }

  

在上面的代碼中,函數f2就被包括在函數f1內部,這時f1內部的所有局部變量,對f2都是可見的。但是反過來就不行,f2內部的局部變量,對f1就是不可見的。這就是Javascript語言特有的”鏈式作用域”結構(chain scope),子對象會一級一級地向上尋找所有父對象的變量。所以,父對象的所有變量,對子對象都是可見的,反之則不成立。

既然f2可以讀取f1中的局部變量,那麼只要把f2作為返回值,我們不就可以在f1外部讀取它的內部變量了嗎!

 

 function f1(){

    var n=999;

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

  

三、閉包的概念

上一節代碼中的f2函數,就是閉包。

各種專業文獻上的”閉包”(closure)定義非常抽象,很難看懂。我的理解是,閉包就是能夠讀取其他函數內部變量的函數。

由於在Javascript語言中,只有函數內部的子函數才能讀取局部變量,因此可以把閉包簡單理解成”定義在一個函數內部的函數”。

所以,在本質上,閉包就是將函數內部和函數外部連接起來的一座橋樑。

四、閉包的用途

閉包可以用在許多地方。它的最大用處有兩個,一個是前面提到的可以讀取函數內部的變量,另一個就是讓這些變量的值始終保持在內存中。

怎麼來理解這句話呢?請看下面的代碼。

  function f1(){

    var n=999;

    nAdd=function(){n+=1}

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

  nAdd();

  result(); // 1000

在這段代碼中,result實際上就是閉包f2函數。它一共運行了兩次,第一次的值是999,第二次的值是1000。這證明了,函數f1中的局部變量n一直保存在內存中,並沒有在f1調用后被自動清除。

為什麼會這樣呢?原因就在於f1是f2的父函數,而f2被賦給了一個全局變量,這導致f2始終在內存中,而f2的存在依賴於f1,因此f1也始終在內存中,不會在調用結束后,被垃圾回收機制(garbage collection)回收。

這段代碼中另一個值得注意的地方,就是”nAdd=function(){n+=1}”這一行,首先在nAdd前面沒有使用var關鍵字,因此nAdd是一個全局變量,而不是局部變量。其次,nAdd的值是一個匿名函數(anonymous function),而這個匿名函數本身也是一個閉包,所以nAdd相當於是一個setter,可以在函數外部對函數內部的局部變量進行操作。

五、使用閉包的注意點

1)由於閉包會使得函數中的變量都被保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導致內存泄露。解決方法是,在退出函數之前,將不使用的局部變量全部刪除。

2)閉包會在父函數外部,改變父函數內部變量的值。所以,如果你把父函數當作對象(object)使用,把閉包當作它的公用方法(Public Method),把內部變量當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函數內部變量的值。

六、思考題

如果你能理解下面兩段代碼的運行結果,應該就算理解閉包的運行機制了。

代碼片段一。

  var name = “The Window”;

  var object = {
    name : “My Object”,

    getNameFunc : function(){
      return function(){
        return this.name;
      };

    }

  };

  alert(object.getNameFunc()());

代碼片段二。

  var name = “The Window”;

  var object = {
    name : “My Object”,

    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };

    }

  };

  alert(object.getNameFunc()());

 

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

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

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

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

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

分類
發燒車訊

Vue項目使用CSS變量實現主題化

主題化管理經常能在網站上看到,一般的思路都是將主題相關的CSS樣式獨立出來,在用戶選擇主題的時候加載相應的CSS樣式文件。現在大部分瀏覽器都能很好的兼容,主題化樣式更容易管理了。最近,使用CSS變量在Vue項目中做了一個主題化實踐,下面來看看整個過程。

可行性測試

為了檢驗方法的可行性,在public文件夾下新建一個themes文件夾,並在themes文件夾新建一個default.css文件:

:root {
  --color: red;
}

在public文件夾的index.html文件中引入外部樣式theme.css,如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title>vue-skin-peeler-demo</title>
    <!-- 引入themes文件夾下的default.css -->
    <link rel="stylesheet" type="text/css" href="src/themes/default.css" rel="external nofollow">
  </head>
  <body>
    <noscript>
      <strong>We're sorry but vue-skin-peeler-demo doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

然後,在Home.vue中使用CSS變量:

<template>
  <div class="home">
    <div :class="$style.demo">變紅色</div>
  </div>
</template>

<script>
export default {
  name: 'home'
}
</script>

<style module lang="scss">
  .demo {
    color: var(--color);
  }
</style>

然後,運行項目並在瀏覽器中打開頁面,頁面显示效果正常。

注意:@vue/cli使用link標籤引入css樣式可能報錯“We’re sorry but vue-skin-peeler-demo doesn’t work properly without JavaScript enabled. Please enable it to continue.”。這是因為@vue/cli將src目錄下的文件都通過webpack打包所引起,所以,靜態文件資源要放在public(如果是@vue/cli 2.x版本放在static)文件夾下。

實現主題切換

這裏主題切換的思路是替換link標籤的href屬性,因此,需要寫一個替換函數,在src目錄下新建themes.js文件,代碼如下:

// themes.js
const createLink = (() => {
  let $link = null
  return () => {
    if ($link) {
      return $link
    }
    $link = document.createElement('link')
    $link.rel = 'stylesheet'
    $link.type = 'text/css'
    document.querySelector('head').appendChild($link)
    return $link
  }
})()

/**
 * 主題切換函數
 * @param {string} theme - 主題名稱, 默認default
 * @return {string} 主題名稱
 */
const toggleTheme = (theme = 'default') => {
  const $link = createLink()
  $link.href = `./themes/${theme}.css`
  return theme
}

export default toggleTheme

然後,在themes文件下創建default.css和dark.css兩個主題文件。創建CSS變量,實現主題化。CSS變量實現主題切換請參考另一篇文章

兼容性

IE瀏覽器以及一些舊版瀏覽器不支持CSS變量,因此,需要使用,是一個,可在舊版和現代瀏覽器中為CSS自定義屬性(也稱為“ CSS變量”)提供客戶端支持。由於要開啟watch監聽,所以還有安裝。

安裝:

npm install css-vars-ponyfill mutationobserver-shim --save

然後,在themes.js文件中引入並使用:

// themes.js
import 'mutationobserver-shim'
import cssVars from 'css-vars-ponyfill'

cssVars({
  watch: true
})

const createLink = (() => {
  let $link = null
  return () => {
    if ($link) {
      return $link
    }
    $link = document.createElement('link')
    $link.rel = 'stylesheet'
    $link.type = 'text/css'
    document.querySelector('head').appendChild($link)
    return $link
  }
})()

/**
 * 主題切換函數
 * @param {string} theme - 主題名稱, 默認default
 * @return {string} 主題名稱
 */
const toggleTheme = (theme = 'default') => {
  const $link = createLink()
  $link.href = `./themes/${theme}.css`
  return theme
}

export default toggleTheme

開啟watch后,在IE 11瀏覽器點擊切換主題開關不起作用。因此,每次切換主題時都重新執行cssVars(),還是無法切換主題,原因是開啟watch后重新執行cssVars()是無效的。最後,只能先關閉watch再重新開啟。成功切換主題的themes.js代碼如下:

// themes.js
import 'mutationobserver-shim'
import cssVars from 'css-vars-ponyfill'

const createLink = (() => {
  let $link = null
  return () => {
    if ($link) {
      return $link
    }
    $link = document.createElement('link')
    $link.rel = 'stylesheet'
    $link.type = 'text/css'
    document.querySelector('head').appendChild($link)
    return $link
  }
})()

/**
 * 主題切換函數
 * @param {string} theme - 主題名稱, 默認default
 * @return {string} 主題名稱
 */
const toggleTheme = (theme = 'default') => {
  const $link = createLink()
  $link.href = `./themes/${theme}.css`
  cssVars({
    watch: false
  })
  setTimeout(function () {
    cssVars({
      watch: true
    })
  }, 0)
  return theme
}

export default toggleTheme

查看所有代碼,請移步。

記住主題

實現記住主題這個功能,一是可以向服務器保存主題,一是使用本地存儲主題。為了方便,這裏主要使用本地存儲主題的方式,即使用localStorage存儲主題。具體實現請移步。

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

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

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

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

分類
發燒車訊

電動車環島不是夢,特斯拉7月將於台灣完成250座充電站

一台特斯拉隨隨便便就要兩、三百萬元台幣的價碼,讓不少喜愛電動車有效率、環保特性的人無法輕易擁有。特斯拉宣布他們跟台灣本地銀行提供的優惠方案,最低每月負擔36,000 元台幣,就可以將特斯拉車子開回家。另外特斯拉的超級充電站也將遍布全台,開著特斯拉環島將不是夢想。

在上週五(6/16)特斯拉Model X 交車派對上,特斯拉香港、澳門及台灣地區區域總監范菁怡表示,在台灣由星展銀行提供的優惠方案,每月最少花36,000 元,就可以開著特斯拉回家。或車主開了3 年之後,除了繼續繳每月的費用,也可以選擇回購專案,將車子賣回給特斯拉。特斯拉也有與車行合作模式,可採租賃模式駕駛特斯拉車。以上幾項不同的方案,供車主依自己的需求選擇最適合的方案。

特斯拉表示,到了7 月,開特斯拉車環島也不會是問題。台灣中部、南部的台南、高雄都將在高速公路交流道一帶設置超級充電站,而東部也會有超級充電站。未來開著特斯拉享受環島行程不再是夢想。Tesla 於今年第二季末正式在台完成超過250 座目的地充電站,分布於全台80 個地點。

▲ 在派對上交車的車輛,5~7 人座都有。

另外不少車主期待的自動輔助駕駛2.0 版與導航功能也將在台啟用,為全世界第一批啟用國家之一。不論是自動輔助轉向、跟車、防撞預警、自動換導、自動停車或是召換功能,都能在台灣使用。未來Model S 或Model X 開在台灣的道路上,透過OTA 機制更新,能夠累積台灣路況,提供最好的駕駛體驗。

▲ 特斯拉與車行合作,提供駕駛特斯拉車的不同方式。

儘管在交車派對上車主是主角,但特斯拉宣布了不少優惠專案。對車主來說,可在一個月內免費申請安裝家用充電設備,另外還有機會到SpaceX 參觀!

 

▲ 特斯拉在台灣與星展銀行合作,推出特斯拉優惠方案。

(合作媒體:。圖片出處:科技新報)

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

【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

Tesla超級充電站計畫開放給他牌電動車充電

 

由於各大廠牌的電動車充電標準不同,加上充電站設立不足,續航力一直是電動車普及化的最大障礙之一,但這個情況隨著特斯拉的投入,或許未來即將有望改變。

Electrek 報導,特斯拉過去經常談到將超級充電站網路(Supercharger network)開放給其他車商的可能,但一直都沒有後續消息傳出。就在16 日能源博覽會中,技術長JB Straubel 再度提及這個想法,他表示,目前正與其他車商針對充電站的設置「積極交流」中。

特斯拉最早提到這個概念是在2015 年9 月,當時執行長馬斯克(Elon Musk)表示,特斯拉會持續擁有並經營所有超級充電站,其他車商只需要為該廠牌電動車的充電費用付費即可。

身為世界領先的電動車大廠,特斯拉的超級充電站無論充電率、覆蓋率都領先其他車廠許多,如果這樣的合作方式成真,相信對電動車普及會很有幫助,只怕充電站的塞車情況會更嚴重。

特斯拉一直有在擴大超級充電站網路,根據官網介紹,目前全球已有超過861 個特斯拉超級充電站,但充電站塞車的情況仍舊持續上演,更別提目前多數電動車充電率都沒辦法達到特斯拉的一半;一旦開放其他廠牌車輛使用,車主恐怕得在充電站等上更久時間。

為了改善這種情況,除了宣布進一步擴展網路以外,特斯拉推出一種新型用戶付費系統,可以提供其他車商的用戶使用。值得一提的是,特斯拉也加入了CCS 標準協會,這意味著未來超級充電站的充電接口兼容性或許會更廣泛。

除此之外,一些車商正在打造更高充電率的電動車,很快特斯拉就不會是唯一能以100kW+ 充電率充電的車輛,包括保時捷、奧迪、賓士都宣布,未來2 年會推出新款電動車,估計這些車輛能接受直流快速充電。

(合作媒體:。圖片出處:wikipedia)

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

【其他文章推薦】

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

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

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

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

分類
發燒車訊

市場電動車需求上升,Nissan將對現有車款EV化

日經新聞報導,日產汽車(Nissan)將大舉擴充電動車(EV)產品陣容,日產社長兼CEO西川廣人27日於橫濱市舉行的定期股東會上表示,「今年度將推出新型『Leaf』,且中期來看,將推動現行已進行量產販售的車款EV化」。

因北美、中國加強環保規範,帶動EV有望進一步普及。日產目前的EV車款僅有「Leaf」等少數幾款,而之後計畫將SUV、輕型汽車以及商用車進行EV化。

另外,日產會長Carlos Ghosn也在股東會上表示,「日產在EV界居領導位置。日產EV累計銷售量超過60萬台、為美國特斯拉(Tesla)的2倍」。

日本市調機構富士經濟(Fuji Keizai)6月22日公布銷售動向報告指出,EV在2025年以後需求將急速增加,預估2030年時EV年銷售量將增至407萬台、超越油電混合車(HV、2030年銷售量預估為391萬台),且之後雙方的差距將持續擴大。在中國需求增加加持下,2035年EV全球銷售量將擴大至630萬台、將達2016年的13.4倍(較2016年增加12.4倍)。

(本文內容由授權使用。圖片出處:)

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

【其他文章推薦】

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

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

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

分類
發燒車訊

Json模塊和Pickle模塊的使用

在對數據進行序列化和反序列化是常見的數據操作,Python提供了兩個模塊方便開發者實現數據的序列化操作,即 json 模塊和 pickle 模塊。這兩個模塊主要區別如下:

  • json 是一個文本序列化格式,而 pickle 是一個二進制序列化格式;
  • json 是我們可以直觀閱讀的,而 pickle 不可以;
  • json 是可互操作的,在 Python 系統之外廣泛使用,而 pickle 則是 Python 專用的;
  • 默認情況下,json 只能表示 Python 內置類型的子集,不能表示自定義的類;但 pickle 可以表示大量的 Python 數據類型。

Json 模塊

Json 是一種輕量級的數據交換格式,由於其具有傳輸數據量小、數據格式易解析等特點,它被廣泛應用於各系統之間的交互操作,作為一種數據格式傳遞數據。它包含多個常用函數,具體如下:

dumps()函數

dumps()函數可以將 Python 對象編碼成 Json 字符串。例如:

#字典轉成json字符串 加上ensure_ascii=False以後,可以識別中文, indent=4是間隔4個空格显示

import json         
d={'小明':{'sex':'男','addr':'上海','age':26},'小紅':{ 'sex':'女','addr':'上海', 'age':24},}
print(json.dumps(d,ensure_ascii=False,indent=4))

#執行結果:
{
    "小明": {
        "sex": "男",
        "addr": "上海",
        "age": 26
    },
    "小紅": {
        "sex": "女",
        "addr": "上海",
        "age": 24
    }
}

dump()函數

dump()函數可以將 Python對象編碼成 json 字符串,並自動寫入到文件中,不需要再單獨寫文件。例如:

#字典轉成json字符串,不需要寫文件,自動轉成的json字符串寫入到‘users.json’的文件中 
import json                                                                         
d={'小明':{'sex':'男','addr':'上海','age':26},'小紅':{ 'sex':'女','addr':'上海', 'age':24},}
#打開一個名字為‘users.json’的空文件
fw =open('users.json','w',encoding='utf-8')

json.dump(d,fw,ensure_ascii=False,indent=4)

loads()函數

loads()函數可以將 json 字符串轉換成 Python 的數據類型。例如:

#這是users.json文件中的內容
{
    "小明":{
        "sex":"男",
        "addr":"上海",
        "age":26
    },
    "小紅":{
        "sex":"女",
        "addr":"上海",
        "age":24
    }
}

#!/usr/bin/python3
#把json串變成python的數據類型   
import json  
#打開‘users.json’的json文件
f =open('users.json','r',encoding='utf-8')
#讀文件
res=f.read()
print(json.loads(res))

#執行結果:
{'小明': {'sex': '男', 'addr': '上海', 'age': 26}, '小紅': {'sex': '女', 'addr': '上海', 'age': 24}}

load()函數

load()loads()功能相似,load()函數可以將 json 字符串轉換成 Python 數據類型,不同的是前者的參數是一個文件對象,不需要再單獨讀此文件。例如:

#把json串變成python的數據類型:字典,傳一個文件對象,不需要再單獨讀文件 
import json   
#打開文件
f =open('users.json','r',encoding='utf-8') 
print(json.load(f))

#執行結果:
{'小明': {'sex': '男', 'addr': '上海', 'age': 26}, '小紅': {'sex': '女', 'addr': '上海', 'age': 24}}

Pickle 模塊

Pickle 模塊與 Json 模塊功能相似,也包含四個函數,即 dump()、dumps()、loads() 和 load(),它們的主要區別如下:

  • dumps 和 dump 的區別在於前者是將對象序列化,而後者是將對象序列化並保存到文件中。
  • loads 和 load 的區別在於前者是將序列化的字符串反序列化,而後者是將序列化的字符串從文件讀取並反序列化。

dumps()函數

dumps()函數可以將數據通過特殊的形式轉換為只有python語言認識的字符串,例如:

import pickle
# dumps功能
import pickle
data = ['A', 'B', 'C','D']  
print(pickle.dumps(data))

b'\x80\x03]q\x00(X\x01\x00\x00\x00Aq\x01X\x01\x00\x00\x00Bq\x02X\x01\x00\x00\x00Cq\x03X\x01\x00\x00\x00Dq\x04e.'

dump()函數

dump()函數可以將數據通過特殊的形式轉換為只有python語言認識的字符串,並寫入文件。例如:

# dump功能
with open('test.txt', 'wb') as f:
    pickle.dump(data, f)
print('寫入成功')

寫入成功

loads()函數

loads()函數可以將pickle數據轉換為python的數據結構。例如:

# loads功能
msg = pickle.loads(datastr)
print(msg)

['A', 'B', 'C', 'D']

load()函數

load()函數可以從數據文件中讀取數據,並轉換為python的數據結構。例如:

# load功能
with open('test.txt', 'rb') as f:
   data = pickle.load(f)
print(data)

['A', 'B', 'C', 'D']

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

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

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

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

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

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