探秘 Map 和 Set 底层:二叉搜索树与哈希表的深度解析,解锁高效数据存储秘密!

目录

二叉搜索树(红黑树)

概念:

示例:

Java代码实现:

性能分析:

哈希表

概念:

哈希冲突:

哈希冲突的避免:

避免方式1 -- 哈希函数设计

避免方式2 -- 负载因子调节(重点掌握)

哈希冲突的解决:

方法1 -- 闭散列(开放定址法)

方法2 -- 开散列(哈希桶)

java代码实现1(基本类型的实现):

Java代码实现2(自定义型):

性能分析:

和java类集的关系:

Map and Set的概念以及场景

基本模型

Map的使用

关于Map的说明

关于Map.Entry的说明

Map的常用方法说明

Set的使用

Set的常用方法说明


二叉搜索树(红黑树)

在了解Map和Set之前,需要先补充一下二叉搜索树(红黑树的概念),二叉搜索树是TreeMapTreeSet的底层数据结构

概念:

二叉搜索树又称为二叉排序树 (注意与二叉平衡树概念区分),它或者是一颗空树,或者是具有以下性质的二叉树

  1. 若它左子树不为空,则左子树上的所有节点值都小于根节点的值

  2. 若它右子树不为空,则右子树上的所有节点值都大于根节点的值

  3. 它的左右子树也满足二叉搜索树的性质

示例:

int[] array = {5, 3, 4, 1, 7, 8, 2, 6, 0, 9}

Java代码实现:
 public class BinarySearchTree {
     //定义二叉搜索树节点
     static class TreeNode{
         public int val;
         public TreeNode left;
         public TreeNode right;
         public TreeNode(int val){
             this.val = val;
         }
     }
     //定义根节点
     public TreeNode root;
     //查找
     public boolean search(int val){
         if(root == null){
             return false;
         }
         TreeNode cur = root;
         while(cur != null){
             if(val > cur.val) {
                 cur = cur.right;
             }else if (val < cur.val) {
                 cur = cur.left;
             }else{
                 //如果val == cur.val返回true
                 return true;
             }
         }
         return false;
     }
     //插入
     public void insert(int val){
         TreeNode node = new TreeNode(val);
         //1.如果根节点为空,插入节点就等于根节点的位置
         if(root == null){
             root = node;
             return;
         }
         TreeNode parent = root;//记录prev上一个节点的位置,方便后续插入
         TreeNode prev = root;//在二叉搜索树中进行查找
         while(prev != null){
             if(val > prev.val){
                 parent = prev;
                 prev = prev.right;
             }else if (val < prev.val){
                 parent = prev;
                 prev = prev.left;
             }else{
                 //如果出现相等情况,直接返回,二叉搜索树不会出现有两个想通过数据的情况
                 return;
             }
         }
         if(val > parent.val){
             parent.right = node;
         }else if(val < parent.val){
             parent.left = node;
         }
     }
     //删除(重点)
     public void remove(int val){
         //树为空,直接返回
         if(root == null){
             return;
         }
         TreeNode cur = root;
         TreeNode parent = null;
         while(cur != null){
             if(cur.val > val){
                 parent = cur;
                 cur = cur.left;
             }else if(cur.val < val){
                 parent = cur;
                 cur = cur.right;
             }else{
                 //cur.val == val
                 removeKey(parent, cur);
             }
         }
     }
 ​
     private void removeKey(TreeNode parent, TreeNode cur) {
         //1.cur.left == null
         if(cur.left == null){
             if(cur == root){
                 root = cur.right;
             }else if(cur == parent.right){
                 parent.right = cur.right;
             }else if(cur == parent.left){
                 parent.left = cur.right;
             }
         } else if(cur.right == null){
             //2.cur.right == null
             if(cur == root){
                 root = cur.left;
             } else if (cur == parent.right) {
                 parent.right = cur.left;
             } else if (cur == parent.left) {
                 parent.left = cur.left;
             }
         }
         else{
             //3.cur.left != null && cur.right != null
             //运用替换法解决 -- 在其右子树中找到最小值
             TreeNode target = cur.right;
             TreeNode targetParent = cur;
             while(target.left != null){
                 targetParent = target;
                 target = target.left;
             }
             cur.val = target.val;
             if(target == targetParent.left){
                 targetParent.left = target.right;
             }else {
                 targetParent.right = target.right;
             }
         }
     }
 }
性能分析:
  • 对于有n个节点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是节点在二叉搜索树中深度的函数即节点越深,则比较次数越多。

  • 对于同一个关键码集合,如果各关键码插入的次序不同,则可能得到不同结构的二叉搜索树

例如:

最优情况下:二叉搜索树为完全二叉树,平均比较次数为logN

最差情况下:二叉搜索树退化为单只树,平衡比较次数为N

哈希表

概念:

顺序结构以及红黑树中,元素关键码与其存储位置之间没有对应的关系,因此查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),红黑树中为树的高度O(logN),搜索的效率取决于搜索过程中元素的比较次数。

理想的搜索方法:不经过任何比较,一次直接从表中得到要搜索的元素。如果构造一种存储结构,通过某种函数是元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。这个理想的结构叫做哈希表,计算的函数叫做哈希函数

在哈希表中:

  • 插入元素:根据待插元素的关键码,用哈希函数计算出该元素的存储位置并按照位置存放

  • 搜索元素:对待搜索元素的关键码进行同样的计算,把求得的函数值当作元素的存储位置,在结构中按此位置取元素比较,若比较相同,则搜索成功

用该种方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快

例如:

哈希冲突:

对于两个数据元素的关键字ki和kj(i!=j),ki != kj,但是又Hash(ki) == Hash(kj),即:不同关键字通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞

哈希冲突的避免:

由于我们哈希表底层数组的容量往往是小于实际要存储的关键字的数量,这就导致了一个问题,冲突的发生是必然的,但是我们尽可能做的就是降低冲突率

避免方式1 -- 哈希函数设计

引起哈希冲突的一个原因可能是:哈希函数设计不合理。

哈希函数设计原则:

  • 哈希函数定义域必须包括需要存储的全部关键码

  • 哈希函数计算出来的地址能均匀分布在整个空间中

  • 哈希函数要简洁、简单

常见哈希函数:

  1. 直接定址法 取关键字的某个线性函数为散列地址:Hash(Key) = A * Key + B

    • 优点:简单、均匀

    • 缺点:需要事先直到关键字的分布情况:适合查找比较小且连续的情况

    • 相关leecode题:字符串中第一个只出现一次的字符

    • leecode代码:

       class Solution {
           public int firstUniqChar(String s) {
               //创建一个数组来存放单词字母
               int[] array = new int[26];//26个英文字母
               for(int i = 0; i < s.length(); i++){
                   array[s.charAt(i) - 'a']++;
               }
               //检查array中的数据,找到第一个等于1的数据
               for(int j = 0; j < s.length(); j++){
                   if(array[s.charAt(j) - 'a'] == 1){
                       return j;
                   }
               }
               return -1;
           }
       }
  2. 除留余数法:

    设哈希表中允许的地址数为m,取一个不大于m,但是最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = Key % p(p <= m),将关键码转换成哈希地址

注意:哈希函数设计越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突

避免方式2 -- 负载因子调节(重点掌握)
  1. 哈希表中的负载因子定义为a = 填入表中元素的个数 / 哈希表的长度

  2. a越大,代表产生哈希冲突的可能性越大;a越小,产生冲突的可能性越小

  3. java的系统库限制了负载因子为0.75,超过此值将resize哈希表

  4. 负载因子和冲突率的关系:

哈希冲突的解决:

已知哈希表中已有关键字的个数是不可变的,我们只能调整的就只有哈希表中的数组的大小

方法1 -- 闭散列(开放定址法)

当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然有空位置,那么可以把Key存放到冲突位置中的“下一个”空位置中,对于查找下一个空位置,有两种方案:线性探测、二次探测

1.线性探测:从冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止

2.二次探测:线性探测的缺点是产生冲突的数据容易堆积到一起,这与其寻找下一个空位置有关,因为找空位置的方式就是挨个寻找,因此,二次探测可以避免上述问题。

二次探测查找函数:Hi = (H0 + i^2) % m 或者Hi = (H0 - i^2) % m:H0未哈希函数第一次计算结果,m为表的大小,Hi表示元素最新地址

研究表明:当表的长度为质数且负载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不

会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情

况,但在插入时必须确保表的负载因子a不超过0.5,如果超出必须考虑增容。

因此,闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷

方法2 -- 开散列(哈希桶

开散列又叫链地址法,首先对于关键码集合用哈希函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称作一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头节点存储在哈希表中

java代码实现1(基本类型的实现):
public class HashBuket {
     static class Node{
         public int key;
         public int val;
         public Node next;
         public Node(int key, int val) {
             this.key = key;
             this.val = val;
         }
     }
 ​
     Node[] array = new Node[10];
     public static double LOAD_FACTOR = 0.75;//负载因子
     public int usedSize;
     //插入
     public void put(int key, int value){
         Node node = new Node(key, value);
         //1.通过哈希函数找到位置
         int index = key % array.length;
         //2.判断index位置处有没有元素和key相同,若有,更新value值
         Node cur = array[index];
         while(cur != null){
             if(cur.key == key){
                 //更新val
                 cur.val = value;
                 return;//直接返回
             }
             cur = cur.next;
         }
         //3.若没有,采取尾插法
         Node findTail = array[index];//用于找到尾节点
         if(findTail == null){
             array[index] = node;
             usedSize++;
             //计算当前负载因子,如果超过0.75,进行扩容
             if(getLoadFactor() >= 0.75){
                 resize();
             }
             return;
         }
         while( findTail.next != null){
             findTail = findTail.next;
         }
         findTail.next = node;
         usedSize++;
         //4.计算当前负载因子,如果超过0.75,进行扩容
         if(getLoadFactor() >= 0.75){
             resize();
         }
     }
     //扩容
     private void resize() {
         Node[] newArray = new Node[array.length*2];//将数组扩大二倍
         for (int i = 0; i < array.length; i++) {
             Node cur = array[i];
             while(cur != null){
                 int newIndex = cur.key % newArray.length;//找新位置
                 Node curN = cur.next;//保存cur下一个节点
                 cur.next = null;//将cur与后面元素断开
                 //如果新地址为null
                 if(newArray[newIndex] == null){
                     newArray[newIndex] = cur;
                 }else{
                     //新地址不为空
                     Node tmp = newArray[newIndex];
                     while(tmp.next != null){
                         tmp = tmp.next;
                     }
                     tmp.next = cur;
                 }
                 //继续向下遍历循环
                 cur = curN;
             }
         }
         array = newArray;
     }
     //求负载因子
 ​
     public double getLoadFactor() {
         return usedSize*1.0 / array.length;
     }
 ​
     //查找
     public int get(int key){
         int index = key % array.length;
         Node cur = array[index];
         while(cur != null){
             if(cur.key == key){
                 return cur.val;
             }else{
                 cur = cur.next;
             }
         }
         return -1;
     }
 }
Java代码实现2(自定义型):
 //以自定义类型Person为例
 public class Person {
     public String id;
 ​
     public Person(String id) {
         this.id = id;
     }
 ​
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         Person person = (Person) o;
         return Objects.equals(id, person.id);
     }
 ​
     @Override
     public int hashCode() {
         return Objects.hash(id);
     }
 ​
     @Override
     public String toString() {
         return "Person{" +
                 "id='" + id + '\'' +
                 '}';
     }
 }
 
 //代码实现
 public class HashBuket2<K,V> {
     static class Node<K,V>{
         public K key;
         public V val;
         Node<K,V> next;
         public Node(K key, V val) {
             this.key = key;
             this.val = val;
         }
     }
 ​
     public Node<K,V>[] array = (Node<K, V>[])new Node[10];
     public int usedSize;
 ​
     public static double LOAD_FACTOR = 0.75;
 ​
     //插入
     public void put(K key, V val){
         Node<K,V> node = new Node<>(key,val);
         //1.通过哈希地址,求在哈希表中存放地址
         int hashCode = key.hashCode();
         int index = hashCode % array.length;
         Node<K,V> cur = array[index];
         //2.若有重复的元素,则直接返回
         while(cur != null){
             if(cur.key.equals(key)){
                 cur.val = val;
                 return;
             }
             cur = cur.next;
         }
         //3.待插入元素不重复
         Node<K,V> findTail = array[index];
         if(findTail == null){
             array[index] = node;
             usedSize++;
             return;
         }
         while(findTail.next != null){
             findTail = findTail.next;
         }
         findTail.next = node;
         usedSize++;
 ​
         //如果负载因子超过0.75,扩容
         if(getLoadFactor() >= 0.75){
             resize();
         }
     }
 ​
     private void resize() {
         Node<K,V>[] newArray = (Node<K, V>[])new Node[array.length * 2];
         for (int i = 0; i < array.length; i++) {
             Node<K,V> cur = array[i];
             while(cur != null){
                 int newIndex = cur.key.hashCode() % newArray.length;
                 Node<K,V> curN = cur.next;
                 cur.next = null;
                 if(newArray[newIndex] == null){
                     newArray[newIndex] = cur;
                 }else{
                     Node<K,V> tmp = newArray[newIndex];
                     while(tmp.next != null){
                         tmp = tmp.next;
                     }
                     tmp.next = cur;
                 }
                 cur = curN;
             }
         }
     }
 ​
     public double getLoadFactor(){
         return usedSize*1.0 / array.length;
     }
 ​
     //查找
     public V get(K key){
         int index = key.hashCode() % array.length;
         Node<K,V> cur = array[index];
         while(cur != null){
             if(cur.key.equals(key)){
                 return cur.val;
             }
             cur = cur.next;
         }
         return null;
     }
 }
 //测试代码 
 public static void main(String[] args) {
     HashBuket2<Person,Integer> hashBuket2 = new HashBuket2<>();
     Person person1 = new Person("1234");
     Person person2 = new Person("4567");
     hashBuket2.put(person1, 10);
     hashBuket2.put(person2, 10);
     System.out.println(hashBuket2.get(person2));
 }

性能分析:

虽然哈希表一直在和冲突作斗争,但是在实际使用过程中,我们认为哈希表的冲突率是不高的,冲突的个数是可控的,也就是说每个桶中的链表长度是一个常数,所以,通常意义下,我们认为哈希表的插入/删除/查找时间复杂度是O(1)

和java类集的关系:

  1. HasMapHashSet即Java中利用哈希表实现的Map和Set

  2. java中使用的是哈希桶解决冲突

  3. java会在冲突链表长度大于一定阈值后,将链表转化为搜索树(红黑树)

  4. java中计算哈希值实际上是调用类的hashCode方法,进行Key的相等性比较是调用Key的*equals*方法。所以,如果需要使用自定义类型,必须要覆写hashCodeequals方法。

Map and Set的概念以及场景

Map和Set是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关。

//插入/删除/查找时间复杂度为:O(1)
 Map<String,Integer> hashMap = new HashMap<>();
 //插入/删除/查找时间复杂度为:O(logN)
 Map<String,Integer> treeMap = new TreeMap<>();
 //插入/删除/查找时间复杂度为:O(1)
 Set<String> hashSet = new HashSet<>();
 //插入/删除/查找时间复杂度为:O(logN)
 Set<String> treeSet = new TreeSet<>();

基本模型

一般把搜索的数据称为关键字(Key),和关键字对应的值(Value),将其称之为Key-Value的键值对,所以模型会有两种:

  • 纯Key模型<K>:只进行相应关键字(Key)查找,比如:

    • 在英文单词"Hello"中查找字母"l"

  • Key-Value模型<K,V>:进行关键字(Key)的查找,并且也同时显示关键字对应的值(Value),比如:

    • 在英文单词"Hello"中查找字母"l",并且显示"l"在字母中出现的次数:<字母,字母出现的次数>

注意:**在Map中存储的是Key-Value模型的数据,Set中存储的是Key模型的数据**。

Map的使用

关于Map的说明

Map是一个接口类,该类没有继承自Collection,该类中存储的是<K,V>结构的键值对,并且K一定是唯一的,不能重复。

关于Map.Entry<K,V>的说明

Map.Entry<K,V>是Map内部实现的用来存放<K,V>键值对映射关系的内部类,该内部类中主要提供了<K,V>的获取V的设置以及K的比较方式。

方法解释
K getkey()返回entry中的key
V getValue()返回entry中的Value
V setValue()将键值对中的Value替换为指定的Value

注意:Map.Entry<K,V>并没有提供设置Key的方法

Map的常用方法说明

方法解释
V get(Object key)返回key对应的V
V getOrDefault(Object key, V defaultValue)返回Key对应的V,若Key不存在,返回默认值(defaultValue)
V put(K key, V Value)设置Key对应的Value(添加元素)
V remove(Object key)删除Key对应的映射关系(删除元素)
Set<K> KeySet()返回所有Key的不重复集合
Collection<V> values()返回所有value的可重复集合
Set<Map.Entry<K,V>> entrySet()返回所有的Key-Value映射关系
boolean containsKey(Object Key)判断是否包含Key
boolean containsValue(Object Value)判断是否包含Value
//常用方法代码示例
 public static void main(String[] args) {
         Map<String,Integer> hashMap = new HashMap<>();
         hashMap.put("Hello", 10);
         hashMap.put("World", 20);
         hashMap.put("Yes", 20);
         System.out.println(hashMap);
         System.out.println(hashMap.get("Yes"));
         System.out.println(hashMap.getOrDefault("Hello", 40));
         hashMap.remove("World");
         System.out.println(hashMap);
         System.out.println(hashMap.keySet());
         System.out.println(hashMap.values());
         System.out.println(hashMap.entrySet());
     }

注意:

  1. Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化TreeMap或者HashMap

  2. Map中存放的Key是唯一的,Value是可以重复的

  3. TreeMap中插入数据的时候,Key不能为空,否则会抛出NullPointerException异常,Value可以为空。但是HashMap的Key和Value都可以为空,由于TreeMap底层为一个二叉搜索树(红黑树),HashMap的底层为哈希桶

  4. Map中的Key可以全部分离出来,存储到Set中进行访问:通过Set<K> KeySet()方法

  5. Map中的Value可以全部分离出来,存储到Collection的任何一个子集合中:通过Collection<V> values()方法

  6. Map中的Key不可以直接修改,如需要修改,只能先将Key删除,再进行插入

  7. TreeMap和HashMap的区别

Map底层结构TreeMapHashMap
底层结构红黑树(二叉搜索树)哈希桶
插入/删除/查找时间复杂度O(logN)O(1)
是否有序关于Key有序无序
线程安全不安全不安全
插入/删除/查找元素的区别需要进行比较不需要进行比较,通过哈希函数计算哈希地址
比较与覆写Key必须可以比较,否则会抛出ClassCastExceotion异常自定义类型需要覆写equalshashCode方法
应用场景需要在Key有序场景下Key是否有序不关心,需要更高的时间性能

Set的使用

Set和Map的主要区别:Set是继承自Collection的接口类,Set中只存储了Key

Set的常用方法说明

方法解释
boolean add(E e)添加元素,单重复元素不会被添加成功
void clear()清空集合
boolean contains(Object o)判断o是否在集合中
Iterator<E> iterator()返回迭代器
boolean remove(Object o)删除集合中的o
int size()返回set中元素的个数
boolean isEmpty()检查set中元素是否为空,空返回true,否则返回false
Object[] toArray()将set中的元素转换为数组返回
boolean containsAll(Collection<?> c)集合c中的元素是否都set中存在,是返回true,否则返回false
boolean addAll(Collection<? extends E> c)将集合c中的元素全部添加到set中,可以达到去重效果
//代码示例
 public static void main(String[] args) {
         Set<String> set = new TreeSet<>();
         //如果“apple”存在返回false,不存在则插入返回true
         boolean isAdd = set.add("apple");
         System.out.println(isAdd);
         set.add("orange");
         set.add("peach");
         set.add("banana");
         System.out.println("set中元素个数" + set.size());
         System.out.println(set);
         System.out.println("hello是否存在set中" + set.contains("hello"));
         System.out.println("set是否为空"+ set.isEmpty());
         set.remove("apple");
         Object[] object = set.toArray();
         System.out.println(Arrays.toString(object));
         //迭代器Iterator
         Iterator<String> iterator = set.iterator();
         while (iterator.hasNext()) {
             System.out.print(iterator.next()+ " ");
         }
     }

注意:

  • Set是继承自Collection的一个接口

  • Set中只存储Key,并要求Key一定要唯一

  • TreeSet的底层是使用Map来实现的,其使用(<key, Object>)Key与Object的一个默认对象作为键值对插入到Map中,以下图片显示的是IDEA中TreeMap的源码

  • Set的最大功能就是对集合中的元素进行去重

  • 实现Set接口的常用类有TreeSet和HashSet,还有一个LinkedHashSetLinkedHashSet是在HashSet的基础上维护了一个双向链表来记录元素的插入次序

  • Set中的值不能修改,如果需要修改,先删除再插入

  • TreeSet中不能插入null的Key,HashSet可以

  • TreeSet和HashSet的区别

Set底层结构TreeSetHashSet
底层结构红黑树哈希桶
插入/删除/查找时间复杂度O(logN)O(1)
是否有序关于Key有序不一定有序
线程安全不安全不安全
插入/删除/查找区别按照红黑树特性1.先计算Key哈希地址 2.进行插入和删除
比较与覆写key必须能够比较,否则抛出ClassCastException异常自定义类型需要覆写equalshashCode方法
应用场景Key有序场景下key是否有序不关注,更注重时间性能

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python从入门到进去

python从入门到进去 第一章、软件和工具的安装一、安装 python 解释器二、安装 pycharm 第二章、初识 python一、注释可分三种二、打印输入语句三、变量1、基本数据类型1.1、整数数据类型 int1.2、浮点数数据类型 float1.3、布尔数据类型 boolean1.4、字符串数据类型 string 2、…

001-监控你的文件-FSWatch-C++开源库108杰

fswatch 原理与应用简介fswatch 安装fswatch 实践应用具体应用场景与细节补充 1. 简介 有些知识&#xff0c;你知道了不算厉害&#xff0c;但你要是不知道&#xff0c;就容易出乱。 很多时候&#xff0c;程序需要及时获取磁盘上某个文件对象&#xff08;文件夹、文件&#xff0…

华为云kubernetes基于keda自动伸缩deployment副本(监听redis队列长度)

1 概述 KEDA&#xff08;Kubernetes-based Event-Driven Autoscaler&#xff0c;网址是https://keda.sh&#xff09;是在 Kubernetes 中事件驱动的弹性伸缩器&#xff0c;功能非常强大。不仅支持根据基础的CPU和内存指标进行伸缩&#xff0c;还支持根据各种消息队列中的长度、…

解锁机器学习核心算法 | 决策树:机器学习中高效分类的利器

引言 前面几篇文章我们学习了机器学习的核心算法线性回归和逻辑回归。这篇文章我们继续学习机器学习的经典算法——决策树&#xff08;Decision Tree&#xff09; 一、决策树算法简介 决策树算法是一种典型的分类方法&#xff0c;也是一种逼近离散函数值的方法。它的核心思想…

CRISPR spacers数据库;CRT和PILER-CR用于MAGs的spacers搜索

iPHoP&#xff1a;病毒宿主预测-CSDN博客 之前介绍了这个方法来预测病毒宿主&#xff0c;今天来介绍另一种比较用的多的方法CRISPR比对 CRISPR spacers数据库 Dash 在这可以下载作者搜集的spacers用于后期比对 CRT和PILER-CR 使用 CRT 和 PILERCR 识别 CRISPR 间隔区&#x…

TestHubo基础教程-创建项目

TestHubo是一款国产开源一站式测试工具&#xff0c;涵盖功能测试、接口测试、性能测试&#xff0c;以及 Web 和 App 测试&#xff0c;可以满足不同类型项目的测试需求。本文将介绍如何快速创建第一个项目&#xff0c;以快速入门上手。 1、创建项目 在 TestHubo 中&#xff0c;…

多模态基础模型第二篇-deepseek-r1部署

分别使用本地windows和云端linux进行部署&#xff0c;测试不同硬件资源的模型推理性能&#xff1a; windos部署&#xff1a;直接打开Download Ollama on Linux 下载&#xff0c;然后本地启动服务&#xff0c; linux部署&#xff1a;curl -fsSL https://ollama.ai/install.sh …

本地 Ollama 部署 Deepseek R1 并使用 Spring AI Alibaba 构建 Chat 应用示例

本地部署 Deepseek R1 并使用 Spring AI Alibaba 构建 Chat 应用示例 Ollama 部署 Deepseek R1 官网&#xff1a;https://www.deepseek.com/ Github&#xff1a;https://github.com/deepseek-ai Ollama&#xff1a;https://ollama.com/ Docker Compose 部署一个 Ollama 和…

【TI C2000】F28002x的系统延时、GPIO配置及SCI(UART)串口发送、接收

【TI C2000】F28002x的系统延时、GPIO配置及SCI&#xff08;UART&#xff09;串口发送、接收 文章目录 系统延时GPIO配置GPIO输出SCI配置SCI发送、接收测试附录&#xff1a;F28002x开发板上手、环境配置、烧录及TMS320F280025C模板工程建立F28002x叙述烧录SDK库文件说明工程建…

LabVIEW中的icon.llb 库

icon.llb 库位于 C:\Program Files (x86)\National Instruments\LabVIEW 2019\vi.lib\Platform 目录下&#xff0c;是 LabVIEW 系统中的一个重要库。它的主要功能是与图标相关的操作&#xff0c;提供了一些实用的 VI 用于处理 LabVIEW 图标的显示、修改和设置。通过该库&#x…

C语言-章节 1:变量与数据类型 ——「未初始化的诅咒」

在那神秘且广袤无垠的「比特大陆」上&#xff0c;阳光奋力地穿过「内存森林」中错综复杂的代码枝叶缝隙&#xff0c;洒下一片片斑驳陆离、如梦似幻的光影。林间的空气里&#xff0c;弥漫着一股浓郁的十六进制锈蚀味&#xff0c;仿佛在诉说着这片森林中隐藏的古老秘密。 一位零基…

Dest1ny漏洞库:用友 U8-CRM 系统 ajaxgetborrowdata.php 存在 SQL 注入漏洞

大家好&#xff0c;今天是Dest1ny漏洞库的专题&#xff01;&#xff01; 会时不时发送新的漏洞资讯&#xff01;&#xff01; 大家多多关注&#xff0c;多多点赞&#xff01;&#xff01;&#xff01; 用友U8-CRM系统ajaxgetborrowdata.php存在SQL注入漏洞&#xff0c;文件多…

全平台搭载旭日5!科沃斯GOAT智能割草机器人全新系列正式开售

要闻 近日&#xff0c;科沃斯全新发布的GOAT A Series 和 GOAT O Series割草机器人&#xff0c;将在多国市场正式上市发售。作为业界最强的割草机器人产品之一&#xff0c;GOAT致力为割草机带来基于机器人视觉的专业定位解决方案。科沃斯GOAT全新系列产品全平台搭载地瓜机器人…

HCIA综合项目之多技术的综合应用实验

十五 HCIA综合实验 15.1 IP规划 #内网分配网段192.168.1.0 24#内网包括骨干链路和两个用户网段&#xff0c;素以需要划分三个&#xff0c;借两位就够用了192.168.1.0 26--骨干192.168.1.64 26---R1下网络192.168.1.128 26---R2下网络192.168.1.192 26--备用​192.168.1.64 26--…

PbootCMS增加可允许上传文件类型,例如webp、mov等文件格式扩展

在PbootCMS日常使用过程中&#xff0c;会涉及一些非常见的文件格式上传。 这时候就需要在PbootCMS配置文件中追加一些允许上传文件扩展名。 操作步骤 1、打开/config/config.php文件&#xff0c;大约在30行&#xff0c;修改upload配置信息&#xff1a; // 上传配置upload &…

DeepSeek应用——与PyCharm的配套使用

目录 一、配置方法 二、使用方法 三、注意事项 1、插件市场无continue插件 2、无结果返回&#xff0c;且在本地模型报错 记录自己学习应用DeepSeek的过程&#xff0c;使用的是自己电脑本地部署的私有化蒸馏模型...... &#xff08;举一反三&#xff0c;这个不单单是可以用…

本地快速部署DeepSeek-R1模型以及可视化工具

这里写目录标题 安装 Ollama下载和部署DeepSeek模型可视化工具 安装 Ollama Ollama 是一个轻量级的可扩展框架&#xff0c;用于在本地计算机上构建和运行语言模型。它提供了一个用于创建、运行和管理模型的简单 API&#xff0c;以及一个可在各种应用程序中轻松使用的预构建模型…

hive高频写入小数据,导致hdfs小文件过多,出现查询效率很低的情况

问题描述 hive高频写入小数据&#xff0c;导致hdfs小文件过多&#xff0c;出现查询效率很低的情况分析过程 先复现现象 select count() from ads.ads_sdd_flow_managemlt_to_ids_mm;–15分钟&#xff0c;小文件10983 select max(mm) from ads.ads_sdd_flow_managemlt_to_ids…

Docker 部署 MySQL 8 详细图文教程

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall ︱vue3-element-admin︱youlai-boot︱vue-uniapp-template &#x1f33a; 仓库主页&#xff1a; GitCode︱ Gitee ︱ Github &#x1f496; 欢迎点赞 &#x1f44d; 收藏 ⭐评论 …

Pythong 解决Pycharm 运行太慢

Pythong 解决Pycharm 运行太慢 官方给Pycharm自身占用的最大内存设低估了限制,我的Pycharm刚开始默认是256mb。 首先找到自己的Pycharm安装目录 根据合适自己的改 保存&#xff0c;重启Pycharm