工信部擬規範新能源汽車廢舊動力電池綜合利用

工業和資訊化部近日就《新能源汽車廢舊動力蓄電池綜合利用行業規範條件》向社會公開徵求意見,擬要求已在禁止建設區域投產運營的廢舊動力蓄電池綜合利用企業,要在一定期限內通過“依法搬遷、轉產”等方式逐步退出。

禁止建設區域包括:國家法律、法規、規章及規劃確定或縣級以上人民政府批准的自然保護區、生態功能保護區、風景名勝區、飲用水水源保護區、基本農田保護區和其他需要特別保護的區域等。

意見稿還對廢舊動力電池綜合利用作出規範。廢舊動力蓄電池綜合利用企業應依據相關國家、行業標準,參考新能源汽車和動力蓄電池生產企業提供的拆卸、拆解技術資訊,嚴格遵循先梯級利用後再生利用的原則,提高綜合利用水準。

根據意見稿,濕法冶煉條件下,鎳、鈷、錳的綜合回收率應不低於98%;火法冶煉條件下,鎳、稀土的綜合回收率應不低於97%;不得擅自丟棄、傾倒、焚燒與填埋廢舊動力電池中的有色金屬、石墨、塑膠、橡膠、隔膜、電解液等零部件和材料。

在能源消耗方面,意見稿規定,企業應加強對拆卸、儲存、拆解、檢測和再生利用等環節的能耗管控,努力降低綜合能耗,提高能源利用效率,鼓勵企業採用先進適用的節能技術工藝及裝備。

此外,工信部同日發佈《新能源汽車廢舊動力蓄電池綜合利用行業規範公告管理暫行辦法(徵求意見稿)》,擬對新能源汽車廢舊動力蓄電池綜合利用企業實行動態管理,委託相關專業機構負責協助做好公告管理相關工作。

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

【其他文章推薦】

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

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

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

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

【algo&ds】2.線性表

1.線性表

線性表(英語:Linear List)是由n(n≥0)個元素()a[0],a[1],a[2]…,a[n-1]組成的。

其中:

  • 數據元素的個數n定義為表的長度 = “list”.length() (”list”.length() = 0(表裡沒有一個元素)時稱為空表)
  • 將非空的線性表(n>=1)記作:(a[0],a[1],a[2],…,a[n-1])
  • 數據元素a[i](0≤i≤n-1)只是個抽象符號,其具體含義在不同情況下可以不同

一個數據元素可以由若干個數據項組成。數據元素稱為記錄,含有大量記錄的線性表又稱為文件。這種結構具有下列特點:存在一個唯一的沒有前驅的(頭)數據元素;存在一個唯一的沒有後繼的(尾)數據元素;此外,每一個數據元素均有一個直接前驅和一個直接後繼數據元素。

2.線性表的存儲結構

  • 鏈表
    • 單鏈表
      • 動態單鏈表
      • 靜態單鏈表
    • 循環鏈表
      • 單循環鏈表
      • 雙循環鏈表
    • 靜態鏈表

3.順序表

利用數組的連續存儲空間順序存放線性表的各元素

3.1結構體定義

如果需要使用自定義的結構體來維護一個順序表,通常來講結構體的元素一般是一個固定大小的數組(可用長度足夠大),以及當前數組存放的元素個數,也即數組的長度

typedef struct LNode *List;
struct LNode {
    ElementType Data[MAXSIZE];
    int Last;//記錄順序表的最後一個元素的下標
} ;
struct LNode L;
List PtrL;

訪問結構體的成員

  • 訪問下標為 i 的元素:L.Data[i] 或 PtrL->Data[i]
  • 線性表的長度:L.Last+1 或 PtrL->Last+1
  • 指針變量PtrL還可以這樣訪問兩個屬性(*PtrL).Data[i](*PtrL).Last,不過這種訪問方式並不常用

而一般來講,為了簡單,不會去維護這樣一個結構體,(因為一旦維護了這個結構體,就需要去封裝相應的函數,比如說常見的插入、刪除、查找等操作),而是直接類似下面這樣

ElementType data[MaxSize];
int length;

定義一個足夠大的數組,然後定義一個對應關聯的變量來時刻維護數組的長度。

這兩種定義方式沒有什麼區別,一種是把常用操作封裝好,方便調用,另一種則是需要時刻自己維護對應的屬性。因為順序表的結構足夠簡單,所以不定義結構體也是可以的。

3.2順序表的常見操作

為了方便,這一節內容記錄的都是在定義的結構體基礎上,封裝的常見操作。

1.初始化

List MakeEmpty( ) {
    List PtrL;
    PtrL = (List )malloc( sizeof(struct LNode) );
    PtrL->Last = -1;
    return PtrL;
}

初始化的順序表,長度為0,所以Last為-1

2.查找

int Find( ElementType X, List PtrL ) {
    int i = 0;
    while( i <= PtrL->Last && PtrL->Data[i]!= X )
        i++;
    if (i > PtrL->Last) return -1; /* 如果沒找到,返回-1 */
    else return i; /* 找到后返回的是存儲位置 */
}

查找操作比較簡單,從順序表的第一個元素(下標為0開始)開始遍歷。

還有一種更加巧妙一點的實現方式,就是引入哨兵思想。

int Find( ElementType X, List PtrL ) {
    PtrL->Data[0] = x;//順序表第一個元素就是哨兵,賦值為x
    int i = PtrL->Last;//從最後一個元素開始遍歷
    while( PtrL->Data[i]!= X )
        i--;
    return i;
}

這樣做的好處很明顯,少了邊界的判斷,可以優化時間複雜度,編碼也更加簡單。

注意:這裏把下標為0的元素設置為哨兵,則要求順序表從下標為1開始存儲。而且,函數如果沒有找到,則一定返回i=0

3.插入

看圖示應該要注意,移動的方向是從后往前移,如果從前往後移,則Data[i]=Data[i+1]=…=Data[n],因為後面的元素都被前面移過來的元素給覆蓋了。

void Insert( ElementType X, int i, List PtrL ) {
    int j;
    if ( PtrL->Last == MAXSIZE-1 ) { /* 表空間已滿,不能插入*/
        printf("表滿");
        return;
    }
    if ( i < 1 || i > PtrL->Last+2) { /*檢查插入位置的合法性*/
        printf("位置不合法");
        return;
    }
    for ( j = PtrL->Last; j >= i-1; j-- )
        PtrL->Data[j+1] = PtrL->Data[j]; /*將 ai~ an倒序向後移動*/
    PtrL->Data[i-1] = X; /*新元素插入*/
    PtrL->Last++; /*Last仍指向最後元素*/
    return;
}

為什麼這裏需要判斷順序表的空間是否已滿?

因為這個數組,是在初始化之後就固定了數組可容納的元素個數MaxSize,一旦超出,則程序下標就會越界。C++提供了動態數組vector,可以很方便的支持動態擴展數組長度,而且基本的插入刪除等操作都封裝好了,可以很方便的使用。

4.刪除

同樣的,需要注意元素移動的方向,如果從后往前移,則最後面的元素會一直覆蓋到Data[i]。

void Delete( int i, List PtrL ) {
    int j;
    if( i < 1 || i > PtrL->Last+1 ) { /*檢查空表及刪除位置的合法性*/
        printf (“不存在第%d個元素”, i );
        return ;
    }
    for ( j = i; j <= PtrL->Last; j++ )
        PtrL->Data[j-1] = PtrL->Data[j]; /*將 ai+1~ an順序向前移動*/
    PtrL->Last--; /*Last仍指向最後元素*/
    return;
}

5.排序

因為排序算法比較多,本文不展開講解,可以參考以下博文,內容包括了常見的十大排序算法的算法分析,時間複雜度和空間複雜度分析以及c實現和動圖圖解。

4.鏈表

不要求邏輯上相鄰的兩個元素物理上也相鄰;通過“鏈”建立起數據元素之間的邏輯關係。插入、刪除不需要移動數據元素,只需要修改“鏈”。

4.1單鏈表

typedef struct LNode *List;
struct LNode {
    ElementType Data;
    List Next;
};
struct Lnode L;
List PtrL;
1.求表長
int Length ( List PtrL ) {
    List p = PtrL; /* p指向表的第一個結點*/
    int j = 0;
    while ( p ) {
        p = p->Next;
        j++; /* 當前p指向的是第 j 個結點*/
    }
    return j;
}

時間複雜度O(n)

2.查找

按序查找

List FindKth( int K, List PtrL ) {
    List p = PtrL;
    int i = 1;
    while (p !=NULL && i < K ) {
        p = p->Next;
        i++;
    }
    if ( i == K ) return p;
    /* 找到第K個,返回指針 */
    else return NULL;
    /* 否則返回空 */
}

時間複雜度O(n)

按值查找

List Find( ElementType X, List PtrL ) {
    List p = PtrL;
    while ( p!=NULL && p->Data != X )
        p = p->Next;
    return p;
}

時間複雜度O(n)

3.插入

(1)先構造一個新結點,用s指向;
(2)再找到鏈表的第 i-1個結點,用p指向;
(3)然後修改指針,插入結點 ( p之後插入新結點是 s)

List Insert( ElementType X, int i, List PtrL ) {
    List p, s;
    if ( i == 1 ) { /* 新結點插入在表頭 */
        s = (List)malloc(sizeof(struct LNode)); /*申請、填裝結點*/
        s->Data = X;
        s->Next = PtrL;
        return s; /*返回新表頭指針*/
    }
    p = FindKth( i-1, PtrL ); /* 查找第i-1個結點 */
    if ( p == NULL ) { /* 第i-1個不存在,不能插入 */
        printf("參數i錯");
        return NULL;
    } else {
        s = (List)malloc(sizeof(struct LNode)); /*申請、填裝結點*/
        s->Data = X;
        s->Next = p->Next; /*新結點插入在第i-1個結點的後面*/
        p->Next = s;
        return PtrL;
    }
}
4.刪除

(1)先找到鏈表的第 i-1個結點,用p指向;
(2)再用指針s指向要被刪除的結點(p的下一個結點);
(3)然後修改指針,刪除s所指結點;
(4)最後釋放s所指結點的空間。

List Delete( int i, List PtrL ) {
    List p, s;
    if ( i == 1 ) { /* 若要刪除的是表的第一個結點 */
        s = PtrL; /*s指向第1個結點*/
        if (PtrL!=NULL) PtrL = PtrL->Next; /*從鏈表中刪除*/
        else return NULL;
        free(s); /*釋放被刪除結點 */
        return PtrL;
    }
    p = FindKth( i-1, PtrL ); /*查找第i-1個結點*/
    if ( p == NULL ) {
        printf("第%d個結點不存在", i-1);
        return NULL;
    } else if ( p->Next == NULL ) {
        printf("第%d個結點不存在", i);
        return NULL;
    } else {
        s = p->Next; /*s指向第i個結點*/
        p->Next = s->Next; /*從鏈表中刪除*/
        free(s); /*釋放被刪除結點 */
        return PtrL;
    }
}

4.2雙鏈表

雙向鏈表,又稱為雙鏈表,是的一種,它的每個數據結點中都有兩個,分別指向直接後繼和直接前驅。所以,從雙向鏈表中的任意一個結點開始,都可以很方便地訪問它的前驅結點和後繼結點。

typedef struct DuLNode {
    ElemType data;
    struct DuLNode *prior, *next;
} DuLNode, *DuLinkList;

4.3循環鏈表

4.3.1單循環鏈表

存儲結構和單鏈表相同。

typedef struct LNode {
    ElemType data;
    struct LNode *next;
} LNode, *LinkList;

// 設立尾指針的單循環鏈表的12個基本操作
void InitList(LinkList *L) { // 操作結果:構造一個空的線性表L
    *L = (LinkList)malloc(sizeof(struct LNode)); // 產生頭結點,並使L指向此頭結點
    if (!*L) // 存儲分配失敗
        exit(OVERFLOW);
    (*L)->next = *L; // 指針域指向頭結點
}

void DestroyList(LinkList *L) { // 操作結果:銷毀線性表L
    LinkList q, p = (*L)->next; // p指向頭結點
    while (p != *L) { // 沒到表尾
        q = p->next;
        free(p);
        p = q;
    }
    free(*L);
    *L = NULL;
}

void ClearList(LinkList *L) /* 改變L */ { // 初始條件:線性表L已存在。操作結果:將L重置為空表
    LinkList p, q;
    *L = (*L)->next; // L指向頭結點
    p = (*L)->next; // p指向第一個結點
    while (p != *L) { // 沒到表尾
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = *L; // 頭結點指針域指向自身
}

Status ListEmpty(LinkList L) { // 初始條件:線性表L已存在。操作結果:若L為空表,則返回TRUE,否則返回FALSE
    if (L->next == L) // 空
        return TRUE;
    else
        return FALSE;
}

int ListLength(LinkList L) { // 初始條件:L已存在。操作結果:返回L中數據元素個數
    int i = 0;
    LinkList p = L->next; // p指向頭結點
    while (p != L) { // 沒到表尾
        i++;
        p = p->next;
    }
    return i;
}

Status GetElem(LinkList L, int i, ElemType *e) { // 當第i個元素存在時,其值賦給e並返回OK,否則返回ERROR
    int j = 1; // 初始化,j為計數器
    LinkList p = L->next->next; // p指向第一個結點
    if (i <= 0 || i > ListLength(L)) // 第i個元素不存在
        return ERROR;
    while (j < i) { // 順指針向後查找,直到p指向第i個元素
        p = p->next;
        j++;
    }
    *e = p->data; // 取第i個元素
    return OK;
}

int LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType)) { // 初始條件:線性表L已存在,compare()是數據元素判定函數
    // 操作結果:返回L中第1個與e滿足關係compare()的數據元素的位序。
    //           若這樣的數據元素不存在,則返回值為0
    int i = 0;
    LinkList p = L->next->next; // p指向第一個結點
    while (p != L->next) {
        i++;
        if (compare(p->data, e)) // 滿足關係
            return i;
        p = p->next;
    }
    return 0;
}

Status PriorElem(LinkList L, ElemType cur_e, ElemType *pre_e) { // 初始條件:線性表L已存在
    // 操作結果:若cur_e是L的數據元素,且不是第一個,則用pre_e返回它的前驅,
    //           否則操作失敗,pre_e無定義
    LinkList q, p = L->next->next; // p指向第一個結點
    q = p->next;
    while (q != L->next) { // p沒到表尾
        if (q->data == cur_e) {
            *pre_e = p->data;
            return TRUE;
        }
        p = q;
        q = q->next;
    }
    return FALSE; // 操作失敗
}

Status NextElem(LinkList L, ElemType cur_e, ElemType *next_e) { // 初始條件:線性表L已存在
    // 操作結果:若cur_e是L的數據元素,且不是最後一個,則用next_e返回它的後繼,
    //           否則操作失敗,next_e無定義
    LinkList p = L->next->next; // p指向第一個結點
    while (p != L) { // p沒到表尾
        if (p->data == cur_e) {
            *next_e = p->next->data;
            return TRUE;
        }
        p = p->next;
    }
    return FALSE; // 操作失敗
}

Status ListInsert(LinkList *L, int i, ElemType e) /* 改變L */ { // 在L的第i個位置之前插入元素e
    LinkList p = (*L)->next, s; // p指向頭結點
    int j = 0;
    if (i <= 0 || i > ListLength(*L) + 1) // 無法在第i個元素之前插入
        return ERROR;
    while (j < i - 1) { // 尋找第i-1個結點
        p = p->next;
        j++;
    }
    s = (LinkList)malloc(sizeof(struct LNode)); // 生成新結點
    s->data = e; // 插入L中
    s->next = p->next;
    p->next = s;
    if (p == *L) // 改變尾結點
        *L = s;
    return OK;
}

Status ListDelete(LinkList *L, int i, ElemType *e) /* 改變L */ { // 刪除L的第i個元素,並由e返回其值
    LinkList p = (*L)->next, q; // p指向頭結點
    int j = 0;
    if (i <= 0 || i > ListLength(*L)) // 第i個元素不存在
        return ERROR;
    while (j < i - 1) { // 尋找第i-1個結點
        p = p->next;
        j++;
    }
    q = p->next; // q指向待刪除結點
    p->next = q->next;
    *e = q->data;
    if (*L == q) // 刪除的是表尾元素
        *L = p;
    free(q); // 釋放待刪除結點
    return OK;
}

void ListTraverse(LinkList L, void(*vi)(ElemType)) { // 初始條件:L已存在。操作結果:依次對L的每個數據元素調用函數vi()
    LinkList p = L->next->next; // p指向首元結點
    while (p != L->next) { // p不指向頭結點
        vi(p->data);
        p = p->next;
    }
    printf("\n");
}

4.3.2雙循環鏈表

// 線性表的雙向鏈表存儲結構
typedef struct DuLNode {
    ElemType data;
    struct DuLNode *prior, *next;
} DuLNode, *DuLinkList;

// 帶頭結點的雙向循環鏈表的基本操作(14個)
void InitList(DuLinkList *L) {
    // 產生空的雙向循環鏈表L
    *L = (DuLinkList)malloc(sizeof(DuLNode));
    if (*L)
        (*L)->next = (*L)->prior = *L;
    else
        exit(OVERFLOW);
}

void DestroyList(DuLinkList *L) {
    // 操作結果:銷毀雙向循環鏈表L
    DuLinkList q, p = (*L)->next; // p指向第一個結點
    while (p != *L) { // p沒到表頭
        q = p->next;
        free(p);
        p = q;
    }
    free(*L);
    *L = NULL;
}

void ClearList(DuLinkList L) { // 不改變L
    // 初始條件:L已存在。操作結果:將L重置為空表
    DuLinkList q, p = L->next; // p指向第一個結點
    while (p != L) { // p沒到表頭
        q = p->next;
        free(p);
        p = q;
    }
    L->next = L->prior = L; // 頭結點的兩個指針域均指向自身
}

Status ListEmpty(DuLinkList L) {
    // 初始條件:線性表L已存在。操作結果:若L為空表,則返回TRUE,否則返回FALSE
    if (L->next == L && L->prior == L)
        return TRUE;
    else
        return FALSE;
}

int ListLength(DuLinkList L) {
    // 初始條件:L已存在。操作結果:返回L中數據元素個數
    int i = 0;
    DuLinkList p = L->next; // p指向第一個結點
    while (p != L) { // p沒到表頭
        i++;
        p = p->next;
    }
    return i;
}

Status GetElem(DuLinkList L, int i, ElemType *e) {
    // 當第i個元素存在時,其值賦給e並返回OK,否則返回ERROR
    int j = 1; // j為計數器
    DuLinkList p = L->next; // p指向第一個結點
    while (p != L && j < i) { // 順指針向後查找,直到p指向第i個元素或p指向頭結點
        p = p->next;
        j++;
    }
    if (p == L || j > i) // 第i個元素不存在
        return ERROR;
    *e = p->data; // 取第i個元素
    return OK;
}

int LocateElem(DuLinkList L, ElemType e, Status(*compare)(ElemType, ElemType)) {
    // 初始條件:L已存在,compare()是數據元素判定函數
    // 操作結果:返回L中第1個與e滿足關係compare()的數據元素的位序。
    // 若這樣的數據元素不存在,則返回值為0
    int i = 0;
    DuLinkList p = L->next; // p指向第1個元素
    while (p != L) {
        i++;
        if (compare(p->data, e)) // 找到這樣的數據元素
            return i;
        p = p->next;
    }
    return 0;
}

Status PriorElem(DuLinkList L, ElemType cur_e, ElemType *pre_e) {
    // 操作結果:若cur_e是L的數據元素,且不是第一個,則用pre_e返回它的前驅,
    // 否則操作失敗,pre_e無定義
    DuLinkList p = L->next->next; // p指向第2個元素
    while (p != L) { // p沒到表頭
        if (p->data == cur_e) {
            *pre_e = p->prior->data;
            return TRUE;
        }
        p = p->next;
    }
    return FALSE;
}

Status NextElem(DuLinkList L, ElemType cur_e, ElemType *next_e) {
    // 操作結果:若cur_e是L的數據元素,且不是最後一個,則用next_e返回它的後繼,
    // 否則操作失敗,next_e無定義
    DuLinkList p = L->next->next; // p指向第2個元素
    while (p != L) { // p沒到表頭
        if (p->prior->data == cur_e) {
            *next_e = p->data;
            return TRUE;
        }
        p = p->next;
    }
    return FALSE;
}

DuLinkList GetElemP(DuLinkList L, int i) { // 另加
    // 在雙向鏈表L中返回第i個元素的地址。i為0,返回頭結點的地址。若第i個元素不存在,
    // 返回NULL
    int j;
    DuLinkList p = L; // p指向頭結點
    if (i < 0 || i > ListLength(L)) // i值不合法
        return NULL;
    for (j = 1; j <= i; j++)
        p = p->next;
    return p;
}

Status ListInsert(DuLinkList L, int i, ElemType e) {
    // 在帶頭結點的雙鏈循環線性表L中第i個位置之前插入元素e,i的合法值為1≤i≤表長+1
    // 改進算法2.18,否則無法在第表長+1個結點之前插入元素
    DuLinkList p, s;
    if (i < 1 || i > ListLength(L) + 1) // i值不合法
        return ERROR;
    p = GetElemP(L, i - 1); // 在L中確定第i個元素前驅的位置指針p
    if (!p) // p=NULL,即第i個元素的前驅不存在(設頭結點為第1個元素的前驅)
        return ERROR;
    s = (DuLinkList)malloc(sizeof(DuLNode));
    if (!s)
        return OVERFLOW;
    s->data = e;
    s->prior = p; // 在第i-1個元素之後插入
    s->next = p->next;
    p->next->prior = s;
    p->next = s;
    return OK;
}

Status ListDelete(DuLinkList L, int i, ElemType *e) {
    // 刪除帶頭結點的雙鏈循環線性表L的第i個元素,i的合法值為1≤i≤表長
    DuLinkList p;
    if (i < 1) // i值不合法
        return ERROR;
    p = GetElemP(L, i); // 在L中確定第i個元素的位置指針p
    if (!p) // p = NULL,即第i個元素不存在
        return ERROR;
    *e = p->data;
    p->prior->next = p->next; // 此處並沒有考慮鏈表頭,鏈表尾
    p->next->prior = p->prior;
    free(p);
    return OK;
}

void ListTraverse(DuLinkList L, void(*visit)(ElemType)) {
    // 由雙鏈循環線性表L的頭結點出發,正序對每個數據元素調用函數visit()
    DuLinkList p = L->next; // p指向頭結點
    while (p != L) {
        visit(p->data);
        p = p->next;
    }
    printf("\n");
}

void ListTraverseBack(DuLinkList L, void(*visit)(ElemType)) {
    // 由雙鏈循環線性表L的頭結點出發,逆序對每個數據元素調用函數visit()
    DuLinkList p = L->prior; // p指向尾結點
    while (p != L) {
        visit(p->data);
        p = p->prior;
    }
    printf("\n");
}

4.4靜態鏈表

前面講解的都是動態鏈表,即需要指針來建立結點之間的連接關係。而對有些問題來說結點的地址是比較小的整數(例如5位數的地址),這樣就沒有必要去建立動態鏈表,而應使用方便得多的靜態鏈表。
靜態鏈表的實現原理是hash,即通過建立一個結構體數組,並令數組的下標直接表示結點的地址,來達到直接訪問數組中的元素就能訪問結點的效果。另外,由於結點的訪問非常方便,因此靜態鏈表是不需要頭結點的。靜態鏈表結點定義的方法如下:

struct Node{
    typename data;//數據域
    int next;//指針域
}node[size];

參考資料:

  • 《算法筆記》
  • 《數據結構和算法》-極客時間專欄

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

【其他文章推薦】

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

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

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

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

【集合系列】- 紅黑樹實現分析

一、故事的起因

JDK1.8最重要的就是引入了紅黑樹的設計(當衝突的鏈表長度超過8個的時候),為什麼要這樣設計呢?好處就是避免在最極端的情況下衝突鏈表變得很長很長,在查詢的時候,效率會非常慢。

  • 紅黑樹查詢:其訪問性能近似於折半查找,時間複雜度O(logn);
  • 鏈表查詢:這種情況下,需要遍歷全部元素才行,時間複雜度O(n);

本文主要是講解紅黑樹的實現,只有充分理解了紅黑樹,對於後面的分析才會更加順利。

簡單的說,紅黑樹是一種近似平衡的二叉查找樹,其主要的優點就是“平衡“,即左右子樹高度幾乎一致,以此來防止樹退化為鏈表,通過這種方式來保障查找的時間複雜度為log(n)。

關於紅黑樹的內容,網上給出的內容非常多,主要有以下幾個特性:

  • 1、每個節點要麼是紅色,要麼是黑色,但根節點永遠是黑色的;
  • 2、每個紅色節點的兩個子節點一定都是黑色;
  • 3、紅色節點不能連續(也即是,紅色節點的孩子和父親都不能是紅色);
  • 4、從任一節點到其子樹中每個恭弘=叶 恭弘子節點的路徑都包含相同數量的黑色節點;
  • 5、所有的恭弘=叶 恭弘節點都是是黑色的(注意這裏說恭弘=叶 恭弘子節點其實是上圖中的 NIL 節點);

在樹的結構發生改變時(插入或者刪除操作),往往會破壞上述條件3或條件4,需要通過調整使得查找樹重新滿足紅黑樹的條件。

二、調整方式

上面已經說到當樹的結構發生改變時,紅黑樹的條件可能被破壞,需要通過調整使得查找樹重新滿足紅黑樹的條件。

調整可以分為兩類:一類是顏色調整,即改變某個節點的顏色,這種比較簡單,直接將節點顏色進行轉換即可;另一類是結構調整,改變檢索樹的結構關係。結構調整主要包含兩個基本操作:左旋(Rotate Left)右旋(RotateRight)

2.1、左旋

左旋的過程是將p的右子樹繞p逆時針旋轉,使得p的右子樹成為p的父親,同時修改相關節點的引用,使左子樹的深度加1,右子樹的深度減1,通過這種做法來調整樹的穩定性。過程如下:

以jdk1.8為例,打開HashMap的源碼部分,紅黑樹內部類TreeNode屬性分析:

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        //指向父節點的指針
        TreeNode<K,V> parent;
        //指向左孩子的指針
        TreeNode<K,V> left;
        //指向右孩子的指針
        TreeNode<K,V> right;
        //前驅指針,跟next屬性相反的指向
        TreeNode<K,V> prev;
        //是否為紅色節點
        boolean red;
        ......
}

左旋方法rotateLeft如下:

/*
 * 左旋邏輯
 */
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                              TreeNode<K,V> p) {
            //root:表示根節點
            //p:表示要調整的節點
            //r:表示p的右節點
            //pp:表示p的parent節點
            //rl:表示p的右孩子的左孩子節點
            TreeNode<K,V> r, pp, rl;
            //r判斷,如果r為空則旋轉沒有意義
            if (p != null && (r = p.right) != null) {
                //多個等號的連接操作從右往左看,設置rl的父親為p
                if ((rl = p.right = r.left) != null)
                    rl.parent = p;
                //判斷p的父親,為空,為根節點,根節點的話就設置為黑色
                if ((pp = r.parent = p.parent) == null)
                    (root = r).red = false;
                //判斷p節點是左兒子還是右兒子
                else if (pp.left == p)
                    pp.left = r;
                else
                    pp.right = r;
                r.left = p;
                p.parent = r;
            }
            return root;
}

2.2、右旋

了解了左旋轉之後,相應的就會有右旋,邏輯基本也是一樣,只是方向變了。右旋的過程是將p的左子樹繞p順時針旋轉,使得p的左子樹成為p的父親,同時修改相關節點的引用,使右子樹的深度加1,左子樹的深度減1,通過這種做法來調整樹的穩定性。實現過程如下:

同樣的,右旋方法rotateRight如下:

/*
 * 右旋邏輯
 */
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                               TreeNode<K,V> p) {
            //root:表示根節點
            //p:表示要調整的節點
            //l:表示p的左節點
            //pp:表示p的parent節點
            //lr:表示p的左孩子的右孩子節點
            TreeNode<K,V> l, pp, lr;
            //l判斷,如果l為空則旋轉沒有意義
            if (p != null && (l = p.left) != null) {
                //多個等號的連接操作從右往左看,設置lr的父親為p
                if ((lr = p.left = l.right) != null)
                    lr.parent = p;
                //判斷p的父親,為空,為根節點,根節點的話就設置為黑色
                if ((pp = l.parent = p.parent) == null)
                    (root = l).red = false;
                //判斷p節點是右兒子還是左兒子
                else if (pp.right == p)
                    pp.right = l;
                else
                    pp.left = l;
                l.right = p;
                p.parent = l;
            }
            return root;
}

三、操作示例介紹

3.1、插入調整過程圖解

3.2、刪除調整過程圖解

3.3、查詢過程圖解

四、總結

至此,紅黑樹的實現就基本完成了,關於紅黑樹的結構,有很多種情況,情況也比較複雜,但是整體調整流程,基本都是先調整結構然後調整顏色,直到最後滿足紅黑樹特性要求為止。整篇文章,如果有理解不當之處,歡迎指正!

五、參考

1、
2、

作者:炸雞可樂
出處:

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

【其他文章推薦】

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

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

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

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

上海推出新能源汽車充電樁綜合保險

近期,上海保險業與國家電網積極合作,推出充電樁綜合保險產品。該產品包括充電樁財產保險和充電樁用電安全責任保險。充電樁財產保險保額最高1萬元,充電樁用電安全責任保險保額3萬元。

合作初期,上海相關險企採取贈送方式,建立專項資金池,向新能源車主贈送推廣充電樁保險。同時也在國家電網營業廳設點,向預報樁客戶宣傳介紹充電樁綜合保險的相關產品和服務。

據瞭解,2015年7月1日起,上海正式實施《上海市電動汽車充電基礎設施建設管理暫行規定》。7月1日後,上海市民必須提供政府備案的充電服務機構出具的“充電設施已裝證明”,才能在銷售企業辦理購買新能源汽車手續,必須安裝好充電樁後才能獲得國家和地方政府補貼、並申請到新能源車免費牌照。此外,《規定》也明確充電設施所有權人應當承擔充電設施維修更新養護及侵害第三者權益責任。

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

【其他文章推薦】

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

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

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

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

日問周刊 | 全棧面試匯總 | 第二期

勤學如春起之苗,不見其增,日有所長;輟學如磨刀之石,不見其損,日有所虧。

我在 github 上新建了一個倉庫 ,每天至少一個問題。有關全棧,graphql,devops,微服務以及軟技能,促進職業成長,歡迎交流。

以諸葛武侯的誡子書與君共勉

夫君子之行,靜以修身,儉以養德。非澹泊無以明志,非寧靜無以致遠。夫學須靜也,才須學也,非學無以廣才,非志無以成學。淫慢則不能勵精,險躁則不能治性。年與時馳,意與日去,遂成枯落,多不接世,悲守窮廬,將復何及!

【Q037】linux 有哪些發行版,你最喜歡哪一個

原文鏈接,歡迎討論:

開放問題,不過你至少得知道一個發行版…

【Q036】http 狀態碼中 301,302和307有什麼區別

原文鏈接,歡迎討論:

  • 301,Moved Permanently。永久重定向,該操作比較危險,需要謹慎操作:如果設置了301,但是一段時間后又想取消,但是瀏覽器中已經有了緩存,還是會重定向。
  • 302,Fount。臨時重定向,但是會在重定向的時候改變 method: 把 POST 改成 GET,於是有了 307
  • 307,Temporary Redirect。臨時重定向,在重定向時不會改變 method

【Q035】http 常見的狀態碼有哪些

原文鏈接,歡迎討論:

【Q034】如何實現一個 loading 動畫

原文鏈接,歡迎討論:

【Q033】如何對接口進行限流]

原文鏈接,歡迎討論:

一般採用漏桶算法:

  1. 漏桶初始為空
  2. API 調用是在往漏桶里注水
  3. 漏桶會以一定速率出水
  4. 水滿時 API 拒絕調用

可以使用 redis 的計數器實現

  1. 計數器初始為空
  2. API 調用計數器增加
  3. 給計數器設置過期時間,隔段時間清零,視為一定速率出水
  4. 計數器達到上限時,拒絕調用

當然,這隻是大致思路,這時會有兩個問題要注意

  1. 最壞情況下的限流是額定限流速率的2倍
  2. 條件競爭問題

不過實際實現時注意以下就好了(話說一般也是調用現成的三方庫做限流…),可以參考我以前的文章

【Q032】js 中什麼是 softbind,如何實現

原文鏈接,歡迎討論:

【Q031】js 中如何實現 bind

原文鏈接,歡迎討論:

最簡單的 bind 一行就可以實現,而在實際面試過程中也不會考察你太多的邊界條件

Function.prototype.fakeBind = function(obj) {
  return (...args) => this.apply(obj, args)
}

測試一下

function f (arg) {
  console.log(this.a, arg)
}

// output: 3, 4
f.bind({ a: 3 })(4)

// output: 3, 4
f.fakeBind({ a: 3 })(4)

【Q030】linux 中如何打印所有網絡接口

原文鏈接,歡迎討論:

ifconfig

ifconfig 是最簡單最常用,但是打印信息太多了

$ ifconfig

netstat

netstatip 也挺好用,特別是它們還可以打印路由表

$ netstat -i

ip

$ ip link

$ ip addr

【Q029】websocket 如何向特定的用戶組推送消息

redis 處維護一個對象,記錄每個 group 所對應的 connections/sockets

{
  'Class:201901': [student1Socket, student2Socket]
}

當 client 剛連入 server 時,便加入某個特定的組,或者叫 room,比如 student01,剛開始連入 server,可能要加入 room:Student:01Class:201901Group:10086

$ who

$ last

一圖勝千言

使用 jsonb_pretty 函數,示例如下

> select jsonb_pretty('{"a": {"b": 4}}'::jsonb)
+----------------+
| jsonb_pretty   |
|----------------|
| {              |
|     "a": {     |
|         "b": 4 |
|     }          |
| }              |
+----------------+
SELECT 1
Time: 0.018s

一個簡單的 Promise 的粗糙實現,關鍵點在於

  1. pending 時, thenable 函數由一個隊列維護
  2. 當狀態變為 resolved(fulfilled) 時,隊列中所有 thenable 函數執行
  3. resolved 時, thenable 函數直接執行

rejected 狀態同理

class Prom {
  static resolve (value) {
    if (value && value.then) {
      return value 
    }
    return new Prom(resolve => resolve(value))
  }

  constructor (fn) {
    this.value = undefined
    this.reason = undefined
    this.status = 'PENDING'

    // 維護一個 resolve/pending 的函數隊列
    this.resolveFns = []
    this.rejectFns = []

    const resolve = (value) => {
      // 注意此處的 setTimeout
      setTimeout(() => {
        this.status = 'RESOLVED'
        this.value = value
        this.resolveFns.forEach(({ fn, resolve: res, reject: rej }) => res(fn(value)))
      })
    }

    const reject = (e) => {
      setTimeout(() => {
        this.status = 'REJECTED'
        this.reason = e
        this.rejectFns.forEach(({ fn, resolve: res, reject: rej }) => rej(fn(e)))
      })
    }

    fn(resolve, reject)
  }


  then (fn) {
    if (this.status === 'RESOLVED') {
      const result = fn(this.value)
      // 需要返回一個 Promise
      // 如果狀態為 resolved,直接執行
      return Prom.resolve(result)
    }
    if (this.status === 'PENDING') {
      // 也是返回一個 Promise
      return new Prom((resolve, reject) => {
        // 推進隊列中,resolved 后統一執行
        this.resolveFns.push({ fn, resolve, reject }) 
      })
    }
  }

  catch (fn) {
    if (this.status === 'REJECTED') {
      const result = fn(this.value)
      return Prom.resolve(result)
    }
    if (this.status === 'PENDING') {
      return new Prom((resolve, reject) => {
        this.rejectFns.push({ fn, resolve, reject }) 
      })
    }
  }
}

Prom.resolve(10).then(o => o * 10).then(o => o + 10).then(o => {
  console.log(o)
})

return new Prom((resolve, reject) => reject('Error')).catch(e => {
  console.log('Error', e)
})

首參不一樣,直接上 API

React.cloneElement(
  element,
  [props],
  [...children]
)

React.createElement(
  type,
  [props],
  [...children]
)

它一般可以使用第三方庫 來實現,源碼很簡單,可以讀一讀

主要有兩個要點

  1. 選中
  2. 複製

選中

選中主要利用了

選中的代碼如下

const selection = window.getSelection();
const range = document.createRange();

range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);

selectedText = selection.toString();

取消選中的代碼如下

window.getSelection().removeAllRanges();

它有現成的第三方庫可以使用:

複製

複製就比較簡單了,execCommand

document.exec('copy')

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

【其他文章推薦】

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

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

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

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

科學警訊:熱浪使熊蜂瀕臨滅絕 連帶影響農糧產量

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

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

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

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

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

斷電惹民怨 南非拚能源轉型

摘錄自2020年2月16日聯合新聞網報導

南非總統拉瑪佛沙日前表示,大量依賴煤炭的南非將會轉型使用更多再生能源,減少嚴重衝擊重建經濟努力的斷電現象。不過,他也警告,近期可能會有更多斷電發生。

南非民眾對今年盛夏分區斷電感到憤怒,投資人也感到擔憂。根據南非能源部,南非約77%電力依賴燃煤火力發電,部分民眾對於官員將停電歸咎於「濕煤」覺得傻眼。

拉瑪佛沙警告,停電將會持續,電力公司Eskom正在進行必要的改變,包括拖延已久的維修。他說,「在未來幾個月,隨著Eskom努力恢復其運營能力,我們將採取措施,從根本上改變我國能源生產軌跡」。

南非政府的解決辦法之一是,允許商業和工業用戶自行發電,並且允許地方政府向獨立發電商購買電力。南非也將向現有的風力和太陽能發電廠購買更多能源。

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

【其他文章推薦】

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

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

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

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

德國計畫為電動車及插電式混合動力車車主提供5500美元補貼

據報導,德國政府官員與汽車行業高管擬協商為電動車及插電式混合動力車購買者提供5,000歐元(約合5,500美元)的補貼。

德國聯邦政府曾計畫到2020年實現電動車保有量達百萬輛的目標,然而到目前為止在鼓勵消費者從購買廉價汽油及柴油車轉為購買更為清潔的電動車方面沒有獲得太大進展。

德國政府正在探討能否與汽車製造商達成融資協議,共同為消費者提供電動車購買補貼,德國總理安格拉•默克爾(AngelaMerkel)將與車企高管們就這個問題進行討論。德國經濟部發言人則表示,德國政府內部已經進行了建設性的討論,希望達成一致的解決方案,幫助德國實現百萬輛電動車保有量的目標。

貝爾吉•施格拉德巴赫(BergischGladbach)汽車管理中心專家StefanBratzel透露,2015年,德國電動車及插電式混合動力車銷量達23,500輛,其中僅12,300輛為純電動汽車。

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

【其他文章推薦】

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

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

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

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

產品標示碳足跡、限制肉類消費好不好? 英辦氣候公民大會徵詢民眾意見

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

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

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

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

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

程序員修神之路–有狀態的服務其實可以做更多的事情

菜菜哥,你換形象啦?


這麼巧,你也換啦!聽說是不會畫畫的菜嫂經過九牛二虎之力的功勞哦!鼓掌……


前幾天我出去面試了,面試官問我微服務的知識,我回答的可好了


看來微服務你真的下功夫研究了呀


是呀是呀,但是碰到一個問題,有狀態的服務是什麼意思呢?


看來你又掛在這個問題上了,且聽這次分解


簡介

對於初學者,心裏對“有狀態服務”的理解可能比較模糊,但是從面向對象編程思想的角度去理解也許會明朗很多。面向對象編程思想提倡的是用編程語言去描述世間萬物,所以面向對象編程的語言都會提供描述對象的容器以及對象行為的表達方式。舉一個很簡單的栗子,在c#或者java中,表達對象的容器就是class,對象的行為通過一系列的接口或者函數來表達。更進一步,對象抽象出來之後,大多數對象都有自己的內部狀態,體現到代碼上也就是常見的類的屬性。

面向對象編程的基本思想本質上是對現實世界的一種抽象,萬物皆可抽象。

根據業務把對象抽象出來之後,每一個實例化的對象其實都可以有自己的狀態,比如:在最常見的遊戲場景中,每一個玩家都是“玩家”這類對象的一個實例,每一個玩家都有自己的名字,性別,等級,HP等屬性,這些屬性本質上就是玩家的狀態,隨着時間的推移,每個玩家的HP,等級等屬性會隨之變化,這些變化其實就是這個玩家狀態的變化。對應到有狀態的服務也是如此,之所以稱之為有狀態,是因為服務內部的對象狀態會隨着業務有着對應的變動,而這些變動只發生在這個服務內部,在外界看來,這個服務好像是有狀態的。

有狀態的服務本質上是一些有狀態對象的集合,這些對象狀態的變化只發生在當前服務進程中

優勢和劣勢

有狀態服務之所以被稱為有狀態,一個很大的原因是它可以追溯狀態的變化過程,也就是說一個有狀態的服務保存着狀態變化的記錄,並可以根據這些歷史記錄恢復到指定的狀態,這在很多場景下非常有用。舉一個很簡單的栗子:我們平時玩的斗地主遊戲,三個玩家,當有一個玩家因為網絡原因掉線,經過一段時間,這個玩家又重新上線,需要根據某些記錄來恢復玩家掉線期間系統自動出牌的記錄,這些出牌記錄在這個業務中其實就是這個玩家的狀態變化記錄。在有狀態的服務中,很容易做到這一點。

其實實際開發中很多場景不需要記錄每個狀態的變化,只保留最新狀態即可,不單單是因為保存每個狀態的變化需要大量的存儲和架構設計,更因為是很多業務根本不需要這些狀態變化記錄,業務需要的只是最新的狀態,所以大部分有狀態的服務只保存着最新的狀態。

有狀態的服務在設計難度上比無狀態的服務要大很多,不僅僅是因為開發設計人員需要更好的抽象能力,更多的是一致性的設計問題。現代的分佈式系統,都是由多個服務器組成一個集群來對外提供服務,當一個對象在服務器A產生之後,如果請求被分配到了服務器B上,這種情況下有狀態的服務毫無意義,為什麼呢?當一個相同的業務對象存在於不同的服務器上的時候,本質上就違背了現實世界的規則,你能說一個人,即出生在中國,又出生在美國嗎? 所以有狀態的服務對於一致性問題有着天然的要求,這種思想和微服務設計理想不謀而合,舉個栗子:一個用戶信息的服務,對外提供查詢修改能力,凡是用戶信息的業務必須通過這個服務來實現。同理,一個對象狀態的查詢修改以及這個對象的行為,必須由這個對象的服務來完成。

有狀態的服務要求相同業務對象的請求必須被路由到同一個服務進程。

因此,有狀態的服務對於同一個對象的橫向擴容是做不到的,就算是做的到,多個相同對象之間的狀態同步工作也必然會花費更多的資源。在很多場景下,有狀態的服務要注意熱點問題,例如最常見的秒殺,這裏並非是說有狀態服務不適合大併發的場景,反而在高併發的場景下,有狀態的服務往往表現的比無狀態服務更加出色。

Actor模型

在眾多的併發模型中,最適合有狀態服務設計的莫過於Actor模型了,如果你對actor模型還不熟悉,可以擼一遍菜菜之前的文章:https://mp.weixin.qq.com/s/eEiypRysw5jsC7iYUp_yAg  actor模型天生就具備了一致性這種特點,讓我們在對業務進行抽象的時候,不必考慮一致性的問題,而且每一個請求都是異步模式,在對象內部修改對象的狀態不必加鎖,這在傳統的架構中是做不到的。

基於actor模型,系統設計的難點在於抽象業務模型,一旦業務模型穩定,我們完全可以用內存方式來保存對象狀態(也可以定時去持久化),內存方式比用其他網絡存儲(例如redis)要快上幾個量級,菜菜也有一篇文章大家可以去擼一下:https://mp.weixin.qq.com/s/6YL3SnSriKEnpCyB5qkk0g  ,既滿足了一致性,又可以利用進程內對象狀態來應對高併發業務場景,何樂而不為呢?

有不少同學問過我,actor模型要避免出現熱點問題,就算有內存狀態為其加速,那併發數還是超過actor的處理能力怎麼辦呢? 其實和傳統做法類似,所有的高併發系統設計無非就是“分”一個字,無論是簡單的負載均衡,還是複雜的分庫分表策略,都是分治的一種體現。一台服務器不夠,我就上十台,百台…..

所有的高併發系統設計都是基於分治思想,把每一台服務器的能力發揮到極致,難度最大的還是其中的調度算法。

用actor模型來應對高併發,我們可以採用讀寫分離的思想,主actor負責寫請求,並利用某種通信機制把狀態的變化通知到多個從actor,從actor負責對外的讀請求,這個DB的讀寫分離思想一致,其中最難的當屬actor的狀態同步問題了,解決問題的方式千百種,總有一種適合你,歡迎你留言寫下你認為最好的解決方案。

案例(玩家信息服務)

由於菜菜是c#出身,對c#的Actor服務框架Orleans比較熟悉,這裏就以Orleans為例,其他語言的coder不要見怪,Orleans是一個非常優秀的Actor模型框架,而且支持最新的netcore 3.0版本,地址為:https://github.com/dotnet/orleans  有興趣的同學可以去看一下,而且分佈式事物已經出正式版,非常給力。其他語言的也非常出色java:https://github.com/akka/akka

golang:

1. 首先我們定義玩家的狀態信息

//玩家的信息,其實也就是玩家的狀態信息
    public class Player {
        /// <summary>
        /// 玩家id,同時也是玩家這個服務的主鍵
        /// </summary>
        public long Id { getset; }
        /// <summary>
        /// 玩家姓名
        /// </summary>
        public string Name { getset; }
        /// <summary>
        /// 玩家等級
        /// </summary>
        public int Level { getset; }
    }

2. 接下來定義玩家的服務接口

 /// <summary>
    /// 玩家的服務接口
    /// </summary>
    interface IPlayerService: Orleans.IGrainWithIntegerKey
    {
        //獲取玩家名稱
        Task<string> GetName();
        //獲取玩家等級
        Task<int> GetLevel();
        //設置玩家等級,這個操作會改變玩家的狀態
        Task<int> SetLevel(int newLevel);
    }

3. 接下來實現玩家服務的接口

public class PlayerService : GrainIPlayerService
    {
        //這裏可以用玩家的信息來代表玩家的狀態信息,而且這個狀態信息又充當了進程內緩存的作用
        Player playerInfo;
        public async Task<intGetLevel()
        
{
            return (await LoadPlayer()).Level;
        }

        public async Task<stringGetName()
        
{
            return (await LoadPlayer()).Name;
        }

        public async Task<intSetLevel(int newLevel)
        
{
            var playerInfo =await LoadPlayer();
            if (playerInfo != null)
            {
                //先進行數據庫的更新,然後在更新緩存的狀態, 進程內緩存更新失敗的幾率幾乎為0
                playerInfo.Level = newLevel;                
            }
            return 1;
        }

        private async Task< Player> LoadPlayer()
        {
            if (playerInfo == null)
            {
                var id = this.GetPrimaryKeyLong();
                //這裏模擬的信息,真實環境完全可以從持久化設備進行讀取
                playerInfo= new Player() { Id = id, Name = "玩家姓名", Level = 1 };
            }
            return playerInfo;
        }
    }

以上只是一個簡單案例,有狀態的服務還有更多的設計方案,以上只供參考

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

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

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

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

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?