JavaScript數據結構——鏈表的實現與應用

来源:https://www.cnblogs.com/jaxu/archive/2019/08/01/11277732.html
-Advertisement-
Play Games

鏈表用來存儲有序的元素集合,與數組不同,鏈表中的元素並非保存在連續的存儲空間內,每個元素由一個存儲元素本身的節點和一個指向下一個元素的指針構成。當要移動或刪除元素時,只需要修改相應元素上的指針就可以了。對鏈表元素的操作要比對數組元素的操作效率更高。下麵是鏈表數據結構的示意圖: 要實現鏈表數據結構,關 ...


  鏈表用來存儲有序的元素集合,與數組不同,鏈表中的元素並非保存在連續的存儲空間內,每個元素由一個存儲元素本身的節點和一個指向下一個元素的指針構成。當要移動或刪除元素時,只需要修改相應元素上的指針就可以了。對鏈表元素的操作要比對數組元素的操作效率更高。下麵是鏈表數據結構的示意圖:

  要實現鏈表數據結構,關鍵在於保存head元素(即鏈表的頭元素)以及每一個元素的next指針,有這兩部分我們就可以很方便地遍歷鏈表從而操作所有的元素。可以把鏈表想象成一條鎖鏈,鎖鏈中的每一個節點都是相互連接的,我們只要找到鎖鏈的頭,整條鎖鏈就都可以找到了。讓我們來看一下具體的實現方式。

  首先我們需要一個輔助類,用來描述鏈表中的節點。這個類很簡單,只需要兩個屬性,一個用來保存節點的值,一個用來保存指向下一個節點的指針。

let Node = function (element) {
    this.element = element;
    this.next = null;
};

  下麵是我們鏈表類的基本骨架:

class LinkedList {
    constructor() {
        this.length = 0;
        this.head = null;
    }

    append (element) {} // 向鏈表中添加節點

    insert (position, element) {} // 在鏈表的指定位置插入節點

    removeAt (position) {} // 刪除鏈表中指定位置的元素,並返回這個元素的值

    remove (element) {} // 刪除鏈表中對應的元素

    indexOf (element) {} // 在鏈表中查找給定元素的索引

    getElementAt (position) {} // 返回鏈表中索引所對應的元素

    isEmpty () {} // 判斷鏈表是否為空

    size () {} // 返回鏈表的長度

    getHead () {} // 返回鏈表的頭元素

    clear () {} // 清空鏈表

    toString () {} // 輔助方法,按指定格式輸出鏈表中的所有元素,方便測試驗證結果
}

  讓我們從查找鏈表元素的方法getElementAt()開始,因為後面我們會多次用到它。

getElementAt (position) {
    if (position < 0 || position >= this.length) return null;

    let current = this.head;
    for (let i = 0; i < position; i++) {
        current = current.next;
    }
    return current;
}

   首先判斷參數position的邊界值,如果值超出了索引的範圍(小於0或者大於length - 1),則返回null。我們從鏈表的head開始,遍歷整個鏈表直到找到對應索引位置的節點,然後返回這個節點。是不是很簡單?和所有有序數據集合一樣,鏈表的索引預設從0開始,只要找到了鏈表的頭(所以我們必須在LinkedList類中保存head值),然後就可以遍歷找到索引所在位置的元素。

  有了getElementAt()方法,接下來我們就可以很方便地實現append()方法,用來在鏈表的尾部添加新節點。

append (element) {
    let node = new Node(element);

    // 如果當前鏈表為空,則將head指向node
    if (this.head === null) this.head = node;
    else {
        // 否則,找到鏈表尾部的元素,然後添加新元素
        let current = this.getElementAt(this.length - 1);
        current.next = node;
    }

    this.length++;
}

   如果鏈表的head為null(這種情況表示鏈表為空),則直接將head指向新添加的元素。否則,通過getElementAt()方法找到鏈表的最後一個節點,將該節點的next指針指向新添加的元素。新添加的元素的next指針預設為null,鏈表最後一個元素的next值為null。將節點掛到鏈表上之後,不要忘記將鏈表的長度加1,我們需要通過length屬性來記錄鏈表的長度。

  接下來我們要實現insert()方法,可以在鏈表的任意位置添加節點。

insert (position, element) {
    // position不能超出邊界值
    if (position < 0 || position > this.length) return false;

    let node = new Node(element);

    if (position === 0) {
        node.next = this.head;
        this.head = node;
    }
    else {
        let previous = this.getElementAt(position - 1);
        node.next = previous.next;
        previous.next = node;
    }

    this.length++;
    return true;
}

  首先也是要判斷參數position的邊界值,不能越界。當position的值為0時,表示要在鏈表的頭部插入新節點,對應的操作如下圖所示。將新插入節點的next指針指向現在的head,然後更新head的值為新插入的節點。

  如果要插入的節點在鏈表的中間或者尾部,對應的操作如下圖。假設鏈表長度為3,要在位置2插入新節點,我們首先找到位置2的前一個節點previous node,將新節點new node的next指針指向previous node的next所對應的節點,然後再將previous node的next指針指向new node,這樣就把新節點掛到鏈表中了。考慮一下,當插入的節點在鏈表的尾部,這種情況也是適用的。而如果鏈表為空,即鏈表的head為null,則參數position會超出邊界條件,從而insert()方法會直接返回false。

  最後,別忘了更新length屬性的值,將鏈表的長度加1。

  按照相同的方式,我們可以很容易地寫出removeAt()方法,用來刪除鏈表中指定位置的節點。

removeAt (position) {
    // position不能超出邊界值
    if (position < 0 || position >= this.length) return null;

    let current = this.head;

    if (position === 0) this.head = current.next;
    else {
        let previous = this.getElementAt(position - 1);
        current = previous.next;
        previous.next = current.next;
    }

    this.length--;
    return current.element;
}

   下麵兩張示意圖說明瞭從鏈表頭部和其它位置刪除節點的情況。

  如果要刪除的節點為鏈表的頭部,只需要將head移到下一個節點即可。如果當前鏈表只有一個節點,那麼下一個節點為null,此時將head指向下一個節點等同於將head設置成null,刪除之後鏈表為空。如果要刪除的節點在鏈表的中間部分,我們需要找出position所在位置的前一個節點,將它的next指針指向position所在位置的下一個節點。總之,刪除節點只需要修改相應節點的指針,使斷開位置左右相鄰的節點重新連接上。被刪除的節點由於再也沒有其它部分的引用而被丟棄在記憶體中,等待垃圾回收器來清除。有關JavaScript垃圾回收器的工作原理,可以查看這裡

  最後,別忘了將鏈表的長度減1。

  下麵我們來看看indexOf()方法,該方法返回給定元素在鏈表中的索引位置。

indexOf (element) {
    let current = this.head;

    for (let i = 0; i < this.length; i++) {
        if (current.element === element) return i;
        current = current.next;
    }

    return -1;
}

  我們從鏈表的頭部開始遍歷,直到找到和給定元素相同的元素,然後返回對應的索引號。如果沒有找到對應的元素,則返回-1。

  鏈表類中的其它方法都比較簡單,就不再分部講解了,下麵是完整的鏈表類的代碼:

  1 class LinkedList {
  2     constructor() {
  3         this.length = 0;
  4         this.head = null;
  5     }
  6 
  7     append (element) {
  8         let node = new Node(element);
  9 
 10         // 如果當前鏈表為空,則將head指向node
 11         if (this.head === null) this.head = node;
 12         else {
 13             // 否則,找到鏈表尾部的元素,然後添加新元素
 14             let current = this.getElementAt(this.length - 1);
 15             current.next = node;
 16         }
 17 
 18         this.length++;
 19     }
 20 
 21     insert (position, element) {
 22         // position不能超出邊界值
 23         if (position < 0 || position > this.length) return false;
 24 
 25         let node = new Node(element);
 26 
 27         if (position === 0) {
 28             node.next = this.head;
 29             this.head = node;
 30         }
 31         else {
 32             let previous = this.getElementAt(position - 1);
 33             node.next = previous.next;
 34             previous.next = node;
 35         }
 36 
 37         this.length++;
 38         return true;
 39     }
 40 
 41     removeAt (position) {
 42         // position不能超出邊界值
 43         if (position < 0 || position >= this.length) return null;
 44 
 45         let current = this.head;
 46 
 47         if (position === 0) this.head = current.next;
 48         else {
 49             let previous = this.getElementAt(position - 1);
 50             current = previous.next;
 51             previous.next = current.next;
 52         }
 53 
 54         this.length--;
 55         return current.element;
 56     }
 57 
 58     remove (element) {
 59         let index = this.indexOf(element);
 60         return this.removeAt(index);
 61     }
 62 
 63     indexOf (element) {
 64         let current = this.head;
 65 
 66         for (let i = 0; i < this.length; i++) {
 67             if (current.element === element) return i;
 68             current = current.next;
 69         }
 70 
 71         return -1;
 72     }
 73 
 74     getElementAt (position) {
 75         if (position < 0 || position >= this.length) return null;
 76 
 77         let current = this.head;
 78         for (let i = 0; i < position; i++) {
 79             current = current.next;
 80         }
 81         return current;
 82     }
 83 
 84     isEmpty () {
 85         // return this.head === null;
 86         return this.length === 0;
 87     }
 88 
 89     size () {
 90         return this.length;
 91     }
 92 
 93     getHead () {
 94         return this.head;
 95     }
 96 
 97     clear () {
 98         this.head = null;
 99         this.length = 0;
100     }
101 
102     toString () {
103         let current = this.head;
104         let s = '';
105 
106         while (current) {
107             let next = current.next;
108             next = next ? next.element : 'null';
109             s += `[element: ${current.element}, next: ${next}] `;
110             current = current.next;
111         }
112 
113         return s;
114     }
115 }
LinkedList

   在isEmpty()方法中,我們可以根據length是否為0來判斷鏈表是否為空,當然也可以根據head是否為null來進行判斷,前提是所有涉及到鏈表節點添加和移除的方法都要正確地更新length和head。toString()方法只是為了方便測試而編寫的,我們來看看幾個測試用例:

let linkedList = new LinkedList();
linkedList.append(10);
linkedList.append(15);
linkedList.append(20);

console.log(linkedList.toString());

linkedList.insert(0, 9);
linkedList.insert(2, 11);
linkedList.insert(5, 25);
console.log(linkedList.toString());

console.log(linkedList.removeAt(0));
console.log(linkedList.removeAt(1));
console.log(linkedList.removeAt(3));
console.log(linkedList.toString());

console.log(linkedList.indexOf(20));

linkedList.remove(20);

console.log(linkedList.toString());

linkedList.clear();
console.log(linkedList.size());

  下麵是執行結果:

雙向鏈表

  上面鏈表中每一個元素只有一個next指針,用來指向下一個節點,這樣的鏈表稱之為單向鏈表,我們只能從鏈表的頭部開始遍歷整個鏈表,任何一個節點只能找到它的下一個節點,而不能找到它的上一個節點。雙向鏈表中的每一個元素擁有兩個指針,一個用來指向下一個節點,一個用來指向上一個節點。在雙向鏈表中,除了可以像單向鏈表一樣從頭部開始遍歷之外,還可以從尾部進行遍歷。下麵是雙向鏈表的數據結構示意圖:

  由於雙向鏈表具有單向鏈表的所有特性,因此我們的雙向鏈表類可以繼承自前面的單向鏈表類,不過輔助類Node需要添加一個prev屬性,用來指向前一個節點。

let Node = function (element) {
    this.element = element;
    this.next = null;
    this.prev = null;
};

  下麵是繼承自LinkedList類的雙向鏈表類的基本骨架:

class DoubleLinkedList extends LinkedList {
    constructor() {
        super();
        this.tail = null;
    }
}

   先來看看append()方法的實現。當鏈表為空時,除了要將head指向當前添加的節點外,還要將tail也指向當前要添加的節點。當鏈表不為空時,直接將tail的next指向當前要添加的節點node,然後修改node的prev指向舊的tail,最後修改tail為新添加的節點。我們不需要從頭開始遍歷整個鏈表,而通過tail可以直接找到鏈表的尾部,這一點比單向鏈表的操作要更方便。最後將length的值加1,修改鏈表的長度。

append (element) {
    let node = new Node(element);

    // 如果鏈表為空,則將head和tail都指向當前添加的節點
    if (this.head === null) {
        this.head = node;
        this.tail = node;
    }
    else {
        // 否則,將當前節點添加到鏈表的尾部
        this.tail.next = node;
        node.prev = this.tail;
        this.tail = node;
    }

    this.length++;
}

   由於雙向鏈表可以從鏈表的尾部往前遍歷,所以我們修改了getElementAt()方法,對基類中單向鏈表的方法進行了改寫。當要查找的元素的索引號大於鏈表長度的一半時,從鏈表的尾部開始遍歷。

getElementAt (position) {
    if (position < 0 || position >= this.length) return null;

    // 從後往前遍歷
    if (position > Math.floor(this.length / 2)) {
        let current = this.tail;
        for (let i = this.length - 1; i > position; i--) {
            current = current.prev;
        }
        return current;
    }
    // 從前往後遍歷
    else {
        return super.getElementAt(position);
    }
}

  有兩種遍歷方式,從前往後遍歷調用的是基類單向鏈表裡的方法,從後往前遍歷需要用到節點的prev指針,用來查找前一個節點。

  我們同時還需要修改insert()和removeAt()這兩個方法。記住,與單向鏈表唯一的區別就是要同時維護head和tail,以及每一個節點上的next和prev指針。

insert (position, element) {
    if (position < 0 || position > this.length) return false;

    // 插入到尾部
    if (position === this.length) this.append(element);
    else {
        let node = new Node(element);

        // 插入到頭部
        if (position === 0) {
            if (this.head === null) {
                this.head = node;
                this.tail = node;
            }
            else {
                node.next = this.head;
                this.head.prev = node;
                this.head = node;
            }
        }
        // 插入到中間位置
        else {
            let current = this.getElementAt(position);
            let previous = current.prev;
            node.next = current;
            node.prev = previous;
            previous.next = node;
            current.prev = node;
        }
    }

    this.length++;
    return true;
}

removeAt (position) {
    // position不能超出邊界值
    if (position < 0 || position >= this.length) return null;

    let current = this.head;
    let previous;

    // 移除頭部元素
    if (position === 0) {
        this.head = current.next;
        this.head.prev = null;
        if (this.length === 1) this.tail = null;
    }
    // 移除尾部元素
    else if (position === this.length - 1) {
        current = this.tail;
        this.tail = current.prev;
        this.tail.next = null;
    }
    // 移除中間元素
    else {
        current = this.getElementAt(position);
        previous = current.prev;
        previous.next = current.next;
        current.next.prev = previous;
    }

    this.length--;
    return current.element;
}

  操作過程中需要判斷一些特殊情況,例如鏈表的頭和尾,以及當前鏈表是否為空等等,否則程式可能會在某些特殊情況下導致越界和報錯。下麵是一個完整的雙向鏈表類的代碼:

 1 class CircularLinkedList extends LinkedList {
 2     constructor () {
 3         super();
 4     }
 5 
 6     append (element) {
 7         let node = new LinkedList.Node(element);
 8 
 9         if (this.head === null) this.head = node;
10         else {
11             let current = this.getElementAt(this.length - 1);
12             current.next = node;
13         }
14 
15         node.next = this.head; // 將新添加的元素的next指向head
16         this.length++;
17     }
18 
19     insert (position, element) {
20         // position不能超出邊界值
21         if (position < 0 || position > this.length) return false;
22 
23         let node = new LinkedList.Node(element);
24 
25         if (position === 0) {
26             node.next = this.head;
27             if (this.length > 0) {
28                 let current = this.getElementAt(this.length - 1);
29                 current.next = node;
30             }
31             this.head = node;
32         }
33         else {
34             let previous = this.getElementAt(position - 1);
35             node.next = previous.next;
36             previous.next = node;
37         }
38 
39         this.length++;
40         return true;
41     }
42 
43     removeAt (position) {
44         if (position < 0 || position >= this.length) return null;
45 
46         let current = this.head;
47 
48         if (position === 0) this.head = current.next;
49         else {
50             let previous = this.getElementAt(position - 1);
51             current = previous.next;
52             previous.next = current.next;
53         }
54         this.length--;
55 
56         if (this.length > 1) {
57             let last = this.getElementAt(this.length - 1);
58             last.next = this.head;
59         }
60 
61 
62         return current.element;
63     }
64 
65     toString () {
66         let current = this.head;
67         let s = '';
68 
69         for (let i = 0; i < this.length; i++) {
70             let next = current.next;
71             next = next ? next.element : 'null';
72             s += `[element: ${current.element}, next: ${next}] `;
73             current = current.next;
74         }
75 
76         return s;
77     }
78 }
CircularLinkedList

  我們重寫了toString()方法以方便更加清楚地查看測試結果。下麵是一些測試用例:

let doubleLinkedList = new DoubleLinkedList();
doubleLinkedList.append(10);
doubleLinkedList.append(15);
doubleLinkedList.append(20);
doubleLinkedList.append(25);
doubleLinkedList.append(30);
console.log(doubleLinkedList.toString());
console.log(doubleLinkedList.getElementAt(1).element);
console.log(doubleLinkedList.getElementAt(2).element);
console.log(doubleLinkedList.getElementAt(3).element);

doubleLinkedList.insert(0, 9);
doubleLinkedList.insert(4, 24);
doubleLinkedList.insert(7, 35);
console.log(doubleLinkedList.toString());

console.log(doubleLinkedList.removeAt(0));
console.log(doubleLinkedList.removeAt(1));
console.log(doubleLinkedList.removeAt(5));
console.log(doubleLinkedList.toString());

  對應的結果如下:

[element: 10, prev: null, next: 15] [element: 15, prev: 10, next: 20] [element: 20, prev: 15, next: 25] [element: 25, prev: 20, next: 30] [element: 30, prev: 25, next: null] 
15
20
25
[element: 9, prev: null, next: 10] [element: 10, prev: 9, next: 15] [element: 15, prev: 10, next: 20] [element: 20, prev: 15, next: 24] [element: 24, prev: 20, next: 25] [element: 25, prev: 24, next: 30] [element: 30, prev: 25, next: 35] [element: 35, prev: 30, next: null] 
9
15
30
[element: 10, prev: null, next: 20] [element: 20, prev: 10, next: 24] [element: 24, prev: 20, next: 25] [element: 25, prev: 24, next: 35] [element: 35, prev: 25, next: null] 

 迴圈鏈表

  顧名思義,迴圈鏈表的尾部指向它自己的頭部。迴圈鏈表可以有單向迴圈鏈表,也可以有雙向迴圈鏈表。下麵是單向迴圈鏈表和雙向迴圈鏈表的數據結構示意圖:

  在實現迴圈鏈表時,需要確保最後一個元素的next指針指向head。下麵是單向迴圈鏈表的完整代碼:

 1 class CircularLinkedList extends LinkedList.LinkedList {
 2     constructor () {
 3         super();
 4     }
 5 
 6     append (element) {
 7         let node = new LinkedList.Node(element);
 8 
 9         if (this.head === null) this.head = node;
10         else {
11             let current = this.getElementAt(this.length - 1);
12             current.next = node;
13         }
14 
15         node.next = this.head; // 將新添加的元素的next指向head
16         this.length++;
17     }
18 
19     insert (position, element) {
20         // position不能超出邊界值
21         if (position < 0 || position > this.length) return false;
22 
23         let node = new LinkedList.Node(element);
24 
25         if (position === 0) {
26             node.next = this.head;
27             let current = this.getElementAt(this.length - 1);
28             current.next = node;
29             this.head = node;
30         }
31         else {
32             let previous = this.getElementAt(position - 1);
33             node.next = previous.next;
34             previous.next = node;
35         }
36 
37         this.length++;
38         return true;
39     }
40 
41     removeAt (position) {
42         if (position < 0 || position >= this.length) return null;
43 
44         let current = this.head;
45 
46         if (position === 0) this.head = current.next;
47         else {
48             let previous = this.getElementAt(position - 1);
49             current = previous.next;
50             previous.next = current.next;
51         }
52         this.length--;
53 
54         if (this.length > 1) {
55             let last = this.getElementAt(this.length - 1);
56             last.next = this.head;
57         }
58 
59 
60         return current.element;
61     }
62 
63     toString () {
64         let current = this.head;
65         let s = '';
66 
67         for (let i = 0; i < this.length; i++) {
68             let next = current.next;
69    

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

-Advertisement-
Play Games
更多相關文章
  • isMemberOfClass - 調用者必須是傳入的類的實例對象才返回YES- 判斷調用者是否是傳入對象的實例,別弄反了,如 [s1 isMemberOfClass:p1] ,意思是s1是否是p1的實例對象- 去去父類遞歸查找判斷 源碼: 有兩個方法,一個實例方法,一個類方法,兩者區別: - 實例 ...
  • 最近做了一些JavaScript的數據處理,有點暈乎找到一篇比較通俗易懂的博客加深了我對其中==運算的理解 以下是原文地址以及自身的一些見解 http://www.admin10000.com/document/9242.html 1、JavaScript有的值有兩種類型:原始類型(Primitiv ...
  • 下載地址:網盤下載 第1章 課程介紹 對課程整體進行介紹第2章 HTML基礎強化講解HTML常見元素、版本(HTML4/XHTML/HTML5的關係)以及HTML元素的分類和嵌套關係。關註元素預設樣式和定製化。第3章 CSS基礎全面講解CSS基礎知識,包括層疊樣式表的基本規則和含義、選擇器(分類、特 ...
  • 未使用express等框架,僅使用原生模塊和一些自定義模塊搭建HTTP伺服器,並支持MP4格式的視頻傳輸和jpg、gif、png的圖片傳輸;該node.js伺服器提供rar文件下載功能。路由模塊通過請求的尾碼名將不同類型的文件分發到客戶端,並設置相應的響應頭。為了監控伺服器的運行狀況,我添加了獲取客... ...
  • 1.CSS簡介【瞭解】 1.1cascading style sheets 層疊樣式表 層疊:一層一層 樣式:格式,更多更豐富 CSS 就是為HTML服務的。 1.2CSS目的 樣式更多 使用CSS,可以實現樣式和頁面的分離,降耦合 HTML專註於做頁面的開發,CSS 專註於做樣式的開發。 2.CS ...
  • 選擇器 1.核心選擇器 標簽選擇器 div{} id選擇器 one{} class選擇器 .second 逗號選擇器 div, id name{} 組合選擇器 div one{} 普通選擇器 一般不適用 (寬度加給父元素,高度加給子元素) 2.層次選擇器 子元素選擇器 .nav ul li {} 後 ...
  • 1.HTML簡介【瞭解】 1.1Hyper text markup language 超文本標記語言 超文本:可以對文本進行格式化編輯,還有文本的鏈接等等,具有傳統文本不具備的特性 標記:html上所有操作都是通過標記實現,標記就是標簽 語言 :不需要編譯,運行在瀏覽器上面 1.2HTML基本思想 ...
  • 前言 瀏覽器緩存是瀏覽器端保存數據用於快速讀取或避免重覆資源請求的優化機制,有效的緩存使用可以避免重覆的網路請求和瀏覽器快速地讀取本地數據,整體上加速網頁展示給用戶。瀏覽器端緩存的機制種類較多,總體歸納為九種,這裡詳細分析下這九種緩存機制的原理和使用場景。打開瀏覽器的調試模式——Applicatio ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...