深入HBase——数据结构与算法
引入
通过前面的文章,我们对HBase已经有了基本认识,下面我们从HBase最核心的算法和数据结构进一步深入HBase。
HBase的一个列簇(Column Family)本质上就是一棵LSM树(Log-Structured Merge-Tree)。LSM树分为内存部分和磁盘部分。内存部分是一个维护有序数据集合的数据结构。一般来讲,内存数据结构可以选择平衡二叉树、红黑树、跳跃表(SkipList)等维护有序集的数据结构,这里由于考虑并发性能,HBase选择了表现更优秀的跳跃表。磁盘部分是由一个个独立的文件组成,每一个文件又是由一个个数据块组成。并且为了避免不必要的IO耗时,还可以在磁盘中存储一些额外的二进制数据,这些数据用来判断对于给定的key是否有可能存储在这个数据块中,这个数据结构称为布隆过滤器(Bloom Filter)。
下面我们就深入介绍一下提到的核心数据结构与算法:
- LSM树(Log-Structured Merge-Tree)
- 跳跃表(SkipList)
- 布隆过滤器(Bloom Filter)
LSM树(Log-Structured Merge-Tree)
LSM树本质上和B+树一样,是一种磁盘数据的索引结构。但和B+树不同的是,LSM树的索引对写入请求更友好。因为无论是何种写入请求,LSM树的索引结构本质都会将其全部转化成磁盘的顺序写入,而HDFS擅长的正是顺序写(且HDFS不支持随机写),因此基于HDFS实现的HBase能极大提高写入操作的性能,但这种设计对读取操作是非常不利的,因为需要在读取的过程中,通过归并所有文件来读取所对应的KV,这是非常消耗IO资源的。因此,在HBase中设计了异步的compaction来降低文件个数,达到提高读取性能的目的。由于HDFS只支持文件的顺序写,不支持文件的随机写,而且HDFS擅长的场景是大文件存储而非小文件,所以上层HBase选择LSM树这种索引结构是最合适的。
原理
LSM树是一种用于存储和管理数据的数据结构,特别适用于写入密集型的工作负载。它的设计灵感来自于日志结构文件系统。LSM树的核心思想是将写操作集中到一个内存中的有序数据结构(如MemTable),待数据积累到一定数量后,将这些数据批量写入到磁盘上的有序文件中。这种设计能够显著提高写操作的性能,因为将随机写转换为顺序写,能够减少磁盘的寻道时间。
结构
LSM树的索引一般由两部分组成,一部分是内存部分,一部分是磁盘部分。内存部分一般采用跳跃表来维护一个有序的KeyValue集合。磁盘部分一般由多个内部KeyValue有序的文件组成。
KeyValue存储格式
在HBase为例,这个字节数组串设计如下图所示,字节数组主要分为以下几个字段:
其中Rowkey、Family、Qualifier、Timestamp、Type这5个字段组成KeyValue中的key部分。
- keyLen:占用4字节,用来存储KeyValue结构中Key所占用的字节长度。
- valueLen:占用4字节,用来存储KeyValue结构中Value所占用的字节长度。
- rowkeyLen:占用2字节,用来存储rowkey占用的字节长度。
- rowkeyBytes:占用rowkeyLen个字节,用来存储rowkey的二进制内容。
- familyLen:占用1字节,用来存储Family占用的字节长度。
- familyBytes:占用familyLen字节,用来存储Family的二进制内容。
- qualifierBytes:占用qualifierLen个字节,用来存储Qualifier的二进制内容。
注意,HBase并没有单独分配字节用来存储qualifierLen,因为可以通过keyLen和其他字段的长度计算出qualifierLen。
代码如下:
qualifierLen=keyLen -2B - rowkeyLen -1B - familyLen -8B -1B
- timestamp:占用8字节,表示timestamp对应的long值。
- type:占用1字节,表示这个KeyValue操作的类型,HBase内有Put、Delete、Delete Column、DeleteFamily,等等。注意,这是一个非常关键的字段,表明了LSM树内存储的不只是数据,而是每一次操作记录。
Value部分直接存储这个KeyValue中Value的二进制内容。所以,字节数组串主要是Key部分的设计。
注意:
在HBase中,timestamp越大的KeyValue,排序越靠前。因为用户期望优先读取到那些版本号更新的数据。
通常来说,在LSM树的KeyValue中的Key部分,有3个字段必不可少:
- Key的二进制内容。
- 一个表示版本号的64位long值,在HBase中对应timestamp;这个版本号通常表示数据的写入先后顺序,版本号越大的数据,越优先被用户读取。甚至会设计一定的策略,将那些版本号较小的数据过期淘汰(HBase中有TTL策略)。
- type,表示这个KeyValue是Put操作,还是Delete操作,或者是其他写入操作。本质上,LSM树中存放的并非数据本身,而是操作记录。这对应了LSM树(Log-Structured Merge-Tree)中Log的含义,即操作日志。
多路归并
现在有K个文件,其中第i个文件内部存储有Ni个正整数(这些整数在文件内按照从小到大的顺序存储),如何设计一个算法将K个有序文件合并成一个大的有序文件?
在排序算法中,有一类排序算法叫做归并排序,里面就有大家熟知的两路归并实现。现在相当于K路归并,因此可以拓展一下,思路类似。对每个文件设计一个指针,取出K个指针中数值最小的一个,然后把最小的那个指针后移,接着继续找K个指针中数值最小的一个,继续后移指针……直到N个文件全部读完为止。
核心实现代码如下:
/*** 该类实现了 k 路归并排序的功能,用于合并多个有序文件的内容。*/public class KMergeSort {/*** 文件读取器接口,定义了从文件中读取数据的方法。*/public interface FileReader {/*** 检查文件是否还有更多数据。* @return 如果文件还有数据,返回 true;否则返回 false 表示到达文件末尾。* @throws IOException 如果在读取文件时发生 I/O 错误。*///true to indicate the file still has some data, false means EOF.boolean hasNext() throws IOException;/*** 从文件中读取下一个值,并将文件读取偏移量向后移动。* @return 文件中的下一个整数值。* @throws IOException 如果在读取文件时发生 I/O 错误。*///Read the next value from file, and move the file read offset.int next() throws IOException;}/*** 文件写入器接口,定义了向文件中写入数据的方法。*/public interface FileWriter {/*** 向文件中追加一个整数值。* @param value 要追加的整数值。* @throws IOException 如果在写入文件时发生 I/O 错误。*/void append(int value) throws IOException;}/*** 实现 k 路归并排序,将多个有序文件的内容合并到一个文件中。* @param readers 一个包含多个 FileReader 的列表,每个 FileReader 对应一个有序文件。* @param writer 用于写入合并后数据的 FileWriter。* @throws IOException 如果在读取或写入文件时发生 I/O 错误。*/public void kMergeSort(final List<FileReader> readers, FileWriter writer)throws IOException {// 创建一个优先队列(最小堆),用于存储每个文件的当前最小值PriorityQueue<Pair<FileReader, Integer>> heap=new PriorityQueue<>((p1, p2) -> p1.getValue() - p2.getValue());// 初始化优先队列,将每个文件的第一个值加入队列for (FileReader fr : readers) {if (fr.hasNext()) {// 将文件读取器和其下一个值作为一个 Pair 加入堆中heap.add(new Pair<>(fr, fr.next()));}}// 不断从堆中取出最小值,并将其写入输出文件while (!heap.isEmpty()) {// 从堆中取出最小值对应的 PairPair<FileReader, Integer> current=heap.poll();// 将该值写入输出文件writer.append(current.getValue());// 获取当前文件读取器FileReader currentReader=current.getKey();// 如果当前文件还有更多数据,将下一个值加入堆中if (currentReader.hasNext()) {// 将当前文件读取器和其下一个值作为一个 Pair 加入堆中heap.add(new Pair<>(currentReader, currentReader.next()));}}}}
LSM树的索引结构
一个LSM树的索引主要由两部分构成:内存部分和磁盘部分。内存部分是一个ConcurrentSkipListMap,Key就是前面所说的Key部分,Value是一个字节数组。数据写入时,直接写入MemStore中。随着不断写入,一旦内存占用超过一定的阈值时,就把内存部分的数据导出,形成一个有序的数据文件,存储在磁盘上。
内存部分导出形成一个有序数据文件的过程称为flush。为了避免flush影响写入性能,会先把当前写入的MemStore设为Snapshot,不再容许新的写入操作写入这个Snapshot的MemStore。另开一个内存空间作为MemStore,让后面的数据写入。一旦Snapshot的MemStore写入完毕,对应内存空间就可以释放。这样就可以通过两个MemStore来实现稳定的写入性能。(看过深入HDFS的小伙伴会发现,这个和SNN合并元数据的操作很类似)
注意:
在整个数据写入过程中,LSM树全部都是使用append操作(磁盘顺序写)来实现数据写入的,没有使用任何seek+write(磁盘随机写)的方式来写入。无论HDD还是SSD,磁盘的顺序写操作性能和延迟都远好于磁盘随机写。因此LSM树是一种对写入极为友好的索引结构,它能将磁盘的写入带宽利用到极致。
随着写入的增加,内存数据会不断地刷新到磁盘上。最终磁盘上的数据文件会越来越多。如果数据没有任何的读取操作,磁盘上产生很多的数据文件对写入并无影响,而且这时写入速度是最快的,因为所有IO都是顺序IO。但是一旦用户有读取请求,则需要将大量的磁盘文件进行多路归并,之后才能读取到所需的数据。因为需要将那些Key相同的数据全局综合起来,最终选择出合适的版本返回给用户,所以磁盘文件数量越多,在读取的时候随机读取的次数也会越多,从而影响读取操作的性能。
为了优化读取操作的性能,我们可以设置一定策略将选中的多个hfile进行多路归并,合并成一个文件。文件个数越少,则读取数据时需要seek操作的次数越少,读取性能则越好。一般来说,按照选中的文件个数,我们将compact操作分成两种类型。一种是major compact,是将所有的hfile一次性多路归并成一个文件。这种方式的好处是,合并之后只有一个文件,这样读取的性能肯定是最高的;但它的问题是,合并所有的文件可能需要很长的时间并消耗大量的IO带宽,所以major compact不宜使用太频繁,适合周期性地跑。
另外一种是minor compact,即选中少数几个hfile,将它们多路归并成一个文件。这种方式的优点是,可以进行局部的compact,通过少量的IO减少文件个数,提升读取操作的性能,适合较高频率地跑;但它的缺点是,只合并了局部的数据,对于那些全局删除操作,无法在合并过程中完全删除。因此,minor compact虽然能减少文件,但却无法彻底清除那些delete操作。而major compact能完全清理那些delete操作,保证数据的最小化。
在HBase中的应用
写操作优化:通过将数据先写入到内存中的MemTable,HBase能够将随机写转换为顺序写,从而大大提高写操作的性能。这对于HBase这种需要处理大量写入请求的数据库来说非常重要。
读操作优化:虽然LSM树的设计主要是为了优化写操作,但通过将多个SSTable的索引信息加载到内存中,HBase也能够有效地处理读取请求。此外,SSTables的有序存储结构也使得范围查询变得非常高效。
数据一致性:在MemTable写入到SSTable之前,HBase会将数据写入WAL,以确保数据的持久性和一致性。如果在写入过程中发生故障,HBase可以通过WAL恢复未写入到SSTable的数据。
优点
高性能写入:LSM树能够将随机写转换为顺序写,从而显著提高写入性能。这对于写入密集型的工作负载非常重要。
有效利用存储空间:通过将多个小的SSTables合并为更大的SSTable,LSM树能够有效地减少存储空间的占用,并提高数据的存储效率。
易于实现压缩:由于SSTables是按主键排序的,因此可以很容易地对它们进行压缩,从而进一步提高存储效率。
缺点
读操作延迟:在读取数据时,HBase需要在多个SSTables之间进行查找和合并,这可能会导致一定的读取延迟。
合并开销:合并SSTables的过程需要消耗大量的系统资源,包括CPU、内存和磁盘I/O。如果合并操作过于频繁或规模过大,可能会对系统的性能产生一定的影响。
跳跃表(SkipList)
跳跃表是一种能高效实现插入、删除、查找的内存数据结构,这些操作的期望复杂度都是O(logN)。与红黑树以及其他的二分查找树相比,跳跃表的优势在于实现简单,而且在并发场景下加锁粒度更小,从而可以实现更高的并发性。正因为这些优点,跳跃表广泛使用于KV数据库中,诸如Redis、LevelDB、HBase都把跳跃表作为一种维护有序数据集合的基础数据结构。
众所周知,链表这种数据结构的查询复杂度为O(N),这里N是链表中元素的个数。在已经找到要删除元素的情况下,再执行链表的删除操作其实非常高效,只需把待删除元素前一个元素的next指针指向待删除元素的后一个元素即可,复杂度为O(1)。但问题是,链表的查询复杂度太高,因为链表在查询的时候,需要逐个元素地查找。如果链表在查找的时候,能够避免依次查找元素,那么查找复杂度将降低。而跳跃表就是利用这一思想,在链表之上额外存储了一些节点的索引信息,达到避免依次查找元素的目的,从而将查询复杂度优化为O(logN)。将查询复杂度优化之后,自然也优化了插入和删除的复杂度。
原理
跳跃表是一种有序数据结构,它通过在每个节点中维护多个指针,以支持快速的插入、删除和查找操作。跳跃表的灵感来自于链表,但它通过添加多层指针来减少查找时间。每个节点在跳跃表中可以有多个层次,每个层次的指针指向比当前节点更大的键。这样,在查找一个键时,可以从最高层开始,快速跳过不相关的节点,从而减少比较的次数。
结构
跳跃表的结构是由多个节点组成的,每个节点包含一个键和多个指向其他节点的指针。节点的层数是随机的,通常通过一个概率函数来确定。最高层的指针可以快速跳过大量的节点,而底层的指针则用于精细的查找。跳跃表的层次结构类似于一个金字塔,最顶层的层数最少,最底层的层数最多。
- 跳跃表由多条分层的链表组成(设为S0, S1, S2, ... , Sn);
- 每条链表中的元素都是有序的;
- 每条链表都有两个元素:+∞(正无穷大)和 -∞(负无穷大),分别表示链表的头部和尾部;
- 从上到下,上层链表元素集合是下层链表元素集合的子集,即S1是S0的子集,S2是S1的子集。
注意:跳跃表的高度为水平链表的层数。
代码示例
/*** 跳跃表类,实现了跳跃表数据结构,支持插入、查找和删除操作。*/public class SkipList {private static final int MAX_LEVEL = 16; // 跳跃表的最大层级private int level; // 当前跳跃表的层级private Node head; // 跳跃表的头节点private Random random; // 用于生成随机数/*** 构造函数,初始化跳跃表。*/public SkipList() {head = new Node(MAX_LEVEL);level = 1;random = new Random();}/*** 跳跃表的节点类,包含键值和指向下一个节点的数组。*/private class Node {int key; // 节点的键值Node[] next; // 指向下一个节点的数组/*** 构造函数,初始化指向下一个节点的数组。* @param level 节点的层级*/Node(int level) {next = new Node[level]; // 初始化指针数组}/*** 构造函数,初始化节点的键值和指向下一个节点的数组。* @param key 节点的键值* @param level 节点的层级*/Node(int key, int level) {this.key = key;next = new Node[level];}}/*** 随机生成节点的层级。* @return 生成的节点层级*/private int randomLevel() {int lvl = 1;// 以 50% 的概率增加层级,直到达到最大层级while (random.nextInt(2) == 0 && lvl < MAX_LEVEL) {lvl++;}return lvl;}/*** 向跳跃表中插入一个键值。* @param key 要插入的键值*/public void insert(int key) {int lvl = randomLevel(); // 随机生成节点的层级// 更新数组用于记录每一层的前驱节点Node[] update = new Node[MAX_LEVEL];// 初始化更新数组,将每一层的前驱节点设为头节点for (int i = 0; i < MAX_LEVEL; i++) {update[i] = head;}Node current = head;// 从最高层开始,找到每一层的插入位置for (int i = level - 1; i >= 0; i--) {while (current.next[i] != null && current.next[i].key < key) {current = current.next[i];}update[i] = current;}// 如果当前层级大于跳跃表的层级,则更新跳跃表的层级if (lvl > level) {level = lvl;}Node newNode = new Node(key, lvl); // 创建新节点// 更新每一层的指针for (int i = 0; i < lvl; i++) {newNode.next[i] = update[i].next[i];update[i].next[i] = newNode;}}/*** 在跳跃表中查找一个键值。* @param key 要查找的键值* @return 如果找到返回 true,否则返回 false*/public Boolean search(int key) {Node current = head;// 从最高层开始,找到可能包含键值的节点for (int i = level - 1; i >= 0; i--) {while (current.next[i] != null && current.next[i].key < key) {current = current.next[i];}}// 检查是否找到键值if (current.next[0] != null && current.next[0].key == key) {return true;} else {return false;}}/*** 从跳跃表中删除一个键值。* @param key 要删除的键值*/public void delete(int key) {Node[] update = new Node[MAX_LEVEL];// 初始化更新数组,将每一层的前驱节点设为头节点for (int i = 0; i < MAX_LEVEL; i++) {update[i] = head;}Node current = head;// 从最高层开始,找到每一层的删除位置for (int i = level - 1; i >= 0; i--) {while (current.next[i] != null && current.next[i].key < key) {current = current.next[i];}update[i] = current;}current = current.next[0];// 如果找到要删除的节点if (current != null && current.key == key) {// 更新每一层的指针for (int i = 0; i < level; i++) {if (update[i].next[i] != current) {break;}update[i].next[i] = current.next[i];}// 如果删除后某一层为空,则降低跳跃表的层级while (level > 1 && head.next[level - 1] == null) {level--;}}}/*** 打印跳跃表的每一层。*/public void printList() {System.out.println("Skip List:");// 从最高层开始,打印每一层的节点for (int i = level - 1; i >= 0; i--) {Node current = head.next[i];System.out.print("Level " + i + ": ");while (current != null) {System.out.print(current.key + " ");current = current.next[i];}System.out.println();}}}
在HBase中的应用
MemStore:跳跃表是HBase的MemStore中的主要数据结构。MemStore是内存中的一个有序数据集,用于存储即将写入到磁盘的数据。跳跃表的高效插入和查找特性使得MemStore能够快速处理大量的写入请求,并在需要时将数据刷新到磁盘上。
Block Index:在HBase的存储引擎中,块索引(Block Index)也使用跳跃表来加速数据的定位。块索引保存了每个数据块的起始键和结束键,通过跳跃表的结构,可以快速确定一个键所在的数据块,从而减少磁盘的随机读取次数。
优点
快速的插入、删除和查找:跳跃表在平均情况下的插入、删除和查找操作的时间复杂度为O(log n),这比传统的链表要快得多。
动态性:跳跃表能够在任何时间进行插入、删除和查找操作,而不需要进行任何预处理或重建操作。
易于实现:跳跃表的实现相对简单,不需要复杂的平衡操作,因此易于理解和实现。
缺点
空间开销:跳跃表需要为每个节点维护多个指针,这会增加空间的开销。在节点数较多的情况下,指针的维护可能会占用较大的内存空间。
性能波动:由于跳跃表的层数是随机生成的,因此在极端情况下,可能会出现层数过高的情况,导致性能下降。然而,这种情况的概率较低,可以通过调整随机概率函数来缓解。
布隆过滤器(Bloom Filter)
关于布隆过滤器,推荐先阅读这篇论文,里面详细介绍了布隆过滤器的原理、变体和在网络中的多种应用,以及使用时需要在空间效率和假阳性概率之间权衡。
原理
布隆过滤器是一种概率性的数据结构,用于判断一个元素是否可能存在于一个集合中。它通过使用多个哈希函数将元素映射到一个位数组中。当一个元素被添加到集合时,它会被多个哈希函数处理,每个哈希函数的结果会设置位数组中对应的位置为1。在查询时,同样使用这些哈希函数处理待查询的元素,如果位数组中所有对应的位置都为1,则认为该元素可能存在于集合中。否则,可以确定该元素一定不存在于集合中。
结构
布隆过滤器的核心是一个位数组和多个哈希函数。位数组的大小和哈希函数的数量会影响布隆过滤器的准确性和空间利用率。较大的位数组和较多的哈希函数可以降低误判率,但会增加空间的使用和计算的时间。
工作流程
-
添加元素
当一个元素被添加到布隆过滤器时,它会通过多个哈希函数生成多个整数,这些整数对应于位数组中的位置。然后,将这些位置的位设置为1。 -
查询元素
要判断一个元素是否存在于集合中,将其通过相同的哈希函数生成对应的位位置,并检查这些位置是否都为1。如果有任何一个位置为0,则可以确定该元素不存在于集合中。否则,认为该元素可能存在,但可能存在误判。 -
误判率
布隆过滤器的误判率是指错误地认为一个不存在的元素存在于集合中的概率。误判率可以通过调整位数组的大小和哈希函数的数量来控制。
在HBase中的应用
HFile:布隆过滤器被广泛应用于HBase的HFile中。HFile是HBase存储数据的基本单元,每个HFile包含多个数据块。在每个数据块中,布隆过滤器用于快速判断某个行键是否可能存在于该块中。当进行数据查询时,首先通过布隆过滤器快速筛选出可能包含目标行键的数据块,从而减少不必要的磁盘I/O操作。
Filter:HBase的过滤器(Filter)也可以利用布隆过滤器来加速数据的过滤过程。例如,在行键过滤器中,可以使用布隆过滤器来判断某个行键是否满足过滤条件,从而快速定位到目标数据。
优点
高效率:布隆过滤器的查询操作非常高效,时间复杂度为O(k),其中k是哈希函数的数量。这使得它非常适合用于大数据场景中的快速查询。
节省空间:与传统的存储所有元素的方式相比,布隆过滤器所需的存储空间非常小。例如,存储一个包含数百万元素的集合可能只需要几千字节的空间。
并行性:布隆过滤器的查询和插入操作可以并行执行,因为它们不涉及任何共享资源的锁定。这使得它非常适用于大规模并行处理系统。
缺点
误判率:布隆过滤器存在一定的误判率,即错误地认为一个不存在的元素存在于集合中的概率。虽然可以通过调整参数来降低误判率,但无法完全消除。
无法删除元素:一旦一个元素被添加到布隆过滤器中,很难将其删除。因为多个元素可能共享同一个位数组中的位置,删除一个元素可能会导致误判率的增加。
参数调整复杂:布隆过滤器的性能和准确度高度依赖于位数组的大小和哈希函数的数量。选择合适的参数需要根据实际的数据量和查询需求进行仔细的测试和调整。
相关文章:
深入HBase——数据结构与算法
引入 通过前面的文章,我们对HBase已经有了基本认识,下面我们从HBase最核心的算法和数据结构进一步深入HBase。 HBase的一个列簇(Column Family)本质上就是一棵LSM树(Log-Structured Merge-Tree)。LSM树…...
学习Linux准备2
使用win10系统带的wsl配置ubuntu系统,通过wsl功能我们可以更简单更轻松的获得Linux系统环境。 首先开启Windows自带的wsl功能 打开控制面板,选中启用或关闭Windows功能 这里我们点击进入 将上图红√点击上,点击确定,然后重新启动…...
MATLAB图像处理:图像分割方法
图像分割将图像划分为具有特定意义的子区域,是目标检测、医学影像分析、自动驾驶等领域的核心预处理步骤。本文讲解阈值分割、边缘检测、区域生长、聚类分割、基于图的方法等经典与前沿技术,提供MATLAB代码实现。 目录 1. 图像分割基础 2. 经典分割方…...
什么是“可迭代”
在 Python 中,“可迭代”(Iterable)是一个非常重要的概念,它指的是任何可以被逐个访问其元素的对象。换句话说,如果一个对象支持迭代操作(比如可以通过 for 循环逐个访问其元素),那么…...
使用Ubuntu搭建Java部署环境
White graces:个人主页 🙉专栏推荐:Java入门知识🙉 🐹今日诗词:小舟从此逝,江海寄余生🐹 ⛳️点赞 ☀️收藏⭐️关注💬卑微小博主🙏 ⛳️点赞 ☀️收藏⭐️关注💬卑微小…...
Linux 文件的三个时间:Access、Modify 和 Change
目录 一、文件的三个时间戳 1. 访问时间(Access Time) 2. 修改时间(Modify Time) 3. 更改时间(Change Time) 二、如何查看文件的三个时间戳 三、文件时间戳的变化规则 1. 修改文件内容 2. 修改文件…...
移植live555 上的 rtsp
一、V4L2视频采集模块(完整示例) #include <linux/videodev2.h> #include <sys/ioctl.h> #include <fcntl.h>// 初始化V4L2摄像头 int init_v4l2_camera(const char* dev_path, int width, int height) {int fd open(dev_path, O_RD…...
基于SpringBoot的“宠物救助及领养平台”的设计与实现(源码+数据库+文档+PPT)
基于SpringBoot的“宠物救助及领养平台”的设计与实现(源码数据库文档PPT) 开发语言:Java 数据库:MySQL 技术:SpringBoot 工具:IDEA/Ecilpse、Navicat、Maven 系统展示 系统整体功能图 系统首页界面 系统注册页面…...
Gemini 2.0助力科学突破,AI联合科学家系统登场
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...
electron提升软件运行权限,以管理员权限运行
大家有任何想法,都可以联系博主沟通。 本系列为实战文章,最终实现的桌面工具软件,获取方式:百度网盘地址:https://pan.baidu.com/s/1yrl0jYpti7QCn8CHBRT2lw?pwd1234 正文开始 前言一、提升electron运行权限的三种方…...
Docker 容器调试技巧
在开发和运维过程中,调试 Docker 容器中的应用或服务是一个常见且重要的任务。由于容器是相互隔离的,容器内部的日志和错误信息可能不像传统的服务器那样直观,因此掌握 Docker 容器调试技巧对于快速定位和解决问题至关重要。 本文将介绍一些…...
TSMaster【第七篇:千机百变——面板设计艺术】
武侠场景导入:唐门暗器阁的启示 江湖传言,唐门暗器之所以独步天下,全凭其「千机匣」中七十二种机关变化。TSMaster面板设计恰似打造暗器机关——控件如同飞镖、机簧、毒针,组合方式不同则威力迥异。昔日某新势力车型因仪表盘刷新…...
ctfshow——robots后台泄露
题目提示:总有人把后台地址写入robots,帮黑阔大佬们引路 题目如上图所示 根据提示,我们可以在URL上查看是否存在robots文件 打开后发现其中的内容如上图所示,存在一个flagishere.txt文件,我们可以继续在URL上进行操作…...
普通人怎样用好Deepseek?
第一章:准备篇(30分钟上手) DeepSeek 是一款功能强大的数据处理和分析工具,广泛应用于数据挖掘、机器学习、商业智能等领域。 本文将通过图文结合的方式,详细介绍 DeepSeek 的安装、配置、基本使用、高级功能以及实用…...
负载均衡集群( LVS 相关原理与集群构建 )
目录 1、LVS 相关原理 1.1、LVS集群的体系结构以及特点 1.1.1 LVS简介 1.1.2 LVS体系结构 1.1.3 LVS相关术语 1.1.4 LVS工作模式 1.1.5 LVS调度算法 1.2 LVS-DR集群介绍 1.2.1 LVS-DR模式工作原理 1.2.2 LVS-DR模式应用特点 1.2.3 LVS-DR模式ARP抑制 1.3 LVS – NA…...
前端面试-JavaScript 数据类型检测全解
目录 一、基础检测方法 二、方法深度解析 1. typeof 运算符 2. instanceof 运算符 3. 终极检测方案 三、特殊场景检测方案 四、手写实现原理 1. 通用类型检测函数 2. 改进版数组检测(兼容旧浏览器) 五、常见面试陷阱 六、最佳实践指南 七、扩…...
百度百舸 DeepSeek 一体机发布,支持昆仑芯 P800 单机 8 卡满血版开箱即用
在私有云环境中成功部署 DeepSeek 满血版并实现性能调优,并不是一件容易的事情。选择合适的 GPU 配置、安装相应的环境、成功部署上线业务、加速推理任务加速、支撑多用户并发 …… 完成业务测试,成功融入生产业务中。 为了帮助企业快速实现 DeepSeek 服…...
微软CEO-纳德拉访谈-AGI计划
在与知名科技播客主播 Dwarkesh Patel 的深度访谈中,微软 CEO 萨提亚・纳德拉围绕 AI、量子计算、微软发展等多方面分享深刻见解,下面是针对访谈内容的介绍,其中还是有很多值得我们学习的地方。 1 AI 领域见解 影响力评估:纳德拉直言行业所标榜的 AGI 里程碑是 “无意义的基…...
Web自动化中Selenium下Chrome与Edge的Webdriver常用Options参数
目录 引言 说明 Add_argument() 添加方式 常用参数 Add_experimental_option() 添加方式 常用方法 任务结束后仍然保持浏览器打开 禁用“Chrome 正受到自动测试软件的控制”提示 设置下载路径 禁用弹窗拦截 禁用图片加载 禁用 JavaScript 注意 引言 …...
1.4 嵌入式系统的软件
嵌入式系统的开发流程中,硬件和固件设计完成后,嵌入式软件承担起实现功能、用户交互、系统集成和性能优化等任务;嵌入式系统软件分为设备驱动、操作系统和应用程序三个层面。 因此嵌入式系统软件开发工程师通常分为三类:嵌入式系统…...
深蓝学院自主泊车第3次作业-IPM
目录 1 题目介绍2 求解 1 题目介绍 已知鱼眼相机的参数, image_width,表示图像的宽度image_height,表示图像的高度 ξ \xi ξ,表示鱼眼相机参数 k 1 k_1 k1、 k 2 k_2 k2,表示径向相机参数 p 1 p_1 p1、 p 2 p…...
Confluence 8.5.18 - windows 安装部署详解
本文我们所讲述的是Confluence - 8.5.18 -windows版本版本的详细安装破解步骤,与Confluence - 8.0.0之前的版本不同的是,部分文件名称发生了变化,以前的破解方式已不适用。 1.首先我们先准备安装所需要的文件 atlassian-confluence-8.5.18-x64.exemysql-connector-java-8.0…...
深入解析过滤器模式:数据筛选与处理的高效工具
过滤器模式:数据筛选与处理的高效工具 在软件开发的复杂领域中,数据的筛选与处理是常见的任务。过滤器模式作为一种实用的设计模式,为解决这类问题提供了有效的解决方案。它允许开发者根据不同的标准对一组对象进行过滤操作,从而…...
linux常用基础命令_最新版
常用命令 查看当前目录下个各个文件大小查看当前系统储存使用情况查看当前路径删除当前目录下所有包含".log"的文件linux开机启动jar更改自动配置文件后操作关闭自启动linux静默启动java服务查询端口被占用查看软件版本重启关机开机启动取别名清空当前行创建文件touc…...
曝光铁三角
一、曝光铁三角的关系解析 光圈(Aperture) 作用:控制光线进入相机的孔径大小,同时影响景深(画面清晰范围)。 数值规则:光圈值(如F/1.8、F/8)越小,孔径越大&am…...
reacct hook useState
useState useState 是一个 React Hook,允许函数组件在内部管理状态。 使用方法 let [str,setStr] useState(‘three’) useState接受的参数为初始值;返回一个数组 第一个元素:当前的状态值;第二个元素:一个更新该状…...
政安晨【零基础玩转各类开源AI项目】DeepSeek 多模态大模型Janus-Pro-7B,本地部署!支持图像识别和图像生成
政安晨的个人主页:政安晨 欢迎 👍点赞✍评论⭐收藏 希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正! 目录 下载项目 创建虚拟环境 安装项目依赖 安装 Gradio(UI) 运…...
计算机毕业设计SpringBoot+Vue.js学生读书笔记共享(源码+LW文档+PPT+讲解+开题报告)
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
QEMU源码全解析 —— 内存虚拟化(17)
接前一篇文章:QEMU源码全解析 —— 内存虚拟化(16) 本文内容参考: 《趣谈Linux操作系统》 —— 刘超,极客时间 《...
Maven 构建中的安全性与合规性检查
在现代软件开发中,确保应用程序的安全性和合规性至关重要。随着开源软件和第三方依赖的广泛使用,构建过程中对依赖的安全性和合规性检查变得尤为重要。通过 Maven 构建工具,我们可以集成多种安全性和合规性检查机制,帮助开发者检测…...
Deepseek R1 和其他的大模型 共同辅助决策交通出行方案
比一比各家大模型 问题描述一、Deepseek R1通勤方式评估报告(一)评分模型说明(二)各选项评分明细(三)加权总分计算(四)结论 二、文心一言通勤方式评估(一)时间…...
HTTPS 通信流程
HTTPS 通信流程时序图: #mermaid-svg-HWoTbFvfih6aYUu6 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-HWoTbFvfih6aYUu6 .error-icon{fill:#552222;}#mermaid-svg-HWoTbFvfih6aYUu6 .error-text{fill:#…...
C++——list模拟实现
目录 前言 一、list的结构 二、默认成员函数 构造函数 析构函数 clear 拷贝构造 赋值重载 swap 三、容量相关 empty size 四、数据访问 front/back 五、普通迭代器 begin/end 六、const迭代器 begin/end 七、插入数据 insert push_back push_front 八、…...
开发指南103-jpa的find**/get**全解
平台使用jpa来访问数据库。在dao层可以通过定义find**/get**函数,通过名字直接绑定数据库字段。find**和get**有一定区别: 1.get**方法查询不到数据抛出异常,find**方法查询不到数据则返回null。 2.get**是延迟加载,在真正访问返…...
初学者如何设置以及使用富文本编辑器[eclipse版]
手把手教你设置富文本编辑器 参考来源:UEditor Docs 初学者按我的步骤来就可以啦 一、设置ueditor编辑器 1.提取文件[文章最底部有链接提取方式] 2.解压文件并放到自己项目中,在WebContent目录下: 3. 修改jar包位置路径 到--> 注意&a…...
Flutter 启动优化
Dart VM在Flutter中的作用是什么?它负责执行Dart代码,无论是JIT还是AOT模式都需要它。在JIT模式下,VM随应用一起运行,而在AOT模式下,代码已经被编译成机器码,VM可能不需要运行时存在?不过实际上…...
Java Idea配置问题
在 cmd 里手动切换: set JAVA_HOMEC:\Program Files\Java\jdk1.8.0_192 set PATH%JAVA_HOME%\bin;%PATH%在Idea的终端查看: java -versionClass com.sun.tools.javac.tree.JCTree$JCImport does not have member field ‘com.sun.tools.javac.tre JDK 版本不匹配 …...
百度搜索,能否将DeepSeek变成“内功”?
最近,所有的云平台和主流APP都在努力接入DeepSeek。其中,搜索类APP与搜索引擎更是“战况激烈”。那么问题来了,接入DeepSeek已经变成了标准配置,到底应该如何做出差异化?接入DeepSeek这件事能不能实现11大于2的效果&am…...
【C++委托与事件】函数指针,回调机制,事件式编程与松耦合的设计模式(上)
前言 上一次发文章已经是在两个月前了hhh,期间也是忙忙碌碌做了不少事情也鸽了不少东西… 本文我们来讲讲博主最近在项目中频繁使用的,也就是广泛运用于C#或者Java的一个常用编程机制(思路)-----委托和事件。由于C在语言特性上没…...
【Java学习】抽象类与接口
面向对象系列四 一、抽象方法 二、抽象类 三、意义检查 1.抽象方法的意义 2.意义检查 体现 四、接口 1.级别层次 2.接口变量 3.意义 4.成员 成员变量: 成员方法: 一、抽象方法 没有方法体即没有任何实现的方法是抽象方法,只有在…...
体育电竞比分网开发流程
开发一个体育电竞比分网的流程可以分为以下几个主要步骤: 1. 需求分析 目标用户:确定网站的主要用户群体,如体育迷、电竞爱好者等。 功能需求:列出网站需要实现的功能,如实时比分更新、赛事日程、新闻资讯、用户评论…...
vue2和vue3的主要区别
Vue 2 和 Vue 3 之间有几个主要区别,涉及到性能、功能和架构上的改进。以下是一些核心的区别: Composition API(组合式 API): Vue 2 使用的是选项式 API(Options API),即通过 data, …...
粘贴到Word里的图片显示不全
粘贴到Word里的图片显示不全,可从Word设置、图片本身、软件与系统等方面着手解决,具体方法如下: Word软件设置 经实践发现,图片在word行距的行距出现问题,可以按照如下调整行距进行处理 修改段落行距: 选…...
直角三角堰计算公式
直角三角堰的计算公式通常用于确定流经直角三角形形状的堰的流量。河北瑾航科技遥测终端机 通过采集液位数据(模拟量、串口485/232),计算得到瞬时流量,然后通过积分进行累计算出累积量;直角三角堰的流量计算公式为: 直角三角堰 计…...
细说Java 引用(强、软、弱、虚)和 GC 流程(一)
一、引用概览 1.1 引用简介 JDK1.2中引入了 Reference 抽象类及其子类,来满足不同场景的 JVM 垃圾回收工作: SoftReference 内存不足,GC发生时,引用的对象(没有强引用时)会被清理;高速缓存使用…...
C++,设计模式,【工厂方法模式】
文章目录 如何用汽车生产线理解工厂方法模式?一、传统生产方式的困境二、工厂方法模式解决方案三、模式应用场景四、模式优势分析五、现实应用启示✅C++,设计模式,【目录篇】 如何用汽车生产线理解工厂方法模式? 某个早晨,某车企CEO看着会议室里堆积如面的新车订单皱起眉…...
分布式之分布式ID
目录 需求 1. 全局唯一性 2. 高性能 3. 高可用性 4. 可扩展性 5. 有序性 6. 时间相关 7. 长度适中 8. 安全性 9. 分布式一致性 10. 易于集成 常见解决方案 选择依据 数据库号段模式 核心概念 工作流程 优点 缺点 实现示例 优化策略 适用场景 Snowflake雪…...
Innovus中快速获取timing path逻辑深度的golden脚本
在实际项目中我们经常会遇到一条timing path级数特别多,可能是一两页都翻不完。此时,我们大都需要手工去数这条path上到底有哪些是设计本身的逻辑,哪些是PR工具插入的buffer和inverter。 数字IC后端手把手培训教程 | Clock Gating相关clock …...
tortoiseGit的使用和上传拉取
tortoiseGit的使用和上传拉取 下载TortoiseGit 通过网盘分享的文件:tortoiseGit.zip 链接: https://pan.baidu.com/s/1EOT_UsM9_OysRqXa8gES4A?pwd1234 提取码: 1234 在电脑桌面新建文件夹并进入 右击鼠标 将网址复制上去 用户名和密码是在git注册的用户名和…...
简单工厂模式 (Simple Factory Pattern) 在Spring Boot 中的应用
简单工厂模式(Simple Factory Pattern)虽然不属于 GoF 23 种经典设计模式,但在实际开发中非常常用,尤其是在 Spring Boot 项目中。它提供了一种简单的方式来创建对象,将对象的创建逻辑集中到一个工厂类中。 一、简单工…...