文档章节

C#创建安全的字典(Dictionary)存储结构

彭泽0902
 彭泽0902
发布于 2016/11/24 18:47
字数 1874
阅读 33
收藏 0
点赞 0
评论 0

    在上面介绍过栈(Stack)的存储结构,接下来介绍另一种存储结构字典(Dictionary)。 字典(Dictionary)里面的每一个元素都是一个键值对(由二个元素组成:键和值) 键必须是唯一的,而值不需要唯一的,键和值都可以是任何类型。字典(Dictionary)是常用于查找和排序的列表。

  接下来看一下Dictionary的部分方法和类的底层实现代码:

  1.Add:将指定的键和值添加到字典中。

public void Add(TKey key, TValue value) {
            Insert(key, value, true); 
        }
private void Insert(TKey key, TValue value, bool add) {
 
            if( key == null ) { 
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
            } 

            if (buckets == null) Initialize(0);
            int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
            int targetBucket = hashCode % buckets.Length; 

#if FEATURE_RANDOMIZED_STRING_HASHING 
            int collisionCount = 0; 
#endif
 
            for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next) {
                if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) {
                    if (add) {
                        ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate); 
                    }
                    entries[i].value = value; 
                    version++; 
                    return;
                } 

#if FEATURE_RANDOMIZED_STRING_HASHING
                collisionCount++;
#endif 
            }
            int index; 
            if (freeCount > 0) { 
                index = freeList;
                freeList = entries[index].next; 
                freeCount--;
            }
            else {
                if (count == entries.Length) 
                {
                    Resize(); 
                    targetBucket = hashCode % buckets.Length; 
                }
                index = count; 
                count++;
            }

            entries[index].hashCode = hashCode; 
            entries[index].next = buckets[targetBucket];
            entries[index].key = key; 
            entries[index].value = value; 
            buckets[targetBucket] = index;
            version++; 

#if FEATURE_RANDOMIZED_STRING_HASHING
            if(collisionCount > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(comparer))
            { 
                comparer = (IEqualityComparer<TKey>) HashHelpers.GetRandomizedEqualityComparer(comparer);
                Resize(entries.Length, true); 
            } 
#endif
 
        }

   2.Clear():从 Dictionary<TKey, TValue> 中移除所有的键和值。

public void Clear() {
            if (count > 0) {
                for (int i = 0; i < buckets.Length; i++) buckets[i] = -1;
                Array.Clear(entries, 0, count); 
                freeList = -1;
                count = 0; 
                freeCount = 0; 
                version++;
            } 
        }

  3.Remove():从 Dictionary<TKey, TValue> 中移除所指定的键的值。

public bool Remove(TKey key) {
            if(key == null) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
            } 

            if (buckets != null) { 
                int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; 
                int bucket = hashCode % buckets.Length;
                int last = -1; 
                for (int i = buckets[bucket]; i >= 0; last = i, i = entries[i].next) {
                    if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) {
                        if (last < 0) {
                            buckets[bucket] = entries[i].next; 
                        }
                        else { 
                            entries[last].next = entries[i].next; 
                        }
                        entries[i].hashCode = -1; 
                        entries[i].next = freeList;
                        entries[i].key = default(TKey);
                        entries[i].value = default(TValue);
                        freeList = i; 
                        freeCount++;
                        version++; 
                        return true; 
                    }
                } 
            }
            return false;
        }

  4.GetEnumerator():返回循环访问 Dictionary<TKey, TValue> 的枚举器。

public Enumerator GetEnumerator() {
            return new Enumerator(this, Enumerator.KeyValuePair); 
        }
 [Serializable] 
        public struct Enumerator: IEnumerator<KeyValuePair<TKey,TValue>>,
            IDictionaryEnumerator 
        { 
            private Dictionary<TKey,TValue> dictionary;
            private int version; 
            private int index;
            private KeyValuePair<TKey,TValue> current;
            private int getEnumeratorRetType;  // What should Enumerator.Current return?
 
            internal const int DictEntry = 1;
            internal const int KeyValuePair = 2; 
 
            internal Enumerator(Dictionary<TKey,TValue> dictionary, int getEnumeratorRetType) {
                this.dictionary = dictionary; 
                version = dictionary.version;
                index = 0;
                this.getEnumeratorRetType = getEnumeratorRetType;
                current = new KeyValuePair<TKey, TValue>(); 
            }
 
            public bool MoveNext() { 
                if (version != dictionary.version) {
                    ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); 
                }

                // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
                // dictionary.count+1 could be negative if dictionary.count is Int32.MaxValue 
                while ((uint)index < (uint)dictionary.count) {
                    if (dictionary.entries[index].hashCode >= 0) { 
                        current = new KeyValuePair<TKey, TValue>(dictionary.entries[index].key, dictionary.entries[index].value); 
                        index++;
                        return true; 
                    }
                    index++;
                }
 
                index = dictionary.count + 1;
                current = new KeyValuePair<TKey, TValue>(); 
                return false; 
            }
 
            public KeyValuePair<TKey,TValue> Current {
                get { return current; }
            }
 
            public void Dispose() {
            } 
 
            object IEnumerator.Current {
                get { 
                    if( index == 0 || (index == dictionary.count + 1)) {
                        ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
                    }
 
                    if (getEnumeratorRetType == DictEntry) {
                        return new System.Collections.DictionaryEntry(current.Key, current.Value); 
                    } else { 
                        return new KeyValuePair<TKey, TValue>(current.Key, current.Value);
                    } 
                }
            }

            void IEnumerator.Reset() { 
                if (version != dictionary.version) {
                    ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); 
                } 

                index = 0; 
                current = new KeyValuePair<TKey, TValue>();
            }

            DictionaryEntry IDictionaryEnumerator.Entry { 
                get {
                    if( index == 0 || (index == dictionary.count + 1)) { 
                         ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen); 
                    }
 
                    return new DictionaryEntry(current.Key, current.Value);
                }
            }
 
            object IDictionaryEnumerator.Key {
                get { 
                    if( index == 0 || (index == dictionary.count + 1)) { 
                         ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
                    } 

                    return current.Key;
                }
            } 

            object IDictionaryEnumerator.Value { 
                get { 
                    if( index == 0 || (index == dictionary.count + 1)) {
                         ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen); 
                    }

                    return current.Value;
                } 
            }
        } 

   上面主要是对字典(Dictionary)的一些常用方法进行一个简单的说明。接下来主要阐述如何创建安全的字典(Dictionary)存储结构。有关线程安全的部分,在这里就不再赘述了。 

/// <summary>
    /// 线程安全通用字典
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TValue"></typeparam>
    public class TDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    {
        /// <summary>
        /// 锁定字典
        /// </summary>
        private readonly ReaderWriterLockSlim _lockDictionary = new ReaderWriterLockSlim();

        /// <summary>
        ///基本字典
        /// </summary>
        private readonly Dictionary<TKey, TValue> _mDictionary;

        // Variables
        /// <summary>
        /// 初始化字典对象
        /// </summary>
        public TDictionary()
        {
            _mDictionary = new Dictionary<TKey, TValue>();
        }

        /// <summary>
        /// 初始化字典对象
        /// </summary>
        /// <param name="capacity">字典的初始容量</param>
        public TDictionary(int capacity)
        {
            _mDictionary = new Dictionary<TKey, TValue>(capacity);
        }

        /// <summary>
        ///初始化字典对象
        /// </summary>
        /// <param name="comparer">比较器在比较键时使用</param>
        public TDictionary(IEqualityComparer<TKey> comparer)
        {
            _mDictionary = new Dictionary<TKey, TValue>(comparer);
        }

        /// <summary>
        /// 初始化字典对象
        /// </summary>
        /// <param name="dictionary">其键和值被复制到此对象的字典</param>
        public TDictionary(IDictionary<TKey, TValue> dictionary)
        {
            _mDictionary = new Dictionary<TKey, TValue>(dictionary);
        }

        /// <summary>
        ///初始化字典对象
        /// </summary>
        /// <param name="capacity">字典的初始容量</param>
        /// <param name="comparer">比较器在比较键时使用</param>
        public TDictionary(int capacity, IEqualityComparer<TKey> comparer)
        {
            _mDictionary = new Dictionary<TKey, TValue>(capacity, comparer);
        }

        /// <summary>
        /// 初始化字典对象
        /// </summary>
        /// <param name="dictionary">其键和值被复制到此对象的字典</param>
        /// <param name="comparer">比较器在比较键时使用</param>
        public TDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
        {
            _mDictionary = new Dictionary<TKey, TValue>(dictionary, comparer);
        }



        public TValue GetValueAddIfNotExist(TKey key, Func<TValue> func)
        {
            return _lockDictionary.PerformUsingUpgradeableReadLock(() =>
            {
                TValue rVal;

                // 如果我们有值,得到它并退出
                if (_mDictionary.TryGetValue(key, out rVal))
                    return rVal;

                // 没有找到,所以做函数得到的值
                _lockDictionary.PerformUsingWriteLock(() =>
                {
                    rVal = func.Invoke();

                    // 添加到字典
                    _mDictionary.Add(key, rVal);

                    return rVal;
                });

                return rVal;
            });
        }


        /// <summary>
        /// 将项目添加到字典
        /// </summary>
        /// <param name="key">添加的关键</param>
        /// <param name="value">要添加的值</param>
        public void Add(TKey key, TValue value)
        {
            _lockDictionary.PerformUsingWriteLock(() => _mDictionary.Add(key, value));
        }

        /// <summary>
        ///将项目添加到字典
        /// </summary>
        /// <param name="item">要添加的键/值</param>
        public void Add(KeyValuePair<TKey, TValue> item)
        {
            var key = item.Key;
            var value = item.Value;
            _lockDictionary.PerformUsingWriteLock(() => _mDictionary.Add(key, value));
        }

        /// <summary>
        /// 如果值不存在,则添加该值。 返回如果值已添加,则为true
        /// </summary>
        /// <param name="key">检查的关键,添加</param>
        /// <param name="value">如果键不存在,则添加的值</param>
        public bool AddIfNotExists(TKey key, TValue value)
        {
            bool rVal = false;

            _lockDictionary.PerformUsingWriteLock(() =>
            {
                // 如果不存在,则添加它
                if (!_mDictionary.ContainsKey(key))
                {
                    // 添加该值并设置标志
                    _mDictionary.Add(key, value);
                    rVal = true;
                }
            });

            return rVal;
        }

        /// <summary>
        /// 如果键不存在,则添加值列表。
        /// </summary>
        /// <param name="keys">要检查的键,添加</param>
        /// <param name="defaultValue">如果键不存在,则添加的值</param>
        public void AddIfNotExists(IEnumerable<TKey> keys, TValue defaultValue)
        {
            _lockDictionary.PerformUsingWriteLock(() =>
            {
                foreach (TKey key in keys)
                {
                    // 如果不存在,则添加它
                    if (!_mDictionary.ContainsKey(key))
                        _mDictionary.Add(key, defaultValue);
                }
            });
        }


        public bool AddIfNotExistsElseUpdate(TKey key, TValue value)
        {
            var rVal = false;

            _lockDictionary.PerformUsingWriteLock(() =>
            {
                // 如果不存在,则添加它
                if (!_mDictionary.ContainsKey(key))
                {
                    // 添加该值并设置标志
                    _mDictionary.Add(key, value);
                    rVal = true;
                }
                else
                    _mDictionary[key] = value;
            });

            return rVal;
        }

        /// <summary>
        /// 如果键存在,则更新键的值。
        /// </summary>
        /// <param name="key"></param>
        /// <param name="newValue"></param>
        public bool UpdateValueIfKeyExists(TKey key, TValue newValue)
        {
            bool rVal = false;

            _lockDictionary.PerformUsingWriteLock(() =>
            {
                // 如果我们有密钥,然后更新它
                if (!_mDictionary.ContainsKey(key)) return;
                _mDictionary[key] = newValue;
                rVal = true;
            });

            return rVal;
        }

        /// <summary>
        /// 如果键值对存在于字典中,则返回true
        /// </summary>
        /// <param name="item">键值对查找</param>
        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            return _lockDictionary.PerformUsingReadLock(() => ((_mDictionary.ContainsKey(item.Key)) &&
                                                               (_mDictionary.ContainsValue(item.Value))));
        }


        public bool ContainsKey(TKey key)
        {
            return _lockDictionary.PerformUsingReadLock(() => _mDictionary.ContainsKey(key));
        }

        /// <summary>
        /// 如果字典包含此值,则返回true
        /// </summary>
        /// <param name="value">找到的值</param>
        public bool ContainsValue(TValue value)
        {
            return _lockDictionary.PerformUsingReadLock(() => _mDictionary.ContainsValue(value));
        }


        public ICollection<TKey> Keys
        {
            get { return _lockDictionary.PerformUsingReadLock(() => _mDictionary.Keys); }
        }


        public bool Remove(TKey key)
        {
            return _lockDictionary.PerformUsingWriteLock(() => (!_mDictionary.ContainsKey(key)) || _mDictionary.Remove(key));
        }


        public bool Remove(KeyValuePair<TKey, TValue> item)
        {
            return _lockDictionary.PerformUsingWriteLock(() =>
            {
                // 如果键不存在则跳过
                TValue tempVal;
                if (!_mDictionary.TryGetValue(item.Key, out tempVal))
                    return false;

                //如果值不匹配,请跳过
                return tempVal.Equals(item.Value) && _mDictionary.Remove(item.Key);
            });
        }

        /// <summary>
        /// 从字典中删除与模式匹配的项
        /// </summary>
        /// <param name="predKey">基于键的可选表达式</param>
        /// <param name="predValue">基于值的选项表达式</param>
        public bool Remove(Predicate<TKey> predKey, Predicate<TValue> predValue)
        {
            return _lockDictionary.PerformUsingWriteLock(() =>
            {
                // 如果没有键退出
                if (_mDictionary.Keys.Count == 0)
                    return true;

                //保存要删除的项目列表
                var deleteList = new List<TKey>();

                // 过程密钥
                foreach (var key in _mDictionary.Keys)
                {
                    var isMatch = false;

                    if (predKey != null)
                        isMatch = (predKey(key));

                    // 如果此项目的值匹配,请添加它
                    if ((!isMatch) && (predValue != null) && (predValue(_mDictionary[key])))
                        isMatch = true;

                    // 如果我们有匹配,添加到列表
                    if (isMatch)
                        deleteList.Add(key);
                }

                // 从列表中删除所有项目
                foreach (var item in deleteList)
                    _mDictionary.Remove(item);

                return true;
            });
        }


        public bool TryGetValue(TKey key, out TValue value)
        {
            _lockDictionary.EnterReadLock();
            try
            {
                return _mDictionary.TryGetValue(key, out value);
            }
            finally
            {
                _lockDictionary.ExitReadLock();
            }

        }


        public ICollection<TValue> Values
        {
            get { return _lockDictionary.PerformUsingReadLock(() => _mDictionary.Values); }
        }

        public TValue this[TKey key]
        {
            get { return _lockDictionary.PerformUsingReadLock(() => _mDictionary[key]); }

            set { _lockDictionary.PerformUsingWriteLock(() => _mDictionary[key] = value); }
        }

        /// <summary>
        /// 清除字典
        /// </summary>
        public void Clear()
        {
            _lockDictionary.PerformUsingWriteLock(() => _mDictionary.Clear());
        }


        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            _lockDictionary.PerformUsingReadLock(() => _mDictionary.ToArray().CopyTo(array, arrayIndex));
        }

        /// <summary>
        /// 返回字典中的项目数
        /// </summary>
        public int Count
        {
            get { return _lockDictionary.PerformUsingReadLock(() => _mDictionary.Count); }
        }


        public bool IsReadOnly
        {
            get { return false; }
        }


        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            Dictionary<TKey, TValue> localDict = null;

            _lockDictionary.PerformUsingReadLock(() => localDict = new Dictionary<TKey, TValue>(_mDictionary));

            return ((IEnumerable<KeyValuePair<TKey, TValue>>)localDict).GetEnumerator();
        }


        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            Dictionary<TKey, TValue> localDict = null;

            _lockDictionary.PerformUsingReadLock(() => localDict = new Dictionary<TKey, TValue>(_mDictionary));

            return localDict.GetEnumerator();
        }

    }

    以上创建安全的字典方法中,主要对字典的一些方法和属性进行重写操作,对某些方法进行锁设置。

© 著作权归作者所有

共有 人打赏支持
彭泽0902
粉丝 0
博文 44
码字总数 57771
作品 0
武汉
高级程序员
Dictionary、KeyValuePair、Hashtable的比较和使用

Dictionary 官方文档:http://msdn.microsoft.com/en-us/library/xfhwa508.aspx 而Directory是HashTable的泛型模式,用KeyValuePair来遍历Directory。 Dictionary泛型 中的任何键都必须是唯一...

蚊子888 ⋅ 2013/05/17 ⋅ 0

OC的plist操作

#import <Foundation/Foundation.h> #import "FJDog.h" #define path @"/Users/IOS1601/Desktop/plist文件/plist1.plist" #define path1 @"/Users/IOS1601/Desktop/plist文件/data.plist" #d......

鹿微微鹿 ⋅ 2016/03/30 ⋅ 0

ConcurrentDictionary 对决 Dictionary+Locking

在 .NET 4.0 之前,如果我们需要在多线程环境下使用 Dictionary 类,除了自己实现线程同步来保证线程安全之外,我们没有其他选择。 很多开发人员肯定都实现过类似的线程安全方案,可能是通过...

嗯哼9925 ⋅ 2017/12/06 ⋅ 0

asp cookies用法与cookies实例教程

如何创建一个Cookie? 为了创建一个Cookie,您需要使用Response.Cookies命令。在下面的例子中,我们将创建一个名为“姓氏”,并指定值“someValue”,它的cookie: <% Response.Cookies("las...

james_laughing ⋅ 2016/11/02 ⋅ 0

11_Oracle_Admin_数据字典和动态性能表概述

一、数据字典 Data Dictionary 存储的是控制信息:如有多少张表,表的结构等等 Performance tables: 也称为DynamicPerformance tables,即动态性能表,反应了系统的性能特征,是动态的,以表的...

wuyeyoulanjian ⋅ 2014/09/23 ⋅ 0

JavaScript字典

字典中存储的是[key,value],其中键名是用来查询特定的元素的。字典和集合很相似,只是集合以[value,value]的格式来存储数据的。字典也叫作映射。 JavaScript实现的字典 下面通过一个实际例...

hello菜bird ⋅ 2016/01/08 ⋅ 0

Java 数据结构

Java 数据结构 Java工具包提供了强大的数据结构。在Java中的数据结构主要包括以下几种接口和类: 枚举(Enumeration) 位集合(BitSet) 向量(Vector) 栈(Stack) 字典(Dictionary) 哈希...

xjt2016 ⋅ 2016/11/20 ⋅ 0

swift Dictionary 字典

swift Dictionary 字典 // // main.swift // 字典 // // Created by zhangbiao on 14-6-15. // Copyright (c) 2014年 理想. All rights reserved. // import Foundation println("字典") /*......

法斗斗 ⋅ 2016/03/03 ⋅ 0

python自我学习 一 python语法,及变量类型

Python安装 python语句后面不需要分号,这和 谷歌主推的kotlin 语言一样,或许,以后的新语言,也会采用这种形式。 使用过C的都知道,变量要先声明定义,才能使用,变量有很多类型,本以为p...

gudianxiaoshuo ⋅ 2017/12/27 ⋅ 0

JavaScript字典

在字典中,我们使用键值对来存储数据。 字典 字典的定义 Dictionary (map, association list) is a data structure, which is generally an association of unique keys with some values. O......

SVD ⋅ 2015/12/11 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

istio 文档

https://istio.io/docs/concepts/ https://istio.io/docs/concepts/traffic-management/handling-failures/ https://istio.io/docs/concepts/traffic-management/rules-configuration/......

xiaomin0322 ⋅ 21分钟前 ⋅ 0

编程语言的作用及与操作系统和硬件的关系

一、编程语言的作用及与操作系统和硬件的关系 作用:编程语言是计算机语言,是一种程序员与计算机之间沟通的介质,通过编程语言可以使得计算机能够根据人的指令一步一步去工作,完成某种特定...

slagga ⋅ 32分钟前 ⋅ 0

runtime实现按钮点击事件

也不能说是实现吧,,,就是有点类似于RAC里边的写法,不用给btn添加另外的点击事件,就那个add...select...这样子很不友好,来看下代码: [self.btn handleControlEvent:UIControlEventTou...

RainOrz ⋅ 32分钟前 ⋅ 0

Windows系统运维转linux系统运维的经历

开篇之前,首先介绍一下我的背景把:我是一个三线城市的甲方运维。最近,在《Linux就该这么学》书籍的影响下和朋友小A(Linux运维已经三年了,工资也比我的高很多)的影响下,决定转行。最近...

linux-tao ⋅ 33分钟前 ⋅ 0

zip压缩工具,tar打包工具

zip压缩工具 zip打包工具跟前面说到的gzip,bz2,xz 工具最大的不一样是zip可以压缩目录。如果没有安装,需要使用yum install -y zip 来安装。安装完之后就可以直接使用了,跟之前提到的压缩...

李超小牛子 ⋅ 41分钟前 ⋅ 0

使用npm发布自己的npm组件包

一、注册npm账号 官网:https://www.npmjs.com/signup 注册之后需要进行邮箱验证,否则后面进行组件包发布时候会提示403错误,让进行邮箱核准。 二、本地新建一个文件夹,cd进入后使用npm i...

灰白发 ⋅ 43分钟前 ⋅ 0

010. 深入JVM学习—垃圾收集策略概览

1. 新生代可用GC策略 1. 串行GC(Serial Copying) 算法:复制(Copying)清理算法; 操作步骤: 扫描年轻代中所有存活的对象; 使用Minor GC进行垃圾回收,同时将存活对象保存到“S0”或“S...

影狼 ⋅ 43分钟前 ⋅ 0

JVM性能调优实践——JVM篇

在遇到实际性能问题时,除了关注系统性能指标。还要结合应用程序的系统的日志、堆栈信息、GClog、threaddump等数据进行问题分析和定位。关于性能指标分析可以参考前一篇JVM性能调优实践——性...

Java小铺 ⋅ 44分钟前 ⋅ 0

误关了gitlab sign-in 功能的恢复记录

本想关sign-up的,误点了sign-in 退出后登录界面提示: No authentication methods configured 一脸懵逼.. 百度后众多方案说修改application_settings 的 signin_enabled字段; 实际上新版本字段...

铂金蛋蛋 ⋅ 45分钟前 ⋅ 0

登录后,后续请求接口没有带登录cookie可能原因

1.XMLHttpRequest.withCredentials没设置好,参考https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/withCredentials...

LM_Mike ⋅ 46分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部