精华内容
下载资源
问答
  • (1)线性数据结构:元素之间一般存在元素之间存在一对一关系,是最常用的一类数据结构典型的有:数组、栈、队列和线性表。 (2)树形结构:结点间具有层次关系,每一层的一个结点能且只能和上一层的一个结点相关...

    我们之前已经知道,数据结构就是计算机存储,组织数据的方式。我们根据存储方式可将数据结构大概分成图1所示以下几种:

         图1

    (1)线性数据结构:元素之间一般存在元素之间存在一对一关系,是最常用的一类数据结构,典型的有:数组、栈、队列和线性表。

    (2)树形结构:结点间具有层次关系,每一层的一个结点能且只能和上一层的一个结点相关,但同时可以和下一层的多个结点相关,称为“一对多”关系,常见类型有:树、堆。

    (3)图形结构:在图形结构中,允许多个结点之间相关,称为“多对多”关系。

    (4)哈希表结构:称为散列表,是根据关键字值(key value)而直接进行访问的数据结构。也就是说,它通过把关键字值映射到一个位置来访问记录,以加快查找的速度。这个映射函数称为哈希函数(也称为散列函数),映射过程称为哈希化,存放记录的数组叫做散列表。

    不管什么样的数据结构,它们基本上都有如下的基本功能:

      ①、如何插入一条新的数据项

      ②、如何寻找某一特定的数据项

      ③、如何删除某一特定的数据项

      ④、如何迭代的访问各个数据项,以便进行显示或其他操作

    图2所示是各种数据结构的异同,大家先有个印象,后面我会逐一去介绍。

      图2

    注:以上图片来源于网络。

     

    展开全文
  • 或许是算法和数据结构,至少很多人这么认为。 本场 Chat 从以下几个方面讨论算法性能: 算法研究科学方法; 编写衡量算法时间性能类 StopWatch; ThreeSum 例子阐述算法方方面面; 衡量时间复杂度一种...

    作为一名程序员,大家有没有想过:编码最本质的知识是什么?或许是算法和数据结构,至少很多人这么认为。

    本场 Chat 从以下几个方面讨论算法的性能:

    1. 算法研究的科学方法;
    2. 编写衡量算法的时间性能类 StopWatch;
    3. ThreeSum 的例子阐述算法的方方面面;
    4. 衡量时间复杂度的一种简单度量:波浪线表示;
    5. 一些典型的 Order of Growth, 比如 log2n, n, nlog2n, n2 , n3;
    6. 分析 Java 中各种类型的内存消耗,包括原生类型,对象类型,字符串和数组。

    1. 科学方法(Scientific method)

    以下 5 个步骤总结了此方法,依次为如下,我们设计的实验必须是可以重现的,我们形成的假设必须是具有真伪的。

    enter image description here

    2. 时间度量

    测试程序运行的精确时间有时是困难的,但是我们有许多辅助工具。在这里,我们简化程序运行时间的模型,考虑各种输入情况,并测试每种情况下的运行时间,编写的这个程序名称为:Stopwatch.java,如下所示:

     1 public class Stopwatch {  2 3    private final long start; 4 5    public Stopwatch() { 6        start = System.currentTimeMillis(); 7    }  8 9    public double elapsedTime() {10        long now = System.currentTimeMillis();11        return (now - start) / 1000.0;12    }1314    public static void main(String[] args) {15        int n = Integer.parseInt(args[0]);1617        // sum of square roots of integers from 1 to n using Math.sqrt(x).18        Stopwatch timer1 = new Stopwatch();19        double sum1 = 0.0;20        for (int i = 1; i <= n; i++) {21            sum1 += Math.sqrt(i);22        }23        double time1 = timer1.elapsedTime();24        StdOut.printf("%e (%.2f seconds)\n", sum1, time1);2526        // sum of square roots of integers from 1 to n using Math.pow(x, 0.5).27        Stopwatch timer2 = new Stopwatch();28        double sum2 = 0.0;29        for (int i = 1; i <= n; i++) {30            sum2 += Math.pow(i, 0.5);31        }32        double time2 = timer2.elapsedTime();33        StdOut.printf("%e (%.2f seconds)\n", sum2, time2);34    }35} 

    对于大多数程序,首先我们能想到的量化观察是它们有问题的大小(problem size)区别,这个表征了计算复杂度或计算难度。

    一般地,问题大小既可以指通过输入数据的大小,也可以指通过命令行参数输入值。

    直觉上,运行时间应该会随着问题大小而增加,但是增加的程度怎么度量,这是我们编程运行程序时常遇到的问题。

    3. ThreeSum 例子

    为了阐述方法,我们先引入一个具体的编程问题:ThreeSum,它是在给定的含有 n 个元素的数组中找出三元组之和等于 0 的个数。

    这个问题最简单的一个解法:枚举。代码如下:

     1 public class ThreeSum { 2 3    // print distinct triples (i, j, k) such that a[i] + a[j] + a[k] = 0 4    public static void printAll(int[] a) { 5        int n = a.length; 6        for (int i = 0; i < n; i++) { 7            for (int j = i+1; j < n; j++) { 8                for (int k = j+1; k < n; k++) { 9                    if (a[i] + a[j] + a[k] == 0) {10                        StdOut.println(a[i] + " " + a[j] + " " + a[k]);11                    }12                }13            }14        }15    } 1617    // return number of distinct triples (i, j, k) such that a[i] + a[j] + a[k] = 018    public static int count(int[] a) {19        int n = a.length;20        int count = 0;21        for (int i = 0; i < n; i++) {22            for (int j = i+1; j < n; j++) {23                for (int k = j+1; k < n; k++) {24                    if (a[i] + a[j] + a[k] == 0) {25                        count++;26                    }27                }28            }29        }30        return count;31    } 3233    public static void main(String[] args)  { 34        int[] a = StdIn.readAllInts();35        Stopwatch timer = new Stopwatch();36        int count = count(a);37        StdOut.println("elapsed time = " + timer.elapsedTime());38        StdOut.println(count);39    } 40} 

    我们在这里主要关心的是输入数据大小与运行时长的关系。我们循着如下的思路分析两者间的关系。

    3.1 加倍假设

    加倍假设(Doubling hypothesis)对于大量的程序而言,我们能很快地形成如下假设:假如输入数据的个数加倍,运行时间怎么变化。

    3.2 经验分析

    经验分析(Empirical analysis)一种简单的实现加倍假设的方法是使输入数据的个数加倍,然后观察对运行时长的影响。

    如下所示为一个简单的通过加倍输入个数,测试运行时长的程序:DoublingTest.Java。

     1public class DoublingTest { 2 3    public static double timeTrial(int n) { 4        int[] a = new int[n]; 5        for (int i = 0; i < n; i++) { 6            a[i] = StdRandom.uniform(2000000) - 1000000; 7        } 8        Stopwatch s = new Stopwatch(); 9        ThreeSum.count(a);10        return s.elapsedTime();11    }121314    public static void main(String[] args) { 15        StdOut.printf("%7s %7s %4s\n", "size", "time", "ratio");16        double previous = timeTrial(256);17        for (int n = 512; true; n += n) {18            double current = timeTrial(n);19            StdOut.printf("%7d %7.2f %4.2f\n", n, current, current / previous);20            previous = current;21        } 22    } 23}

    再回到 ThreeSum.java 程序中,我们生成一系列随机数,填充到输入数组中,每一个时步都加倍输入元素个数,然后观察到每一次程序所运行的时间都会大致增加 8 倍,这个就可以让我们下结论说输入数据加倍后运行时间增加了 8 倍。

    如下图中左侧视图所示,当输入大小为 4K 时,运行时长为 64T,当输入带下为 8K 时,运行时长变为原来的 8 倍:512T。

    enter image description here

    Log–logplot:绘制 log 图也是衡量运行时间的一种方法,因为是log级别的,所以与上面说的经验公式没有本质区别,如图右所示,取完对数后,输入数据大小与运行时间的对数变为线性关系。

    3.3 数学分析

    总的运行时长受到两个主要指标影响:

    • 执行每一个语句的成本
    • 执行每一个语句的次数

    其中,第一个是系统属性,后面一个是算法的属性。假如我们知道了程序中所有指令的这两个属性值,那么两者相乘求和后便是整个程序的运行时间。

    主要的挑战是确定第二个指标,即语句的执行次数。一些语句是很容易分析出执行次数,比如将 count 设置初始值为 0,在 ThreeSum.count() 中仅仅执行一次。

    但是,有些需要更高层次的推理演算才能得到语句的执行频次,比如 if 语句被执行的次数恰好为:n(n-1)(n-2)/6。

    4. 波浪线符号

    我们用波浪线符号形成更加简单的近似表示。首先,我们用数学表达式的主项作为波浪线的近似表示。写作:∼g(n) 。表示为算法的时间复杂度,当它被 f(n) 除的时候,随着 n 的增大,g(n) 接近1。我们也可以写 g(n) ~ f(n) 表示,随着 n 的增大g(n)/f(n) 接近 1。

    在这个表示下,我们忽略表达式中次要项。例如,在 ThreeSum.count() 中的 if 语句的执行次数用波浪线表示为:∼n3/6。因为 n(n-1)(n-2)/6 = n3/6 –n2/2+ n/3,当被n3/6 相除时,随着 n 的增长,等于1。

    我们专注于那些执行次数最多的指令,有时指内层循环。在 ThreeSum.java 程序中,我们可以合理地推理出内层循环以外的指令相对来说不重要。

    5. 增长的阶数(order of growth)

    分析一个程序的运行时长时,关键的一个点是大多数程序的运行时长满足关系:T(n) ~cf(n),此处c是一个常数,f(n) 是一个关于时间的增长阶次函数。对于一些典型的程序,f(n) 是一个函数,例如: log2n、n、nlog2n、n2、n3。

    再看 ThreeSum.java 程序,增长的阶数等于 n3,常数 c 的值取决于执行指令的成本和次数的细节上,但是我们通常不需要算出精确值。刚才说加倍输入数据个数,运行时长变为原来 8 倍,具体推导公式如下:

    enter image description here

    下面详细列出程序的增长阶数的典型例子:

    enter image description here

    6. 内存消耗

    除了需要考虑时间成本,我们也要注意内存消耗。内存消耗在 Java 程序中很好地被定义,但是 Java 程序可以编译在各种不同配置环境的计算设备上,内存消耗因实现方式不同而不同,在这里讨论 Java 中三种类型的内存消耗。

    6.1 原生类型(Primitive types)

    因为 java int 数据类型是整数值的集合,取值范围位于:−2,147,483,648 ~ 2,147,483,647,所占的字节数为 4 个。如下图所示为主要原生类型所占的内存字节数:

    enter image description here

    6.2 对象

    对象(objects)为了确定一个对象的内存消耗,我们需要求以下两者的和:

    • 每一个实例的内存消耗
    • 每一个对象关联的头部消耗,典型的是 8 个字节

    例如,一个复数对象消耗内存为 32 个字节,其中 16 个字节被头部所占,另外,每个 double 变量各占 8 个字节。

    enter image description here

    对一个对象的引用通常消耗 8 个字节的内存。当一个数据类型包含一个对象的引用时,我们必须单独分配 8 个字节用于存储引用关系,每个对象的头部消 耗16 个字节,还包括此对象的实例变量所耗内存。

    6.3 数组和字符串(Arraysand strings)

    在 Java 中,数组是通过 objects 被实现的,典型的实现方法:带有 2 个实例变量,一个指针指向第一个元素的首地址,另一个指向元素长度。对于原生类型,含有 n 个元素的数组用 24 字节的头部信息,另外包括存储一个元素需要的字节数乘以元素个数。典型的例子如下:

    enter image description here


    本文首发于GitChat,未经授权不得转载,转载需与GitChat联系。

    阅读全文: http://gitbook.cn/gitchat/activity/5b35856c18887615fe36ffa7

    您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

    FtooAtPSkEJwnW-9xkCLqSTRpBKX

    展开全文
  • Redis 的数据类型可谓是 Redis 的精华所在,同样的数据类型,例如字符串存储不同的值对应的实际存储结构也是不同,当你存储的 int 值是实际的存储结构也是 int,如果是短字符串(小于 44 字节)实际存储的结构为 ...

    Redis 的数据类型可谓是 Redis 的精华所在,同样的数据类型,例如字符串存储不同的值对应的实际存储结构也是不同,当你存储的 int 值是实际的存储结构也是 int,如果是短字符串(小于 44 字节)实际存储的结构为 embstr,长字符串对应的实际存储结构是 raw,这样设计的目的是为了更好的节约内存。

    我们本文的面试题是 Redis 有哪些数据类型?

    典型回答

    Redis 最常用的数据类型有 5 种:String(字符串类型)、Hash(字典类型)、List(列表类型)、Set(集合类型)、ZSet(有序集合类型)。

    1.字符串类型

    字符串类型(Simple Dynamic Strings 简称 SDS),译为:简单动态字符串,它是以键值对 key-value 的形式进行存储的,根据 key 来存储和获取 value 值,它的使用相对来说比较简单,但在实际项目中应用非常广泛。

    字符串的使用如下:

    127.0.0.1:6379> set k1 v1 # 添加数据 
    OK
    127.0.0.1:6379> get k1 # 查询数据
    "v1"
    127.0.0.1:6379> strlen k1 # 查询字符串的长度
    (integer) 5
    

    我们也可以在存储字符串时设置键值的过期时间,如下代码所示:

    127.0.0.1:6379> set k1 v1 ex 1000 # 设置 k1 1000s 后过期(删除)
    OK
    

    我们还可以使用 SDS 来存储 int 类型的值,并且可以使用 incr 指令和 decr 指令来操作存储的值 +1 或者 -1,具体实现代码如下:

    127.0.0.1:6379> get k1 # 查询 k1=3
    "3"
    127.0.0.1:6379> incr k1 # 执行 +1 操作
    (integer) 4
    127.0.0.1:6379> get k1 # 查询 k1=4
    "4"
    127.0.0.1:6379> decr k1 # 执行 -1 操作
    (integer) 3
    127.0.0.1:6379> get k1 # 查询 k1=3
    "3"
    

    字符串的常见使用场景:

    • 存放用户(登录)信息;
    • 存放文章详情和列表信息;
    • 存放和累计网页的统计信息(存储 int 值)。

    ……

    2.字典类型

    字典类型 (Hash) 又被成为散列类型或者是哈希表类型,它是将一个键值 (key) 和一个特殊的“哈希表”关联起来,这个“哈希表”表包含两列数据:字段和值。例如我们使用字典类型来存储一篇文章的详情信息,存储结构如下图所示: 哈希表存储结构.png 同理我们也可以使用字典类型来存储用户信息,并且使用字典类型来存储此类信息就无需手动序列化和反序列化数据了,所以使用起来更加的方便和高效。

    字典类型的使用如下:

    127.0.0.1:6379> hset myhash key1 value1 # 添加数据
    (integer) 1
    127.0.0.1:6379> hget myhash key1 # 查询数据
    "value1"
    

    字典类型的数据结构,如下图所示:

    Redis-HashType-02.png

    通常情况下字典类型会使用数组的方式来存储相关的数据,但发生哈希冲突时才会使用链表的结构来存储数据。

    3.列表类型

    列表类型 (List) 是一个使用链表结构存储的有序结构,它的元素插入会按照先后顺序存储到链表结构中,因此它的元素操作 (插入和删除) 时间复杂度为 O(1),所以相对来说速度还是比较快的,但它的查询时间复杂度为 O(n),因此查询可能会比较慢。

    列表类型的使用如下:

    127.0.0.1:6379> lpush list 1 2 3 # 添加数据
    (integer) 3
    127.0.0.1:6379> lpop list # 获取并删除列表的第一个元素
    1
    

    列表的典型使用场景有以下两个:

    • 消息队列:列表类型可以使用 rpush 实现先进先出的功能,同时又可以使用 lpop 轻松的弹出(查询并删除)第一个元素,所以列表类型可以用来实现消息队列;
    • 文章列表:对于博客站点来说,当用户和文章都越来越多时,为了加快程序的响应速度,我们可以把用户自己的文章存入到 List 中,因为 List 是有序的结构,所以这样又可以完美的实现分页功能,从而加速了程序的响应速度。

    4.集合类型

    集合类型 (Set) 是一个无序并唯一的键值集合。

    集合类型的使用如下:

    127.0.0.1:6379> sadd myset v1 v2 v3 # 添加数据
    (integer) 3
    127.0.0.1:6379> smembers myset # 查询集合中的所有数据
    1) "v1"
    2) "v3"
    3) "v2"
    

    集合类型的经典使用场景如下:

    • 微博关注我的人和我关注的人都适合用集合存储,可以保证人员不会重复;
    • 中奖人信息也适合用集合类型存储,这样可以保证一个人不会重复中奖。

    集合类型(Set)和列表类型(List)的区别如下:

    • 列表可以存储重复元素,集合只能存储非重复元素;
    • 列表是按照元素的先后顺序存储元素的,而集合则是无序方式存储元素的。

    5.有序集合类型

    有序集合类型 (Sorted Set) 相比于集合类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序结合的元素值,一个是排序值。有序集合的存储元素值也是不能重复的,但分值是可以重复的。

    当我们把学生的成绩存储在有序集合中时,它的存储结构如下图所示:

    学生存储值.png

    有序集合类型的使用如下:

    127.0.0.1:6379> zadd zset1 3 golang 4 sql 1 redis # 添加数据
    (integer) 3
    127.0.0.1:6379> zrange zset 0 -1 # 查询所有数据
    1) "redis"
    2) "mysql"
    3) "java"
    

    有序集合的经典使用场景如下:

    • 学生成绩排名;
    • 粉丝列表,根据关注的先后时间排序。

    考点分析

    关于 Redis 数据类型的这个问题,对于大多数人既熟悉又陌生,熟悉的是每天都在使用 Redis 存取数据,陌生的是对于 Redis 的数据类型知之甚少,因为对于普通的开发工作使用字符串类型就可以搞定了。但是善用 Redis 的数据类型可以到达意想不到的效果,不但可以提高程序的运行速度又可以减少业务代码,可谓一举两得。

    例如我们经常会把用户的登录信息存储在 Redis 中,但通常的做法是先将用户登录实体类转为 JSON 字符串存储在 Redis 中,然后读取时先查询数据再反序列化为 User 对象,这个过程看似没什么问题,但我们可以有更优的解决方案来处理此问题,比如我们可以使用 Hash 存储用户的信息,这样就无需序列化的过程了,并且读取之后无需反序列化,直接使用 Map 来接收就可以了,这样既提高了程序的运行速度有省去了序列化和反序列化的业务代码。

    与此知识点相关的面试题还有以下几个:

    • 有序列表的实际存储结构是什么?
    • 除了五种基本的数据类型之外,还有什么数据类型?

    知识扩展

    有序列表的内部实现

    有序集合是由 ziplist (压缩列表) 或 skiplist (跳跃表) 组成的。

    ziplist 介绍

    当数据比较少时,有序集合使用的是 ziplist 存储的,如下代码所示:

    127.0.0.1:6379> zadd myzset 1 db 2 redis 3 mysql
    (integer) 3
    127.0.0.1:6379> object encoding myzset
    "ziplist"
    

    从结果可以看出,有序集合把 myset 键值对存储在 ziplist 结构中了。 有序集合使用 ziplist 格式存储必须满足以下两个条件:

    • 有序集合保存的元素个数要小于 128 个;
    • 有序集合保存的所有元素成员的长度都必须小于 64 字节。

    如果不能满足以上两个条件中的任意一个,有序集合将会使用 skiplist 结构进行存储。 接下来我们来测试以下,当有序集合中某个元素长度大于 64 字节时会发生什么情况? 代码如下:

    127.0.0.1:6379> zadd zmaxleng 1.0 redis
    (integer) 1
    127.0.0.1:6379> object encoding zmaxleng
    "ziplist"
    127.0.0.1:6379> zadd zmaxleng 2.0 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    (integer) 1
    127.0.0.1:6379> object encoding zmaxleng
    "skiplist"
    

    通过以上代码可以看出,当有序集合保存的所有元素成员的长度大于 64 字节时,有序集合就会从 ziplist 转换成为 skiplist。

    小贴士:可以通过配置文件中的 zset-max-ziplist-entries(默认 128)和 zset-max-ziplist-value(默认 64)来设置有序集合使用 ziplist 存储的临界值。

    skiplist 介绍

    skiplist 数据编码底层是使用 zset 结构实现的,而 zset 结构中包含了一个字典和一个跳跃表,源码如下:

    typedef struct zset {
        dict *dict;
        zskiplist *zsl;
    } zset;
    

    跳跃表的结构如下图所示: 有序集合-跳跃表.png

    根据以上图片展示,当我们在跳跃表中查询值 32 时,执行流程如下:

    • 从最上层开始找,1 比 32 小,在当前层移动到下一个节点进行比较;
    • 7 比 32 小,当前层移动下一个节点比较,由于下一个节点指向 Null,所以以 7 为目标,移动到下一层继续向后比较;
    • 18 小于 32,继续向后移动查找,对比 77 大于 32,以 18 为目标,移动到下一层继续向后比较;
    • 对比 32 等于 32,值被顺利找到。

    从上面的流程可以看出,跳跃表会想从最上层开始找起,依次向后查找,如果本层的节点大于要找的值,或者本层的节点为 Null 时,以上一个节点为目标,往下移一层继续向后查找并循环此流程,直到找到该节点并返回,如果对比到最后一个元素仍未找到,则返回 Null。

    高级数据类型

    除了有 5 大基本数据类型外,还有 GEO(地理位置类型)、HyperLogLog(统计类型)、Stream(流类型)。

    GEO(地理位置类型)是 Redis 3.2 版本中新增的数据类型,用于存储和查询地理位置的,使用它我们可以实现查询附近的人或查询附近的商家等功能(这部分的内容会在后面的章节单独讲解)。

    Stream(流类型)是 Redis 5.0 版本中新增的数据类型,因为使用 Stream 可以实现消息消费确认的功能,使用“xack key group-key ID”命令,所以此类型的出现给 Redis 更好的实现消息队列提供了很大的帮助。

    HyperLogLog(统计类型)是本文介绍的重点,HyperLogLog (下文简称为 HLL) 是 Redis 2.8.9 版本添加的数据结构,它用于高性能的基数 (去重) 统计功能,它的缺点就是存在极低的误差率。

    HLL 具有以下几个特点:

    • 能够使用极少的内存来统计巨量的数据,它只需要 12K 空间就能统计 2^64 的数据;
    • 统计存在一定的误差,误差率整体较低,标准误差为 0.81%;
    • 误差可以被设置辅助计算因子进行降低。

    HLL 的命令只有 3 个,但都非常的实用,下面分别来看。

    1.添加元素

    127.0.0.1:6379> pfadd key "redis"
    (integer) 1
    127.0.0.1:6379> pfadd key "java" "sql"
    (integer) 1
    

    相关语法: pfadd key element [element ...] 此命令支持添加一个或多个元素至 HLL 结构中。

    2.统计不重复的元素

    127.0.0.1:6379> pfadd key "redis"
    (integer) 1
    127.0.0.1:6379> pfadd key "sql"
    (integer) 1
    127.0.0.1:6379> pfadd key "redis"
    (integer) 0
    127.0.0.1:6379> pfcount key
    (integer) 2
    

    从 pfcount 的结果可以看出,在 HLL 结构中键值为 key 的元素, 有 2 个不重复的值:redis 和 sql,可以看出结果还是挺准的。 相关语法: pfcount key [key ...]
    此命令支持统计一个或多个 HLL 结构。

    3.合并一个或多个 HLL 至新结构

    新增 k 和 k2 合并至新结构 k3 中,代码如下:

    127.0.0.1:6379> pfadd k "java" "sql"
    (integer) 1
    127.0.0.1:6379> pfadd k2 "redis" "sql"
    (integer) 1
    127.0.0.1:6379> pfmerge k3 k k2
    OK
    127.0.0.1:6379> pfcount k3
    (integer) 3
    

    相关语法:pfmerge destkey sourcekey [sourcekey ...] ** pfmerge 使用场景:当我们需要合并两个或多个同类页面的访问数据时,我们可以使用 pfmerge 来操作。

    总结

    本文我们介绍了 Redis 的 5 大基础数据类型的概念以及简单的使用:String(字符串类型)、Hash(字典类型)、List(列表类型)、Set(集合类型)、ZSet(有序集合类型),还深入的介绍了 ZSet 的底层数据存储结构:ziplist (压缩列表) 或 skiplist (跳跃表)。除此之外我们还介绍了 Redis 中的提前 3 个高级的数据类型:GEO(地理位置类型)用于实现查询附近的人、HyperLogLog(统计类型)用于高效的实现数据的去重统计(存在一定的误差)、Stream(流类型)主要应用于消息队列的实现。

    展开全文
  • 文章目录对比关系型数据库NoSQL 数据库特性良好的扩展性,容易通过集群部署读写性能高,支持大数据量不限制表结构,灵活的数据模型NoSQL 数据库应用Key-Value 数据库文档型数据库列存储数据库图形数据库 对比关系型...

    之前介绍了数据库读写分离和分库分表相关知识,都是针对关系型数据库的,即通常说的 RDBMS。除了关系型数据库,NoSQL 在项目开发中也有着越来越重要的作用,与此同时,NoSQL 相关的内容也是面试的常客。今天我们一起来看下 NoSQL 数据库有哪些应用。

    对比关系型数据库

    在介绍 NoSQL 数据库之前,先回顾下关系型数据库。还记得 SQL 语言的全称吗?Structured Query Language,也就是结构化查询语言,结构化查询对应的存储实现是关系型数据库,我们熟悉的 MySQL、Oracle 和 SQL Server,都是关系型数据库的代表。

    关系型数据库通过关系模型来组织数据,在关系型数据库当中一个表就是一个模型,一个关系数据库可以包含多个表,不同数据表之间的联系反映了关系约束。

    不知道你是否应用过 ER 图?在早期的软件工程中,数据表的创建都会通过 ER 图来定义,ER 图(Entity Relationship Diagram)称为实体-联系图,包括实体、属性和关系三个核心部分。

    下面是在电商领域中,一个简化的会员、商品和订单的 ER 图:

    在这里插入图片描述

    ER图中的实体采用矩形表示,即数据模型中的数据对象,例如电商业务模型中的会员、商品、订单等,每个数据对象具有不同的属性,比如会员有账户名、电话、地址等,商品有商品名称、价格、库存等属性。不同的数据对象之间又对应不同的关系,比如会员购买商品、创建订单。

    有了 ER 图等的辅助设计,关系型数据库的数据模型可以非常好的描述物理世界,比较方便地创建各种数据约束。

    另外一方面,关系型数据库对事务支持较好,支持 SQL 规范中的各种复杂查询,比如 join、union 等操作。正是由于对 SQL 规范的支持,也使得关系型数据库对扩展不友好,比较难进行分布式下的集群部署。

    NoSQL 数据库特性

    NoSQL 数据库是在 SQL 的基础上发展的,对 NoSQL 的具体解释,你可以认为是 Not Only SQL,也可以认为是 Non-Relational SQL。

    NoSQL 对应非关系型数据库,不同于传统的关系型数据库,如果说关系型数据库是武侠小说中的正统功夫,NoSQL 数据库就是野路子,少了很多约束,也就不拘一格、自成一派。那么对比关系型数据库,NoSQL 型数据库有哪些优点呢?

    良好的扩展性,容易通过集群部署

    关系型数据库在进行扩展时,要考虑到如何分库分表、扩容等,各种实现方案都比较重,对业务侵入较大。NoSQL 数据库去掉了关系型数据库的关系特性,天生对集群友好,这样就非常容易扩展。

    读写性能高,支持大数据量

    关系型数据库对一致性的要求较高,数据表的结构复杂,读写的性能要低于非关系型数据库。另外一方面,部分 NoSQL 数据库采用全内存实现,更适合一些高并发的访问场景。

    不限制表结构,灵活的数据模型

    应用关系型数据库,需要通过 DML 语句创建表结构,数据表创建以后,增删字段需要重新修改表结构。如果使用 NoSQL,一般不需要事先为数据建立存储结构和字段,可以存储各种自定义的数据。

    NoSQL 数据库有这么多优点,但是在项目开发中,关系型数据库和非关系型数据库不是对立的,而是相辅相成的。从性能的角度来讲,NoSQL 数据库的性能优于关系型数据库,从持久化角度,关系型数据库优于 NoSQL 数据库。

    专栏最开始提到了 CAP 理论,从 CAP 的角度,NoSQL 数据库一般提供弱一致性的保证,实现最终一致性,也就是关系型数据库强调 CP 模型,而 NoSQL 关注的是 AP 模型,同时应用 NoSQL 和关系型数据库,可以满足高性能的基础上,同时保证数据可靠性。

    NoSQL 数据库应用

    非关系型数据库有很多类型,应用的侧重点也不同,可以从以下几个角度进行分类。

    Key-Value 数据库

    Key-Value 存储就是我们熟悉的 Map 结构,支持高性能的通过 Key 定位和存储。通常用来实现缓存等应用,典型的有 Redis 和 Memcached。

    以 Redis 为例,作为应用最多的非关系型数据库之一,Redis 可以说是日常工作中的一把瑞士军刀。

    从性能的角度,为了提高读写效率,Redis 在最开始的版本中一直使用单线程模型,避免上下文切换和线程竞争资源,并且采用了 IO 多路复用的技术,提升了性能,另外在最近的版本更新中,Redis也开始支持多线程处理,感兴趣的同学可以查看相关的资料了解。

    从存储结构的角度,Redis 支持多种数据结构,得益于这些,Redis 有丰富的应用场景,并且针对不同的数据规模等,Redis 采取多种内存优化方式,尽量减少内存占用。比如,List 结构内部有压缩列表和双向链表两种实现,在数据规模较小时采用 ZipList 实现,特别是在新的版本更新中,又添加了 QuickList 的实现,减少内存的消耗。

    从高可用的角度,作为一个内存数据库,Redis实现了AOF和RDB的数据持久化机制,另外,Redis支持了多种集群方式,包括主从同步,Sentinel和Redis Cluster等机制,提高了整体的数据安全和高可用保障。

    文档型数据库

    文档型数据库可以存储结构化的文档,比如 JSON 或者 XML,从这个角度上看,文档型数据库比较接近关系型数据库。但是对比关系型数据库,文档性数据库中不需要预先定义表结构,并且可以支持文档之间的嵌套,典型的比如 MongoDB,这一点和关系型数据库有很大的不同。

    以 MongoDB 为例,采用了基于 JSON 扩展的 BSON 存储结构,可以进行自我描述,这种灵活的文档类型,特别适合应用在内容管理系统等业务中。MongoDB 还具备非常优秀的扩展能力,对分片等集群部署的支持非常全面,可以快速扩展集群规模。

    列存储数据库

    列式数据库被用来存储海量数据,比如 Cassandra、HBase 等,特点是大数据量下读写速度较快、可扩展性强,更容易进行分布式部署。

    以 HBase 为例,HBase 支持海量数据的读写,特别是写入操作,可以支持 TB 级的数据量。列式数据库通常不支持事务和各种索引优化,比如 HBase 使用 LSM 树组织数据,对比 MySQL 的 B+ 树,在高并发写入时有更好的性能。

    图形数据库

    在一些特定的应用场景可以应用特殊的数据库,比如图形数据库。在学习数据结构时我们知道,社交网络中的用户关系可以使用图来存储,于是诞生了一些图形数据库,可以方便地操作图结构的相关算法,比如最短路径、关系查找等。

    图形数据库在一般的工程开发中应用较少,笔者使用过neo4j,感兴趣的同学可以去了解一下。

    展开全文
  • 虽然它的主要内容看起来是数组,链表,栈,队列,集合,树,图,这些典型的数据结构。但这并不是它的任务,他的任务是教你分析计算复杂度,根据问题的特点构造或者选择现有的数据结构合理的解决问题。这些结构都是...
  • HBase 是典型的 NoSQL 数据库,通常被描述成稀疏的、分布式的、持久化的,由行键、列键和时间戳进行索引的多维有序映射数据库,主要用来存储非结构化和半结构的数据。因为 HBase 基于 Hadoop 的 HDFS 完成分布式...
  • C#数据结构

    2013-12-10 11:49:54
    储在计算机中的数据进行操作,可以有哪些操作,如何实现这些操作以及如何对 同一问题的不同操作方法进行评价;四是必须理解每种数据结构的性能特征,以 便选择一个适合于某个特定问题的数据结构。这些问题就是数据...
  • 在上一篇中,我们讲解了二叉堆,今天堆排序算法主要就是依赖于二叉堆来完成,不清楚二叉堆是什么鬼,可以看下:【算法与数据结构】二叉堆是什么鬼?用辅助数组来实现堆排序算法假如给你一个二叉堆,根据二叉堆...
  • 数据结构常用的数据结构算法的五大特性数据结构和数据类型的区别线性结构和非线性结构的区别比较顺序存储和链式存储的优缺点数组和链表的区别对比栈和队列数据结构的特点,并举例它们的典型应用什么是栈溢出,并举个...
  • 树呢就是一种典型的非线性结构,经常有着一对多,多对多的关系,那关于树的一些基本知识有哪些呢? 树 树是多个节点的有限集。 有且仅有一个根节点,其余的节点组成的集合叫根的子树 根节点和叶子节点 树的末端...
  • 算法数据结构

    2012-06-02 16:55:00
    现在要给出一种解决方案,对于用户输入单词,根据给定字典找出输入单词有哪些兄弟单词。请具体说明数据结构和查询流程,要求时间和空间效率尽可能地高。字典树的典型应用2、系统中维护了若干数据项,我们对数据...
  • 什么是数据结构? 字面意思就是研究数据的一种方法,...常用的有哪些数据结构? (1)集合(Set) 和数学的集合一样,具有唯一性,确定性,无序性。 (2)线性结构 典型的数据库二维表,一对一的关系。 (3)树形结构
  • 在上一篇中,我们讲解了二叉堆,今天堆排序算法主要就是依赖于二叉堆来完成,不清楚二叉堆是什么鬼,可以看下:【算法与数据结构】二叉堆是什么鬼?用辅助数组来实现堆排序算法假如给你一个二叉堆,根据二叉堆...
  • HBase优势有哪些

    2021-02-04 16:22:32
    HBase 是典型的 NoSQL 数据库,通常被描述成稀疏的、分布式的、持久化的,由行键、列键和时间戳进行索引的多维有序映射数据库,主要用来存储非结构化和半结构的数据。因为 HBase 基于 Hadoop 的 HDFS 完成分布式...
  • 最后,我们了这个表(数据结构),肯定要用它,那么就是要对这张表中记录进行查询,修改,删除等操作,对这个表可以进行哪些操作以及如何实现这些操作就是数据运算问题了。 1.3 常用存储表示方法哪几种?
  • 高效Python爬虫框架有哪些?1.ScrapyScrapy是一个为了爬取网站数据,提取结构数据而编写应用框架,可以应用在包括数据挖掘,信息处理或存储历史数据等一系列程序中。2.PySpiderPyspider 是一个用python实现...
  • 一、算法概述 1、什么是算法?  可以理解为:对特定问题求解步骤,由一些基本运算和规定运算顺序而构成,代表了用系统方法来描述解决问题一种... 按算法应用来分:基本算法、数据结构相关算法、几何
  • Python是现在非常流行的编程语言,而爬虫则是Python语言中最典型的应用,下面神龙给大家分享几种高效的Python爬虫框架,看看你是否都用过呢? 1.Scrapy Scrapy是一个为了爬取网站数据,提取结构数据而编写的应用...
  • 关系模型指的就是二维表格模型,关系型数据库最典型的数据结构就是表,是由二维表及其之间的联系所组成的一个数据组织。 优点: 1. 容易理解: 二维表结构是非常贴近逻辑世界的一个概念,关系模型相对网状、层次等...
  • 本文已收录GitHub,更互联网大厂面试真题,面试攻略,高效学习资料等 本文主要和大家分享一下连接表...关系型数据库的典型数据结构就是数据表,这些数据表组成都是结构化(Structured)。你可以把关系模型理解..
  • Python是现在非常流行的编程语言,而爬虫则是Python语言中最典型的应用,下面神龙给大家分享几种高效的Python爬虫框架,看看你是否都用过呢?1.ScrapyScrapy是一个为了爬取网站数据,提取结构数据而编写的应用框架...
  • 本书论述在设计和建造数据仓库中涉及的所有主要问题,论述分析型环境(决策支持系统环境)以及在这种环境中的数据构造。...10.1.5 典型的数据仓库设计复查 176 10.2 小结 185 附录 186 技术词汇 215 参考文献 222
  • 本文主要和大家分享一下连接表操作。在分享之前,我想先给大家介绍下连接(JOIN)在 SQL 中重要性。我们知道 SQL 英文全称叫做 ...关系型数据库的典型数据结构就是数据表,这些数据表组成都是结构化(Structu...
  • 当前计算机主要是基于冯诺依曼体系结构设计的,下图为典型的冯诺依曼计算机结构框架图。 一、冯·诺依曼体系结构计算机的组成——硬件+软件 一)硬件 1.存储器分为外存储器和内存储器,这里所说的主要...
  • 先进者先出,这就是典型的“队列”结构。 支持两个操作:入队enqueue(),放一个数据到队尾;出队dequeue(),从队头取一个元素。 所以,和栈一样,队列也是一种操作受限的线性表。 顺序队列 链式队列 循环队列 ...
  • Python工程师需要学习哪些知识?Python开发需要熟悉Linux系统及Django或Tornado、Flask等开发框架;会灵活运用JavaScript、HTML前端...算法和数据结构。 Python工程师需要学习内容: 一、Python基础 熟悉Linu...

空空如也

空空如也

1 2 3 4 5 ... 12
收藏数 237
精华内容 94
关键字:

典型的数据结构有哪些

数据结构 订阅