精华内容
下载资源
问答
  • 下面做了基本的总结: 对比项 Open Addressing Closed Addressing 说明 元素容量 最大为数组大小 最大会比数组大小大很多 不考虑扩容的场景 数据聚集 存在数据聚集问题 不存在数据聚集问题 说明是数据聚集参考后面的...

    一、前言

    看到标题大家都应该觉得奇怪,我们去面试被问到HashMap的实现,大家不都是说的基于数组+链表的方式么。为什么我们会说HashMap不是基于数组+链表的方式实现的呢?其实这是大家的狭义理解导致的。真正的HashMap是广义的概念,我们平常所说的HashMap都是只Java里面的HashMap实现。这只是所有HashMap实现方法中的一种。

    广义的HashMap从寻址方式上分为Open Addressing HashMap和Closed Addressing HashMap。而Open Addressing又根据探测技术细分为:Linear Probing、Quadratic Probing和Double Hashing等。在Open Addressing中又有Primary Clustering和Secondary Clustering的概念。

    看到这些概念想必很多同学都已经晕了,是不是发现很多概念都没有听过。接下来楼主就为大家详细讲解广义HashMap的概念和底层实现原理。

    二、广义HashMap的原理

    1、寻址方式

    首先我们简单介绍下什么是寻址方式。对Java HashMap有了解的同学对寻址方式应该不陌生。在Java HashMap中,其主要结构是数组+链表。我们根据Hash查找一个key应该落在数组中的哪个位置的过程就叫做寻址。寻址即如何找到Key在数组中的位置。因为所有的HashMap能够快速找到数据基础都是数组的快速定位。

    广义的HashMap基于寻址方式的不同,将HashMap分为两大类:Open Addressing(开放寻址)和Closed Addressing(闭合寻址)。下面我们将对其进行深入讲解。

    A、Closed Addressing(闭合寻址)

    这种寻址方式大家容易理解。因为Java HashMap中的寻址方式就是Closed Addressing。其特点为:无论什么元素(元素的key)只要其通过hash定位到数组的某个位置,那么它必须放在这个位置。当出现Hash冲突的时候,我们可以使用额外存储空间来解决,如Java HashMap中使用链表的方式。

    B、Open Addressing(开放寻址)

    开发寻址方式是相对闭合寻址方式而言。即当我们通过hash的方式将元素(元素的key)定位到数组的某个位置时,我们不必一定将该元素放在数组的这个位置。而是可以通过其他的寻址方式(后续会讲解)将其放到数组的其他位置。

    通过下图我们就可以明显看出两种寻址方式的不同(其中数字0-9表示数组的位置,而圆形圈中的数组则表示对应的元素)。

    C、Open Addressing vs Closed Addressing

    通过上面的分析我们可以看到Open Addressing和Closed Addressing有比较明显的区别。下面做了基本的总结:

    对比项Open AddressingClosed Addressing说明
    元素容量最大为数组大小最大会比数组大小大很多不考虑扩容的场景
    数据聚集存在数据聚集问题不存在数据聚集问题说明是数据聚集参考后面的章节
    缓存行利用可以充分利用缓存行不能利用缓存行利用缓存行可以提高读取性能
    空间占用仅为数组大小会占用比数组大小更多的空间不考虑元素自身大小
    高load factor性能load factor=0.9时性能都极好load factor=0.75就需要扩容load factor:数组元素相对数组大小的占比

    说明:

    • 因为Closed Addressing会通过链表等额外空间的方式存储元素,所以它的容量比较大。可以说上限
    • 数据聚集(后续会介绍)会导致读取数据慢
    • Open Addressing的数据都放在数组中,数组结构可以充分利用CPU能力被加载到同一个缓存行中,从而提高读取性能
    • Closed Adressing有链表等额外结构空间,会导致占用更多的内存。
    • Closed Addressing使用链表解决Hash冲突,导致高load factor下链表很长,且不能利用缓存行导致查询性能很差。而Open Addressing的数据都在一个数组中,且能够利用缓存行,同时好的Open Addressing实现能够很好的平衡数据聚集问题,所以其在load factor=0.9时都能够有很好的性能。

    通过上面的对比我们可以看到Open Addressing还是有很多优势的。特别是在高load factor下的性能表现,再加上大家都Closed Addressing的代表实现Java HashMap应该是非常的熟悉了,且网上也有很多文章讲解Java HashMap,因此本文就不再对Closed Addressing做进一步讲解。接下来我们重点讲解大家比较陌生的Open Addressing HashMap。

    2、Open Addressing的探测技术

    因为Open Addressing没有额外空间来解决hash冲突的问题,因此当存在Hash冲突的时候,其需要在数组中为这个元素找到一个空位置。这个当遇到hash冲突去寻找空位置的过程就叫做探测。这里使用的技术就叫做寻址技术。目前比较常用的寻址技术有Linear Probing、Quadratic Probing和Double Hashing。接下来我们详解对齐进行介绍。

    A、Linear Probing(线性探测)

    线性探测的方式比较简单。当写入元素的时候出现hash冲突时,我们直接去查看该位置的下一个是否可用,如果可用则直接插入元素。否则继续查找该位置的下一个,一直这样循环处理,知道找到合适的位置,或者触发Rehash。

    这种探测技术计算可用位置的公式如下(i为通过hash确定的初始位置):

    • i + 1
    • i + 2
    • i + 3

    这种探测方式存在一个问题。它会导致大量的元素聚集在一块,形成一个连续的链(物理地址上连续,不是链表)。当我们查找的数据在这个链里面的时候,需要不断一个一个查找。如果链越长,则查询的效率则越低。这种数据聚集在一起的现象就叫做聚集(Clustering),也可以叫做Primary Clustering。

    B、Quadratic Probing(二次方探测)

    二次方探测也比较简单,就是每次计算可用位置的时候不是直接+1,而是加二次方。

    这种探测技术计算可用位置的公式如下(i为通过hash确定的初始位置):

    • i + 1*1
    • i + 2*2
    • i + 3*3

    这种方式通过这种次方跳跃的方式寻找可用位置,虽然不容易产生Primary Clustering。但是也会产生另外一种链,比如hash冲突很严重(大量元素的hash到同一个位置),那么这些元素也会构成一个链(物理上不连续),在查找的时候仍然会导致查询慢的问题。这种数据链也是一种数据聚集。而这种数据聚集就叫做Secondary Clustering。

    C、Double Hashing(二次Hash探测)

    二次Hash探测顾名思义,就是当出现hash冲突的时候通过另外一个hash来计算下一个可用位置。

    这种探测技术计算可用位置的公式如下(i为通过hash确定的初始位置,j=另外一个hash(key)值):

    • i + 1 × j
    • i + 2 × j
    • i + 3 × j

    这种探测技术,相对来说就不会出现Primary Clustering和Secondary Clustering了。具体原因大家可以思考一下。

    D、Linear Probing vs Quadratic Probing vs Double Hashing

    探测技术

    冲突slot计算方式

    优点

    缺点

    Linear Probing

    • i + 1
    • i + 2
    • i + 3

    很好的利用缓存行

    存在primary clustering问题

    存在secondary clustering问题

    Quadratic Probing

    • i + 1*1
    • i + 2*2
    • i + 3*3

    有间隙可以避免primary clustering

    存在secondary clustering问题

    无法利用缓存行

    Double Hashing

    j=hash2(key)

    • i + 1 × j
    • i + 2 × j
    • i + 3 × j

    避免了primary clustering问题

    避免了secondary clustering问题

    无法利用缓存行

    性能差(多一次hash)

    3、Clustering元素聚集

    元素聚集就是只元素在数组上形成一个链,可以是物理上连续的,也可以是物理上不连续的。这种链会导致数查询的时候性能降低。就像Java HashMap中一样,当链表变长了之后HashMap的查询效率就会降低。

    A、Primary Clustering

    就是说元素聚集发生在物理上的连续,即在数据上的元素相邻挨着一起,中间无间隔。

    B、Secondary Clustering

    表示数组上的元素根据探测技术的算法形成了一个物理上不连续,但是在探测算法上连续的链。即通过通过探测技术的探测可用空位时,发现多次计算的位置都是被占用的,这就形成了一个物理上不连续但是逻辑上连续的链。

    三、Open Addressing的增删查

    Open Addressing的增删查和咱们熟知的Closed Addressing还是有很多不同。接下来我们就以线性探测(Linear Probing)为基础对Open Addressing Hash Map的增删查分别多进一步的讲解。

    1、删除元素

    在Open Addressing中要删除元素主要涉及到三种场景,如下图:

    场景1:元素独立存在(图中元素12)

    由于该元素独立存在于数组中,我们只需要直接将其从数组中删除接口。

    场景2:元素在Clustering末尾(图中元素9)

    这种场景和场景1是一样的,我们可以直接将其从数组中删除即可。

    场景3:元素在Clustering中(图中元素6)

    这种场景和前面的场景则不一样了。如果我们直接将元素6从数组中删除。则会导致一个问题,即下次查询的时候会查询不到7和8。因为由于6被删除,查询的时候查询到空位置则终止了(参考后续查询逻辑)。

    这个问题的解决办法有如下两种:

    tombstones法:即被删除的元素不直接从数组中删除,而是标记为不可用,查询的时候仍然可以继续向后查询到元素7和8。这样被标记删除的元素叫做tombstones,即墓碑。

    backward shift deletion法:元素6被删除之后,该链中后面的元素都向前移动一格。这样元素4、5、7、8就挨着一起了。也不会影响后续的查询。

    这两种删除方式各有各优缺点。

    删除方式执行速度查询性能影响性能测试
    tombstones直接标记,速度快删除元素不删除,会导致链很长,影响查询性能罗宾汉墓碑法
    backward shift deletion需要移动元素,速度慢删除元素被删除,不会影响查询性能罗宾汉backward shift法

    PS:墓碑法的性能比较低,直接使用backward shift deletion法,其性能很高。截图可以参考中连接关于罗宾汉HashMap的两种不同删除元素的性能测试连接。

    2、查找元素

    查找元素就比较简单了,首先根据hash直接定位到数组的位置,然后对key进行equal比较。如果key不匹配则通过对应的探测技术继续探测下一个位置。如果在探测的过程中发现了墓碑(tombstones)元素,则跳过,即寻找下一个。一直循环要么找到对应的元素,要么找到一个空位置结束。

    3、插入元素

    首先根据hash定位到数组的位置,如果该位置为空或者为墓碑(tombstones),则将元素放在该位置,结束插入操作。如果不为空或者墓碑,则通过对应的探测技术继续寻找下一个位置,如此循环直到找到空位置或者墓碑位置,则插入元素。或则触发rehash,重新查找。

    关于广义HashMap的技术与原理就介绍到这里,下期我们将介绍Open Addressing HashMap的具体实现例子:ThreadLocal和Robin Hood HashMap。

    四、惯例

    如果你对本文有任何疑问或者高见,欢迎添加公众号lifeofcoder共同交流探讨(添加公众号可以获得楼主最新博文推送以及”Java高级架构“上10G视频和图文资料哦)。

     

    展开全文
  • 水平聚集密集数据

    2021-01-29 11:56:13
    我在一个2D numpy数组中有一组大约34000个数据标签,它们各自的特征(状态概率)被可视化为散点图,看起来是。在很容易看到b点的大部分数据。我想用聚类算法来提取底部区域。我不追求完美的结果。这只是关于提取大多数...

    我在一个2D numpy数组中有一组大约34000个数据标签,它们各自的特征(状态概率)被可视化为散点图,看起来是

    UlYJG.png。在

    很容易看到b点的大部分数据。我想用聚类算法来提取底部区域。我不追求完美的结果。这只是关于提取大多数b点。在

    到目前为止,我已经尝试了DBSCAN算法:import sklearn.cluster as sklc

    data1, data2 = zip(*dist_list[1])

    data = np.array([data1, data2]).T

    core_samples, labels_db = sklc.dbscan(

    data, # array has to be (n_samples, n_features)

    eps=2.0,

    min_samples=5,

    metric='euclidean',

    algorithm='auto'

    )

    core_samples_mask = np.zeros_like(labels_db, dtype=bool)

    core_samples_mask[core_samples] = True

    unique_labels = set(labels_db)

    n_clusters_ = len(unique_labels) - (1 if -1 in labels_db else 0)

    colors = plt.cm.Spectral(np.linspace(0, 1, len(unique_labels)))

    for k, col in zip(unique_labels, colors):

    if k == -1:

    # Black used for noise.

    col = 'k'

    class_member_mask = (labels_db == k)

    xy = data[class_member_mask & core_samples_mask]

    plt.plot(xy[:, 0], xy[:, 1], 'o', markerfacecolor=col, markeredgecolor='k', markersize=6)

    xy = data[class_member_mask & ~core_samples_mask]

    plt.plot(xy[:, 0], xy[:, 1], 'x', markerfacecolor=col, markeredgecolor='k', markersize=4)

    plt.rcParams["figure.figsize"] = (15, 15)

    plt.title('Estimated number of clusters: %d' % n_clusters_)

    plt.show()

    产生

    PPX1E.png。在

    增加最小样本量只会导致更小的垂直线被归类为噪声,而更长(更密集)的垂直线会停留。在

    我还尝试使用scipy.cluster.hierarchy进行群集:

    ^{pr2}$

    这导致了类似的垂直分类。请查看该图的注释。以我的声誉,我不允许发布超过两个链接。在

    现在我的问题是,我在校准算法时犯了错误吗?或者我选择的算法一开始就错了?如何提取b数据点的密集区域?在

    展开全文
  • 对于成功的数据分析而言,把握数据整体的性质是至关重要的,使用统计量来检查数据特征,主要是检查数据的集中程度、离散程度和分布形状,通过这些统计量可以识别数据集整体上的一些重要性质,对后续的数据分析,有很...

    对于成功的数据分析而言,把握数据整体的性质是至关重要的,使用统计量来检查数据特征,主要是检查数据的集中程度、离散程度和分布形状,通过这些统计量可以识别数据集整体上的一些重要性质,对后续的数据分析,有很大的参考作用。

    一,基本统计量

    用于描述数据的基本统计量主要分为三类,分别是中心趋势统计量、散布程度统计量和分布形状统计量。

    1,中心趋势统计量

    中心趋势统计量是指表示位置的统计量,直观地说,给定一个属性,它的值大部分落在何处?

    (1)均值

    均值(mean)又称算数平均数,描述数据去指导额平均位置,数学表达式:均值 =  ∑x  /  n;

    有时,一组数据中的每个值可以和一个权重Wi相关联,权重反映的的是依附值的重要性或出现的频率,这种均值称作加权均值 =  ∑xw  /  n;

    尽管均值是描述数据集中心趋势的最有用的统计量,但是,它并非总是度量数据中心的最佳方法,这是因为,均值对极端值(离群点)很敏感。为了抵消少数极端值的影响,我们可以使用截尾均值,截尾均值是指丢弃极端值后的均值。

    (2)中位数

    对于倾斜(非对称)的数据,能够更好地描述数据中心的统计量是中位数(median),中位数是有序数据值的中间值,中位数可避免极端数据,代表这数据总体的中等情况。例如:从小到大排序,总数是奇数,取中间的数,总数是偶数,取中间两个数的平均数。

    (3)众数

    众数(mode)是变量中出现频率最大的值,通常用于对定性数据确定众数,例如:用户状态(正常,欠费停机,申请停机,拆机、消号),该变量的众数是“正常”,这种情况是正常的。

    2,表示数据离散程度的统计量

    度量数据离散程度的统计量主要是标准差和四分位极差。

    (1)标准差(或方差)

    标准差用于度量数据分布的离散程度,低标准差意味着数据观测趋向于靠近均值,高标准差表示数据散步在一个大的值域中。

    (2)四分位极差

    极差(range),也称作值域,是一组数据中的最大值和最小值的差, range = Max - Min。

    百分位数(quantile)是把数据值按照从小到大的顺序排列,把数据分成100份。中位数是数据的中间位置上的数据,第一个四分位数记作Q1,是指第25个百分位上的数据,第三个四分位数记作(Q3),是指第75个百分位上的数据。

    四分位极差(IQR)= Q3 - Q1 ,IQR是指第一个四分位和第三个四分位之间的距离,它给出被数据的中间一半所覆盖的范围,是表示数据离散程度的一个简单度量。

    3,表示分布形状的统计量

    分布形状使用偏度系数和峰度系数来度量,

    偏度是用于衡量数据分布对称性的统计量:通过对偏度系数的测量,我们能够判定数据分布的不对称程度以及方向。

    • 对于正态分布(或严格对称分布)偏度等于0
    • 若偏度为负, 则x均值左侧的离散度比右侧强;
    • 若偏度为正, 则x均值左侧的离散度比右侧弱;

    峰度是用于衡量数据分布陡峭或平滑的统计量,通过对峰度系数的测量,我们能够判定数据分布相对于正态分布而言是更陡峭还是平缓。

    • 正态分布的峰度为3,
    • 当时间序列的曲线峰值比正态分布的高时,峰度大于3;
    • 当比正态分布的低时,峰度小于3。

    (1)偏度系数

    偏度系数反映数据分布偏移中心位置的程度,记为SK,则有 SK= (均值一中位数)/标准差。偏度系数是描述分布偏离对称性程度的一个特征数。

    正态分布的偏度为0,偏度<0称分布具有负偏离(左偏态),此时数据位于均值左边的位于右边的多,有个尾巴拖到左边,说明左边有极端值,偏度>0称分布具有正偏离(右偏态)。偏度接近如于0 ,可认为分布对称。例如:知道分布有可能在偏度上偏离正态分布,则可用偏度来检验分布的正态性。偏度的绝对值数值越大表示其分布形态的偏斜程度越大。

    (2)峰度系数

    峰度系数(Kurtosis)用来度量数据在中心聚集程度,记为K,描述总体中所有取值分布形态陡缓程度的统计量(与正态分布比较,,就是正态分布的峰顶)。

    例如:正态分布的峰度系数值是3,K>3的峰度系数说明观察量更集中,有比正态分布更短的尾部;K<3的峰度系数说明观测量不那么集中,有比正态分布更长的尾部。

    峰度系数公式是:

    示例,本文使用vcd包中的Arthritis数据集来演示如何进行统计量分析:

    head(Arthritis)
      ID Treatment  Sex Age Improved
    57   Treated Male  27     Some
    46   Treated Male  29     None
    77   Treated Male  30     None
    17   Treated Male  32   Marked
    36   Treated Male  46   Marked
    23   Treated Male  58   Marked
    

    其中变量Improved和Sex是因子类型,ID和Age是数值类型。

    二,集中趋势度量

    集中趋势通过均值、中位数和众数来度量。

    1,均值

    均值是所有数据的平均值,使用mean()函数来计算向量的均值:

    age.mean <- mean(Arthritis$Age)
    

    有时,为了反映在均值中不同成分所占的权重,为数据中的每个元素X赋予一个权重Wi,这样就得到了加权平均值,使用weighted.mean(x,w)来计算加权平均值。

    weighted.mean(x,w)
    

    x为数据向量,w为权重向量,x中每一个元素都对应w中的一个权重值。

    根据Sex来设置权重(weight),男性的Age的权重为95%,女性的Age的权重为105%,那么得到的加权平均值是:

    age.wt <- ifelse(Arthritis$Sex=="Male",0.95,1.05)
    age.wt.mean <- weighted.mean(Arthritis$Age,age.wt)
    

    如果数据中存在极端值或者数据是偏态分布的,那么均值就不能很好地度量数据的集中趋势,为了消除少数极端值的影响,可以使用截断均值或者中位数来度量数据的集中趋势。截断均值是指去掉极端值之后的平均值。

    2,中位数

    中位数是把一组观察值从小到大按顺序排列,位于中间的那个数据。使用median(x)计算中位数。

    age.median <- median(Arthritis$Age)
    

    3,众数

    众数是指数据集中出现最频繁的值,众数常用于定性数据。R没有标准的内置函数来计算众数,因此,我们将创建一个用户自定义函数来计算数据集的众数。

    该函数以向量作为输入,以众数值作为输出。

    getmode <- function(v) {
       uniqv <- unique(v)
       uniqv[which.max(tabulate(match(v, uniqv)))]
    }
    

    三,离中趋势度量

    衡量离中趋势的四个度量值:

    • 值域(Range)的计算公式:Range = Max - Min
    • 标准差:度量数据偏离均值的程度
    • 变异系数(CV):变异系数度量标准差相对于均值的离中趋势,计算公式是:CV=标准差/均值
    • 四分位数间距(IQR)是上四分位数QU和下四分位数QL之差,其间包含全部观察值的一般,其值越大,说明数据的变异程度越大,离中趋势越明显。

     查看Arthritis数据集的离中趋势:

    get_stat <- function(v){
      v.mean <- mean(v)
      v.median <- median(v)
      v.range <- max(v)-min(v)
      v.sd <- sd(v) v.cv <- v.sd/v.mean v.iqr <- quantile(v,0.75) - quantile(v,0.25) d.stat <- data.frame(mean=v.mean,median=v.median,range=v.range,sd=v.sd,cv=v.cv,iqr=v.iqr, row.names = NULL) } mystat <- get_stat(Arthritis$Age) 
    

    四,偏度和峰度

    基础安装包中没有提供计算偏度和峰度的函数,用户可以自行添加:

    mystats <- function(x, na.omit=FALSE){
        if (na.omit)
        x <- x[!is.na(x)]
        m <- mean(x)
        n <- length(x)
        s <- sd(x)
        skew <- sum((x-m)^3/s^3)/n
        kurt <- sum((x-m)^4/s^4)/n - 3
        return(c(n=n, mean=m, stdev=s, skew=skew, kurtosis=kurt))
    }
    myvars <- c("mpg", "hp", "wt")
    sapply(mtcars[myvars], mystats)
    

    为大家推荐一篇文章:关于偏度与峰度的一些探索,引用该文中的峰度影响实验的结论:

    尾部或离群点对峰度影响为正向,且影响程度最大。而高概率区对峰度影响也为正向,但是比较少;而山腰位置,中等概率区域则影响为负向。

    参考文档:

    关于偏度与峰度的一些探索

    展开全文
  • 动作描述 使用聚集索引 使用非聚集索引列经常被分组排序 应 应返回某范围内的数据 应 不应一个或极少不同值 不应 不应小数目的不同值 应 不应大数目的不同值 不应 应频繁更新的列 不应 应外键列 应 应主键列 应 应...

    办公室','办公室,通信科,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,外事科','这是中间的25万条记录')

    set @i=@i+1

    end

    GO

    declare @h int

    set @h=1

    while @h<=100

    begin

    declare @i int

    set @i=2002

    while @i<=2003

    begin

    declare @j int

    set @j=0

    while @j<50

    begin

    declare @k int

    set @k=0

    while @k<50

    begin

    insert into Tgongwen(fariqi,neibuyonghu,reader,title) values(cast(@i as varchar(4))+'-8-15 3:'+cast(@j as varchar(2))+':'+cast(@j as varchar(2)),'通信科','办公室,通信科,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,外事科','这是最后的50万条记录')

    set @k=@k+1

    end

    set @j=@j+1

    end

    set @i=@i+1

    end

    set @h=@h+1

    end

    GO

    declare @i int

    set @i=1

    while @i<=9000000

    begin

    insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-5-5','通信科','通信科,办公室,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,治安支队,外事科','这是最后添加的900万条记录')

    set @i=@i+1000000

    end

    GO通过以上语句,我们创建了25万条由通信科于2004年2月5日发布的记录,25万条由办公室于2004年9月6日发布的记录,2002年和2003年各100个2500条相同日期、不同分秒的由通信科发布的记录(共50万条),还有由通信科于2004年5月5日发布的900万条记录,合计1000万条。一、因情制宜,建立“适当”的索引建立“适当”的索引是实现查询优化的首要前提。索引(index)是除表之外另一重要的、用户定义的存储在物理介质上的数据结构。当根据索引码的值搜索数据时,索引提供了对数据的快速访问。事实上,没有索引,数据库也能根据SELECT语句成功地检索到结果,但随着表变得越来越大,使用“适当”的索引的效果就越来越明显。注意,在这句话中,我们用了“适当”这个词,这是因为,如果使用索引时不认真考虑其实现过程,索引既可以提高也会破坏数据库的工作性能。(一)深入浅出理解索引结构实际上,您可以把索引理解为一种特殊的目录。微软的SQL SERVER提供了两种索引:聚集索引(clustered index,也称聚类索引、簇集索引)和非聚集索引(nonclustered index,也称非聚类索引、非簇集索引)。下面,我们举例来说明一下聚集索引和非聚集索引的区别:其实,我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;同样的,如果查“张”字,那您也会将您的字典翻到最后部分,因为“张”的拼音是“zhang”。也就是说,字典的正文部分本身就是一个目录,您不需要再去查其他目录来找到您需要找的内容。我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而需要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序方法,比如您查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分别位于“张”字的上下方,现在您看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码。我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非聚集索引”。通过以上例子,我们可以理解到什么是“聚集索引”和“非聚集索引”。进一步引申一下,我们可以很容易的理解:每个表只能有一个聚集索引,因为目录只能按照一种方法进行排序。(二)何时使用聚集索引或非聚集索引下面的表总结了何时使用聚集索引或非聚集索引(很重要)。动作描述 使用聚集索引 使用非聚集索引列经常被分组排序 应 应返回某范围内的数据 应 不应一个或极少不同值 不应 不应小数目的不同值 应 不应大数目的不同值 不应 应频繁更新的列 不应 应外键列 应 应主键列 应 应频繁修改索引列 不应 应事实上,我们可以通过前面聚集索引和非聚集索引的定义的例子来理解上表。如:返回某范围内的数据一项。比如您的某个表有一个时间列,恰好您把聚合索引建立在了该列,这时您查询2004年1月1日至2004年10月1日之间的全部数据时,这个速度就将是很快的,因为您的这本字典正文是按日期进行排序的,聚类索引只需要找到要检索的所有数据中的开头和结尾数据即可;而不像非聚集索引,必须先查到目录中查到每一项数据对应的页码,然后再根据页码查到具体内容。(三)结合实际,谈索引使用的误区理论的目的是应用。虽然我们刚才列出了何时应使用聚集索引或非聚集索引,但在实践中以上规则却很容易被忽视或不能根据实际情况进行综合分析。下面我们将根据在实践中遇到的实际问题来谈一下索引使用的误区,以便于大家掌握索引建立的方法。1、主键就是聚集索引这种想法笔者认为是极端错误的,是对聚集索引的一种浪费。虽然SQL SERVER默认是在主键上建立聚集索引的。通常,我们会在每个表中都建立一个ID列,以区分每条数据,并且这个ID列是自动增大的,步长一般为1。我们的这个办公自动化的实例中的列Gid就是如此。此时,如果我们将这个列设为主键,SQL SERVER会将此列默认为聚集索引。这样做有好处,就是可以让您的数据在数据库中按照ID进行物理排序,但笔者认为这样做意义不大。显而易见,聚集索引的优势是很明显的,而每个表中只能有一个聚集索引的规则,这使得聚集索引变得更加珍贵。从我们前面谈到的聚集索引的定义我们可以看出,使用聚集索引的最大好处就是能够根据查询要求,迅速缩小查询范围,避免全表扫描。在实际应用中,因为ID号是自动生成的,我们并不知道每条记录的ID号,所以我们很难在实践中用ID号来进行查询。这就使让ID号这个主键作为聚集索引成为一种资源浪费。其次,让每个ID号都不同的字段作为聚集索引也不符合“大数目的不同值情况下不应建立聚合索引”规则;当然,这种情况只是针对用户经常修改记录内容,特别是索引项的时候会负作用,但对于查询速度并没有影响。在办公自动化系统中,无论是系统首页显示的需要用户签收的文件、会议还是用户进行文件查询等任何情况下进行数据查询都离不开字段的是“日期”还有用户本身的“用户名”。通常,办公自动化的首页会显示每个用户尚未签收的文件或会议。虽然我们的where语句可以仅仅限制当前用户尚未签收的情况,但如果您的系统已建立了很长时间,并且数据量很大,那么,每次每个用户打开首页的时候都进行一次全表扫描,这样做意义是不大的,绝大多数的用户1个月前的文件都已经浏览过了,这样做只能徒增数据库的开销而已。事实上,我们完全可以让用户打开系统首页时,数据库仅仅查询这个用户近3个月来未阅览的文件,通过“日期”这个字段来限制表扫描,提高查询速度。如果您的办公自动化系统已经建立的2年,那么您的首页显示速度理论上将是原来速度8倍,甚至更快。在这里之所以提到“理论上”三字,是因为如果您的聚集索引还是盲目地建在ID这个主键上时,您的查询速度是没有这么高的,即使您在“日期”这个字段上建立的索引(非聚合索引)。下面我们就来看一下在1000万条数据量的情况下各种查询的速度表现(3个月内的数据为25万条):(1)仅在主键上建立聚集索引,并且不划分时间段:Select gid,fariqi,neibuyonghu,title from tgongwen用时:128470毫秒(即:128秒)(2)在主键上建立聚集索引,在fariq上建立非聚集索引:select gid,fariqi,neibuyonghu,title from Tgongwen

    where fariqi> dateadd(day,-90,getdate())用时:53763毫秒(54秒)(3)将聚合索引建立在日期列(fariqi)上:select gid,fariqi,neibuyonghu,title from Tgongwen

    where fariqi> dateadd(day,-90,getdate())用时:2423毫秒(2秒)虽然每条语句提取出来的都是25万条数据,各种情况的差异却是巨大的,特别是将聚集索引建立在日期列时的差异。事实上,如果您的数据库真的有1000万容量的话,把主键建立在ID列上,就像以上的第1、2种情况,在网页上的表现就是超时,根本就无法显示。这也是我摒弃ID列作为聚集索引的一个最重要的因素。得出以上速度的方法是:在各个select语句前加:declare @d datetime

    set @d=getdate()并在select语句后加:select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate())

    2、只要建立索引就能显著提高查询速度事实上,我们可以发现上面的例子中,第2、3条语句完全相同,且建立索引的字段也相同;不同的仅是前者在fariqi字段上建立的是非聚合索引,后者在此字段上建立的是聚合索引,但查询速度却有着天壤之别。所以,并非是在任何字段上简单地建立索引就能提高查询速度。从建表的语句中,我们可以看到这个有着1000万数据的表中fariqi字段有5003个不同记录。在此字段上建立聚合索引是再合适不过了。在现实中,我们每天都会发几个文件,这几个文件的发文日期就相同,这完全符合建立聚集索引要求的:“既不能绝大多数都相同,又不能只有极少数相同”的规则。由此看来,我们建立“适当”的聚合索引对于我们提高查询速度是非常重要的。3、把所有需要提高查询速度的字段都加进聚集索引,以提高查询速度上面已经谈到:在进行数据查询时都离不开字段的是“日期”还有用户本身的“用户名”。既然这两个字段都是如此的重要,我们可以把他们合并起来,建立一个复合索引(compound index)。很多人认为只要把任何字段加进聚集索引,就能提高查询速度,也有人感到迷惑:如果把复合的聚集索引字段分开查询,那么查询速度会减慢吗?带着这个问题,我们来看一下以下的查询速度(结果集都是25万条数据):(日期列fariqi首先排在复合聚集索引的起始列,用户名neibuyonghu排在后列)(1)select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>'2004-5-5'查询速度:2513毫秒(2)select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>'2004-5-5' and neibuyonghu='办公室'查询速度:2516毫秒(3)select gid,fariqi,neibuyonghu,title from Tgongwen where neibuyonghu='办公室'查询速度:60280毫秒从以上试验中,我们可以看到如果仅用聚集索引的起始列作为查询条件和同时用到复合聚集索引的全部列的查询速度是几乎一样的,甚至比用上全部的复合索引列还要略快(在查询结果集数目一样的情况下);而如果仅用复合聚集索引的非起始列作为查询条件的话,这个索引是不起任何作用的。当然,语句1、2的查询速度一样是因为查询的条目数一样,如果复合索引的所有列都用上,而且查询结果少的话,这样就会形成“索引覆盖”,因而性能可以达到最优。同时,请记住:无论您是否经常使用聚合索引的其他列,但其前导列一定要是使用最频繁的列。(四)其他书上没有的索引使用经验总结1、用聚合索引比用不是聚合索引的主键速度快下面是实例语句:(都是提取25万条数据)select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'使用时间:3326毫秒select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid<=250000使用时间:4470毫秒这里,用聚合索引比用不是聚合索引的主键速度快了近1/4。2、用聚合索引比用一般的主键作order by时速度快,特别是在小数据量情况下select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by fariqi用时:12936

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by gid用时:18843这里,用聚合索引比用一般的主键作order by时,速度快了3/10。事实上,如果数据量很小的话,用聚集索引作为排序列要比使用非聚集索引速度快得明显的多;而数据量如果很大的话,如10万以上,则二者的速度差别不明显。3、使用聚合索引内的时间段,搜索时间会按数据占整个数据表的百分比成比例减少,而无论聚合索引使用了多少个select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1'用时:6343毫秒(提取100万条)select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-6-6'用时:3170毫秒(提取50万条)select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'用时:3326毫秒(和上句的结果一模一样。如果采集的数量一样,那么用大于号和等于号是一样的)select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' and fariqi用时:3280毫秒4、日期列不会因为有分秒的输入而减慢查询速度下面的例子中,共有100万条数据,2004年1月1日以后的数据有50万条,但只有两个不同的日期,日期精确到日;之前有数据50万条,有5000个不同的日期,日期精确到秒。 select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' order by fariqi用时:6390毫秒select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi用时:6453毫秒(五)其他注意事项“水可载舟,亦可覆舟”,索引也一样。索引有助于提高检索性能,但过多或不当的索引也会导致系统低效。因为用户在表中每加进一个索引,数据库就要做更多的工作。过多的索引甚至会导致索引碎片。所以说,我们要建立一个“适当”的索引体系,特别是对聚合索引的创建,更应精益求精,以使您的数据库能得到高性能的发挥。当然,在实践中,作为一个尽职的数据库管理员,您还要多测试一些方案,找出哪种方案效率最高、最为有效。二、改善SQL语句很多人不知道SQL语句在SQL SERVER中是如何执行的,他们担心自己所写的SQL语句会被SQL SERVER误解。比如:select * from table1 where name='zhangsan' and tID > 10000和执行:

    select * from table1 where tID > 10000 and name='zhangsan'一些人不知道以上两条语句的执行效率是否一样,因为如果简单的从语句先后上看,这两个语句的确是不一样,如果tID是一个聚合索引,那么后一句仅仅从表的10000条以后的记录中查找就行了;而前一句则要先从全表中查找看有几个name='zhangsan'的,而后再根据限制条件条件tID>10000来提出查询结果。事实上,这样的担心是不必要的。SQL SERVER中有一个“查询分析优化器”,它可以计算出where子句中的搜索条件并确定哪个索引能缩小表扫描的搜索空间,也就是说,它能实现自动优化。虽然查询优化器可以根据where子句自动的进行查询优化,但大家仍然有必要了解一下“查询优化器”的工作原理,如非这样,有时查询优化器就会不按照您的本意进行快速查询。在查询分析阶段,查询优化器查看查询的每个阶段并决定限制需要扫描的数据量是否有用。如果一个阶段可以被用作一个扫描参数(SARG),那么就称之为可优化的,并且可以利用索引快速获得所需数据。SARG的定义:用于限制搜索的一个操作,因为它通常是指一个特定的匹配,一个值得范围内的匹配或者两个以上条件的AND连接。形式如下:列名 操作符或操作符列名列名可以出现在操作符的一边,而常数或变量出现在操作符的另一边。如:Name=’张三’价格>5000

    50005000如果一个表达式不能满足SARG的形式,那它就无法限制搜索的范围了,也就是SQL SERVER必须对每一行都判断它是否满足WHERE子句中的所有条件。所以一个索引对于不满足SARG形式的表达式来说是无用的。介绍完SARG后,我们来总结一下使用SARG以及在实践中遇到的和某些资料上结论不同的经验:1、Like语句是否属于SARG取决于所使用的通配符的类型如:name like ‘张%’,这就属于SARG而:name like ‘%张’ ,就不属于SARG。原因是通配符%在字符串的开通使得索引无法使用。2、or会引起全表扫描Name=’张三’ and价格>5000符号SARG,而:Name=’张三’ or价格>5000则不符合SARG。使用or会引起全表扫描。3、非操作符、函数引起的不满足SARG形式的语句不满足SARG形式的语句最典型的情况就是包括非操作符的语句,如:NOT、!=、<>、!、NOT EXISTS、NOT IN、NOT LIKE等,另外还有函数。下面就是几个不满足SARG形式的例子:ABS(价格)<5000

    Name like ‘%三’有些表达式,如:WHERE价格*2>5000

    SQL SERVER也会认为是SARG,SQL SERVER会将此式转化为:WHERE价格>2500/2但我们不推荐这样使用,因为有时SQL SERVER不能保证这种转化与原始表达式是完全等价的。4、IN的作用相当与OR语句:Select * from table1 where tid in (2,3)和Select * from table1 where tid=2 or tid=3是一样的,都会引起全表扫描,如果tid上有索引,其索引也会失效。5、尽量少用NOT

    6、exists和in的执行效率是一样的很多资料上都显示说,exists要比in的执行效率要高,同时应尽可能的用not exists来代替not in。但事实上,我试验了一下,发现二者无论是前面带不带not,二者之间的执行效率都是一样的。因为涉及子查询,我们试验这次用SQL SERVER自带的pubs数据库。运行前我们可以把SQL SERVER的statistics I/O状态打开。(1)select title,price from titles where title_id in (select title_id from sales where qty>30)该句的执行结果为:表'sales'。扫描计数18,逻辑读56次,物理读0次,预读0次。表'titles'。扫描计数1,逻辑读2次,物理读0次,预读0次。(2)select title,price from titles where exists (select * from sales where sales.title_id=titles.title_id and qty>30)第二句的执行结果为:表'sales'。扫描计数18,逻辑读56次,物理读0次,预读0次。表'titles'。扫描计数1,逻辑读2次,物理读0次,预读0次。我们从此可以看到用exists和用in的执行效率是一样的。7、用函数charindex()和前面加通配符%的LIKE执行效率一样前面,我们谈到,如果在LIKE前面加上通配符%,那么将会引起全表扫描,所以其执行效率是低下的。但有的资料介绍说,用函数charindex()来代替LIKE速度会有大的提升,经我试验,发现这种说明也是错误的:select gid,title,fariqi,reader from tgongwen where charindex('刑侦支队',reader)>0 and fariqi>'2004-5-5'用时:7秒,另外:扫描计数4,逻辑读7155次,物理读0次,预读0次。select gid,title,fariqi,reader from tgongwen where reader like '%' + '刑侦支队' + '%' and fariqi>'2004-5-5'用时:7秒,另外:扫描计数4,逻辑读7155次,物理读0次,预读0次。8、union并不绝对比or的执行效率高我们前面已经谈到了在where子句中使用or会引起全表扫描,一般的,我所见过的资料都是推荐这里用union来代替or。事实证明,这种说法对于大部分都是适用的。select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' or gid>9990000用时:68秒。扫描计数1,逻辑读404008次,物理读283次,预读392163次。select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'

    union

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid>9990000用时:9秒。扫描计数8,逻辑读67489次,物理读216次,预读7499次。看来,用union在通常情况下比用or的效率要高的多。但经过试验,笔者发现如果or两边的查询列是一样的话,那么用union则反倒和用or的执行速度差很多,虽然这里union扫描的是索引,而or扫描的是全表。select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' or fariqi='2004-2-5'用时:6423毫秒。扫描计数2,逻辑读14726次,物理读1次,预读7176次。select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'

    union

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-2-5'用时:11640毫秒。扫描计数8,逻辑读14806次,物理读108次,预读1144次。9、字段提取要按照“需多少、提多少”的原则,避免“select *”我们来做一个试验:select top 10000 gid,fariqi,reader,title from tgongwen ord

    展开全文
  • 众所周知,索引是关系型数据库中给数据库表中一列或多列的值排序后的存储结构,SQL的主流索引结构有B+树以及Hash结构,聚集索引以及非聚集索引用的是B+树索引。 关于索引的分类,可以查看之前的文章:...
  • 摘抄和整理,非原创!!!官方定义:在《数据库原理》一书中是这么解释聚簇索引和非聚...对于聚集索引,叶子结点即存储了真实的数据行,不再有另外单独的数据页。 在一张表上最多只能创建一个聚集索引,因为真实数据...
  • 那么,常用的Innodb聚集索引结构是怎样的呢?InnoDB的数据文件本身(.ibd文件)就是索引文件。在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key...
  • 但是你回答的是具体的索引,实际上索引大体可以分成二大类:聚集索引和非聚集索引。下面我们具体介绍。 聚集索引 (又叫聚簇索引) 聚集索引就好比只有正文(没有目录)的汉语字典一样。我们知道汉语字典的排列顺序是...
  • mysql创建字段+数据处理函数+汇总数据(聚集函数)+分组数据【0】README0.1)本文部分文字描述转自“MySQL 必知必会”,旨在review“MySQL创建字段+数据处理函数+汇总数据(聚集函数)+分组数据” 的基础知识;...
  • 文章目录1.提问:InnoDB和MyISAM引擎是针对表还是数据库?2.如何存储?2.1 我们看/usr/local/...聚集索引和非聚集索引3.1 区别3.2 用两张图对比一下4.哪个更快?5.总结 1.提问:InnoDB和MyISAM引擎是针对表还是数据库?
  • 文章目录**数据仓库复习用:**6、 数据预处理的主要任务有哪些?每个任务要解决的问题主要有哪些?**7 、脏数据主要有哪几种?产生的主要原因是什么?8、缺失值的处理方法有哪些?9 、什么是噪音数据?产生的原因有...
  • 介绍一MySQL的Innodb存储引擎的索引分为聚集索引和非聚集索引两大类,理解聚集索引和非聚集索引可通过对比汉语字典的索引。汉语字典提供了两类检索汉字的方式,第一类是拼音检索(前提是知道该汉字读音),比如拼音为...
  • 数据挖掘概览:
  • 索引简介众所周知,索引是关系型数据库中给数据库表中一列或多列的值排序后的存储结构,SQL的主流索引结构有B+树以及Hash结构,聚集索引以及非聚集索引用的是B+树索引。这篇文章会总结SQL Server以及MySQL的InnoDB和...
  • Linux后台服务器开发交流qun720209036B-树的特点一个M阶的B树具有如下几个特征:(如下图 M=3)(下文的关键字可以理解为有效数据,而不是单纯的索引)定义任意非叶子结点最多只有 M 个儿子,且 M>2根结点的儿子数为...
  • 如果我们把从这两个渠道收集上来的数据进行集成、挖掘。无论使用什么算法,结果也不是正确的。因为这两个分数,代表的含义完全不同。所以说,有时候数据变换比算法选择更重要。数据错了,算法再正确也是错的。那么,...
  • 聚集索引的顺序就是数据的物理存储顺序,而对非聚集索引的解释是:索引顺序与数据物理排列顺序无关。正式因为如此,所以一个表最多只能有一个聚集索引。聚集索引的特点:1)聚集索引对于那些经常要搜索范围值得列特别...
  • 什么是SQL的聚集函数以及如何利用他们汇总表的数据。一、聚集函数我们经常需要汇总数据而不用把它们实际检索出来,为此MySQL提供了专门的函数。使用这些函数,MySQL查询可用于检索数据,以便分析和报表生成。重复一...
  • 聚集索引(Non-clustered Index),非聚集索引并不决定数据在磁盘上的物理排序,索引上只包含被建立索引的数据,以及一个行定位符 row-locator,这个行定位符,可以理解为一个聚集索引物理排序的指针,通过这个...
  • Elasticsearch聚集查询之指标聚集

    千次阅读 2021-11-17 20:03:02
    聚集查询与SQL语言中的聚集函数非常像,聚集函数在Elasticsearch中相当于是聚集查询的一种聚集类型。比如在SQL中的avg函数用于求字段平均值,而在Elasticsearch中要实现相同的功能可以使用avg聚集类型。 聚集查询的...
  • 字典正文内容本身就是一种按照一定规则排列的索引,索引中的叶子节点保存的就是要查找的数据,这种称为聚集索引。如果是用偏旁部首来查,会得到某个字在哪一页,索引中的叶子节点保存的是索引,这种称为非聚...
  • 聚集索引的叶子节点就是数据节点,key为主键的值,value为其余列数据以及rowid、rollback pointer、trx id等信息。聚集索引的条件:a.首先选择显示定义的主键为聚集索引;b.如果没有则选择第一个非NUL...
  • 【MySQL】数据处理函数 与 聚集函数

    万次阅读 2021-07-16 19:02:56
    在用于文本数据时,如果数据按相应的列排序,则MAX()返回最后一行。MAX()函数忽略列值为NULL的行。 MIN()返回最前面的行。 【注4】:SUM()函数忽略列值为NULL的行。 6. 聚集不同值 以上5个聚集函数都可以如下使用: ...
  • (二)、示意图 下图是聚集索引的存储示例图: 从上图可以看出,聚集索引的叶子节点就是对应的数据节点,可以直接获取到对应的全部列的数据,而后面我们即将介绍的非聚集索引,查询的时候可能需要二次查询,因此在...
  • 一、数据模型架构原则 1. 数仓分层原则 优秀可靠的数仓体系,往往需要清晰的数据分层结构,即要保证数据层的稳定又要屏蔽对下游的影响,并且要避免链路过长。那么问题来了,一直在讲数仓要分层,那数仓分几层最好? ...
  • 数据分析实战

    千次阅读 2021-03-23 15:14:35
    数据分析实战数据分析基础数据分析全景图及修炼指南学习数据挖掘的最佳路径学数据分析要掌握哪些基本概念用户画像:标签化就是数据的抽象能力数据采集:如何自动化采集数据数据采集:如何用八爪鱼采集微博上的“D&...
  • 索引简介众所周知,索引是关系型数据库中给数据库表中一列或多列的值排序后的存储结构,SQL的主流索引结构有B+树以及Hash结构,聚集索引以及非聚集索引用的是B+树索引。这篇文章会总结SQL Server以及MySQL的InnoDB和...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 186,338
精华内容 74,535
关键字:

数据聚集

友情链接: 1216.zip