最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

從零實現(xiàn)Nginx風格內(nèi)存池完整代碼

 更新時間:2026年05月27日 09:28:08   作者:UrSpecial  
本文將從為什么需要內(nèi)存池講起,重點探討內(nèi)存池的設計哲學,并詳細拆解其實現(xiàn)方案,從而搭建起讀者對內(nèi)存池從理論到實踐的完整認知模型,感興趣的朋友跟隨小編一起看看吧

1.引言

本文將從為什么需要內(nèi)存池講起,重點探討內(nèi)存池的設計哲學,并詳細拆解其實現(xiàn)方案,從而搭建起讀者對內(nèi)存池從理論到實踐的完整認知模型。

2.為什么需要內(nèi)存池

為什么需要內(nèi)存池?這個問題可以換一種問法:為什么不直接malloc/freenew/delete?
我們一層一層的回答這個問題。

1.malloc/free有較高的性能開銷。 內(nèi)存的申請不光是給我們一塊地址這么簡單,malloc底層還要做很多事情,比如分割內(nèi)存塊,合并碎片等。在多線程場景下,malloc內(nèi)部還會涉及到鎖競爭,避免給不同線程返回相同的內(nèi)存塊。這些操作,都需要性能開銷。

2.減少系統(tǒng)調(diào)用。 申請內(nèi)存屬于系統(tǒng)級操作,所以會涉及到用戶態(tài)和內(nèi)核態(tài)的切換,也就不可避免的進行CPU的上下文切換。而且,用戶態(tài)有用戶態(tài)的棧,內(nèi)核態(tài)有內(nèi)核態(tài)的棧,不能共用,所以也需要切換。這種用戶態(tài)到內(nèi)核態(tài)的切換,相對于在同一個態(tài)里直接調(diào)用函數(shù)來說,開銷更高一些。內(nèi)存池通過一次申請大塊內(nèi)存,后面再申請內(nèi)存時直接從這一大塊中切出來,減少了系統(tǒng)調(diào)用。因此,高性能系統(tǒng)總在想辦法減少系統(tǒng)調(diào)用次數(shù)。

3.減少內(nèi)存碎片。 普通的malloc/free會產(chǎn)生內(nèi)存碎片。雖然內(nèi)存的總容量夠,但是就是分配不出來滿足需求的連續(xù)內(nèi)存。內(nèi)存池通過將生命周期一致的對象放一起,比如將一個請求的所有對象放一起,請求結束時將整個池一起釋放,不需要單獨的free,這就減少了內(nèi)存碎片。

4.減少鎖競爭。 前面也有提到,多個線程同時調(diào)用malloc時,底層是會有鎖競爭的,但是內(nèi)存池通常是線程私有的,互不影響,幾乎無鎖。

3.內(nèi)存對齊

因為在內(nèi)存池的實現(xiàn)過程中,涉及到內(nèi)存對齊,所以這里先介紹一下。

內(nèi)存對齊本質(zhì)上是編譯器對變量地址存放位置的一個強制約束。

為什么需要內(nèi)存對齊?

CPU在讀取數(shù)據(jù)的時候,是按地址總線的寬度來讀取的。如果數(shù)據(jù)按一定的對齊值進行了對齊,那么就可以減少CPU額外的讀取次數(shù),從而提高CPU訪問數(shù)據(jù)的效率。

如何進行內(nèi)存對齊?

主要有以下3條規(guī)則:

  • 按各自的對齊值對齊。
  • 整體按最大對齊值對齊。
  • 結構體的大小是最大對齊值的整數(shù)倍。

進行內(nèi)存對齊,就一定會有部分內(nèi)存浪費。所以,內(nèi)存對齊本質(zhì)上是一種 空間換時間 的權衡之策。

4.內(nèi)存池設計的核心思想

因為大對象如果也在內(nèi)存池中分配的話,很快就會把內(nèi)存池給沾滿。所以,在設計內(nèi)存池時,對小對象和大對象做了區(qū)分。

小對象:從內(nèi)存池中分配內(nèi)存。
大對象:直接malloc分配內(nèi)存。

5.代碼設計

小對象的節(jié)點設計

struct mp_node_s {
    unsigned char *last;
    unsigned char* end;
    struct mp_node_s *next;
    size_t failed;
};

last: 下一個分配的起始地址。下一次有小對象申請內(nèi)存時,從last位置開始向后切出來分配內(nèi)存。
end: 當前節(jié)點內(nèi)存的末尾。
next: 指向下一個節(jié)點的指針。
failed: 記錄當前節(jié)點內(nèi)存申請失敗次數(shù)。這個字段主要是用來做優(yōu)化的,如果該節(jié)點的內(nèi)存申請失敗次數(shù)達到一定值,我們直接就跳過該節(jié)點,不在該節(jié)點中申請內(nèi)存。

大對象的節(jié)點設計

struct mp_large_s {
    struct mp_large_s *next;
    void *alloc;
};

next: 指向下一個節(jié)點的指針。
alloc: 存放的是malloc函數(shù)的返回值,也就是指向的內(nèi)存塊起始地址。

線程池的總體設計

struct mp_pool_s {
    size_t max;
    struct mp_node_s *current;
    struct mp_large_s *large;
    struct mp_node_s head[];
};

max: 小對象和大對象的分界點,申請的內(nèi)存大于max的,視為大對象,走的是malloc;申請的內(nèi)存小于max的,視為小對象,走的是內(nèi)存池切塊。
current: 指向小對象鏈表中某個節(jié)點的指針。current指向哪個節(jié)點,下次分配內(nèi)存時,就首先從該節(jié)點嘗試分配。
large: 指向下一個大對象節(jié)點的指針。
head: 柔性數(shù)組,整個小塊鏈表的頭節(jié)點。

這個結構體,作為整個內(nèi)存池的入口和管理者。

內(nèi)存池的創(chuàng)建

struct mp_pool_s *mp_create_pool(size_t size) {
    struct mp_pool_s *p;
    int ret = posix_memalign((void **)&p, MP_ALIGNMENT, 
        size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
    if (ret != 0) {
        printf("[%s:%d]mp_create_pool failed!\n", __func__, __LINE__);
        return NULL;
    }
    p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
    p->current = p->head;
    p->large = NULL;
    // 當前可分配的起點
    p->head->last = (unsigned char *)p + 
        sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
    p->head->end = p->head->last + size;
    p->head->failed = 0;
    ret

int posix_memalign(void **memptr, size_t alignment, size_t size)
分配一塊地址對齊的內(nèi)存,相對于malloc來說,多了內(nèi)存對齊的功能。

我們除了要分配用戶申請的內(nèi)存大小外,還要給元數(shù)據(jù)申請內(nèi)存。而且,每一個mp_node_s節(jié)點的大小是一樣的,last指針會隨著節(jié)點中內(nèi)存的分配情況而移動。

小對象節(jié)點的創(chuàng)建

static void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {
    unsigned char* m;
    struct mp_node_s *h = pool->head;
    //所有擴容block大小和初始block保持一致
    size_t psize = (size_t)(h->end - (unsigned char *)h);
    int ret = posix_memalign((void **)&m, MP_ALIGNMENT, psize);
    if (ret != 0) {
        printf("[%s:%d]mp_alloc_block failed!\n", __func__, __LINE__);
        return NULL;
    }
    struct mp_node_s *p, *new_node, *current;
    new_node = (struct mp_node_s *)m;
    new_node->end = m + psize;
    new_node->next = NULL;
    new_node->failed = 0;
    m += sizeof(struct mp_node_s);// 跳過node的元數(shù)據(jù)
    m = mp_align_ptr(m, MP_ALIGNMENT);//因為元數(shù)據(jù)可能會破壞內(nèi)存對齊,所以這里需要做一次內(nèi)存對齊
    new_node->last = m + size;
    current = pool->current;
    // 找鏈表的尾部,同時更新current字段和failed字段,用于表示失敗的次數(shù),以便用作優(yōu)化
    for (p = current; p->next; p = p->next) {
        if (p->failed > 4) {
            current = p->next;
            p->failed++;
        }
    }
    p->next = new_node;
    pool->current = current ? current : new_node;
    return m;
}

創(chuàng)建新節(jié)點,意味著從current指向的節(jié)點往后的所有節(jié)點的剩余可用空間都小于當前用戶申請的內(nèi)存大小。
創(chuàng)建出來的新節(jié)點,和前面的節(jié)點大小是一樣的。

大對象的節(jié)點創(chuàng)建

static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {
    void *p = malloc(size);
    if (p == NULL) {
        printf("[%s:%d]mp_alloc_large failed!\n", __func__, __LINE__);
        return NULL;
    }
    size_t n = 0;
    struct mp_large_s *large;
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }
        if (n++ >= MP_MAX_LARGE_SCAN) break;
    }
    // large本身不是malloc出來的,而是在內(nèi)存池中分配的
    large = mp_alloc(pool, sizeof(struct mp_large_s));
    if (large == NULL) {
        free(p);
        printf("[%s:%d]mp_alloc failed!\n", __func__, __LINE__);
        return NULL;
    }
    // 頭插
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;
    return p;
}

對于大對象,直接malloc分配內(nèi)存。同時,還需要申請節(jié)點本身的空間,這里large節(jié)點的內(nèi)存,不是直接malloc來的,而是走小對象內(nèi)存分配的路,從內(nèi)存池中切割出來的。對于直接malloc出來的大塊內(nèi)存,用完直接free,不用統(tǒng)一釋放。
再強調(diào)一遍,mp_large_s結構體,它內(nèi)部的指針指向malloc出來的大內(nèi)存,但是這個結構體本身,不是我們直接malloc出來的,而是從內(nèi)存池中申請得到的。因為mp_alloc函數(shù)內(nèi)部,對于小于一定大小的空間,走的是內(nèi)存池分配,不是malloc。

空間分配函數(shù)

void *mp_alloc(struct mp_pool_s *pool, size_t size) {
    unsigned char *m;
    struct mp_node_s *p;
    if (size <= pool->max) {
        p = pool->current;
        do {
            m = mp_align_ptr(p->last, MP_ALIGNMENT);
            if ((size_t)(p->end - m) >= size) {
                p->last = m + size;
                return m;
            }
            p->failed++;
            p = p->next;
        }while (p);
        return mp_alloc_block(pool, size);
    }
    return mp_alloc_large(pool, size);
}
void *mp_nalloc(struct mp_pool_s *pool, size_t size) {
    unsigned char *m;
    struct mp_node_s *p;
    if (size <= pool->max) {
        p = pool->current;
        do {
            m = p->last;
            if ((size_t)(p->end - m) >= size) {
                p->last = m + size;
                return m;
            }
            p = p->next;
        }while(p);
        return mp_alloc_block(pool, size);
    }
    return mp_alloc_large(pool, size);
}

外部申請內(nèi)存的時候,調(diào)用的就是這兩個函數(shù)。總體的邏輯就是:小對象走內(nèi)存池分配,大對象直接malloc。
這兩個函數(shù)的區(qū)別在哪?有沒有內(nèi)存對齊。

mp_alloc是有內(nèi)存對齊的,而mp_nalloc沒有。

CPU會按固定寬度類型來訪問數(shù)據(jù)的,通常需要內(nèi)存對齊。按字節(jié)流或文本訪問數(shù)據(jù)的,通常可以不對齊。
我們常見的int、long等這些基本類型,通常就需要內(nèi)存對齊。而字符串就沒必要進行內(nèi)存對齊。

內(nèi)存池的銷毀

void mp_destroy_pool(struct mp_pool_s *pool) {
    struct mp_node_s *head = pool->head->next;
    struct mp_node_s *next = NULL;
    struct mp_large_s *cur = pool->large;
    while (cur) {
        if (cur->alloc) free(cur->alloc);
        cur = cur->next;
    }
    while (head) {
        next = head->next;
        free(head);
        head = next;
    }
    free(pool);
}

如果有大對象的內(nèi)存沒有釋放,那么這里會一并釋放,最后在釋放這個內(nèi)存池。

6.內(nèi)存池完整代碼

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#define MP_ALIGNMENT 32
#define MP_PAGE_SIZE 4096
#define MP_MAX_LARGE_SCAN   4
#define MP_MAX_ALLOC_FROM_POOL	(MP_PAGE_SIZE-1)
#define mp_align_ptr(p, alignment) \
    (void *)((((size_t)p)+(alignment-1)) & ~(alignment-1))
struct mp_large_s {
    struct mp_large_s *next;
    void *alloc;
};
struct mp_node_s {
    unsigned char *last;
    unsigned char* end;
    struct mp_node_s *next;
    size_t failed;
};
struct mp_pool_s {
    size_t max;//內(nèi)存池允許的小塊分配上限
    struct mp_node_s *current;
    struct mp_large_s *large;
    struct mp_node_s head[];
};
void *mp_alloc(struct mp_pool_s *pool, size_t size);
struct mp_pool_s *mp_create_pool(size_t size) {
    struct mp_pool_s *p;
    int ret = posix_memalign((void **)&p, MP_ALIGNMENT, 
        size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
    if (ret != 0) {
        printf("[%s:%d]mp_create_pool failed!\n", __func__, __LINE__);
        return NULL;
    }
    p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
    p->current = p->head;
    p->large = NULL;
    // 當前可分配的起點
    p->head->last = (unsigned char *)p + 
        sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
    p->head->end = p->head->last + size;
    p->head->failed = 0;
    return p;
}
void mp_destroy_pool(struct mp_pool_s *pool) {
    struct mp_node_s *head = pool->head->next;
    struct mp_node_s *next = NULL;
    struct mp_large_s *cur = pool->large;
    while (cur) {
        if (cur->alloc) free(cur->alloc);
        cur = cur->next;
    }
    while (head) {
        next = head->next;
        free(head);
        head = next;
    }
    free(pool);
}
void mp_reset_pool(struct mp_pool_s *pool) {
    struct mp_large_s *cur = pool->large;
    struct mp_node_s *head = pool->head;
    while (cur) {
        free(cur->alloc);
        cur = cur->next;
    }
    pool->large = NULL;
    while (head) {
        head->last = mp_align_ptr((unsigned char *)
            head + sizeof(struct mp_node_s), MP_ALIGNMENT);
        head->failed = 0;
        head = head->next;
    }
    pool->current = pool->head;
}
static void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {
    unsigned char* m;
    struct mp_node_s *h = pool->head;
    //所有擴容block大小和初始block保持一致
    size_t psize = (size_t)(h->end - (unsigned char *)h);
    int ret = posix_memalign((void **)&m, MP_ALIGNMENT, psize);
    if (ret != 0) {
        printf("[%s:%d]mp_alloc_block failed!\n", __func__, __LINE__);
        return NULL;
    }
    struct mp_node_s *p, *new_node, *current;
    new_node = (struct mp_node_s *)m;
    new_node->end = m + psize;
    new_node->next = NULL;
    new_node->failed = 0;
    m += sizeof(struct mp_node_s);// 跳過node的元數(shù)據(jù)
    m = mp_align_ptr(m, MP_ALIGNMENT);//因為元數(shù)據(jù)可能會破壞內(nèi)存對齊,所以這里需要做一次內(nèi)存對齊
    new_node->last = m + size;
    current = pool->current;
    // 找鏈表的尾部,同時更新current字段和failed字段,用于表示失敗的次數(shù),以便用作優(yōu)化
    for (p = current; p->next; p = p->next) {
        if (p->failed > 4) {
            current = p->next;
            p->failed++;
        }
    }
    p->next = new_node;
    pool->current = current ? current : new_node;
    return m;
}
static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {
    void *p = malloc(size);
    if (p == NULL) {
        printf("[%s:%d]mp_alloc_large failed!\n", __func__, __LINE__);
        return NULL;
    }
    size_t n = 0;
    struct mp_large_s *large;
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }
        if (n++ >= MP_MAX_LARGE_SCAN) break;
    }
    // large節(jié)點不是malloc出來的,而是在small pool分配的
    large = mp_alloc(pool, sizeof(struct mp_large_s));
    if (large == NULL) {
        free(p);
        printf("[%s:%d]mp_alloc failed!\n", __func__, __LINE__);
        return NULL;
    }
    // 頭插
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;
    return p;
}
void *mp_memalign(struct mp_pool_s *pool, size_t size, size_t alignment) {
    void *p;
    int ret = posix_memalign(&p, alignment, size);
    if (ret != 0) {
        printf("[%s:%d]mp_memalign failed!\n", __func__, __LINE__);
        return NULL;
    }
    struct mp_large_s *large = mp_alloc(pool, size);
    if (large == NULL) {
        free(p);
        printf("[%s:%d]mp_memalign failed!\n", __func__, __LINE__);
        return NULL;
    }
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;
    return p;
}
/**
 * brief:帶內(nèi)存對齊的空間分配函數(shù)
 */
void *mp_alloc(struct mp_pool_s *pool, size_t size) {
    unsigned char *m;
    struct mp_node_s *p;
    if (size <= pool->max) {
        p = pool->current;
        do {
            m = mp_align_ptr(p->last, MP_ALIGNMENT);
            if ((size_t)(p->end - m) >= size) {
                p->last = m + size;
                return m;
            }
            p = p->next;
        }while (p);
        return mp_alloc_block(pool, size);
    }
    return mp_alloc_large(pool, size);
}
/**
 * brief:不帶內(nèi)存對齊的空間分配函數(shù),因為不是所有場景下都需要內(nèi)存對齊
 */
void *mp_nalloc(struct mp_pool_s *pool, size_t size) {
    unsigned char *m;
    struct mp_node_s *p;
    if (size <= pool->max) {
        p = pool->current;
        do {
            m = p->last;
            if ((size_t)(p->end - m) >= size) {
                p->last = m + size;
                return m;
            }
            p = p->next;
        }while(p);
        return mp_alloc_block(pool, size);
    }
    return mp_alloc_large(pool, size);
}
void *mp_calloc(struct mp_pool_s *pool, size_t size) {
    void *p = mp_alloc(pool, size);
    if (p) memset(p, 0, size);
    return p;
}
void mp_free(struct mp_pool_s *pool, void *p) {
    struct mp_large_s *cur;
    for (cur = pool->large; cur; cur = cur->next) {
        if (p == cur->alloc) {
            free(cur->alloc);
            cur->alloc = NULL;
            return;
        }
    }
}
int main() {
    int size = 1 << 12;
    struct mp_pool_s *pool = mp_create_pool(size);
    for (int i = 0; i < 10; i++) {
        void *mp = mp_alloc(pool, 512);
    }
    for (int i = 0; i < 5; i++) {
        void *mp = mp_alloc(pool, 5120);
        mp_free(pool, mp);
    }
    mp_reset_pool(pool);
    for (int i = 0; i < 50; i++) {
        void *mp = mp_alloc(pool, 512);
    }
    mp_destroy_pool(pool);
    return 0;
}

7.整體結構圖

8.結語

到此這篇關于從零實現(xiàn)Nginx風格內(nèi)存池的文章就介紹到這了,更多相關Nginx內(nèi)存池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • nginx?流控使用的項目實踐

    nginx?流控使用的項目實踐

    本文主要介紹了nginx?流控使用的項目實踐
    2024-03-03
  • Nginx 中文域名配置詳解及實現(xiàn)

    Nginx 中文域名配置詳解及實現(xiàn)

    這篇文章主要介紹了Nginx中 文域名配置詳解及實現(xiàn)的相關資料,Nginx虛擬主機上綁定一個帶中文域名但是不能跳轉,這里給大家說下如何實現(xiàn),需要的朋友可以參考下
    2016-12-12
  • 實例詳解SpringBoot+nginx實現(xiàn)資源上傳功能

    實例詳解SpringBoot+nginx實現(xiàn)資源上傳功能

    這篇文章主要介紹了SpringBoot+nginx實現(xiàn)資源上傳功能,由于小編最近在使用nginx放置靜態(tài)資源問題,遇到很多干貨,特此分享到腳本之家平臺,供大家參考,需要的朋友可以參考下
    2019-10-10
  • Nginx反爬蟲策略,防止UA抓取網(wǎng)站

    Nginx反爬蟲策略,防止UA抓取網(wǎng)站

    目前網(wǎng)絡上的爬蟲非常多,有對網(wǎng)站收錄有益的,比如百度蜘蛛(Baiduspider),也有不但不遵守robots規(guī)則對服務器造成壓力,還不能為網(wǎng)站帶來流量的無用爬蟲,為防止網(wǎng)站有可能會被別人爬,通過配置Nginx, 我們可以攔截大部分爬蟲
    2020-09-09
  • 深度剖析Nginx限速模塊的3個核心陷阱與5種正確實踐

    深度剖析Nginx限速模塊的3個核心陷阱與5種正確實踐

    這篇文章主要為大家詳細介紹了Nginx中限速模塊的3個核心陷阱與5種正確實踐,文中的示例代碼簡潔易懂,感興趣的小伙伴可以跟隨小編一起學習一下
    2025-11-11
  • 使用Nginx搭建代理服務器(正向代理HTTPS網(wǎng)站)的操作指南

    使用Nginx搭建代理服務器(正向代理HTTPS網(wǎng)站)的操作指南

    在網(wǎng)絡應用中,代理服務器是用于中轉用戶請求和服務端響應的工具,正向代理主要用于客戶端與外部服務器之間的訪問代理,幫助客戶端隱藏其 IP 地址或訪問受限資源,本文將詳細介紹如何使用 Nginx 搭建正向代理服務器,特別是針對 HTTPS 網(wǎng)站的代理
    2024-11-11
  • nginx校驗指定conf文件是否正確的方法

    nginx校驗指定conf文件是否正確的方法

    校驗 Nginx 指定配置文件的語法是否正確,是保證 Nginx 服務穩(wěn)定運行的關鍵一步,可以使用 Nginx 內(nèi)置的 -t 測試參數(shù),配合 -c 參數(shù)來指定配置文件,下面小編給大家詳細介紹一下,需要的朋友可以參考下
    2025-08-08
  • Nginx熱部署的實現(xiàn)

    Nginx熱部署的實現(xiàn)

    本文主要介紹了Nginx熱部署的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • 解決Nginx 配置 proxy_pass 后 返回404問題

    解決Nginx 配置 proxy_pass 后 返回404問題

    這篇文章主要介紹了Nginx 配置 proxy_pass 后 返回404問題,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-01-01
  • nginx add_header指令使用方法

    nginx add_header指令使用方法

    這篇文章主要介紹了nginx add_header指令使用方法,nginx配置文件通過使用add_header指令來設置response header,需要的朋友可以參考下
    2014-03-03

最新評論

张家口市| 永德县| 郯城县| 松江区| 南澳县| 丹棱县| 喜德县| 葵青区| 岐山县| 宁城县| 武威市| 宁海县| 呼图壁县| 宜君县| 南京市| 北流市| 历史| 雷波县| 五大连池市| 孙吴县| 镇平县| 搜索| 安阳市| 万年县| 丹巴县| 莎车县| 敦煌市| 道孚县| 盐城市| 彰化市| 绥阳县| 绥棱县| 绵竹市| 保定市| 大冶市| 宁德市| 延边| 阿荣旗| 荥经县| 平武县| 沭阳县|