广告
返回顶部
首页 > 资讯 > 精选 >java LRU算法及Apache LRUMap源码实例分析
  • 801
分享到

java LRU算法及Apache LRUMap源码实例分析

2023-06-21 20:06:49 801人浏览 独家记忆
摘要

本篇内容主要讲解“java LRU算法及Apache LRUMap源码实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“java LRU算法及Apache LRUMap源

本篇内容主要讲解“java LRU算法及Apache LRUMap源码实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“java LRU算法及Apache LRUMap源码实例分析”吧!

    1. 什么是LRU

    LRU(least recently used) : 最近最少使用

    LRU就是一种经典的算法,在容器中,对元素定义一个最后使用时间,当新的元素写入的时候,如果容器已满,则淘汰最近最少使用的元素,把新的元素写入。

    1.1 自定义实现LRU的要求

    比如redis,如何自己实现简易版的Redis缓存

    那么我们需要一种数据结构,支持set和get操作。

    1) get操作时间复杂度O(1);

    2)需要支持RLU算法,空间不足时,需要将使用最少的元素移除,为新元素让空间;

    3)时间失效remove(这个先不谈,比较麻烦)。

    1.2 Apache LRUMap示例

    1.2.1 pom依赖
    <dependency>            <groupId>org.apache.commons</groupId>            <artifactId>commons-collections4</artifactId>            <version>4.2</version>        </dependency>
    1.2.2 demo
    LRUMap<String, String> map = new LRUMap<>(3);        map.put("1", "1");        map.put("2", "2");        map.put("3", "3");         map.get("2");         System.out.println("---------------------------------");        map.forEach((k,v)->            System.out.println(k+"\t"+v)        );         map.put("4", "4");        map.put("5", "5");         System.out.println("---------------------------------");        map.forEach((k,v)->                System.out.println(k+"\t"+v)        );         map.put("6", "6");         System.out.println("---------------------------------");        map.forEach((k,v)->                System.out.println(k+"\t"+v)        );

    结果如下:

    ---------------------------------

    11

    33

    22

    ---------------------------------

    22

    44

    55

    ---------------------------------

    44

    55

    66

    可以看出在get("2"),2的位置挪后,然后移除的顺序就延后。

    容量不足时,总是移除,使用最少的,时间最远的。

    2. 源码解析

    2.1 设计

    public class LRUMap<K, V>        extends AbstractLinkedMap<K, V> implements BoundedMap<K, V>, Serializable, Cloneable {

    进一步查看AbstractLinkedMap,AbstractHashedMap

    public abstract class AbstractLinkedMap<K, V> extends AbstractHashedMap<K, V> implements OrderedMap<K, V> {
    public class AbstractHashedMap<K, V> extends AbstractMap<K, V> implements IterableMap<K, V> {

    本质是自定义AbstractMap

    我们看看HashMap LinkedHashMap

    public class LinkedHashMap<K,V>    extends HashMap<K,V>    implements Map<K,V>
    public class HashMap<K,V> extends AbstractMap<K,V>    implements Map<K,V>, Cloneable, Serializable {

    可以看出AbstractMap,AbstractHashedMap,LRUMap的本质其实也是HashMap。

    2.2 数据结构

    protected static final int DEFAULT_MAX_SIZE = 100; public LRUMap() {        this(DEFAULT_MAX_SIZE, DEFAULT_LOAD_FACTOR, false);}

    可以看出默认初始化容量100,最大容量也是100.

    进一步跟踪

    public LRUMap(final int maxSize, final float loadFactor, final boolean scanUntilRemovable) {        this(maxSize, maxSize, loadFactor, scanUntilRemovable);}     public LRUMap(final int maxSize,                  final int initialSize,                  final float loadFactor,                  final boolean scanUntilRemovable) {         super(initialSize, loadFactor);        if (maxSize < 1) {            throw new IllegalArgumentException("LRUMap max size must be greater than 0");        }        if (initialSize > maxSize) {            throw new IllegalArgumentException("LRUMap initial size must not be greather than max size");        }        this.maxSize = maxSize;        this.scanUntilRemovable = scanUntilRemovable;    }

    跟踪super(initialSize, loadFactor);

    public abstract class AbstractLinkedMap<K, V> extends AbstractHashedMap<K, V> implements OrderedMap<K, V> {     protected AbstractLinkedMap(final int initialCapacity, final float loadFactor) {        super(initialCapacity, loadFactor);    }//又super,再上一层追踪 public class AbstractHashedMap<K, V> extends AbstractMap<K, V> implements IterableMap<K, V> {    //定义一些基本初始化数据        protected static final int DEFAULT_CAPACITY = 16;        protected static final int DEFAULT_THRESHOLD = 12;        protected static final float DEFAULT_LOAD_FACTOR = 0.75f;        protected static final int MAXIMUM_CAPACITY = 1 << 30;         transient float loadFactor;        transient int size;        transient HashEntry<K, V>[] data;        transient int threshold;        transient int modCount;        transient EntrySet<K, V> entrySet;        transient KeySet<K> keySet;        transient Values<V> values;     protected AbstractHashedMap(int initialCapacity, final float loadFactor) {        super();        if (initialCapacity < 0) {            throw new IllegalArgumentException("Initial capacity must be a non negative number");        }        if (loadFactor <= 0.0f || Float.isNaN(loadFactor)) {            throw new IllegalArgumentException("Load factor must be greater than 0");        }        this.loadFactor = loadFactor;        initialCapacity = calculateNewCapacity(initialCapacity);        this.threshold = calculateThreshold(initialCapacity, loadFactor);        this.data = new HashEntry[initialCapacity];        init();    }         protected void init() {        //没有任何逻辑,仅用于子类构造    }

    DEFAULT_LOAD_FACTOR = 0.75f; 负载因子0.75

    可以看出LRUMap的本质,HashEntry数组

    上面的init方法没有实现逻辑,但是在他的子类中AbstractLinkedMap有相关的定义。

        transient LinkEntry<K, V> header;         @Override    protected LinkEntry<K, V> createEntry(final HashEntry<K, V> next, final int hashCode, final K key, final V value) {        return new LinkEntry<>(next, hashCode, converTKEy(key), value);    }     protected static class LinkEntry<K, V> extends HashEntry<K, V> {                protected LinkEntry<K, V> before;                protected LinkEntry<K, V> after;                 protected LinkEntry(final HashEntry<K, V> next, final int hashCode, final Object key, final V value) {            super(next, hashCode, key, value);        }    }         @Override    protected void init() {        header = createEntry(null, -1, null, null);        header.before = header.after = header;    }

    这个很关键。可以看出LRUMap是持有LinkEntry header,的双链表结构,初始header为null,前后节点都是自身。将HashEntry转成LinkEntry。

    解析HashEntry

    transient HashEntry<K, V>[] data;//构造初始化this.data = new HashEntry[initialCapacity];

    再跟踪

     protected static class HashEntry<K, V> implements Map.Entry<K, V>, KeyValue<K, V> {                protected HashEntry<K, V> next;                protected int hashCode;                protected Object key;                protected Object value;

    key,value,next单链表。

    public int hashCode() {            return (getKey() == null ? 0 : getKey().hashCode()) ^                   (getValue() == null ? 0 : getValue().hashCode());        }

    hashCode方法可以看出是key的hash与value的hash按位^运算。

    在此我们看透LRU的本质了,数组+单链表。同时是持有头结点的双链表结构(怎么看就是LinkedHashMap的结构,只是有尾节点)。

    public class LinkedHashMap<K,V>    extends HashMap<K,V>    implements Map<K,V>{            transient LinkedHashMap.Entry<K,V> head;         transient LinkedHashMap.Entry<K,V> tail;

    那么LRUMap是如何实现LRU算法的?

    2.3 方法解析put get remove

    2.3.1 get方法
    public V get(final Object key) {        return get(key, true);} public V get(final Object key, final boolean updateToMRU) {        final LinkEntry<K, V> entry = getEntry(key);        if (entry == null) {            return null;        }        if (updateToMRU) {            moveToMRU(entry);        }        return entry.getValue();} //父类方法获取值entryprotected HashEntry<K, V> getEntry(Object key) {        key = convertKey(key);        final int hashCode = hash(key);        HashEntry<K, V> entry = data[hashIndex(hashCode, data.length)]; // no local for hash index        while (entry != null) {            if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) {                return entry;            }            entry = entry.next;        }        return null;}

    下面看不一样的moveToMRU(entry);

        protected void moveToMRU(final LinkEntry<K, V> entry) {        if (entry.after != header) {            modCount++;            // remove            if(entry.before == null) {                throw new IllegalStateException("Entry.before is null." +                    " Please check that your keys are immutable, and that you have used synchronization properly." +                    " If so, then please report this to dev@commons.apache.org as a bug.");            }            entry.before.after = entry.after;            entry.after.before = entry.before;            // add first            entry.after = header;            entry.before = header.before;            header.before.after = entry;            header.before = entry;        } else if (entry == header) {            throw new IllegalStateException("Can't move header to MRU" +                " (please report this to dev@commons.apache.org)");        }    }

    看出LRU的一个本质,每次get方法拨动指针,将get的元素移动到header的前一个位置。

    2.3.2 remove方法

    remove方法使用的父类的方法

        @Override    public V remove(Object key) {        key = convertKey(key);        final int hashCode = hash(key);        final int index = hashIndex(hashCode, data.length);        HashEntry<K, V> entry = data[index];        HashEntry<K, V> previous = null;        while (entry != null) {            if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) {                final V oldValue = entry.getValue();                removeMapping(entry, index, previous);                return oldValue;            }            previous = entry;            entry = entry.next;        }        return null;    }         protected void removeMapping(final HashEntry<K, V> entry, final int hashIndex, final HashEntry<K, V> previous) {        modCount++;        removeEntry(entry, hashIndex, previous);        size--;        destroyEntry(entry);    }     protected void removeEntry(final HashEntry<K, V> entry, final int hashIndex, final HashEntry<K, V> previous) {        if (previous == null) {            data[hashIndex] = entry.next;        } else {            previous.next = entry.next;        }    }     protected void destroyEntry(final HashEntry<K, V> entry) {        entry.next = null;        entry.key = null;        entry.value = null;    }

    这里并没有移除header双链表的数据。

    2.3.3 put方法
        @Override    public V put(final K key, final V value) {        final Object convertedKey = convertKey(key);        final int hashCode = hash(convertedKey);        final int index = hashIndex(hashCode, data.length);        HashEntry<K, V> entry = data[index];        //仅在元素存在才循环,更新updateEntry,header前一个位置        while (entry != null) {            if (entry.hashCode == hashCode && isEqualKey(convertedKey, entry.key)) {                final V oldValue = entry.getValue();                updateEntry(entry, value);                return oldValue;            }            entry = entry.next;        }         addMapping(index, hashCode, key, value);        return null;    }

    updateEntry(entry, value);

        @Override    protected void updateEntry(final HashEntry<K, V> entry, final V newValue) {        moveToMRU((LinkEntry<K, V>) entry);  // handles modCount        entry.setValue(newValue);    }

     moveToMRU((LinkEntry<K, V>) entry);  // handles modCount

    上面get方法有讲,更新了链表的指针,新添加的元素在双链表的header前一个位置,仅在元素存在的时候,while循环才生效。

     那么新增的元素呢?

    下面看重点 addMapping(index, hashCode, key, value); 这句代码定义了,容量满了的处理策略。

        @Override    protected void addMapping(final int hashIndex, final int hashCode, final K key, final V value) {        //容量是否已满        if (isFull()) {            LinkEntry<K, V> reuse = header.after;            boolean removeLRUEntry = false;            //默认是false            if (scanUntilRemovable) {                //这里不知道干啥,难道是以后扩展?                while (reuse != header && reuse != null) {                    //removeLRU一定返回true,很奇怪,估计以后扩展用                    if (removeLRU(reuse)) {                        removeLRUEntry = true;                        break;                    }                    reuse = reuse.after;                }                if (reuse == null) {                    throw new IllegalStateException(                        "Entry.after=null, header.after" + header.after + " header.before" + header.before +                        " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +                        " Please check that your keys are immutable, and that you have used synchronization properly." +                        " If so, then please report this to dev@commons.apache.org as a bug.");                }            } else {                //一定返回true                removeLRUEntry = removeLRU(reuse);            }             if (removeLRUEntry) {                if (reuse == null) {                    throw new IllegalStateException(                        "reuse=null, header.after=" + header.after + " header.before" + header.before +                        " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +                        " Please check that your keys are immutable, and that you have used synchronization properly." +                        " If so, then please report this to dev@commons.apache.org as a bug.");                }                reuseMapping(reuse, hashIndex, hashCode, key, value);            } else {                super.addMapping(hashIndex, hashCode, key, value);            }        } else {            super.addMapping(hashIndex, hashCode, key, value);        }    }     protected boolean removeLRU(final LinkEntry<K, V> entry) {        return true;    }

    先判断容量

    public boolean isFull() {        return size >= maxSize;}

    未满就直接添加

    super.addMapping(hashIndex, hashCode, key, value);

    protected void addMapping(final int hashIndex, final int hashCode, final K key, final V value) {        modCount++;        final HashEntry<K, V> entry = createEntry(data[hashIndex], hashCode, key, value);        addEntry(entry, hashIndex);        size++;        checkCapacity();    }

    //这里调用了AbstractLinkedMap的方法 

    addEntry(entry, hashIndex);

        @Override    protected void addEntry(final HashEntry<K, V> entry, final int hashIndex) {        final LinkEntry<K, V> link = (LinkEntry<K, V>) entry;        link.after  = header;        link.before = header.before;        header.before.after = link;        header.before = link;        data[hashIndex] = link;    }

     放在header的前一个位置,最早的元素链接到header。

    双向环回链表。

     如果容量满了,执行LRU算法 reuseMapping(reuse, hashIndex, hashCode, key, value);

        protected void reuseMapping(final LinkEntry<K, V> entry, final int hashIndex, final int hashCode,                                final K key, final V value) {        // find the entry before the entry specified in the hash table        // remember that the parameters (except the first) refer to the new entry,        // not the old one        try {            //要干掉的元素下标            final int removeIndex = hashIndex(entry.hashCode, data.length);            final HashEntry<K, V>[] tmp = data;  // may protect against some sync issues            HashEntry<K, V> loop = tmp[removeIndex];            HashEntry<K, V> previous = null;            //避免已经被删除            while (loop != entry && loop != null) {                previous = loop;                loop = loop.next;            }            //如果被其他线程删除,抛异常            if (loop == null) {                throw new IllegalStateException(                    "Entry.next=null, data[removeIndex]=" + data[removeIndex] + " previous=" + previous +                    " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +                    " Please check that your keys are immutable, and that you have used synchronization properly." +                    " If so, then please report this to dev@commons.apache.org as a bug.");            }             // reuse the entry            modCount++;            //双链表移除旧元素,AbstractHashedMap移除旧元素            removeEntry(entry, removeIndex, previous);            //复用移除的对象,减少创建对象和GC;增加AbstractHashedMap单链表next指向            reuseEntry(entry, hashIndex, hashCode, key, value);            //复用的元素加AbstractLinkedMap双链表和AbstractHashedMap单链表            addEntry(entry, hashIndex);        } catch (final NullPointerException ex) {            throw new IllegalStateException(                    "NPE, entry=" + entry + " entryIsHeader=" + (entry==header) +                    " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +                    " Please check that your keys are immutable, and that you have used synchronization properly." +                    " If so, then please report this to dev@commons.apache.org as a bug.");        }    }
        @Override    protected void removeEntry(final HashEntry<K, V> entry, final int hashIndex, final HashEntry<K, V> previous) {        final LinkEntry<K, V> link = (LinkEntry<K, V>) entry;        link.before.after = link.after;        link.after.before = link.before;        link.after = null;        link.before = null;        super.removeEntry(entry, hashIndex, previous);    }         protected void removeEntry(final HashEntry<K, V> entry, final int hashIndex, final HashEntry<K, V> previous) {        if (previous == null) {            data[hashIndex] = entry.next;        } else {            previous.next = entry.next;        }    }         protected void reuseEntry(final HashEntry<K, V> entry, final int hashIndex, final int hashCode,                              final K key, final V value) {        entry.next = data[hashIndex];        entry.hashCode = hashCode;        entry.key = key;        entry.value = value;    }         @Override    protected void addEntry(final HashEntry<K, V> entry, final int hashIndex) {        final LinkEntry<K, V> link = (LinkEntry<K, V>) entry;        link.after  = header;        link.before = header.before;        header.before.after = link;        header.before = link;        data[hashIndex] = link;    }

    到此,相信大家对“java LRU算法及Apache LRUMap源码实例分析”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

    --结束END--

    本文标题: java LRU算法及Apache LRUMap源码实例分析

    本文链接: https://www.lsjlt.com/news/300417.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    本篇文章演示代码以及资料文档资料下载

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档
    猜你喜欢
    • java LRU算法及Apache LRUMap源码实例分析
      本篇内容主要讲解“java LRU算法及Apache LRUMap源码实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“java LRU算法及Apache LRUMap源...
      99+
      2023-06-21
    • LRU算法及Apache LRUMap源码实例解析
      目录1. 什么是LRU1.1 自定义实现LRU的要求1.2 Apache LRUMap示例1.2.1 pom依赖1.2.2 demo2. 源码解析2.1 设计2.2 数据结构2.3 ...
      99+
      2022-11-12
    • Java中Redis回收算法LRU的示例分析
      这篇文章给大家分享的是有关Java中Redis回收算法LRU的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。如何通俗易懂的理解LRU算法?1.LRU是什么?LRU全称Least Recently Used...
      99+
      2023-06-20
    • Java Zookeeper分布式分片算法源码分析
      这篇文章主要介绍了Java Zookeeper分布式分片算法源码分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Java Zookeeper分布式分片算法源码分析文章都会有所收获,下面我们...
      99+
      2023-07-05
    • Java经典排序算法源码分析
      本篇内容主要讲解“Java经典排序算法源码分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java经典排序算法源码分析”吧!1.1 快速排序快速排序,一种排序很快的方法,使用分治思想,就是说快...
      99+
      2023-07-05
    • ShardingSphere数据分片算法及测试源码分析
      这篇文章主要介绍“ShardingSphere数据分片算法及测试源码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“ShardingSphere数据分片算法及测试源码分析”文章能帮助大家解决问题。...
      99+
      2023-07-05
    • JAVA 枚举单例模式及源码分析的实例详解
      JAVA 枚举单例模式及源码分析的实例详解      单例模式的实现有很多种,网上也分析了如今实现单利模式最好用枚举,好处不外乎三点:线程安全不会因为序列化而产生新实例防止反射攻击但是貌似没...
      99+
      2023-05-31
      java 枚举 单例
    • 【Mybatis源码解析】mapper实例化及执行流程源码分析
      文章目录 简介 环境搭建 源码解析 附 基础环境:JDK17、SpringBoot3.0、mysql5.7 储备知识:《【Spring6源码・AOP】AOP源码解析》、《JDBC详细...
      99+
      2023-08-20
      mybatis java spring boot
    • java锁机制ReentrantLock源码实例分析
      目录一:简述二:ReentrantLock类图三:流程简图四:源码分析lock()源码分析:非公平实现:公平锁实现:tryAcquire()方法公平锁实现:非公平锁实现:addWai...
      99+
      2022-11-13
    • 如何用源码分析Java HashMap实例
      本篇文章为大家展示了如何用源码分析Java HashMap实例,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。引言HashMap在键值对存储中被经常使用,那么它到底是如何实现键值存储的呢?一 Entr...
      99+
      2023-06-17
    • Java Base64编码方法详解及实例分析
      Base64.encodeBase64String方法是一个Java语言中用于Base64编码的方法。它可以将二进制数据转换为Base64编码的字符串形式。在本文中,我们将详细解释这个方法的使用,提供完整的实例和代码,并分析运行结果。最后,...
      99+
      2023-09-04
      java 开发语言
    • Java贪心算法实例分析
      这篇“Java贪心算法实例分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Java贪心算法实例分析”文章吧。贪心算法贪心算...
      99+
      2023-06-29
    • PHP算法题实例代码分析
      本篇内容主要讲解“PHP算法题实例代码分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“PHP算法题实例代码分析”吧!题目给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 ...
      99+
      2023-07-05
    • 如何进行Netlink源码及实例的分析
      本篇文章给大家分享的是有关如何进行Netlink源码及实例的分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。前言这几天在看 ipvs 相关代码的时候又遇到了 netlink ...
      99+
      2023-06-15
    • Python密码学XOR算法编码流程及乘法密码实例分析
      本文小编为大家详细介绍“Python密码学XOR算法编码流程及乘法密码实例分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“Python密码学XOR算法编码流程及乘法密码实例分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来...
      99+
      2023-06-30
    • Java的位运算实例代码分析
      今天小编给大家分享一下Java的位运算实例代码分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。位运算很久以前学习过位运算,...
      99+
      2023-07-06
    • Vue3源码分析reactivity实现方法示例
      目录深入分析对于map、set、weakMap、weakSet的响应式拦截(1).mutableInstrumentations(2).shallowInstrumentations...
      99+
      2023-01-28
      Vue3源码分析reactivit方法 Vue reactivit
    • Java分治法与二分搜索算法实例分析
      本文实例讲述了Java分治法与二分搜索算法。分享给大家供大家参考,具体如下:1、分治法分治法的基本思想是将一个规模为n的问题分解为k个规模较小的子问题,这些子问题相互独立且与原问题相同。递归的解这些子问题,然后将各子问题的解合并得到原问题的...
      99+
      2023-05-30
      java 分治法 二分搜索
    • Java的Arrays.sort()方法排序算法实例分析
        暂时网上看过很多JDK8中Arrays.sort的底层原理,有些说是插入排序,有些说是归并排序,也有说大于域值用计数排序法,否则就使用插入排序。。。其实不全对。让我们分析个究竟:...
      99+
      2022-11-13
    • JavaScript二叉树及遍历算法实例分析
      这篇“JavaScript二叉树及遍历算法实例分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看...
      99+
      2022-10-19
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作