国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

redis到底是怎么樣進(jìn)行漸進(jìn)式Rehash的

這篇具有很好參考價(jià)值的文章主要介紹了redis到底是怎么樣進(jìn)行漸進(jìn)式Rehash的。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

Redis 是一個(gè)開(kāi)源(BSD許可)的,內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)系統(tǒng),它可以用作數(shù)據(jù)庫(kù)、緩存和消息中間件。那么redis的底層是如何來(lái)存儲(chǔ)數(shù)據(jù)的呢?

一、redis如何在存儲(chǔ)大量的key時(shí)候,查詢速度還能接近O(1)呢?

查詢速度接近O(1)的數(shù)據(jù)結(jié)構(gòu)通常讓我們想到的就是HashMap結(jié)構(gòu),那下面我從源碼來(lái)追蹤下redis到底是不是使用的HashMap結(jié)構(gòu)呢?生成的全局hashTable的大小為4
redis的數(shù)據(jù)最外層的結(jié)構(gòu)是redisDb(server.h文件) ,其定義如下:

typedef struct redisDb {
    dict *dict;                 /* The keyspace for this DB */
    dict *expires;              /* Timeout of keys with a timeout set */
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)*/
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    int id;                     /* Database ID */
    long long avg_ttl;          /* Average TTL, just for stats */
    unsigned long expires_cursor; /* Cursor of the active expire cycle. */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;

從上面定義我們可以看出redisDb 的保存數(shù)據(jù)的結(jié)構(gòu)是dict(dict.h),那么我們從文件中獲取

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
} dict;
/* This is our hash table structure. Every dictionary has two of this as we
 * implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;

dict 包含了兩個(gè)hash表(dictht ht[2]),這里使用兩個(gè)hash表就是為了后續(xù)給漸進(jìn)式rehash來(lái)進(jìn)行服務(wù)的.屬性rehashidx == -1時(shí)候代表不是處于reshaing中。
dictht 就一個(gè)hashtable,其包含dictEntry 的數(shù)組。然后我們繼續(xù)看下

   typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

dictEntry 的就是hash表中的一個(gè)鍵值對(duì),那么根據(jù)上面的代碼我們可以繪出redis中內(nèi)存結(jié)構(gòu)圖。
redis到底是怎么樣進(jìn)行漸進(jìn)式Rehash的

redis的rehash過(guò)程怎么處理呢?

隨著redis中key的數(shù)據(jù)量增多,隨著key的增多,那么dictEntry 連越來(lái)越長(zhǎng),這個(gè)時(shí)候查詢出來(lái)的性能將會(huì)越來(lái)越慢。這個(gè)時(shí)候就需要對(duì)hashTable進(jìn)行擴(kuò)容,在數(shù)據(jù)量大的時(shí)候如果等到所有的擴(kuò)容完成,那么必然會(huì)導(dǎo)致redis長(zhǎng)時(shí)間等待,那么這個(gè)時(shí)候我們就采用漸進(jìn)式rehash方式來(lái)進(jìn)行擴(kuò)容。

什么是漸進(jìn)式rehash呢?

Redis 默認(rèn)使用了兩個(gè)全局哈希表:dictht[0]和哈希表 dictht[1],一開(kāi)始,當(dāng)你剛插入數(shù)據(jù)時(shí),默認(rèn)使用dictht[0],此時(shí)的dictht[1] 并沒(méi)有被分配空間。隨著數(shù)據(jù)逐步增多,Redis 開(kāi)始執(zhí)行 rehash,這個(gè)過(guò)程分為三步:

1、給dictht[1]分配更大的空間,一般是當(dāng)前dictht[0]已使用大小的2倍,但是必須滿足是2的冪次倍!
2、把哈希表0 中的數(shù)據(jù)重新映射并拷貝到哈希表1 中(在hash表1下進(jìn)行重新計(jì)算hash值);
3、釋放哈希表 0 的空間
4、把dictht[0]指向剛剛創(chuàng)建好的dictht[1]

什么時(shí)候進(jìn)行hash

  • 1、在沒(méi)有fork子進(jìn)程進(jìn)行RDS或者AOF數(shù)據(jù)備份的時(shí)候且ht[0] .used >= ht[0].size時(shí)
  • 2、 在有fork子進(jìn)程進(jìn)行RDS或者AOF數(shù)據(jù)備份的時(shí)候且ht[0] .used > ht[0].size * 5時(shí)
    擴(kuò)容,肯定是在添加數(shù)據(jù)的時(shí)候才會(huì)擴(kuò)容,所以我們找一個(gè)添加數(shù)據(jù)的入口,我們從源碼層面進(jìn)行下驗(yàn)證:
int dictReplace(dict *d, void *key, void *val)
{
    dictEntry *entry, *existing, auxentry;

    /* Try to add the element. If the key
     * does not exists dictAdd will succeed. */
    entry = dictAddRaw(d,key,&existing);
    if (entry) {
        dictSetVal(d, entry, val);
        return 1;
    }

    /* Set the new value and free the old one. Note that it is important
     * to do that in this order, as the value may just be exactly the same
     * as the previous one. In this context, think to reference counting,
     * you want to increment (set), and then decrement (free), and not the
     * reverse. */
    auxentry = *existing;
    dictSetVal(d, existing, val);
    dictFreeVal(d, &auxentry);
    return 0;
}

然后繼續(xù)查看dictAddRaw方法

dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
{
    long index;
    dictEntry *entry;
    dictht *ht;

    if (dictIsRehashing(d)) _dictRehashStep(d);

    /* Get the index of the new element, or -1 if
     * the element already exists. */
    if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
        return NULL;

    /* Allocate the memory and store the new entry.
     * Insert the element in top, with the assumption that in a database
     * system it is more likely that recently added entries are accessed
     * more frequently. */
    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
    entry = zmalloc(sizeof(*entry));
    entry->next = ht->table[index];
    ht->table[index] = entry;
    ht->used++;

    /* Set the hash entry fields. */
    dictSetKey(d, entry, key);
    return entry;
}

然后繼續(xù)往下看_dictKeyIndex方法

static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing)
{
    unsigned long idx, table;
    dictEntry *he;
    if (existing) *existing = NULL;

    /* Expand the hash table if needed */
    if (_dictExpandIfNeeded(d) == DICT_ERR)
        return -1;
    for (table = 0; table <= 1; table++) {
        idx = hash & d->ht[table].sizemask;
        /* Search if this slot does not already contain the given key */
        he = d->ht[table].table[idx];
        while(he) {
            if (key==he->key || dictCompareKeys(d, key, he->key)) {
                if (existing) *existing = he;
                return -1;
            }
            he = he->next;
        }
        if (!dictIsRehashing(d)) break;
    }
    return idx;
}

從上面代碼注釋可以看出來(lái),_dictExpandIfNeeded就是用來(lái)進(jìn)行擴(kuò)容的

   /* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d)
{
    /* Incremental rehashing already in progress. Return. */
    if (dictIsRehashing(d)) return DICT_OK;

    /* If the hash table is empty expand it to the initial size. */
    if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);

    /* If we reached the 1:1 ratio, and we are allowed to resize the hash
     * table (global setting) or we should avoid it but the ratio between
     * elements/buckets is over the "safe" threshold, we resize doubling
     * the number of buckets. */
    if (!dictTypeExpandAllowed(d))
        return DICT_OK;
    if ((dict_can_resize == DICT_RESIZE_ENABLE &&
         d->ht[0].used >= d->ht[0].size) ||
        (dict_can_resize != DICT_RESIZE_FORBID &&
         d->ht[0].used / d->ht[0].size > dict_force_resize_ratio))
    {
        return dictExpand(d, d->ht[0].used + 1);
    }
    return DICT_OK;
}
  • 1、在hashtable擴(kuò)容的時(shí)候,如果正在擴(kuò)容的時(shí)將不會(huì)出發(fā)擴(kuò)容操作
  • 2、DICT_HT_INITIAL_SIZE的大小為4,即默認(rèn)創(chuàng)建的hashtable大小為4
  • 3、dict_force_resize_ratio的值為5
    *這里需要關(guān)注dict_can_resize 這個(gè)字段什么時(shí)候被賦值了,

如何進(jìn)行擴(kuò)容?

對(duì)hashtable真正擴(kuò)容的方法是dictExpand(d, d->ht[0].used + 1)

/* return DICT_ERR if expand was not performed */
int dictExpand(dict *d, unsigned long size) {
    return _dictExpand(d, size, NULL);
}
int _dictExpand(dict *d, unsigned long size, int* malloc_failed)
{
    if (malloc_failed) *malloc_failed = 0;

    /* the size is invalid if it is smaller than the number of
     * elements already inside the hash table */
    if (dictIsRehashing(d) || d->ht[0].used > size)
        return DICT_ERR;

    dictht n; /* the new hash table */
    unsigned long realsize = _dictNextPower(size);

    /* Detect overflows */
    if (realsize < size || realsize * sizeof(dictEntry*) < realsize)
        return DICT_ERR;

    /* Rehashing to the same table size is not useful. */
    if (realsize == d->ht[0].size) return DICT_ERR;

    /* Allocate the new hash table and initialize all pointers to NULL */
    n.size = realsize;
    n.sizemask = realsize-1;
    if (malloc_failed) {
        n.table = ztrycalloc(realsize*sizeof(dictEntry*));
        *malloc_failed = n.table == NULL;
        if (*malloc_failed)
            return DICT_ERR;
    } else
        n.table = zcalloc(realsize*sizeof(dictEntry*));

    n.used = 0;

    /* Is this the first initialization? If so it's not really a rehashing
     * we just set the first hash table so that it can accept keys. */
    if (d->ht[0].table == NULL) {
        d->ht[0] = n;
        return DICT_OK;
    }

    /* Prepare a second hash table for incremental rehashing */
    d->ht[1] = n;
    d->rehashidx = 0;
    return DICT_OK;
}

1、先定義一個(gè)新的全局表,大小2^2 到 2的n次冪跟size來(lái)進(jìn)行比較,取第一次滿足的時(shí)候的條件,_dictNextPower(size)的代碼如下:

   while(1) {
        if (i >= size)
            return i;
        i *= 2;
    }

2、設(shè)置dictht 的size等于剛剛計(jì)算好的realSize,掩碼等于realsize-1
3、給dictht 的table分配地址和做初始化操作
4、接下來(lái)就判斷ht[0].table是否為null,如果為null說(shuō)明是第一次進(jìn)行初始存放數(shù)據(jù),而不是真正的進(jìn)行rehash。此時(shí)只需要將ht[0] = n,即把剛剛創(chuàng)建的全局hashtable賦值給ht[0]就可以了
5、如果不是那么把剛剛創(chuàng)建的全局hashtable賦值給ht[1],然后dict對(duì)應(yīng)的rehashidx 值修改為0
至此我們完成了hash表的擴(kuò)容

那redis的數(shù)據(jù)如何進(jìn)行遷移的

答案就是我們剛剛說(shuō)到的使用漸進(jìn)式rehash方法,那具體是如何做的?
假如一次性把數(shù)據(jù)遷移會(huì)很耗時(shí)間,會(huì)讓單條指令等待很久很久,會(huì)形成阻塞。所以,Redis采用的是漸進(jìn)式Rehash,所謂漸進(jìn)式,就是慢慢的,不會(huì)一次性把所有數(shù)據(jù)遷移。
那什么時(shí)候會(huì)進(jìn)行漸進(jìn)式數(shù)據(jù)遷移?
1.每次進(jìn)行key的crud操作都會(huì)進(jìn)行一個(gè)hash桶的數(shù)據(jù)遷移
2.定時(shí)任務(wù),進(jìn)行部分?jǐn)?shù)據(jù)遷移

  • 執(zhí)行crud時(shí)候?qū)?shù)據(jù)的操作,進(jìn)行rehash操作
    redis到底是怎么樣進(jìn)行漸進(jìn)式Rehash的
    redis到底是怎么樣進(jìn)行漸進(jìn)式Rehash的
    redis到底是怎么樣進(jìn)行漸進(jìn)式Rehash的
    redis到底是怎么樣進(jìn)行漸進(jìn)式Rehash的
  • 定時(shí)任務(wù)執(zhí)行
    源碼來(lái)源于server.c
/* This function handles 'background' operations we are required to do
 * incrementally in Redis databases, such as active key expiring, resizing,
 * rehashing. */
void databasesCron(void) {
    /* Expire keys by random sampling. Not required for slaves
     * as master will synthesize DELs for us. */
    if (server.active_expire_enabled) {
        if (iAmMaster()) {
            activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
        } else {
            expireSlaveKeys();
        }
    }

    /* Defrag keys gradually. */
    activeDefragCycle();

    /* Perform hash tables rehashing if needed, but only if there are no
     * other processes saving the DB on disk. Otherwise rehashing is bad
     * as will cause a lot of copy-on-write of memory pages. */
    if (!hasActiveChildProcess()) {
        /* We use global counters so if we stop the computation at a given
         * DB we'll be able to start from the successive in the next
         * cron loop iteration. */
        static unsigned int resize_db = 0;
        static unsigned int rehash_db = 0;
        int dbs_per_call = CRON_DBS_PER_CALL;
        int j;

        /* Don't test more DBs than we have. */
        if (dbs_per_call > server.dbnum) dbs_per_call = server.dbnum;

        /* Resize */
        for (j = 0; j < dbs_per_call; j++) {
            tryResizeHashTables(resize_db % server.dbnum);
            resize_db++;
        }

        /* Rehash */
        if (server.activerehashing) {
            for (j = 0; j < dbs_per_call; j++) {
                int work_done = incrementallyRehash(rehash_db);
                if (work_done) {
                    /* If the function did some work, stop here, we'll do
                     * more at the next cron loop. */
                    break;
                } else {
                    /* If this db didn't need rehash, we'll try the next one. */
                    rehash_db++;
                    rehash_db %= server.dbnum;
                }
            }
        }
    }
}

那接下來(lái)我們真正看下真正執(zhí)行rehash操作的方法:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-487216.html

static void _dictRehashStep(dict *d) {
    if (d->pauserehash == 0) dictRehash(d,1);
}

/* Performs N steps of incremental rehashing. Returns 1 if there are still
 * keys to move from the old to the new hash table, otherwise 0 is returned.
 *
 * Note that a rehashing step consists in moving a bucket (that may have more
 * than one key as we use chaining) from the old to the new hash table, however
 * since part of the hash table may be composed of empty spaces, it is not
 * guaranteed that this function will rehash even a single bucket, since it
 * will visit at max N*10 empty buckets in total, otherwise the amount of
 * work it does would be unbound and the function may block for a long time. */
int dictRehash(dict *d, int n) {
    int empty_visits = n*10; /* Max number of empty buckets to visit. */
    if (dict_can_resize == DICT_RESIZE_FORBID || !dictIsRehashing(d)) return 0;
    if (dict_can_resize == DICT_RESIZE_AVOID && 
        (d->ht[1].size / d->ht[0].size < dict_force_resize_ratio))
    {
        return 0;
    }

    while(n-- && d->ht[0].used != 0) {
        dictEntry *de, *nextde;

        /* Note that rehashidx can't overflow as we are sure there are more
         * elements because ht[0].used != 0 */
        assert(d->ht[0].size > (unsigned long)d->rehashidx);
        while(d->ht[0].table[d->rehashidx] == NULL) {
            d->rehashidx++;
            if (--empty_visits == 0) return 1;
        }
        de = d->ht[0].table[d->rehashidx];
        /* Move all the keys in this bucket from the old to the new hash HT */
        while(de) {
            uint64_t h;

            nextde = de->next;
            /* Get the index in the new hash table */
            h = dictHashKey(d, de->key) & d->ht[1].sizemask;
            de->next = d->ht[1].table[h];
            d->ht[1].table[h] = de;
            d->ht[0].used--;
            d->ht[1].used++;
            de = nextde;
        }
        d->ht[0].table[d->rehashidx] = NULL;
        d->rehashidx++;
    }

    /* Check if we already rehashed the whole table... */
    if (d->ht[0].used == 0) {
        zfree(d->ht[0].table);
        d->ht[0] = d->ht[1];
        _dictReset(&d->ht[1]);
        d->rehashidx = -1;
        return 0;
    }

    /* More to rehash... */
    return 1;
}
  • 1、基于rehashidx從0開(kāi)始把數(shù)據(jù)從ht[0]轉(zhuǎn)移到ht[1]中
  • 2、當(dāng)整個(gè)ht[0]中已使用的數(shù)量為0時(shí),就會(huì)把原來(lái)ht[0]中所占用的內(nèi)存進(jìn)行釋放,然后ht[0]指向ht[1],最后重置rehashIndex為-1
    根據(jù)上面的分析我們可以知道rehash過(guò)程中,在redis中設(shè)置值的操作大致如下:
    redis到底是怎么樣進(jìn)行漸進(jìn)式Rehash的

到了這里,關(guān)于redis到底是怎么樣進(jìn)行漸進(jìn)式Rehash的的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • redis是單線程的,那么他是怎么樣避免阻塞的

    redis是單線程的,那么他是怎么樣避免阻塞的

    Redis 實(shí)例在運(yùn)行時(shí),要和許多對(duì)象進(jìn)行交互,這些不同的交互就會(huì)涉及不同的操作,下 面我們來(lái)看看和 Redis 實(shí)例交互的對(duì)象,以及交互時(shí)會(huì)發(fā)生的操作。 客戶端 :網(wǎng)絡(luò) IO,鍵值對(duì)增刪改查操作,數(shù)據(jù)庫(kù)操作; 磁盤(pán) :生成 RDB 快照,記錄 AOF 日志,AOF 日志重寫(xiě); 主從節(jié)點(diǎn)

    2024年02月13日
    瀏覽(31)
  • 如何利用ChatGPT進(jìn)行論文潤(rùn)色-ChatGPT潤(rùn)色文章怎么樣

    如何利用ChatGPT進(jìn)行論文潤(rùn)色-ChatGPT潤(rùn)色文章怎么樣

    ChatGPT可以潤(rùn)色文章,使用其潤(rùn)色功能可以為用戶提供更加整潔、清晰、文采動(dòng)人的文本。但需要注意以下幾點(diǎn): 需要保持文本的一致性和完整性。當(dāng)使用ChatGPT進(jìn)行潤(rùn)色時(shí),需要注意保持文本的一致性和完整性。不應(yīng)改變?cè)嘉恼碌囊饬x、論點(diǎn)和邏輯結(jié)構(gòu),尤其是在非常規(guī)文

    2024年02月08日
    瀏覽(22)
  • 2023-06-17:說(shuō)一說(shuō)redis中漸進(jìn)式rehash?

    2023-06-17:說(shuō)一說(shuō)redis中漸進(jìn)式rehash?

    2023-06-17:說(shuō)一說(shuō)redis中漸進(jìn)式rehash? 答案2023-06-17: 在Redis中,如果哈希表的數(shù)組一直保持不變,就會(huì)增加哈希沖突的可能性,從而降低檢索效率。為了解決這個(gè)問(wèn)題,Redis會(huì)對(duì)數(shù)組進(jìn)行擴(kuò)容,通常是將數(shù)組大小擴(kuò)大為原來(lái)的兩倍。然而,這個(gè)擴(kuò)容過(guò)程會(huì)引起元素在哈希桶中的

    2024年02月09日
    瀏覽(27)
  • Redis(三)存儲(chǔ)原理與數(shù)據(jù)模型(hash沖突、漸進(jìn)式rehash)

    Redis(三)存儲(chǔ)原理與數(shù)據(jù)模型(hash沖突、漸進(jìn)式rehash)

    Redis(一)原理及基本命令(柔性數(shù)組) Redis(二)網(wǎng)絡(luò)協(xié)議和異步方式(樂(lè)觀鎖悲觀鎖) Redis(三)存儲(chǔ)原理與數(shù)據(jù)模型(hash沖突、漸進(jìn)式rehash) Redis跳表 Redis是key-value的結(jié)構(gòu),其中value包含:字典,雙向鏈表,壓縮列表,跳表,整數(shù)數(shù)組,動(dòng)態(tài)字符串。 其中redis中各valu

    2024年02月16日
    瀏覽(32)
  • Redis4 漸進(jìn)式遍歷/自定義客戶端/持久化

    Redis4 漸進(jìn)式遍歷/自定義客戶端/持久化

    1.keys *一次性把所有的key都獲取到.但是存在一個(gè)問(wèn)題,一旦數(shù)據(jù)過(guò)多,redis就會(huì)被阻塞住,就無(wú)暇顧及其他的命令,這樣的影響很大. 2.那么就出現(xiàn)了漸進(jìn)式遍歷,可以做到既能獲取所有的key,又不會(huì)阻塞服務(wù)器.漸進(jìn)式不是一個(gè)命令把所有的key獲取到,而是沒(méi)執(zhí)行一次命令只獲取其中的

    2024年02月06日
    瀏覽(24)
  • 【MyBatis學(xué)習(xí)】MyBatis操縱數(shù)據(jù)庫(kù)進(jìn)行查詢操作 ?MyBatis與JDBC想比怎么樣,趕快與我一起探索吧 ! ! !

    【MyBatis學(xué)習(xí)】MyBatis操縱數(shù)據(jù)庫(kù)進(jìn)行查詢操作 ?MyBatis與JDBC想比怎么樣,趕快與我一起探索吧 ! ! !

    前言: 大家好,我是 良辰丫 ,從今天開(kāi)始我們就要進(jìn)入MyBatis的學(xué)習(xí)了,請(qǐng)君與我一起操縱數(shù)據(jù)庫(kù),MyBatis到底是什么呢?我們慢慢往下瞧! ! !?????? ??個(gè)人主頁(yè):良辰針不戳 ??所屬專欄:javaEE進(jìn)階篇之框架學(xué)習(xí) ??勵(lì)志語(yǔ)句:生活也許會(huì)讓我們遍體鱗傷,但最終這些傷口會(huì)成為我

    2024年02月09日
    瀏覽(17)
  • 低代碼技術(shù)怎么樣

    低代碼技術(shù)是IT行業(yè)的一種新技術(shù),主要通過(guò)可視化圖形模型化建立應(yīng)用程序,從而使得開(kāi)發(fā)速度更快、迭代周期更短。數(shù)聚股份將從低代碼技術(shù)及其優(yōu)缺點(diǎn),以及如何在軟件開(kāi)發(fā)中正確地使用低代碼技術(shù)為切入點(diǎn),詳細(xì)講解一下低代碼的應(yīng)用。 低代碼技術(shù)優(yōu)點(diǎn) 低代碼技術(shù)主

    2024年02月13日
    瀏覽(21)
  • 證照之星軟件怎么樣?證照之星怎么換背景色

    證照之星軟件怎么樣?證照之星怎么換背景色

    隨著科技的快速發(fā)展,越來(lái)越多的軟件應(yīng)用于各個(gè)方面,為人們的生活和工作帶來(lái)便利。今天,我們要介紹的就是一款證件照制作方面的軟件——證照之星。那么,證照之星到底是什么軟件?它好用嗎?這篇文章將為大家詳細(xì)解答。 一、證照之星是什么軟件 證照之星是一款

    2024年02月16日
    瀏覽(26)
  • 現(xiàn)在學(xué)習(xí)python怎么樣

    現(xiàn)在學(xué)習(xí)python怎么樣

    Python,年齡可能比很多讀者都要大,但是它在更新快速的編程界卻一直表現(xiàn)出色,甚至有人把它比作是編程界的《葵花寶典》,只是Python的速成之法相較《葵花寶典》有過(guò)之而無(wú)不及。 Python簡(jiǎn)潔,高效的特點(diǎn),大大提升了程序員的編碼速度,極大的提高了程序員的辦公效率,

    2023年04月13日
    瀏覽(26)
  • 嵌入式就業(yè)怎么樣?

    嵌入式就業(yè)怎么樣?

    嵌入式就業(yè)怎么樣? 現(xiàn)在的IT行業(yè),嵌入式是大熱門(mén),下面也要來(lái)給大家介紹下學(xué)習(xí)嵌入式之后的發(fā)展以及就業(yè)怎么樣。 ? 首先是好找工作。 嵌入式人才目前是處于供不應(yīng)求的狀態(tài)中,據(jù)權(quán)威統(tǒng)計(jì)機(jī)構(gòu)統(tǒng)計(jì)在所有軟件開(kāi)發(fā)類人才的需求中,對(duì)嵌入式工程師的需求達(dá)到全部需求量

    2023年04月24日
    瀏覽(20)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包