IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 源码浅析ConcurrentReferenceHashMap -> 正文阅读

[移动开发]源码浅析ConcurrentReferenceHashMap

对ConcurrentReferenceHashMap进行代码分析并对主要部分加注释


package com.zzd.boot.autoconfigure.util;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReentrantLock;

public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {

    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    private static final float DEFAULT_LOAD_FACTOR = 0.75F;
    private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
    private static final ReferenceType DEFAULT_REFERENCE_TYPE;
    private static final int MAXIMUM_CONCURRENCY_LEVEL = 65536;
    private static final int MAXIMUM_SEGMENT_SIZE = 1073741824;
    private final Segment[] segments;
    private final float loadFactor;
    private final ConcurrentReferenceHashMap.ReferenceType referenceType;
    private final int shift;
    private Set<Map.Entry<K, V>> entrySet;

    public ConcurrentReferenceHashMap() {
        this(16, 0.75F, 16, DEFAULT_REFERENCE_TYPE);
    }

    public ConcurrentReferenceHashMap(int initialCapacity) {
        this(initialCapacity, 0.75F, 16, DEFAULT_REFERENCE_TYPE);
    }

    public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor) {
        this(initialCapacity, loadFactor, 16, DEFAULT_REFERENCE_TYPE);
    }

    public ConcurrentReferenceHashMap(int initialCapacity, int concurrencyLevel) {
        this(initialCapacity, 0.75F, concurrencyLevel, DEFAULT_REFERENCE_TYPE);
    }

    public ConcurrentReferenceHashMap(int initialCapacity, ReferenceType referenceType) {
        this(initialCapacity, 0.75F, 16, referenceType);
    }

    public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
        this(initialCapacity, loadFactor, concurrencyLevel, DEFAULT_REFERENCE_TYPE);
    }

    public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel, ReferenceType referenceType) {
        Assert.isTrue(initialCapacity >= 0, "Initial capacity must not be negative");
        Assert.isTrue(loadFactor > 0.0F, "Load factor must be positive");
        Assert.isTrue(concurrencyLevel > 0, "Concurrency level must be positive");
        Assert.notNull(referenceType, "Reference type must not be null");
        this.loadFactor = loadFactor;
        //计算最小值需要位移的次数
        this.shift = calculateShift(concurrencyLevel, 65536);
        //最小需要位移的位数再向右位移一位即*2
        int size = 1 << this.shift;
        this.referenceType = referenceType;

        int roundedUpSegmentCapacity = (int)(((long)(initialCapacity + size) - 1L) / (long)size);
        //创建数组,数组的个数为需要位移的次数,最大值为65536需要右移的次数为16
        this.segments = (Segment[])((Segment[]) Array.newInstance(Segment.class, size));

        for(int i = 0; i < this.segments.length; ++i) {
            //初始化数组,roundedUpSegmentCapacity这个为每个数组初始的容量
            this.segments[i] = new Segment(roundedUpSegmentCapacity);
        }

    }

    protected final float getLoadFactor() {
        return this.loadFactor;
    }

    //返回分段数组的长度
    protected final int getSegmentsSize() {
        return this.segments.length;
    }

    //获取分段数组的第index段数据
    protected final ConcurrentReferenceHashMap<K, V>.Segment getSegment(int index) {
        return this.segments[index];
    }

    //创建ReferenceManager对象
    protected ReferenceManager createReferenceManager() {
        return new ReferenceManager();
    }

    protected int getHash(Object o) {
        //使用native方法获取hash值
        int hash = o == null ? 0 : o.hashCode();
        //暂不知道为什么需要去对hash值进行处理
        hash += hash << 15 ^ -12931;
        hash ^= hash >>> 10;
        hash += hash << 3;
        hash ^= hash >>> 6;
        hash += (hash << 2) + (hash << 14);
        hash ^= hash >>> 16;
        return hash;
    }

    @Override
    public V get(Object key) {
        Reference<K, V> reference = this.getReference(key, Restructure.WHEN_NECESSARY);
        Entry<K, V> entry = reference != null ? reference.get() : null;
        return entry != null ? entry.getValue() : null;
    }

    @Override
    public boolean containsKey(Object key) {
        Reference<K, V> reference = this.getReference(key, Restructure.WHEN_NECESSARY);
        Entry<K, V> entry = reference != null ? reference.get() : null;
        return entry != null && ObjectUtils.nullSafeEquals(entry.getKey(), key);
    }

    //通过key获取对应的value(1、获取hash值,2、获取此hash在第几个Segment,3、获取对应的Reference,4、遍历Reference获取key对应的value值)
    protected final Reference<K, V> getReference(Object key, Restructure restructure) {
        int hash = this.getHash(key);
        return this.getSegmentForHash(hash) //返回第k个Segment(段)
                .getReference(key, hash, restructure); //获取对应的值
    }

    @Override
    public V put(K key, V value) {
        return this.put(key, value, true);
    }

    @Override
    public V putIfAbsent(K key, V value) {
        return this.put(key, value, false);
    }

    //每次put值的时候都新建线程来实现
    private V put(K key, final V value, final boolean overwriteExisting) {
        return this.doTask(key, new Task<V>(new TaskOption[]{TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE}) {
            @Override
            protected V execute(Reference<K, V> reference, Entry<K, V> entry, Entries entries) {
                if (entry != null) {
                    V previousValue = entry.getValue();
                    if (overwriteExisting) {
                        entry.setValue(value);
                    }

                    return previousValue;
                } else {
                    entries.add(value);
                    return null;
                }
            }
        });
    }

    /*
        创建新线程--删除key对应的值并返回其value
     */
    @Override
    public V remove(Object key) {
        return this.doTask(key, new Task<V>(new TaskOption[]{TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY}) {
            @Override
            protected V execute(Reference<K, V> reference, Entry<K, V> entry) {
                if (entry != null) {
                    reference.release();
                    return entry.value;
                } else {
                    return null;
                }
            }
        });
    }

    /*
        创建新线程 -- 执行删除 根据key和value值进行删除,当key的value值与输入的value值一致时删除并返回true否则返回false
     */
    @Override
    public boolean remove(Object key, final Object value) {
        return (Boolean)this.doTask(key, new Task<Boolean>(new TaskOption[]{TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY}) {
            @Override
            protected Boolean execute(Reference<K, V> reference, Entry<K, V> entry) {
                if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), value)) {
                    reference.release();
                    return true;
                } else {
                    return false;
                }
            }
        });
    }

    /*
        根据key和value将结果替换为新值,并返回替换成功失败的结果
     */
    @Override
    public boolean replace(K key, final V oldValue, final V newValue) {
        return (Boolean)this.doTask(key, new Task<Boolean>(new TaskOption[]{TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY}) {
            @Override
            protected Boolean execute(Reference<K, V> reference, Entry<K, V> entry) {
                if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), oldValue)) {
                    entry.setValue(newValue);
                    return true;
                } else {
                    return false;
                }
            }
        });
    }

    /*
        根据key替换value值,并返回旧值(被替换的值)
     */
    @Override
    public V replace(K key, final V value) {
        return this.doTask(key, new ConcurrentReferenceHashMap<K, V>.Task<V>(new TaskOption[]{TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY}) {
            @Override
            protected V execute(Reference<K, V> reference, Entry<K, V> entry) {
                if (entry != null) {
                    V previousValue = entry.getValue();
                    entry.setValue(value);
                    return previousValue;
                } else {
                    return null;
                }
            }
        });
    }

    /*
        遍历每一个segment并对每一个segment进行clear处理
     */
    @Override
    public void clear() {
        Segment[] var1 = this.segments;
        int var2 = var1.length;

        for(int var3 = 0; var3 < var2; ++var3) {
            Segment segment = var1[var3];
            segment.clear();
        }

    }

    public void purgeUnreferencedEntries() {
        Segment[] var1 = this.segments;
        int var2 = var1.length;

        for(int var3 = 0; var3 < var2; ++var3) {
            ConcurrentReferenceHashMap<K, V>.Segment segment = var1[var3];
            segment.restructureIfNecessary(false);
        }

    }

    @Override
    public int size() {
        int size = 0;
        Segment[] var2 = this.segments;
        int var3 = var2.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            ConcurrentReferenceHashMap<K, V>.Segment segment = var2[var4];
            size += segment.getCount();
        }

        return size;
    }

    @Override
    public Set<java.util.Map.Entry<K, V>> entrySet() {
        if (this.entrySet == null) {
            this.entrySet = new EntrySet();
        }

        return this.entrySet;
    }

    //TODO:看到了此处
    private <T> T doTask(Object key, Task<T> task) {
        int hash = this.getHash(key);
        return this.getSegmentForHash(hash) //获取此hash值在的Segment的id
                .doTask(hash, key, task);   //根据传入的task对象执行相应的增加和删除操作
    }

    //获取此hash值在的Segment的id
    private Segment getSegmentForHash(int hash) {
        return this.segments[hash >>> 32 - this.shift & this.segments.length - 1];
    }

    //计算最小值需要位移的次数 minimumValue初始值为16
    protected static int calculateShift(int minimumValue, int maximumValue) {
        int shift = 0;

        for(int value = 1; value < minimumValue && value < maximumValue; ++shift) {
            value <<= 1;
        }

        return shift;
    }

    static {
        DEFAULT_REFERENCE_TYPE = ReferenceType.SOFT;
    }

    private static final class WeakEntryReference<K, V> extends WeakReference<Entry<K, V>> implements Reference<K, V> {
        private final int hash;
        private final Reference<K, V> nextReference;

        public WeakEntryReference(Entry<K, V> entry, int hash, Reference<K, V> next, ReferenceQueue<Entry<K, V>> queue) {
            super(entry, queue);
            this.hash = hash;
            this.nextReference = next;
        }

        @Override
        public int getHash() {
            return this.hash;
        }

        @Override
        public Reference<K, V> getNext() {
            return this.nextReference;
        }

        @Override
        public void release() {
            this.enqueue();
            this.clear();
        }
    }

    private static final class SoftEntryReference<K, V> extends SoftReference<Entry<K, V>> implements Reference<K, V> {
        private final int hash;
        private final Reference<K, V> nextReference;

        public SoftEntryReference(Entry<K, V> entry, int hash, Reference<K, V> next, ReferenceQueue<Entry<K, V>> queue) {
            super(entry, queue);
            this.hash = hash;
            this.nextReference = next;
        }

        @Override
        public int getHash() {
            return this.hash;
        }

        @Override
        public Reference<K, V> getNext() {
            return this.nextReference;
        }

        @Override
        public void release() {
            this.enqueue();
            this.clear();
        }
    }

    protected class ReferenceManager {
        private final ReferenceQueue<Entry<K, V>> queue = new ReferenceQueue();

        protected ReferenceManager() {
        }

        public Reference<K, V> createReference(Entry<K, V> entry, int hash, Reference<K, V> next) {
            return (Reference)(ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK ? new WeakEntryReference(entry, hash, next, this.queue) : new SoftEntryReference(entry, hash, next, this.queue));
        }

        public Reference<K, V> pollForPurge() {
            return (Reference)this.queue.poll(); //移除并返回第一个值
        }
    }

    protected static enum Restructure {
        WHEN_NECESSARY,
        NEVER;

        private Restructure() {
        }
    }

    private class EntryIterator implements Iterator<Map.Entry<K, V>> {
        private int segmentIndex;
        private int referenceIndex;
        private Reference<K, V>[] references;
        private Reference<K, V> reference;
        private Entry<K, V> next;
        private Entry<K, V> last;

        public EntryIterator() {
            this.moveToNextSegment();
        }

        @Override
        public boolean hasNext() {
            this.getNextIfNecessary();
            return this.next != null;
        }

        @Override
        public Entry<K, V> next() {
            this.getNextIfNecessary();
            if (this.next == null) {
                throw new NoSuchElementException();
            } else {
                this.last = this.next;
                this.next = null;
                return this.last;
            }
        }

        private void getNextIfNecessary() {
            while(this.next == null) {
                this.moveToNextReference();
                if (this.reference == null) {
                    return;
                }

                this.next = this.reference.get();
            }

        }

        private void moveToNextReference() {
            if (this.reference != null) {
                this.reference = this.reference.getNext();
            }

            while(this.reference == null && this.references != null) {
                if (this.referenceIndex >= this.references.length) {
                    this.moveToNextSegment();
                    this.referenceIndex = 0;
                } else {
                    this.reference = this.references[this.referenceIndex];
                    ++this.referenceIndex;
                }
            }

        }

        private void moveToNextSegment() {
            this.reference = null;
            this.references = null;
            if (this.segmentIndex < ConcurrentReferenceHashMap.this.segments.length) {
                this.references = ConcurrentReferenceHashMap.this.segments[this.segmentIndex].references;
                ++this.segmentIndex;
            }

        }

        @Override
        public void remove() {
            Assert.state(this.last != null, "No element to remove");
            ConcurrentReferenceHashMap.this.remove(this.last.getKey());
        }
    }

    private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
        private EntrySet() {
        }

        @Override
        public Iterator<java.util.Map.Entry<K, V>> iterator() {
            return ConcurrentReferenceHashMap.this.new EntryIterator();
        }

        @Override
        public boolean contains(Object o) {
            if (o != null && o instanceof java.util.Map.Entry) {
                java.util.Map.Entry<?, ?> entry = (java.util.Map.Entry)o;
                Reference<K, V> reference = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER);
                Entry<K, V> other = reference != null ? reference.get() : null;
                if (other != null) {
                    return ObjectUtils.nullSafeEquals(entry.getValue(), other.getValue());
                }
            }

            return false;
        }

        @Override
        public boolean remove(Object o) {
            if (o instanceof java.util.Map.Entry) {
                java.util.Map.Entry<?, ?> entry = (java.util.Map.Entry)o;
                return ConcurrentReferenceHashMap.this.remove(entry.getKey(), entry.getValue());
            } else {
                return false;
            }
        }

        @Override
        public int size() {
            return ConcurrentReferenceHashMap.this.size();
        }

        @Override
        public void clear() {
            ConcurrentReferenceHashMap.this.clear();
        }
    }

    private abstract class Entries {
        private Entries() {
        }

        public abstract void add(V var1);
    }

    private static enum TaskOption {
        RESTRUCTURE_BEFORE,
        RESTRUCTURE_AFTER,
        SKIP_IF_EMPTY,
        RESIZE;

        private TaskOption() {
        }
    }

    private abstract class Task<T> {
        private final EnumSet<TaskOption> options;

        public Task(TaskOption... options) {
            this.options = options.length == 0 ? EnumSet.noneOf(TaskOption.class) : EnumSet.of(options[0], options);
        }

        public boolean hasOption(TaskOption option) {
            return this.options.contains(option);
        }

        protected T execute(Reference<K, V> reference, Entry<K, V> entry, Entries entries) {
            return this.execute(reference, entry);
        }

        protected T execute(Reference<K, V> reference, Entry<K, V> entry) {
            return null;
        }
    }

    protected static final class Entry<K, V> implements java.util.Map.Entry<K, V> {
        private final K key;
        private volatile V value;

        public Entry(K key, V value) {
            this.key = key;
            this.value = value;
        }

        @Override
        public K getKey() {
            return this.key;
        }

        @Override
        public V getValue() {
            return this.value;
        }

        @Override
        public V setValue(V value) {
            V previous = this.value;
            this.value = value;
            return previous;
        }

        @Override
        public String toString() {
            return this.key + "=" + this.value;
        }

        @Override
        public final boolean equals(Object other) {
            if (this == other) {
                return true;
            } else if (!(other instanceof java.util.Map.Entry)) {
                return false;
            } else {
                java.util.Map.Entry otherEntry = (java.util.Map.Entry)other;
                return ObjectUtils.nullSafeEquals(this.getKey(), otherEntry.getKey()) && ObjectUtils.nullSafeEquals(this.getValue(), otherEntry.getValue());
            }
        }

        @Override
        public final int hashCode() {
            return ObjectUtils.nullSafeHashCode(this.key) ^ ObjectUtils.nullSafeHashCode(this.value);
        }
    }

    protected interface Reference<K, V> {
        Entry<K, V> get();

        int getHash();

        Reference<K, V> getNext();

        void release();
    }

    protected final class Segment extends ReentrantLock {
        private final ReferenceManager referenceManager = ConcurrentReferenceHashMap.this.createReferenceManager();
        private final int initialSize;
        private volatile Reference<K, V>[] references;
        private volatile int count = 0;
        private int resizeThreshold;

        public Segment(int initialCapacity) {
            this.initialSize = 1 << calculateShift(initialCapacity, 1073741824);
            this.setReferences(this.createReferenceArray(this.initialSize));
        }

        //获取对应key的value值(通过hash获取key在references中的位置)
        public Reference<K, V> getReference(Object key, int hash, Restructure restructure) {
            if (restructure == Restructure.WHEN_NECESSARY) {
                this.restructureIfNecessary(false);
            }

            if (this.count == 0) {
                return null;
            } else {
                Reference<K, V>[] references = this.references;
                int index = this.getIndex(hash, references);
                Reference<K, V> head = references[index];
                return this.findInChain(head, key, hash);
            }
        }

        public <T> T doTask(final int hash, final Object key, Task<T> task) {
            boolean resize = task.hasOption(TaskOption.RESIZE);
            if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) {
                //对当前的Segment进行扩容处理
                this.restructureIfNecessary(resize);
            }

            if (task.hasOption(TaskOption.SKIP_IF_EMPTY) && this.count == 0) {
                return (T) task.execute((Reference)null, (Entry)null, (Entries)null); //返回null
            } else {
                //锁住当前Segment对象
                this.lock();

                Object var10;
                try {
                    final int index = this.getIndex(hash, this.references); //获取key对应的index值
                    final Reference<K, V> head = this.references[index];   //获取Reference对象
                    Reference<K, V> reference = this.findInChain(head, key, hash); //同一个hash可能会保存多个值,此时就需要遍历所有的值,直到key与其值一致
                    Entry<K, V> entry = reference != null ? reference.get() : null;
                    Entries entries = new Entries() {
                        @Override
                        public void add(V value) {
                            Entry<K, V> newEntry = new Entry(key, value);
                            Reference<K, V> newReference = Segment.this.referenceManager.createReference(newEntry, hash, head); //创建一个Reference对象
                            Segment.this.references[index] = newReference; //将结果直接赋值到对应数组的index上
                            Segment.this.count++; //数据量加一
                        }
                    };
                    var10 = task.execute(reference, entry, entries); //返回执行后的结果
                } finally {
                    this.unlock(); //解锁
                    if (task.hasOption(TaskOption.RESTRUCTURE_AFTER)) {
                        //在加锁的过程中count值有变化,需要对是否需要重新扩容处理
                        this.restructureIfNecessary(resize);
                    }

                }

                return (T) var10;
            }
        }

        public void clear() {
            if (this.count != 0) {
                this.lock();

                try {
                    this.setReferences(this.createReferenceArray(this.initialSize));
                    this.count = 0;
                } finally {
                    this.unlock();
                }

            }
        }

        //判断是否需要扩容
        protected final void restructureIfNecessary(boolean allowResize) {
            boolean needsResize = this.count > 0 && this.count >= this.resizeThreshold;
            Reference<K, V> reference = this.referenceManager.pollForPurge();  //移除并返回第一个值
            if (reference != null || needsResize && allowResize) {
                //加锁,进行扩容
                this.lock();

                try {
                    int countAfterRestructure = this.count;
                    Set<Reference<K, V>> toPurge = Collections.emptySet(); //新建空Set
                    if (reference != null) {
                        for(toPurge = new HashSet(); reference != null; reference = this.referenceManager.pollForPurge()) {
                            ((Set)toPurge).add(reference);   //将所有的this.referenceManager.pollForPurge()的reference放入新建的Set中
                        }
                    }

                    countAfterRestructure -= ((Set)toPurge).size();
                    needsResize = countAfterRestructure > 0 && countAfterRestructure >= this.resizeThreshold; //计算是否需要扩容
                    boolean resizing = false;
                    int restructureSize = this.references.length;
                    if (allowResize && needsResize && restructureSize < 1073741824) { //1073741824 为int的最大值
                        restructureSize <<= 1;
                        resizing = true;
                    }

                    Reference<K, V>[] restructured = resizing ? this.createReferenceArray(restructureSize) : this.references;

                    for(int i = 0; i < this.references.length; ++i) {
                        reference = this.references[i];
                        if (!resizing) {
                            restructured[i] = null;
                        }

                        for(; reference != null; reference = reference.getNext()) {
                            if (!((Set)toPurge).contains(reference) && reference.get() != null) {
                                int index = this.getIndex(reference.getHash(), restructured);
                                restructured[index] = this.referenceManager.createReference(reference.get(), reference.getHash(), restructured[index]);//根据值新建一个Reference<K, V>对象,并放到对应的数组中
                            }
                        }
                    }

                    if (resizing) {
                        this.setReferences(restructured);
                    }
                    //把扩容后的容量赋值到count中
                    this.count = Math.max(countAfterRestructure, 0);
                } finally {
                    //解锁
                    this.unlock();
                }
            }

        }

        //同一个hash可能会保存多个值,此时就需要遍历所有的值,直到key与其值一致
        private Reference<K, V> findInChain(Reference<K, V> reference, Object key, int hash) {
            for(; reference != null; reference = reference.getNext()) {
                if (reference.getHash() == hash) {
                    Entry<K, V> entry = reference.get();
                    if (entry != null) {
                        K entryKey = entry.getKey();
                        if (entryKey == key || entryKey.equals(key)) {
                            return reference;
                        }
                    }
                }
            }

            return null;
        }

        //根据传入的size创建数组
        private Reference<K, V>[] createReferenceArray(int size) {
            return (Reference[])((Reference[])Array.newInstance(Reference.class, size));
        }

        //获取当前此hash在对应的那个位置Segment
        private int getIndex(int hash, Reference<K, V>[] references) {
            return hash & references.length - 1;
        }

        //把数组放到Segment中,并设置此Segment需要重新扩容或缩容的值
        private void setReferences(Reference<K, V>[] references) {
            this.references = references;
            this.resizeThreshold = (int)((float)references.length * ConcurrentReferenceHashMap.this.getLoadFactor());
        }

        public final int getSize() {
            return this.references.length;
        }

        public final int getCount() {
            return this.count;
        }
    }

    public static enum ReferenceType {
        SOFT,
        WEAK;

        private ReferenceType() {
        }
    }
}

欢迎评论,交流

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-04-04 12:22:54  更:2022-04-04 12:25:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 20:54:10-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码