leveldb使用指南

这篇文章是levelDB官方文档的译文,原文地址:LevelDB library documentation

这篇文章主要讲leveldb接口使用和注意事项。 
leveldb是一个持久型的key-value数据库。key,value可以是任意的字节数组,key之间是有序的。key的比较函数可以由用户指定。

1. 打开数据库

leveldb使用文件系统目录名作为name,并把数据库所有内容都存储在这个目录中。这是个打开数据库,并且指定如果数据库不存在就新建的例子:

  #include <cassert>#include "leveldb/db.h"leveldb::DB* db;leveldb::Options options;options.create_if_missing = true;leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db);assert(status.ok());...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

如果想在创建数据库时发现已经存在就报错,那么调用leveldb::DB::Open之前添加下面这一行:

  options.error_if_exists = true;
  • 1
  • 1

2. 返回值状态Status

你也许已经注意到上面的leveldb::Status type . 这种类型是leveldb大多数函数的返回值,函数的返回值有可能是error。可以通过判断result是不是ok来判断:

   leveldb::Status s = ...;if (!s.ok()) cerr << s.ToString() << endl;
  • 1
  • 2
  • 1
  • 2

3. 关闭数据库

当数据库操作完成,关闭数据库只需要删除数据库对象:

  ... open the db as described above ...... do something with db ...delete db;
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

4. 读和写

数据库提供Put, Delete, and Get函数来修改查询数据库。下面的例子把key1的值赋给key2:

  std::string value;leveldb::Status s = db->Get(leveldb::ReadOptions(), key1, &value);if (s.ok()) s = db->Put(leveldb::WriteOptions(), key2, value);if (s.ok()) s = db->Delete(leveldb::WriteOptions(), key1);
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

5. 原子性更新

如果上面的代码中,再给key2赋值之后,删除key1之前,进程退出了,那么key1 and key2就会有相同的值。这种情况可以使用WriteBatch class来原子性的进行一系列的更新:

  #include "leveldb/write_batch.h"...std::string value;leveldb::Status s = db->Get(leveldb::ReadOptions(), key1, &value);if (s.ok()) {leveldb::WriteBatch batch;batch.Delete(key1);batch.Put(key2, value);s = db->Write(leveldb::WriteOptions(), &batch);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

WriteBatch是一系列对数据库的更新操作,并且这些批量操作之间有一定的顺序性。注意到我们虽然在给key2赋值之前删除,使用writebtch最终并不会错误的造成vaue丢失。 
撇开writebatch带来的原子性优势,writebatch也能通过把多个更新放在一个批量操里面来加速操作。

6. 同步写

通常情况下,所有的leveldb写操作都是异步的:当leveldb把写操作交个操作系统之后就返回。从操作系统内存到硬盘等持久性存储是异步的。如果在写的时候打开同步写选项,那么只有当数据持久化到硬盘之后才会返回。(On Posix systems, this is implemented by calling either fsync(...) orfdatasync(...) or msync(..., MS_SYNC) before the write operation returns.)

  leveldb::WriteOptions write_options;write_options.sync = true;db->Put(write_options, ...);
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

异步写通常比同步写快1000倍以上。异步写的不足就是当机器宕机时会丢失最后更新的数据。写进程的异常退出并不会造成数据的丢失。

通常情况下异步写能够被妥善的处理。例如,当你在网数据库写大量的数据时,在机器宕机之后能通过重新写一次数据来修复。混合使用同步和异步也是可以的。例如每N次写做一次同步。当机器宕机的时候,只需要重新写最后一次同步写之后的数据。同步写一个新增一个标记来记录上一次同步写的位置。

WriteBatch是一个异步写。一个WriteBatch内部的多个更新操作放在一起也可以使用同步写操作,(i.e., write_options.sync is set to true). 可以通过批量操作降低同步写的消耗。

7. 并发

一个数据库每次只能被一个进程打开。leveldb为了防止误操作需要一个lock。在一个进程内部,同一个leveldb::DB对象可以在这个进程的多个并发线程之间安全的共享。 例如,不同的线程可以写,获取指针,或者读取相同的数据库,而不需要额外的同步操作,因为leveldb自动做了请求的同步。然而,其他的对象,例如迭代器或者WriteBatch,需要外部的同步操作。如果两个线程共享同一个这样的对象,那么他们必须用自己的lock protocal对数据库操作进行保护。这在公共的header文件里有更详细的内容。

8. 迭代器

下面的例子说明了如何输出数据库的所有key-value对:

  leveldb::Iterator* it = db->NewIterator(leveldb::ReadOptions());for (it->SeekToFirst(); it->Valid(); it->Next()) {cout << it->key().ToString() << ": "  << it->value().ToString() << endl;}assert(it->status().ok());  // Check for any errors found during the scandelete it;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

处理[start,limit)范围内的key:

  for (it->Seek(start);it->Valid() && it->key().ToString() < limit;it->Next()) {...}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

逆序处理:(逆序会比顺序慢一些)

  for (it->SeekToLast(); it->Valid(); it->Prev()) {...}
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

9. Snapshots快照

快照在整个key-value存储状态上提供了一个持久性的只读视图。非空的ReadOptions::snapshot提供了一个针对db特定状态的只读视图。如果ReadOptions::snapshot是NULL,那么读操作是在对当前数据库状态的隐式视图上的进行的。

使用DB::GetSnapshot()方法创建Snapshots:

  leveldb::ReadOptions options;options.snapshot = db->GetSnapshot();... apply some updates to db ...leveldb::Iterator* iter = db->NewIterator(options);... read using iter to view the state when the snapshot was created ...delete iter;db->ReleaseSnapshot(options.snapshot);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

如果快照不再需要了,应该使用DB::ReleaseSnapshot接口来释放,这会消除为了维持快照的状态多与操作。

10. Slice分片

上面代码中it->key() and it->value() 调用的返回值都是leveldb::Slice类型的实例,slice是一个包含长度和一个纸箱字节数组的简单结构体。因为我们不需要每次都复制很多的keys和values,所以返回Slice比返回std::string是一个更好的选择。另外,level-db不返回以null结尾的c类型的字符串,是因为leveldb允许key和value中包含'\0'字符。

C++ strings和null-terminated C-style strings可以很容易的转换为Slice:

   leveldb::Slice s1 = "hello";std::string str("world");leveldb::Slice s2 = str;
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

A Slice can be easily converted back to a C++ string:

   std::string str = s1.ToString();assert(str == std::string("hello"));
  • 1
  • 2
  • 1
  • 2

使用slice的时候需要仔细,需要确保在使用slice的时候,他的指针所指向的地址是有效的。例如,下面不正当的使用:

   leveldb::Slice slice;if (...) {std::string str = ...;slice = str;}Use(slice);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在if的作用域外,str已经被销毁了,所以slice所指向的内存地址已经被释放了。

11. Comparators

前面的例子都是使用默认的排序函数,也就是字典序。另外,我们也在打开数据库的时候也可以指定一个排序比较函数。

例如,假设数据库的key由两个数字组成,我们首先用第一个数字排序,第一个数相等时使用第二个数比较。首先,我们先定义一个leveldb::Comparator的子类来实现我们的想法:

  class TwoPartComparator : public leveldb::Comparator {public:// Three-way comparison function://   if a < b: negative result//   if a > b: positive result//   else: zero resultint Compare(const leveldb::Slice& a, const leveldb::Slice& b) const {int a1, a2, b1, b2;ParseKey(a, &a1, &a2);ParseKey(b, &b1, &b2);if (a1 < b1) return -1;if (a1 > b1) return +1;if (a2 < b2) return -1;if (a2 > b2) return +1;return 0;}// Ignore the following methods for now:const char* Name() const { return "TwoPartComparator"; }void FindShortestSeparator(std::string*, const leveldb::Slice&) const { }void FindShortSuccessor(std::string*) const { }};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

使用自定义的comparator创建数据库:

  TwoPartComparator cmp;leveldb::DB* db;leveldb::Options options;options.create_if_missing = true;options.comparator = &cmp;leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db);...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

12. 后向兼容

当数据库创建的时候comparator的Name()函数也附加在数据库上,并且后续每次打开的时候都会进行检查。如果comparator的name变了,leveldb::DB::Open就会失败。因此,当且仅当1,新的key格式和比较函数和现存的数据库不兼容,2,丢弃当前的数据库的所有内容也无所谓。的时候才会修改comparator的name.

不过你仍然可以逐渐的进化key的格式。例如,你可以存储为每个key存储一个版本号,(多数情况下一个字节足够用),当想使用一个新的key格式的时候,a,使用相同的comparator name,b,为新的key格式增加版本号,c,修改comparator函数,能通过key里面的版本来判断怎么解析key。

13. 性能

可以通过修改include/leveldb/options.h里面参数的默认值进行性能调优。

13.1 块大小

leveldb把相邻的key放进同一个block,block是读写数据库时的单元。默认的未压缩block大小是4KB。那些经常对数据库做块读取的应用希望增加块的大小。如果把块大小调小对性能有提升的话,那些经常随机数据库的应用会希望减小块的大小。一般来说,块小于1KB或者大于几MB是无益的。并且,当块比较大的时候数据压缩效率会高一些。

13.2 压缩

每个块在写到持久存储之前是独立进行压缩的。由于默认的压缩算法非常快,所以压缩默认是打开的。并且,对于不可压缩的数据,也会自动停止压缩。在很少的情况下,应用程序可能会完全禁用压缩,但是除非benchmark表明性能有提成否则不建议这么做。

  leveldb::Options options;options.compression = leveldb::kNoCompression;... leveldb::DB::Open(options, name, ...) ....
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

13.3 Cache

数据库的内容存储在文件系统上的一系列文件中。每个文件里面有很多的压缩数据块。如果options.cache是非空的,那么数据库会使用cache来缓存经常使用的未压缩的数据块。

  #include "leveldb/cache.h"leveldb::Options options;options.cache = leveldb::NewLRUCache(100 * 1048576);  // 100MB cacheleveldb::DB* db;leveldb::DB::Open(options, name, &db);... use the db ...delete dbdelete options.cache;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

cache存的是未压缩的数据,因此,cache需要根据应用层的数据大小,计算应该缓存的数据量。当进行批量读的时候,应用可能会希望禁用cache,以防止批量读的数据不要把已经缓存的内容替换掉。一个顺序指针可以满足要求:

  leveldb::ReadOptions options;options.fill_cache = false;leveldb::Iterator* it = db->NewIterator(options);for (it->SeekToFirst(); it->Valid(); it->Next()) {...}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

13.4 Key Layout

数据传输单位和缓存单位都是数据块。根据数据库排序算法,相邻的key一般情况下会放在同一个数据块里面。因此,通过把经常一起访问的相邻key放在一个block里面,把不经常使用的key放在分隔的块里面,应用程序可以提升性能。

例如,我们在leveldb之上实现一个文件系统。我们希望entry的类型会这样存储:

   filename -> permission-bits, length, list of file_block_idsfile_block_id -> data
  • 1
  • 2
  • 1
  • 2

我们希望文件名的前缀是某个字符,如’/’,file_block_id的前缀是不同的字符,例如’0’,这样我们就能只浏览metadate,而不用强制缓存大量的文件内容了。

13.5 过滤器Filters

由于leveldb在磁盘上组织数据的方式,一个Get()调用可能导致多次磁盘读操作。可选的FilterPolicy机制可以潜在的减少磁盘读操作。

   leveldb::Options options;options.filter_policy = NewBloomFilterPolicy(10);leveldb::DB* db;leveldb::DB::Open(options, "/tmp/testdb", &db);... use the database ...delete db;delete options.filter_policy;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上面的代码在数据库中使用一个基于Bloom filter的filtering策略。基于Bloom filter的filtering策略为每个key在内存保存一些数据位,这上面的例子中,为每个key保存10位),这个filter可以降低Get()调用操作中不必要的磁盘读大概100倍。增加每个key的位数可以更大的降低磁盘读,但是会增加内存的使用。建议那些工作集不适合放在内存的应用,以及随机读比较多的应用使用filter policy。

如果你使用的是自定义的comparator,应该确保filter policy和comparator是兼容的。例如,一个在key进行比较的时候会删除前后的空格的comparator。应用程序也应该提供一个忽略前后空格的自定义filter policy。例如:

  class CustomFilterPolicy : public leveldb::FilterPolicy {private:FilterPolicy* builtin_policy_;public:CustomFilterPolicy() : builtin_policy_(NewBloomFilterPolicy(10)) { }~CustomFilterPolicy() { delete builtin_policy_; }const char* Name() const { return "IgnoreTrailingSpacesFilter"; }void CreateFilter(const Slice* keys, int n, std::string* dst) const {// Use builtin bloom filter code after removing trailing spacesstd::vector<Slice> trimmed(n);for (int i = 0; i < n; i++) {trimmed[i] = RemoveTrailingSpaces(keys[i]);}return builtin_policy_->CreateFilter(&trimmed[i], n, dst);}bool KeyMayMatch(const Slice& key, const Slice& filter) const {// Use builtin bloom filter code after removing trailing spacesreturn builtin_policy_->KeyMayMatch(RemoveTrailingSpaces(key), filter);}};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

高级应用程序可以使用不依赖 bloom filter的策略,或者根据key集合的特征提供自己的filter policy。详情可以参照leveldb/filter_policy.h 。

14. 校验和

leveldb把校验和和存储在文件系统中的数据联系起来。下面是两种校验和验证的方式: 
ReadOptions::verify_checksums可以设置为true,来强制对从文件系统读取的所有数据进行校验和验证。默认不使用。

Options::paranoid_checks可以在打开数据库之前设置为true,来确保一旦检测到内部错误就尽快抛出异常。当数据库打开的时候可能抛出异常,或者后续的数据库操时抛出。默认情况下,会禁用多疑的检测,这样的话,即使部分持久性存储崩溃数据库依旧可以使用。

如果数据库崩溃了,如果多疑检测打开的话,可能无法打开这个数据库,可以使用leveldb::RepairDB函数来修复尽可能多的数据。

15. 空间估算

GetApproximateSizes方法可以用来估算一个或多个key占用文件系统空间。

   leveldb::Range ranges[2];ranges[0] = leveldb::Range("a", "c");ranges[1] = leveldb::Range("x", "z");uint64_t sizes[2];leveldb::Status s = db->GetApproximateSizes(ranges, 2, sizes);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

上面的代码中,size[0]是key范围在[a..c)之间的内容占用的文件空间的大小估算值。sizes[1]是key范围在[a..c)之间的内容占用的文件空间的大小估算值。

16. 环境变量

leveldb所有文件操作和其他的系统调用通过leveldb::Env对象来判断如何使用,复杂的客户端可能希望提供自己的Env实现做到更好的控制。例如,应用程序可以人为为文件IO操作增加延时以降低leveldb对系统的其他应用带来的影响。

  class SlowEnv : public leveldb::Env {.. implementation of the Env interface ...};SlowEnv env;leveldb::Options options;options.env = &env;Status s = leveldb::DB::Open(options, ...);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

17. 可移植性

通过提供leveldb/port/port.h引用的types/methods/functions的平台特定实现,leveldb就能移植到新的平台。leveldb/port/port_example.h里面有更详细的内容。

另外,新平台可能需要新的leveldb::Env实现。See leveldb/util/env_posix.h for an example.

18. 其他

leveldb的更多实现细节可以看下面的文档: 
levelDB实现细节 
levelDB immutable Table的文件格式 
leveldb日志文件格式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/509722.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java中同步锁的原理和实现

接口 Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构&#xff0c;可以具有差别很大的属性&#xff0c;可以支持多个相关的 Condition 对象。 锁是控制多个线程对共享资源进行访问的工具。通常&#xff0c;锁提供了对共享资…

HugeGraph 配置参数

gremlin-server.yaml 参数名称参数说明默认值scriptEvaluationTimeout查询超时时间&#xff0c;单位毫秒30000rest-server.properties 参数名称参数说明默认值restserver.max_worker_threads服务器最大工作线程2*cpurestserver.min_free_memory当服务器内存小于该值时&#x…

本地yum仓库以及网络版yum的私有仓库详细的安装配置

本地仓的配置 第一步&#xff1a;开启CD/DVD 设备&#xff0c;并且把centos镜像链接添加至设备中 第二步&#xff1a;创建一个 文件夹用来挂在 光驱文件 mkdir /mnt/cdrom &#xff08;通常约定挂载在/mnt目录下 &#xff09; 第三步&#xff1a;mount -t iso9660 -o ro /de…

LevelDB简述

转自&#xff1a;http://www.cnblogs.com/melons/p/5791855.html 既然开始了&#xff0c;哪有停下的道理&#xff0c;先了解一下Levedb的关荣历史、完美现在和光辉的未来&#xff1a; Leveldb: 1.Leveldb是一个google实现的非常高效的kv数据库&#xff0c;目前能够支持billion级…

HBase原理 – snapshot 快照

目录 snapshot&#xff08;快照&#xff09;基础原理 snapshot能实现什么功能&#xff1f; hbase snapshot用法大全 hbase snapshot分布式架构&#xff0d;两阶段提交 snapshot核心实现 clone_snapshot如何实现呢&#xff1f; 其他需要注意的 参考文献 更多信息可参考《…

linux如何自动化部署脚本实现免密登录并访问资源

任务把weijie主机jdk文件安装到weijie1中。 首先再各台主机中安装必要的命令&#xff1a; expect、wget、httpd、ssh 执行命令 如&#xff1a;expect提示命令不存在&#xff0c;则分别安装命令 yum install expect yum install wget yum install httpd yum install ssh 开…

比赛2016年暑假集训盲打首秀赛结果

4 53 2015计算机科学与技术1班 15111205046 鞠明杭 149 Fishing For Jasmine 75.50KPM 99.34% 149 2016-06-25 19:23:04 不及格&#xff0c;加强训练&#xff01;

时序数据库技术体系 – InfluxDB TSM存储引擎之数据读取

任何一个数据库系统内核关注的重点无非&#xff1a;数据在内存中如何存储、在文件中如何存储、索引结构如何存储、数据写入流程以及数据读取流程。关于InfluxDB存储内核&#xff0c;笔者在之前的文章中已经比较全面的介绍了数据的文件存储格式、倒排索引存储实现以及数据写入流…

java多线程之生产者和消费者问题

线程通信:不同的线程执行不同的任务,如果这些任务有某种关系,线程之间必须能够通信,协调完成工作. 经典的生产者和消费者案例(Producer/Consumer):分析案例:1):生产者和消费者应该操作共享的资源(实现方式来做).2):使用一个或多个线程来表示生产者(Producer).3):使用一个或多个…

数据结构上机测试1:顺序表的应用

题目描述 在长度为n&#xff08;n<1000)的顺序表中可能存在着一些值相同的“多余”数据元素&#xff08;类型为整型&#xff09;&#xff0c;编写一个程序将“多余”的数据元素从顺序表中删除&#xff0c;使该表由一个“非纯表”&#xff08;值相同的元素在表中可能有多个&a…

学习方法

在看书籍或者网上看技术知识的时候&#xff0c;要学会以点带面&#xff0c;以一点知识联想到与其相关的知识&#xff0c;再有相关的知识引申到其他相关的知识&#xff0c;这样的话看一本书或者一个技术点的时候就能学到更多的新知识&#xff0c;而且还能够回想起以前学过的知识…

时序数据库技术体系 – InfluxDB TSM存储引擎之数据写入

之前两篇文章笔者分别从TSM File文件存储格式、倒排索引文件存储格式这两个方面对InfluxDB最基础、最底层也最核心的存储模块进行了介绍&#xff0c;接下来笔者会再用两篇文章在存储文件的基础上分别介绍InfluxDB是如何处理用户的写入&#xff08;删除&#xff09;请求和读取请…

zookeeper结构和命令详解

1.1. zookeeper特性1、Zookeeper&#xff1a;一个leader&#xff0c;多个follower组成的集群 2、全局数据一致&#xff1a;每个server保存一份相同的数据副本&#xff0c;client无论连接到哪个server&#xff0c;数据都是一致的 3、分布式读写&#xff0c;更新请求转发&#xf…

时序数据库技术体系 – InfluxDB 多维查询之倒排索引

在时序数据库概述一文中&#xff0c;笔者提到时序数据库的基础技术栈主要包括高吞吐写入实现、数据分级存储&#xff5c;TTL、数据高压缩率、多维度查询能力以及高效聚合能力等&#xff0c;上文《时序数据库技术体系 – InfluxDB存储引擎TSM》基于InfluxDB存储引擎TSM介绍了时序…

顺序表应用1:多余元素删除之移位算法

题目描述 一个长度不超过10000数据的顺序表&#xff0c;可能存在着一些值相同的“多余”数据元素&#xff08;类型为整型&#xff09;&#xff0c;编写一个程序将“多余”的数据元素从顺序表中删除&#xff0c;使该表由一个“非纯表”&#xff08;值相同的元素在表中可能有多个…

《暗时间》-----摘记

http://blog.sina.com.cn/s/blog_6af5df430101ab83.html -------整理 最近花了3天左右的下班时间和上班之前的时间把刘未鹏先生关于学习方法的相关博客看了一遍&#xff0c;觉得讲得很有道理&#xff0c;下面把觉得我感受很深的地方摘录下来了。 特别说明&#xff1a;本文是我对…

Hbase 协处理器 RegionObserver

参考链接1&#xff1a;https://www.cnblogs.com/ios123/p/6370724.html 参考链接2&#xff1a;http://www.zhyea.com/2017/04/13/using-hbase-coprocessor.html RegionObserver 注&#xff1a;每次更新协处理器方法&#xff0c;最好加上版本更新&#xff0c;否则可能会出现更…

搭建zookeeper集群环境详解

第一步:上传zookeeper.jar.gz文件至一台虚拟机&#xff0c;并解压至 /root/apps 目录下&#xff08;如没有该目录则手动创建&#xff09; 第二步&#xff1a;进入在/root/apps/zookeeper目录下 &#xff0c;删除不必要文件 rm -rf .txt .xml docs dist-maven src 第三步: 进入…

顺序表应用2:多余元素删除之建表算法

题目描述 一个长度不超过10000数据的顺序表&#xff0c;可能存在着一些值相同的“多余”数据元素&#xff08;类型为整型&#xff09;&#xff0c;编写一个程序将“多余”的数据元素从顺序表中删除&#xff0c;使该表由一个“非纯表”&#xff08;值相同的元素在表中可能有多个…

OSG框架分析

本文参考<<osg最长一帧>>, <<OpenSceneGraph三维渲染引擎编程指南>>, <<OpenSceneGraph三维渲染引擎设计与实践>> 整理而来, 感谢大牛们的精彩著作. 相比Ogre来说, Ogre代码很规范, 只是入门资料较少,如果能在学习之前能总体上对架构有个…