主頁 > 後端開發 > REDIS簡單動態字串

REDIS簡單動態字串

2021-01-05 06:35:09 後端開發

REDIS簡單動態字串

在 redis 中,沒有直接使用 c語言 傳統的字串(以空字串結尾的字符陣列),而是自己構建了一種名為簡單動態字串(simple dynamic string,SDS)的抽象型別,并將 SDS 用作 redis 的默認字串表示,

舉個例子:

redis> SET msg "hello world"
OK

執行上訴陳述句之后,redis 將在資料庫中創建一個新的鍵值對,該鍵值對包括:

  • 鍵是一個字串物件,底層實作是一個保存著字串 "msg" 的 SDS
  • 值也是一個字串物件,底層實作是一個保存著字串 "hello world" 的 SDS,

SDS的定義

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    //記錄buf陣列中已使用的位元組數量
    uint64_t len; 
    //分配的buf陣列長度,不包括頭和空字符結尾 
    uint64_t alloc;
    // 標志位, 最低3位表示型別,另外5個位沒有使用
    unsigned char flags; 
    char buf[];
};

通過原始碼可以看出 SDS 有5中 header 型別,主要是為了讓不同的字串使用不同程度的header,從而節省記憶體,

SDS 結構體的組成:

  1. len 記錄 buf 陣列中已使用的位元組數量
  2. alloc 分配的 buf 陣列長度,不包括頭和空字符結尾
  3. flags 標志位,最低3為表示 header 型別,另外 5 個位沒有使用,型別對應下面5中型別,
#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4

  1. buf[] 字符陣列,用于存放字串,

REDIS-SDS結構

redis 雖然不是使用 c 中的字串格式,但是遵循了 c 字串以空字符結尾的慣例,保存空字符的 1 個位元組不計算在 SDS 的 len 屬性里,這樣做的好處是,可以直接重用一部分 c 字串函式庫里面的函式,

SDS相對C字串的優勢

獲取字串的長度復雜度為 O(1)

由于 C 字串并不記錄自身的長度資訊,所以為了獲取一個 C 字串的長度,程式必須遍歷整個字串,對遇到的每個字符進行計數,直到遇到代表字串結尾的空字符為止,這個操作的復雜度為 O(N) ,

和C字串不同,因為SDS在len屬性中記錄了SDS 身的長度,寫代碼時只要直接獲取len屬性值,就可以知道字串長度,所以獲取一個SDS長度的復雜度僅為 O(1) ,

防止緩沖去溢位(buffer overflow)

舉個例子,<string.h>/strcat 函式可以將 src 字串中的內容拼接到 dest 字串的末尾:

char *strcat(char *dest, const char *src);

因為 C 字串不記錄自身的長度,所以 strcat 假定用戶在執行這個函式時,已經為 dest 分配了足夠多的記憶體,可以容納 src 字串中的所有內容,而一旦這個假定不成立時,就會產生緩沖區溢位,

舉個例子,假設程式里有兩個在記憶體中緊鄰著的 C 字串 s1 和 s2 ,其中 s1 保存了字串 "hello" ,而 s2 則保存了字串 "redis",如下所示:

c存盤兩個字串在記憶體中相鄰

此時,如果執行:

strcat(s1,' world')

將 s1 的內容修改為 "hello world" ,但粗心的他卻忘了在執行 strcat 之前為 s1 分配足夠的空間,那么在 strcat 函式執行之后,s1 的資料將溢位到 s2 所在的空間中,導致 s2 保存的內容被意外地修改,

與 c 字串不同的是

SDS 的空間分配策略完全杜絕了發生緩沖區溢位的可能性:

當 SDS API 需要對 SDS 進行修改時,API 會先檢查 SDS 的空間是否滿足修改所需的要求,如果不滿足的話,API 會自動將 SDS 的空間擴展至執行修改所需的大小(即:sdsMakeRoomFor),然后才執行實際的修改操作,所以使用 SDS 既不需要手動修改 SDS 的空間大小,也不會出現前面所說的緩沖區溢位問題,

sds sdscatlen(sds s, const void *t, size_t len) {
    size_t curlen = sdslen(s);
    s = sdsMakeRoomFor(s,len);
    if (s == NULL) return NULL;
    memcpy(s+curlen, t, len);
    sdssetlen(s, curlen+len);
    s[curlen+len] = '\0';
    return s;
}

減少因修改字串帶來的記憶體重分配次數

因為 C 字串的長度和底層陣列的長度之間存在著關聯性, 所以每次增長或者縮短一個 C 字串, 程式都總要對保存這個 C 字串的陣列進行一次記憶體重分配操作:

  • 如果程式執行的是增長字串的操作, 比如拼接操作(append), 那么在執行這個操作之前, 程式需要先通過記憶體重分配來擴展底層陣列的空間大小 —— 如果忘了這一步就會產生緩沖區溢位,
  • 如果程式執行的是縮短字串的操作, 比如截斷操作(trim), 那么在執行這個操作之后, 程式需要通過記憶體重分配來釋放字串不再使用的那部分空間 —— 如果忘了這一步就會產生記憶體泄漏,

因為記憶體重分配涉及復雜的演算法,并且可能需要執行系統呼叫,所以它通常是一個比較耗時的操作:

  • 在一般程式中,如果修改字串長度的情況不太常出現,那么每次修改都執行一次記憶體重分配是可以接受的,
  • Redis 作為資料庫,經常被用于速度要求嚴苛、資料被頻繁修改的場合,如果每次修改字串的長度都需要執行一次記憶體重分配的話,那么光是執行記憶體重分配的時間就會占去修改字串所用時間的一大部分,如果這種修改頻繁地發生的話,可能還會對性能造成影響,

為了避免 C 字串的這種缺陷, SDS 通過已使用空間解除了字串長度和底層陣列長度之間的關聯,在更改字串的時候可以只更改len屬性,而不重新分配記憶體,

空間預分配

對字串進行增長操作時,會使用一個sdsMakeRoomFor函式來擴展字串,

sds sdsMakeRoomFor(sds s, size_t addlen) {
    void *sh, *newsh;
    size_t avail = sdsavail(s);
    size_t len, newlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;
    /* Return ASAP if there is enough space left. */
    if (avail >= addlen) return s;
    len = sdslen(s);
    sh = (char*)s-sdsHdrSize(oldtype);
    newlen = (len+addlen);
    if (newlen < SDS_MAX_PREALLOC)
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;
    type = sdsReqType(newlen);
    /* Don't use type 5: the user is appending to the string and type 5 is
     * not able to remember empty space, so sdsMakeRoomFor() must be called
     * at every appending operation. */
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;
    hdrlen = sdsHdrSize(type);
    if (oldtype==type) {
        newsh = s_realloc(sh, hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+hdrlen;
    } else {
        /* Since the header size changes, need to move the string forward,
         * and can't use realloc */
        newsh = s_malloc(hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len);
    }
    sdssetalloc(s, newlen);
    return s;
}

sdsMakeRoomFor是sds實作中很重要的一個函式,關于它的實作代碼,我們需要注意的是:

  • 如果原來字串中的空余空間夠用(avail >= addlen),那么它什么也不做,直接回傳,
  • 如果需要分配空間,它會比實際請求的要多分配一些,以防備接下來繼續追加,它在字串已經比較長(長度將大于等于 1 MB)的情況下要至少多分配 SDS_MAX_PREALLOC 個位元組,這個常量在sds.h中定義為(1024*1024)=1MB,
  • 按分配后的空間大小,可能需要更換 header 型別(原來 header 的 alloc 欄位太短,表達不了增加后的容量),
  • 如果需要更換 header,那么整個字串空間(包括 header )都需要重新分配(s_malloc),并拷貝原來的資料到新的位置,
  • 如果不需要更換header(原來的 header 夠用),那么呼叫一個比較特殊的 s_realloc,試圖在原來的地址上重新分配空間,s_realloc 的具體實作得看Redis編譯的時候選用了哪個 allocator(在Linux上默認使用jemalloc),但不管是哪個 realloc 的實作,它所表達的含義基本是相同的:它盡量在原來分配好的地址位置重新分配,如果原來的地址位置有足夠的空余空間完成重新分配,那么它回傳的新地址與傳入的舊地址相同;否則,它分配新的地址塊,并進行資料搬遷,

總結:在擴展SDS空間之前, SDS API 會先檢查未使用空間是否足夠, 如果足夠的話, API 就會直接使用未使用空間, 而無須執行記憶體重分配,通過這樣的空間預分配策略, Redis可以減少連續執行字串增長操作所需的記憶體重分配次數,

惰性空間釋放

惰性空間釋放用于優化 SDS 的字串縮短操作: 當 SDS 的 API 需要縮短 SDS 保存的字串時,程式并不立即使用記憶體重分配來回收縮短后多出來的位元組,而是把縮短后的長度記錄在len屬性中,剩余空間用于未來擴展字串用,

如 sdstrim 函式:

/* Remove the part of the string from left and from right composed just of
 * contiguous characters found in 'cset', that is a null terminted C string.
 *
 * After the call, the modified sds string is no longer valid and all the
 * references must be substituted with the new pointer returned by the call.
 *
 * Example:
 *
 * s = sdsnew("AA...AA.a.aa.aHelloWorld     :::");
 * s = sdstrim(s,"Aa. :");
 * printf("%s\n", s);
 *
 * Output will be just "HelloWorld".
 */
sds sdstrim(sds s, const char *cset) {
    char *start, *end, *sp, *ep;
    size_t len;

    sp = start = s;
    ep = end = s+sdslen(s)-1;
    while(sp <= end && strchr(cset, *sp)) sp++;
    while(ep > sp && strchr(cset, *ep)) ep--;
    len = (sp > ep) ? 0 : ((ep-sp)+1);
    if (s != sp) memmove(s, sp, len);
    s[len] = '\0';
    sdssetlen(s,len);
    return s;
}

可以看出程式結束時并沒有reallocate記憶體空間,而是修改結構體中len的屬性,
當用戶真正想free掉空閑空間時,可以使用sdsRemoveFreeSpace函式:

/* Reallocate the sds string so that it has no free space at the end.
 * 重新分配sds字串,使其末尾沒有可用空間,
 * The contained string remains not altered, but next concatenation operations
 * will require a reallocation.
 * 其中包含的字串保持不變,但進行后續連接操作將需要重新分配,
 *
 * After the call, the passed sds string is no longer valid and all the
 * references must be substituted with the new pointer returned by the call. 
 * 呼叫后,傳遞的sds字串不再有效,并且所有參考必須替換為呼叫回傳的新指標,
 */
sds sdsRemoveFreeSpace(sds s) {
    void *sh, *newsh;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
    size_t len = sdslen(s);
    size_t avail = sdsavail(s);
    sh = (char*)s-oldhdrlen;

    /* Return ASAP if there is no space left. */
    if (avail == 0) return s;

    /* Check what would be the minimum SDS header that is just good enough to
     * fit this string. */
    type = sdsReqType(len);
    hdrlen = sdsHdrSize(type);

    /* If the type is the same, or at least a large enough type is still
     * required, we just realloc(), letting the allocator to do the copy
     * only if really needed. Otherwise if the change is huge, we manually
     * reallocate the string to use the different header type. */
    if (oldtype==type || type > SDS_TYPE_8) {
        newsh = s_realloc(sh, oldhdrlen+len+1);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+oldhdrlen;
    } else {
        newsh = s_malloc(hdrlen+len+1);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len);
    }
    sdssetalloc(s, len);
    return s;
}

可以存放二進制資料

C 字串中的字符必須符合某種編碼(比如 ASCII), 并且除了字串的末尾之外, 字串里面不能包含空字符, 否則最先被程式讀入的空字符將被誤認為是字串結尾 —— 這些限制使得 C 字串只能保存文本資料, 而不能保存像圖片、音頻、視頻、壓縮檔案這樣的二進制資料,
因此, 為了確保 Redis 可以適用于各種不同的使用場景, SDS 的 API 都是二進制安全的(binary-safe): 所有 SDS API 都會以處理二進制的方式來處理 SDS 存放在 buf 陣列里的資料, 程式不會對其中的資料做任何限制、過濾、或者假設 —— 資料在寫入時是什么樣的, 它被讀取時就是什么樣,

這也是我們將 SDS 的 buf 屬性稱為位元組陣列的原因 —— Redis 不是用這個陣列來保存字符, 而是用它來保存一系列二進制資料,

SDS相關 API 總結

參考書籍:Redis 設計與實作

函式 作用 時間復雜度
sdsnew 創建一個包含給定 C 字串的 SDS , O(N) , N 為給定 C 字串的長度,
sdsempty 創建一個不包含任何內容的空 SDS , O(1)
sdsfree 釋放給定的 SDS , O(1)
sdslen 回傳 SDS 的已使用空間位元組數, 這個值可以通過讀取 SDS 的 len 屬性來直接獲得,復雜度為 O(1) ,
sdsavail 回傳 SDS 的未使用空間位元組數, 這個值可以通過讀取 SDS 的 free 屬性來直接獲得,復雜度為 O(1) ,
sdsdup 創建一個給定 SDS 的副本(copy), O(N) , N 為給定 SDS 的長度,
sdsclear 清空 SDS 保存的字串內容, 因為惰性空間釋放策略,復雜度為 O(1) ,
sdscat 將給定 C 字串拼接到 SDS字串的末尾, O(N) , N 為被拼接 C 字串的長度,
sdscatsds 將給定 SDS 字串拼接到另一個 SDS字串的末尾, O(N) , N 為被拼接 SDS 字串的長度,
sdscpy 將給定的 C 字串復制到 SDS 里面,覆寫 SDS 原有的字串, O(N) , N 為被復制 C 字串的長度,
sdsgrowzero 用空字符將 SDS 擴展至給定長度, O(N) , N 為擴展新增的位元組數,
sdsrange 保留 SDS 給定區間內的資料,不在區間內的資料會被覆寫或清除, O(N) , N 為被保留資料的位元組數,
sdstrim 接受一個 SDS 和一個 C 字串作為引數,從 SDS 左右兩端分別移除所有在 C字串中出現過的字符, O(M*N) , M 為 SDS 的長度,N 為給定 C 字串的長度,
sdscmp 對比兩個 SDS 字串是否相同, O(N) , N 為兩個 SDS 中較短的那個 SDS的長度,

字串相關命令及解釋

  • SET

如果 key 已經持有其他值,SET 就覆寫舊值,無視型別,

當 SET 命令對一個帶有生存時間(TTL)的鍵進行設定之后,該鍵原有的 TTL 將被清除,

  • SETNX

只在鍵 key 不存在的情況下,將鍵 key 的值設定為 value ,

若鍵 key 已經存在,則 SETNX 命令不做任何動作,

  • SETEX

將鍵 key 的值設定為 value ,并將鍵 key 的生存時間設定為 seconds 秒鐘,

如果鍵 key 已經存在,那么 SETEX 命令將覆寫已有的值,

SET key value
EXPIRE key seconds  # 設定生存時間

SETEX 和這兩個命令的不同之處在于 SETEX 是一個原子(atomic)操作,它可以在同一時間內完成設定值和設定過期時間這兩個操作,因此 SETEX 命令在儲存快取的時候非常實用,

  • PSETEX

這個命令和 SETEX 命令相似,但它以毫秒為單位設定 key 的生存時間,

  • GET

回傳與鍵 key 相關聯的字串值,

  • GETSET

將鍵 key 的值設為 value ,并回傳鍵 key 在被設定之前的舊值,

  • STRLEN

回傳鍵 key 儲存的字串值的長度,

  • APPEND

如果鍵 key 已經存在并且它的值是一個字串,APPEND 命令將把 value 追加到鍵 key 現有值的末尾,

如果 key 不存在,APPEND 就簡單地將鍵 key 的值設為 value ,就像執行 SET key value 一樣,

  • SETRANGE (SETRANGE key offset value)

從偏移量 offset 開始,用 value 引數覆寫(overwrite)鍵 key 儲存的字串值,

不存在的鍵 key 當作空白字串處理,

SETRANGE 命令會確保字串足夠長以便將 value 設定到指定的偏移量上,如果鍵 key 原來儲存的字串長度比偏移量小(比如字串只有 5 個字符長,但你設定的 offset 是 10 ),那么原字符和偏移量之間的空白將用零位元組(zerobytes, "\x00" )進行填充,

  • GETRANGE (GETRANGE key start end)

回傳鍵 key 儲存的字串值的指定部分,字串的截取范圍由 start 和 end 兩個偏移量決定(包括 start 和 end 在內),

  • INCR

為鍵 key 儲存的數字值加上一,

如果鍵 key 不存在,那么它的值會先被初始化為 0 ,然后再執行 INCR 命令,

如果鍵 key 儲存的值不能被解釋為數字,那么 INCR 命令將回傳一個錯誤,

本操作的值限制在 64 位(bit)有符號數字表示之內,

  • INCRBY

為鍵 key 儲存的數字值加上增量 increment ,

如果鍵 key 不存在,那么鍵 key 的值會先被初始化為 0 ,然后再執行 INCRBY 命令,

如果鍵 key 儲存的值不能被解釋為數字,那么 INCRBY 命令將回傳一個錯誤,

本操作的值限制在 64 位(bit)有符號數字表示之內,

  • INCRBYFLOAT

為鍵 key 儲存的值加上浮點數增量 increment

  • DECR

為鍵 key 儲存的數字值減去一

  • DECRBY

將鍵 key 儲存的整數值減去減量 decrement

EG: DECRBY key decrement

  • MSET (MSET key value [key value …])

如果某個給定鍵已經存在,那么 MSET 將使用新值去覆寫舊值,如果這不是你所希望的效果,請考慮使用 MSETNX 命令,這個命令只會在所有給定鍵都不存在的情況下進行設定,

MSET 是一個原子性(atomic)操作,所有給定鍵都會在同一時間內被設定,不會出現某些鍵被設定了但是另一些鍵沒有被設定的情況,

  • MSETNX

當且僅當所有給定鍵都不存在時,為所有給定鍵設定值,

即使只有一個給定鍵已經存在,MSETNX 命令也會拒絕執行對所有鍵的設定操作,

MSETNX 是一個原子性(atomic)操作,所有給定鍵要么就全部都被設定,要么就全部都不設定,不可能出現第三種狀態,

  • MGET

回傳給定的一個或多個字串鍵的值,

如果給定的字串鍵里面,有某個鍵不存在,那么這個鍵的值將以特殊值 nil 表示,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/244583.html

標籤:其他

上一篇:javaSE 8

下一篇:Redis--部署操作

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more