減小記憶體的占用問題——享元模式和單例模式的對比分析

来源:http://www.cnblogs.com/kubixuesheng/archive/2016/02/02/5174025.html
-Advertisement-
Play Games

俗話說,自己寫的代碼,6個月後也是別人的代碼……複習!複習!複習!總結的知識點如下: 享元模式概念和實現例子 使用了享元模式的Java API String類 java.lang.Integer 的 valueOf(int)方法源碼分析 使用享元模式的條件 享元模式和單例模式的區別 前面的策略模式的


俗話說,自己寫的代碼,6個月後也是別人的代碼……複習!複習!複習!總結的知識點如下:

  • 享元模式概念和實現例子
  • 使用了享元模式的Java API String類
  • java.lang.Integer 的 valueOf(int)方法源碼分析
  • 使用享元模式的條件
  • 享元模式和單例模式的區別

  前面的策略模式的話題提起了:如何解決策略類膨脹的問題,說到 “有時候可以通過把依賴於環境Context類的狀態保存到客戶端裡面,而將策略類設計成可共用的,這樣策略類實例可以被不同客戶端使用。”換言之,可以使用享元模式來減少對象的數量,享元模式它的英文名字叫Flyweigh模式,又有人翻譯為羽量級模式,它是構造型模式之一,它通過與其他類似對象共用數據來減小記憶體占用,也正應了它的名字:享-分享。

  那麼到底是什麼意思呢?有什麼用呢?下麵看個例子:我們有一個文檔,裡面寫了很多英文,大家知道英文字母有26個,大小寫一起一共是52個:

  那麼我保存這個文件的時候,所有的單詞都占據了一份記憶體,每個字母都是一個對象,如果文檔里的字母有重覆的,怎麼辦?難道每次都要創建新的字母對象去保存麽?答案是否定的,其實每個字母只需要創建一次,然後把他們保存起來,當再次使用的時候直接在已經創建好的字母里取就ok了,這就是享元模式的一個思想的體現。說到這兒,其實想起了Java的String類,這個類就是應用了享元模式。稍後再說,先看享元模式的類圖和具體實現例子。

  抽象享元角色(介面或者抽象類):所有具體享元類的父類,規定一些需要實現的公共介面。

  具體享元角色:抽象享元角色的具體實現類,並實現了抽象享元角色規定的方法。

  享元工廠角色:負責創建和管理享元角色。它必須保證享元對象可以被系統適當地共用。當一個客戶端對象調用一個享元對象的時候,享元工廠角色會檢查系統中是否已經有一個符合要求的享元對象。如果已經有了,享元工廠角色就應當提供這個已有的享元對象,如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當創建一個合適的享元對象。代碼如下:

 1 public interface ICharacter {
 2     /**
 3      * 享元模式的抽象享元角色,所有具體享元類的父類,規定一些需要實現的公共介面。其實沒有這個介面也可以的。
 4      *
 5      * 顯式我自己的字母
 6      */
 7     void displayCharacter();
 8 }
 9 
10 public class ChracterBuilder implements ICharacter {
11     private char aChar;
12 
13     public ChracterBuilder(char c) {
14         this.aChar = c;
15     }
16     /**
17      * 具體的享元模式角色
18      */
19     @Override
20     public void displayCharacter() {
21         System.out.println(aChar);
22     }
23 }
24 
25 public class FlyWeightFactory {
26     /**
27      * 註意:享元模式採用一個共用來避免大量擁有相同內容對象的開銷。這種開銷最常見、最直觀的就是記憶體的損耗。
28      * 我們這裡使用數組也行,或者 HashMap
29      */
30     private Map<Character, ICharacter> characterPool;
31 
32     public FlyWeightFactory() {
33         this.characterPool = new HashMap<>();
34     }
35 
36     public ICharacter getICharater(Character character) {
37         // 先去pool里判斷
38         ICharacter iCharacter = this.characterPool.get(character);
39 
40         if (iCharacter == null) {
41             // 如果池子里沒有就new一個新的,並加到pool里
42             iCharacter = new ChracterBuilder(character);
43             this.characterPool.put(character, iCharacter);
44         }
45 
46         // 否則直接從pool里取出
47         return iCharacter;
48     }
49 }
View Code

下麵使用客戶端調用,先看看普通的方法

public class MainFlyWeight {
    public static void main(String[] args) {
        //===================================
        // 不用享元模式,我們每次創建相同內容的字母的時候,都要new一個新的對象
        ICharacter iCharacter = new ChracterBuilder('a');
        ICharacter iCharacter1 = new ChracterBuilder('b');
        ICharacter iCharacter2 = new ChracterBuilder('b');
        ICharacter iCharacter3 = new ChracterBuilder('b');
        ICharacter iCharacter4 = new ChracterBuilder('b');

        iCharacter.displayCharacter();
        iCharacter1.displayCharacter();
        iCharacter2.displayCharacter();
        iCharacter3.displayCharacter();
        iCharacter4.displayCharacter();

        // 再通過實驗判斷
        if (iCharacter2 == iCharacter1) {
            System.out.print("true");
        } else {
            // 列印了 false,說明是兩個不同的對象
            System.out.print("false");
        }
}
View Code

下麵使用享元模式,必須指出的是,使用享元模式,那麼客戶端絕對不可以直接將具體享元類實例化,而必須通過一個工廠得到享元對象。

public class MainFlyWeight {
    public static void main(String[] args) {
        // 使用享元模式
        // 必須指出的是,客戶端不可以直接將具體享元類實例化,而必須通過一個工廠
        FlyWeightFactory flyWeightFactory = new FlyWeightFactory();

        ICharacter iCharacter = flyWeightFactory.getICharater('a');
        ICharacter iCharacter1 = flyWeightFactory.getICharater('b');
        ICharacter iCharacter2 = flyWeightFactory.getICharater('b');

        iCharacter.displayCharacter();
        iCharacter1.displayCharacter();
        iCharacter2.displayCharacter();

        if (iCharacter1 == iCharacter2) {
            // 確實列印了
            System.out.print("============");
        }
        // 同樣列印的都一樣,但是對象記憶體的占據卻不一樣了,減少了記憶體的占用
    }
}
View Code

  類圖如下:

  一般而言,享元工廠對象在整個系統中只有一個,因此也可以使用單例模式,由工廠方法產生所需要的享元對象且設計模式不用拘泥於具體代碼, 代碼實現可能有n多種方式,n多語言……再看一例子,有老師類,繼承Person類,老師類里保存一個數字編號,客戶端可以通過它來找到對應的老師。

  1 public class Person {
  2     private String name;
  3 
  4     private int age;
  5 
  6     private String sex;
  7 
  8     /**
  9      * person是享元抽象角色
 10      *
 11      * @param age int
 12      * @param name String
 13      * @param sex String
 14      */
 15     public Person(int age, String name, String sex) {
 16         this.age = age;
 17         this.name = name;
 18         this.sex = sex;
 19     }
 20 
 21     public Person() {
 22 
 23     }
 24 
 25     public int getAge() {
 26         return age;
 27     }
 28 
 29     public void setAge(int age) {
 30         this.age = age;
 31     }
 32 
 33     public String getName() {
 34         return name;
 35     }
 36 
 37     public void setName(String name) {
 38         this.name = name;
 39     }
 40 
 41     public String getSex() {
 42         return sex;
 43     }
 44 
 45     public void setSex(String sex) {
 46         this.sex = sex;
 47     }
 48 }
 49 
 50 public class Teacher extends Person {
 51     private int number;
 52 
 53     /**
 54      * teacher是具體的享元角色
 55      *
 56      * @param number int
 57      * @param age int
 58      * @param name String
 59      * @param sex String
 60      */
 61     public Teacher(int number, int age, String name, String sex) {
 62         super(age, name, sex);
 63         this.number = number;
 64     }
 65 
 66     public Teacher() {
 67         super();
 68     }
 69 
 70     public int getNumber() {
 71         return number;
 72     }
 73 
 74     public void setNumber(int number) {
 75         this.number = number;
 76     }
 77 }
 78 
 79 public class TeacherFactory {
 80     private Map<Integer, Teacher> integerTeacherMapPool;
 81 
 82     private TeacherFactory() {
 83         this.integerTeacherMapPool = new HashMap<>();
 84     }
 85 
 86     public static TeacherFactory getInstance() {
 87         return Holder.instance;
 88     }
 89 
 90     public Teacher getTeacher(int num) {
 91         Teacher teacher = integerTeacherMapPool.get(num);
 92 
 93         if (teacher == null) {
 94             // TODO 模擬用,不要把teacher寫死,每次使用set
 95             teacher = new Teacher();
 96             teacher.setNumber(num);
 97 
 98             integerTeacherMapPool.put(num, teacher);
 99         }
100 
101         return teacher;
102     }
103 
104     /**
105      * 使用靜態內部類,靜態內部類相當於外部類的static域,它的對象與外部類對象間不存在依賴關係,因此可直接創建。
106      * 因為靜態內部類相當於其外部類的成員,所以在第一次被使用的時候才被會裝載。且只裝載一次。
107      * 而對象內部類的實例,是綁定在外部對象實例中的。
108      * 靜態內部類中可以定義靜態方法,在靜態方法中只能夠引用外部類中的靜態成員方法或者成員變數。
109      *
110      * 在某些情況中,JVM 含地了同步,這些情況下就不用自己再來進行同步控制了。這些情況包括:
111      *1.由靜態初始化器(在靜態欄位上或static{}塊中的初始化器)初始化數據時
112      *2.訪問final欄位時
113      *3.在創建線程之前創建對象時
114      *4.線程可以看見它將要處理的對象時
115      * 
116      * 故,我使用了靜態初始化器來實現線程安全的單例類,它由 JVM 來保證線程安全性。
117      * 且這種實現方式,會在類裝載的時候(使用這個類的時候)就初始化對象,不管使用者需要不需要,且只實例化一次。
118      * 
119      * 故,我在外部類里再創建一個靜態內部類,在靜態內部類里去創建本類(外部類)的對象,這樣只要不使用這個靜態內部類,那就不創建對象實例,從而同時實現延遲載入和線程安全。
120      */
121     private static class Holder {
122         private static final TeacherFactory instance = new TeacherFactory();
123     }
124 }
View Code

客戶端調用

public class MainClass {
    public static void main(String[] args) {
        // 先創建工廠
        TeacherFactory teacherFactory = TeacherFactory.getInstance();
        // 通過工廠得到具體對象
        Teacher teacher = teacherFactory.getTeacher(1000);
        Teacher teacher1 = teacherFactory.getTeacher(1001);
        Teacher teacher2 = teacherFactory.getTeacher(1000);

        System.out.println(teacher.getNumber());
        System.out.println(teacher1.getNumber());
        System.out.println(teacher2.getNumber());

        // 判斷是否是相等對象
        if (teacher == teacher2) {
            // 確實列印了,ok
            System.out.print("____________-");
        }
    }
}
View Code

類圖

  小結,到底系統需要滿足什麼樣的條件才能使用享元模式。對於這個問題,總結出以下幾點: 

  • 一個系統中存在著大量的細粒度對象,且這些細粒度對象耗費了大量的記憶體。 
  • 這些細粒度對象的狀態中的大部分都可以外部化
  • 這些細粒度對象可以按照內蘊狀態分成很多的組,當把外蘊對象從對象中剔除時,每一個組都可以僅用一個對象代替。 
  • 軟體系統不依賴於這些對象的身份,換言之,這些對象可以是不可分辨的。

滿足以上的這些條件的系統可以使用享元模型。最後,使用享元模式需要維護一個記錄了系統已有的所有享元的哈希表,也稱之為對象池,而這也需要耗費一定的資源。因此應當在有足夠多的享元實例可供共用時才值得使用享元模式。

  好了,現在多了幾個新的概念(外部化,內蘊,外蘊……),再次用教科書的理論,分析之前的享元模式的例子和概念:

  內蘊狀態:存儲在享元對象內部的對象,並且這些對象是不會隨環境的改變而有所不同。因此,一個享元可以具有內蘊狀態並可以共用

  外蘊狀態:隨環境的改變而改變不可以共用的對象。享元對象的外蘊狀態必須由客戶端保存,併在享元對象被創建之後,在需要使用的時候再傳入到享元對象內部。外蘊狀態不可以影響享元對象的內蘊狀態,它們是相互獨立的。

  享元對象能做到共用的關鍵是區分內蘊狀態(Internal State)和外蘊狀態(External State)。回憶之前的例子:最近的Teacher例子,具體享元角色類Teacher類其實就是有一個內蘊狀態,在本例中一個int類型的number屬性,它的值應當在享元對象被創建時賦予,也就是內蘊狀態對象讓享元對象自己去保存,且可以被客戶端共用所有的內蘊狀態在對象創建之後,就不會再改變了。下麵看一個具有外蘊狀態的享元模式:

public interface BaseDao {
    /**
     * 連接數據源,享元模式的抽象享元角色
     *
     * @param session String 和數據源連接的session,該參數就是外蘊狀態
     */
    void connect(String session);
}
View Code

  如果一個享元對象有外蘊狀態的話,所有的外部狀態都必須存儲在客戶端,在使用享元對象時,再由客戶端傳入享元對象。這裡只有一個外蘊狀態,connect()方法的參數就是由外部傳入的外蘊狀態

public class DaoA implements BaseDao {
    /**
     * 內蘊狀態
     */
    private String strConn = null;

    /**
     * 內蘊狀態在創建享元對象的時候作為參數傳入構造器
     *
     * @param s String
     */
    public DaoA(String s) {
        this.strConn = s;
    }

    /**
     * 外蘊狀態 session 作為參數傳入抽象方法,可以改變方法的行為,但是對於內蘊狀態不做改變,兩者獨立
     * 外蘊狀態(對象)存儲在客戶端,當客戶端使用享元對象的時候才被傳入享元對象,而不是開始就有。
     *
     * @param session Session 和數據源連接的session,該參數就是外蘊狀態
     */
    @Override
    public void connect(String session) {
        System.out.print("內蘊狀態 是" + this.strConn);
        System.out.print("外蘊狀態 是" + session);
    }
}
View Code

  享元工廠

public enum Factory {
    /**
     * 單例模式的最佳實現是使用枚舉類型。只需要編寫一個包含單個元素的枚舉類型即可
     * 簡潔,且無償提供序列化,並由JVM從根本上提供保障,絕對防止多次實例化,且能夠抵禦反射和序列化的攻擊。
     */
    instance;

    /**
     * 可以有自己的操作
     */
    private Map<String, BaseDao> stringBaseDaoMapPool = new HashMap<>();

    public BaseDao factory(String s) {
        BaseDao baseDao = this.stringBaseDaoMapPool.get(s);

        if (baseDao == null) {
            baseDao = new DaoA(s);
            this.stringBaseDaoMapPool.put(s, baseDao);
        }

        return baseDao;
    }
}
View Code

雖然客戶端申請了三個享元對象,但是實際創建的享元對象只有兩個,這就是共用的含義

public class Client {
    public static void main(String[] args) {
        BaseDao baseDao = Factory.instance.factory("A連接數據源");
        BaseDao baseDao1 = Factory.instance.factory("B連接數據源");
        BaseDao baseDao2 = Factory.instance.factory("A連接數據源");
        baseDao.connect("session1");
        baseDao1.connect("session2");
        baseDao2.connect("session1");

        if (baseDao == baseDao2) {
            // 列印了
            System.out.print("===========");
        }
    }
}
View Code

 

  在JDK中有哪些使用享元模式的例子?舉例說明。

  說兩個,第一個是String類,第二個是java.lang.Integer 的 valueOf(int)方法。針對String,也是老生常談了,它是final的,字元串常量通常是在編譯的時候就確定好的,定義在類的方法區,也就是說,不同的類,即使用了同樣的字元串, 還是屬於不同的對象。所以才需要通過引用字元串常量來減少相同的字元串的數量。

        String s1 = "hello";
        String s2 = "he" + "llo";

        if (s1 == s2) {
            System.out.print("====");// 列印了,說明是1,2引用了一個對象hello
        }
View Code

使用相同的字元序列而不是使用new關鍵字創建的兩個字元串會創建指向Java字元串常量池中的同一個字元串的指針。字元串常量池是Java節約資源的一種方式。其實就是使用了享元模式的思想。字元串的分配,和其他的對象分配一樣,耗費高昂的時間與空間代價。JVM為了提高性能和減少記憶體開銷,在實例化字元串常量的時候進行了一些優化。為了減少在JVM中創建的字元串的數量,字元串類維護了一個字元串池,每當代碼創建字元串常量時,JVM會首先檢查字元串常量池。如果字元串已經存在池中,就返回池中的實例引用。如果字元串不在池中,就會實例化一個字元串並放到池中。Java能夠進行這樣的優化是因為字元串是不可變的,可以不用擔心數據衝突進行共用。

  java.lang.Integer 的 valueOf(int)方法源碼分析(8.0版本)

        Integer a = 1;
        Integer b = 1;

        System.out.print(a == b);// true
View Code

再看一例子;

        Integer a = new Integer(1);
        Integer b = new Integer(1);

        System.out.print(a == b);// false
View Code

很容易理解,再看

        Integer a = 200;
        Integer b = 200;

        System.out.println(a == b);// false
View Code

怎麼還是false呢?看看到底發生了什麼,反編譯上述程式;

public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 19 L0
    SIPUSH 200
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    ASTORE 1
   L1
    LINENUMBER 20 L1
    SIPUSH 200
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    ASTORE 2
View Code

我發現每次都是使用了自動裝箱

Integer c = Integer.valueOf(200);

再看該方法源碼;

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
View Code

我發現,當使用Integer的自動裝箱的時候,值在low和high之間時,會用緩存保存起來,供多次使用,以節約記憶體。如果不在這個範圍內,則創建一個新的Integer對象。這不就是尼瑪享元模式嗎!看看範圍:-128~+127

    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }
View Code

   

  享元模式的缺陷是什麼?

  享元模式的優點在於它大幅度地降低記憶體中對象的數量。但是,它做到這一點所付出的代價也是很高的:

  ●  享元模式使得系統更加複雜。為了使對象可以共用,需要將一些狀態外部化,這使得程式的邏輯複雜化。

  ●  享元模式將享元對象的狀態外部化,而讀取外部狀態使得運行時間稍微變長。

 

  享元模式比起工廠,單例,策略,裝飾,觀察者等模式,其實不算是常用的設計模式,它主要用在底層的設計上比較多,比如之前提到的String類,Integer的valueOf(int)方法等。好了,享元模式到這裡總結的差不多了,記得之前有個老師的例子,對老師的工廠類使用了單例模式創建了工廠對象,後來又有一個BaseDao例子,工廠Factory類使用了枚舉作為單例模式的實現,那麼這裡還要順便總結一個老生常談,但是又不見得真的談對了的設計模式——單例模式,如下之前總結的C++版的; 軟體開發常用設計模式—單例模式總結,發現Java實現的單例模式和C++的線上程安全上還是有些區別的。下麵主要說下Java版的單例模式的線程安全性。之前的一些私有靜態屬性(餓漢式),雙重檢測加鎖……不再贅述。

  簡單看看,單例模式還得單開文章總結,涉及到了枚舉實現和記憶體模型:單例類只能有一個實例,單例類必須自己創建自己的唯一實例,單例類必須給所有其他對象提供這一實例(提供全局訪問點)。

 

  小結:享元模式和單例模式的異同

  享元是對象級別的, 也就是說在多個使用到這個對象的地方都只需要使用這一個對象即可滿足要求, 而單例是類級別的, 就是說這個類必須只能實例化出來一個對象, 可以這麼說, 單例是享元的一種特例, 設計模式不用拘泥於具體代碼, 代碼實現可能有n多種方式, 而單例可以看做是享元的實現方式中的一種, 但是他比享元更加嚴格的控制了對象的唯一性。

  


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文從3個方面對Socket編程進行詳解: 一,網路編程中兩個主要的問題 二,兩類傳輸協議:TCP;UDP 三,基於Socket的java網路編程 一,網路編程中兩個主要的問題 一個是如何準確的定位網路上一臺或多台主機,另一個就是找到主機後如何可靠高效的進行數據傳輸。 在TCP/IP協議中IP層主要
  • <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <m
  • <!-- MyBatis框架 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.2.8</version> </dependency> <!-- MySql資料庫驅動
  • 之前寫了一題費用流,竟然硬是在寫SPFA時為DIS數組賦初始值用了MEMSET數組QAQ 調試了很久也沒有弄明白自己是卡在那裡了,,,感覺被自己蠢哭了QWQ 錯誤的姿勢!! #include <cstring> #include <iostream> #include <cstdio> using
  • list是一種有序的集合,可以隨時添加和刪除其中的元素。定義有序的集合: classmates = ['Michael', 'Bob', 'Tracy'] 用len()函數可以獲得list元素的個數: len(classmates) 用索引來訪問list中每一個位置的元素,記得索引是從0開始的: c...
  • >>> print('The quick brown fox', 'jumps over', 'the lazy dog') The quick brown fox jumps over the lazy dog print()會依次列印每個字元串,遇到逗號“,”會輸出一個空格,因此,輸出的字元串是...
  • 試題描述: 有 n 個同學(編號為 1 到 n )正在玩一個信息傳遞的游戲。在游戲里每人都有一個固定的信息傳遞對象,其中,編號為 i 的同學的信息傳遞對象是編號為 T_i 的同學。游戲開始時,每人都只知道自己的生日。之後每一輪中,所有人會同時將自己當前所知的生日信息告訴各自的信息傳遞對象(註意:可能
  • 回到目錄 分頁組件網上有很多,MVC.Pager,JSPager等,通過實現方式大體分為前端分頁和後端分頁,前端分頁是前臺對list記憶體本地集合進行分頁,缺點就是在大數據情況下,記憶體占用過高;後端分頁就是UI把要返回的頁號告訴後臺,由後臺組織數據並返回,這種方法就是我們經常看到的了;而根據後臺集合種
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...