Java集合提升
1. 手写ArrayList
1.1. ArrayList底层原理细节
- 底层结构是一个长度可以动态增长的数组(顺序表)
transient Object[] elementData;
- 特点:在内存中分配连续的空间,只存储数据,不存储地址信息。位置就隐含着地址。
- 优点
- 节省存储空间,因为分配给数据的存储单元全用于存放节点的数据(不考虑c/c++中数组需指定大小的情况),节点之间的逻辑关系没有占有额外的存储空间
- 索引查找效率高,即每一个节点对应一个序号,由该序号可以直接计算出来节点的存储地址。
- 缺点
- 插入和删除操作需要移动元素,效率较低
- 必须提前分配固定数量的空间,如果存储元素少,可能导致空闲浪费
- 按照内容查询效率低,因为需要逐个比较判断
- 通过无参构造方法创建对象时,JDK1.7初始长度时10;JDK1.8初始长度是0,在第一次添加元素时就进行扩容
- 容量不足时,每次扩容增加50%,
int newCapacity = oldCapacity + (oldCapacity >> 1);
- 提供一个内部类ltr,实现了Iterator接口。用来对ArrayList进行遍历
public Iterator<E> iterator() {return new itr();
}
private class itr implements Iterator<E> {
}
1.2. 定义List接口和Iterator接口
public interface Iterator<T> {boolean hasNext();T next();
}
public interface List {// 返回线性表的大小,即数据元素的个数public int size();// 返回线性表中序号为i的数据元素public Object get(int i);// 如果线性表为空返回true,否则返回falsepublic boolean isEmpty();// 判断线性表是否包含数据元素epublic boolean contains(Object e);// 返回数据元素e在线性表中的序号public int indexOf(Object e);// 将数据元素e插入到线性表中i号位置public void add(int i, Object e);// 将数据元素e插入到线性表末尾public void add(Object e);// 将数据元素e插入到元素obj之前public boolean addBefore(Object obj, Object e);// 将数据元素e插入到元素obj之后public boolean addAfter(Object obj, Object e);// 删除线性表中序号为i的元素并返回public Object remove(int i);// 删除线性表中第一个与e相同的元素public boolean remove(Object e);// 替换线性表中序号为i的数据元素为e,返回原数据元素public Object replace(int i, Object e);// 迭代Listpublic Iterator iterator();
}
1.3. 手写ArrayList
/*
* 自定义数组越界异常
* */
public class IndexOutOfBoundsException extends RuntimeException {public IndexOutOfBoundsException() {}public IndexOutOfBoundsException(String message) {super(message);}
}
public class ArrayList implements List {// 底层数组来存储多个元素private Object[] elementData;// 存储元素的个数,线性表的长度,不是数组长度private int size;public ArrayList() {// 查看真实的ArrayList无参数构造方法是怎么实现的this(10);// this.elementData = new Object[]();// this.elementData = new Object[0];}public ArrayList(int initialCapacity) {// 如果initialCapacity<0,抛出异常if(initialCapacity < 0) {throw new IndexOutOfBoundsException("初始长度要大于0: " + initialCapacity);}// 按照初始长度给数组分配空间elementData = new Object[initialCapacity];// 指定线性表初始元素个数,可以省略,int成员变量默认值0// this.size = 0;}@Overridepublic int size() {return size;}@Overridepublic Object get(int i) {// 对i值进行判断if(i >= size) {throw new IndexOutOfBoundsException("数组指针越界异常:" + i);}return elementData[i];}@Overridepublic boolean isEmpty() {return size == 0;}@Overridepublic boolean contains(Object e) {// 逐个判断各元素是否与要查询元素内容相同,效率低下// 注意不是elementData.length,而是size/*for (int i = 0; i < size; i++) {if(e.equals(elementData[i])) return true;}return false;*/return this.indexOf(e) >= 0;}@Overridepublic int indexOf(Object e) {// 逐个判断各个元素是否与要查询元素内容相同,效率低下// 有漏洞,如果e为null,使用if分别处理for (int i = 0; i < size; i++) {if(e.equals(elementData[i])) {return i;}}return -1;}@Overridepublic void add(int i, Object e) {// 如果i超过了size,抛出异常if(i > size || i < 0) {throw new IndexOutOfBoundsException("数组指针越界异常:" + i);}// 需要先判断length是否足够,如果已经满了,需要扩容if(size == this.elementData.length) {grow();}// 添加元素// 从后向前移后面元素一个位置for (int j = size; j > i; j--) {this.elementData[i] = this.elementData[j-1];}// 添加元素到指定位置this.elementData[i] = e;// 元素个数+1this.size++;}@Overridepublic void add(Object e) {// 需要先判断length是否足够,如果已经满了,需要扩容if(size == this.elementData.length) {grow();}// 增加元素this.elementData[size] = e;// 元素长度+1size++;// 可以合并成一条语句// this.elementData[size++] = e;}private void grow() {// 创建一个新的更长数组/*Object[] newArr = new Object[this.elementData.length * 2];// 将旧数组数据放入到新数组for (int i = 0; i < this.elementData.length; i++) {newArr[i] = this.elementData[i];}// 旧数组引用指向新数组this.elementData = newArr;*/elementData = Arrays.copyOf(elementData, this.elementData.length * 2);}@Overridepublic String toString() {if(this.size == 0) return "[]";StringBuilder builder = new StringBuilder();builder.append("[");for (int i = 0; i < size; i++) {if(i != size - 1) {builder.append(this.get(i) + ",");}else {builder.append(this.get(i));}}builder.append("]");return builder.toString();}public Iterator iterator() {return new Itr();}private class Itr<T> implements Iterator<T> {// 指向当前元素,默认第一个(索引是0)int cursor = 0;// 指定当前元素的前一个元素用途在于remove使用int lastRet = -1;@Overridepublic boolean hasNext() {return cursor != size;}@Overridepublic T next() {int i = cursor;cursor++;// 如果不进行hasNext()判断,就可能越界if(i > size) {throw new RuntimeException("没有这个元素了");}return (T)elementData[i];}}
}
1.4. 测试ArrayList
public class TestArrayList {public static void main(String[] args) {// 创建线性顺序表List list = new ArrayList();// 向末尾添加元素list.add("111");list.add("222");list.add(2, "AAAA");// 进行各种操作验证添加System.out.println(list.size()); // 3System.out.println(list.isEmpty()); // falseSystem.out.println(list.get(2)); // AAAASystem.out.println(list.contains("2")); // falseSystem.out.println(list.indexOf("222")); // 1System.out.println(list.toString()); // [111,222,AAAA]Iterator<String> it = list.iterator();while (it.hasNext()) {String elem = it.next();System.out.println(elem);}}
}
2. 手写单链表和LinkedList
2.1. 单链表技能点
- 认识单链表
* 特点- 数据元素的存储对应的是不连续的存储空间,每个存储节点对应一个需要存储的数据元素
- 每个节点是由数据域和指针域组成。元素之间的逻辑关系通过存储节点之间的链接关系反映出来。逻辑上相邻的节点物理上不必相邻。
- 缺点
- 比顺序存储结构的存储密度小(每个节点都由数据域和指针域组成,所以相同空间内假设全存满,顺序比链式存储的更多)
- 按照索引查找节点时,链式存储比顺序存储满(每个节点地址不连续、无规律,导致按照索引查询效率低下)
- 优点
- 插入、删除灵活(不必移动节点,只要改变节点中的指针,但是需要先定位到元素上)
- 有元素才会分配节点空间,不会有闲置的节点
- 添加节点
- 删除节点
- 带头节点的单链表
在使用单链表实现线性表时,为了使程序更加简洁,通常在单链表最前面添加一个哑元节点,也称头节点。
在头节点中不存储任何实质的数据对象,其next域指向线性表中0号元素所在的节点
可以对空表、非空表的情况以及对首元节点进行统一管理,编程更方便,常用头节点。
一个不带头节点和带头节点的单链表实现线性表的结构如图所示
2.2. 手写单链表
- 定义单链表节点
public class Node {// 存储数据Object data;// 指向下一个节点的指针Node next;public Node() {super();}public Node(Object data) {super();this.data = data;}public Node(Object data, Node next) {super();this.data = data;this.next = next;}public Object getData() {return data;}public void setData(Object data) {this.data = data;}public Node getNext() {return next;}public void setNext(Node next) {this.next = next;}@Overridepublic String toString() {return "Node{" +"data=" + data +", next=" + next +'}';}
}
- 手写单链表
public class SingleLinkedList implements List{// 头节点,哑巴节点,不存储数据,为了方便编程private Node head = new Node();// 节点的个数private int size = 0;@Overridepublic int size() {return size;}@Overridepublic Object get(int i) {// i从0开始// 判断i值是否合理if(i < 0 || i >= size) {throw new IndexOutOfBoundsException("索引越界:" + i);}// 从第一个节点开始查找Node p = head.next; // p初始指向第一个节点(不是头节点)for (int j = 0; j < i; j++) {// p指向下个节点p = p.next;}// 返回第i个节点,而不是第i个节点的datareturn p;}@Overridepublic void add(int i, Object e) {// i值合理性判断if(i < 0 || i > size) {throw new IndexOutOfBoundsException("索引越界:" + i);}// 先找到前一个(如果当前索引是0,前一个就是head)Node p = head;if(i != 0) {p = (Node)this.get(i - 1);}// 创建一个新节点,给data复制Node newNode = new Node(e);// 指定新节点的下一个节点newNode.next = p.next;// 指定前一个节点的下一个节点是当前新节点p.next = newNode;// 数量+1size++;}@Overridepublic Object remove(int i) {if(i < 0 || i>= size) {throw new IndexOutOfBoundsException("索引越界:" + i);}// 先找到前一个(如果当前索引是0,前一个就是head)Node p = head;if(i != 0) {p = (Node)this.get(i - 1);}// 指向要删除的节点Node currentNode = p.next;// 完成删除操作p.next = currentNode.next;currentNode.next = null;// 提示将要删除的节点变成垃圾currentNode = null;// 数量-1size--;return null;}@Overridepublic String toString() {StringBuilder builder = new StringBuilder("[");// p初始指向第一个节点(不是头节点)Node p = head.next;for (int j = 0; j < size; j++) {builder.append(p.data + " ");// p指向下一个节点p = p.next;}builder.append("]");return builder.toString();}@Overridepublic boolean isEmpty() {return size == 0;}
}
-
单链表添加、删除、查询原理
-
测试单链表
public class TestList {public static void main(String[] args) {java.util.ArrayList list2;// 创建线性顺序表List list = new SingleLinkedList();// 向末尾添加元素list.add("1");list.add("2");list.add("3");list.add(3, "4");list.remove(2);// 进行各种操作验证添加System.out.println(list.size()); // 3System.out.println(list.isEmpty()); // falseSystem.out.println(list.contains("4")); // trueSystem.out.println(list.indexOf("2")); // 1System.out.println(list.toString()); // [1 2 4]}
}
2.3. 手写LinkedList
- LinkedList底层原理
- LinkedList底层是一个双向链表;添加、删除元素效率高;按照索引查找效率低。可以两个方向查询
- 每个节点都包含了对前一个和后一个元素的引用
- LinkedList同时实现了List、Deque、Queue接口,所以可以当作线性表、队列、双端队列、栈使用
- LinkedList是非同步的(线程不安全)
- 不存在LinkedList容量不足的问题
- LinkedList底层是一个双向链表;添加、删除元素效率高;按照索引查找效率低。可以两个方向查询
- 定义LinkedList节点
public class LinkedList implements List{/** 静态内部类,代表LinkedList的每个节点* */public static class Node<E> {E item;Node<E> next;Node<E> prev;Node(Node<E> prev, E element, Node<E> next) {this.item = element;this.next = next;this.prev = prev;}@Overridepublic String toString() {return "Node{" +"item=" + item +", next=" + next +", prev=" + prev +'}';}}
}
public class LinkedList implements List{transient int size = 0;transient Node first;transient Node last;@Overridepublic int size() {return size;}@Overridepublic Object get(int i) {return node(i).item;}@Overridepublic boolean isEmpty() {return size == 0;}@Overridepublic boolean contains(Object e) {return false;}@Overridepublic int indexOf(Object e) {return 0;}@Overridepublic void add(int index, Object element) {if(index == size) {linkLast(element);}else {linkBefore(element, node(index));}}Node node(int index) {if(index < (size >> 1)) {Node x = first;for (int i = 0; i < index; i++) {x = x.next;}return x;}else {Node x = last;for (int i = size - 1; i > index; i--) {x = x.prev;}return x;}}void linkBefore(Object e, Node succ) {final Node pred = succ.prev;final Node newNode = new Node<>(pred, e, succ);succ.prev = newNode;if(pred == null) {first = newNode;}else {pred.next = newNode;}size++;}@Overridepublic void add(Object e) {linkLast(e);}public void linkLast(Object e) {final Node l = last;final Node newNode = new Node<>(l, e, null);last = newNode;if(l == null) {first = newNode;}else {l.next = newNode;}size++;}public String toString() {return first.toString();}
}
- LinkedList的add(Object)方法的实现过程
- 测试LinkedList
public class TestList1 {public static void main(String[] args) {// 创建线性顺序表List list = new LinkedList();// 向末尾添加元素list.add(1);list.add(2);list.add(3);list.add(3, 4);// 进行各种操作验证添加System.out.println(list.size()); // 4System.out.println(list.isEmpty()); // falseSystem.out.println(list.get(0)); // 1}
}
3. 手写HashMap
3.1. HashMap的原理
- 底层结构是哈希表,采用了顺序表+链表结合的结构;同一个链表上所有元素的存储地址都是相同的,是发生冲突的元素
- 链表上每个节点的就是一个Entry,字段包括四部分
同一个链表上节点的哈希码可能不相同,存储的地址是相同的 - 哈希表的优点
- 添加块、查询块(通过计算得到存储位置,不是通过比较)
- 无序
- key唯一
- 关键参数
- 默认主数组长度16
- 默认装填因子0.75
- 每次主数组扩容为原来的2倍
- JDK8中,当链表长度大于8时,链表变为红黑树
- 第一步计算哈希码,不仅调用了hashCode(),又进行了多次散列。目的在于key不同,哈希码尽量不同,减少冲突
final int hash(Object k) {int h = 0;h ^= k.hashCode();h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);
}
- 细节:发生冲突,经过比较不存在相同key的元素,要添加一个新的节点。不是加到链表最后,而是添加到链表最前。
3.2. 定义Map接口
public interface Map {// 定义方法public void put(Object key, Object value);public Object get(Object key);public int size();public boolean isEmpty();// 定义内部接口interface Entry {public Object getKey();public Object getValue();}
}
3.3. 手写HashMap
public class HashMap implements Map{// 指向哈希表的数组引用public Entry[] table;// 键值对数量int size;public HashMap() {// 哈希表主数组默认长度16table = new Entry[11];}@Overridepublic void put(Object key, Object value) {// 计算哈希码int hash = key.hashCode();// 根据哈希码计算存储位置int index = hash % table.length;// 存入指定位置// 如果该位置没有元素,增加一个节点if(table[index] == null) {table[index] = new Entry(hash, key, value);size++;}else {// 如果有元素,进行逐个比较Entry entry = table[size];while (entry != null) {// 如果找到了相同的keyif(entry.getKey().equals(key)) {// 新的value替代旧的valueentry.value = value;return;}entry = entry.next;}// 循环结束,表明也没有相同的key,在链表最前添加一个节点Entry firstEntry = table[index];table[index] = new Entry(hash, key, value, firstEntry);size++;}}@Overridepublic Object get(Object key) {// 计算哈希码int hash = key.hashCode();// 根据哈希码计算存储位置int index = hash % table.length;// 查找对应的EntryEntry entry = null;if(table[index] != null) {Entry currentEntry = table[index];while (currentEntry != null) {if(currentEntry.getKey().equals(key)) {entry = currentEntry;break;}currentEntry = currentEntry.next;}}return entry == null ? null : entry.getValue();}@Overridepublic String toString() {StringBuilder builder = new StringBuilder("{");for (int i = 0; i < table.length; i++) {if(table[i] != null) {Entry entry = table[i];while (entry != null) {builder.append(entry.getKey() + "=" + entry.getValue() + "," + entry = entry.next);}}}if(builder.length() != 1) {builder.deleteCharAt(builder.length() - 1);}builder.append("}");return builder.toString();}@Overridepublic int size() {return size;}@Overridepublic boolean isEmpty() {return size == 0;}
}
public class Entry implements Map.Entry{private int hash;private Object key;private Object value;// 指向下一个节点的指针private Entry next;public Entry() {}public Entry(int hash, Object key, Object value) {this.hash = hash;this.key = key;this.value = value;}public Entry(int hash, Object key, Object value, Entry next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}@Overridepublic Object getKey() {return key;}@Overridepublic Object getValue() {return value;}@Overridepublic String toString() {return "Entry{" +"hash=" + hash +", key=" + key +", value=" + value +", next=" + next +'}';}
}
3.4 测试HashMap
public class TestHashMap {public static void main(String[] args) {java.util.HashMap map1;HashMap map = new HashMap();map.put(23, "china");map.put(36, "Japan");System.out.println(map.size());System.out.println(Arrays.toString(map.table));System.out.println(map);}
}
4. 新一代并发集合类
4.1. 手写HashSet
- HashSet底层就是HashMap,哈希表的每个Entry的key是每个元素,value统一都是new Object()
public interface Set {public int size();public void add(Object obj);public boolean isEmpty();public boolean contains(Object obj);
}
public class HashSet implements Set {private transient HashMap map;private static final Object PRESENT = new Object();public HashSet() {this.map = new HashMap();}@Overridepublic int size() {return map.size();}@Overridepublic void add(Object key) {map.put(key, PRESENT);}@Overridepublic boolean isEmpty() {return map.isEmpty();}@Overridepublic boolean contains(Object obj) {return map.get(obj) != null;}
}
4.2. 三代集合类发展过程
- 第一代线程安全集合类
- Vector、Hashtable:使用synchronized修饰方法来保证线程安全
- 缺点:效率低下
- 第二代线程非安全集合类(主流)
- ArrayList、HashMap:线程不安全,但性能好,用来替代Vector、Hashtable
- 线程安全的:使用Collections.synchronizedList(list)和Collections.synchronizedMap(m),底层使用synchronized代码块锁,虽然也是锁住了所有的代码,但是所在方法里面,比所在方法外性能稍有提高,毕竟进方法本身是要分配资源的
- 第三代线程安全集合类(波浪式前进,螺旋式上升)
- 在有大量并发下,使用java.util.concurrent.*中的ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet来提高集合的效率和安全,底层大都采用Lock锁(1.8的ConcurrentHashMap不使用Lock锁),保证安全的同时,性能也很高
4.3. 新一代并发集合类
- ConcurrentHashMap:分段(segment)锁定+Lock锁
- JDK1.7中
- 比HashMap的线程安全,并且性能比Hashtable好,Collections.synchronizedMap(m)都有提高
- 使用的不是synchronized代码块锁或方法锁,使用了锁分离技术,用多个锁来控制对hash表的不同部分(段segment)进行的修改,采用ReentrantLock锁来实现
- 如果多个修改操作发生在不同的段上,就可以并发执行,从而提高效率
- JDK1.8中
- 摒弃了segment锁段的概念,而是采用了volatile+CAS实现无锁化操作。**底层由数组+链表+红黑树的方式(JDK1.8中HashMap的实现)。**为了做到并发,又增加了很多辅助的类,如TreeBin,Traverser等对象内部类
- JDK1.7中
- CopyOnWriteArrayList:CopyOnWrite+Lock锁
- 对于set()、add()、remove()等方法使用ReentrantLock的Lock和unlock来加锁和解锁,读操作不需要加锁(之前集合安全类,即使读操作也要加锁,保证数据的实时一致)
- CopyOnWrite原理:写时复制
- 当往一个容器中添加元素时,不直接添加,而是先将容器进行copy,然后往新容器中添加元素
- 添加完元素之后,再将原容器的引用指向新容器。这样做的好处是可以对CopyOnWrite容器进行并发读,而不需要加锁,因为当前容器不会添加任何元素,所以CopyOnWrite容器也是一种读写分离的思想,读和写使用不同的容器。
- 适合对于读操作远多于写操作的应用,特别在并发的情况下,可以提高性能的并发读取
- CopyOnWrite容器只能保证数据的最终一致性,不能保证数据实时一致性。因此,如果希望写入数据马上能读到,就不要使用CopyOnWrite容器
public boolean add(E e) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;// 复制出新数组Object[] newElements = Arrays.copyOf(elements, len + 1);// 把新元素添加到新数组中newElements[len] = e;// 把原数组引用指向新数组setArray(newElements);return true;}finally {lock.unlock();}}final void setArray(Object[] a) {array = a;}public E get(int index) {return get(getArray(), index);}
注意:读时不需要加锁,如果读时有多个线程正在向ArrayList添加数据,读还是会读到旧数据,因为写时不会锁住旧的ArrayList
- CopyOnWriteArraySet:CopyOnWrite+Lock锁
- 线程安全的无序集合,CopyOnWriteArraySet和HashSet都继承于共同的父类AbstractSet;但是HashSet是通过散列表HashMap实现的,而CopyOnWriteArraySet是通过动态数组CopyOnWriteArrayList实现的
- CopyOnWriteArraySet在CopyOnWriteArrayList基础上使用了Java的装饰模式,所以底层是相同的。 而CopyOnWriteArrayList本质是动态数组队列,所以CopyOnWriteArraySet相当于通过动态数组实现的集合
- CopyOnWriteArrayList中允许有重复的元素;但CopyOnWriteArraySet不能有重复的元素。因此,CopyOnWriteArrayList额外提供了addIfAbsent()和addAllAbsent()这两个添加元素的API,通过API来添加元素,只有当元素不存在时才执行添加操作。
相关文章:
Java集合提升
1. 手写ArrayList 1.1. ArrayList底层原理细节 底层结构是一个长度可以动态增长的数组(顺序表)transient Object[] elementData; 特点:在内存中分配连续的空间,只存储数据,不存储地址信息。位置就隐含着地址。优点 节…...
uniapp 微信小程序生成水印图片
效果 源码 <template><view style"overflow: hidden;"><camera device-position"back" flash"auto" class"camera"><cover-view class"text-white padding water-mark"><cover-view class"…...
ElasticSearch相关知识点
ElasticSearch中的倒排索引是如何工作的? 倒排索引是ElasticSearch中用于全文检索的一种数据结构,与正排索引不同的是,正排索引将文档按照词汇顺序组织。而倒排索引是将词汇映射到包含该词汇的文档中。 在ElasticSearch中,倒排索…...
css 文字图片居中及网格布局
以下内容纯自已个人理解,直接上代码: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><…...
解决ImportError: DLL load failed while importing _rust: 找不到指定的程序
解决ImportError: DLL load failed while importing _rust: 找不到指定的程序 python使用库cryptography 当 from cryptography.hazmat.bindings._rust import exceptions as rust_exceptions 时,会报错: ImportError: DLL load failed while importin…...
集合-List去重
1.利用Set去重 @Test public void distinctList() {List<String> oldList = new ArrayList<>();oldList.add("a");oldList.add("a");oldList.add("b");oldList.add("c");oldList.add("d");List<String> …...
ST-LINK USB communication error 非常有效的解决方法
文章目录 一、检查确定是ST-LINK USB communication error的问题二、关闭文件,打开keil软件所在文件夹,找到STLink文件夹,找到该应用程序双击 一、检查确定是ST-LINK USB communication error的问题 二、关闭文件,打开keil软件所在…...
探索CSS的:future-link伪类:选择指向未来文档的链接
CSS(层叠样式表)是Web设计中用于描述网页元素样式的语言。随着CSS4的提案,引入了许多新的选择器,其中之一是:future-link伪类。然而,需要注意的是,:future-link伪类目前还处于提议阶段,并没有在…...
【C++】序列与关联容器(三)map与multimap容器
【C】序列与关联容器(三)map与multimap容器 一、map二、multiset / multimap 一、map 树中的每个结点的类型是一个std::pair //pair的类型是<const key,value> pair是一个包含两个指针的结构体,第一个指针指向该节点的key,…...
ActiveMQ、RabbitMQ、Kafka、RocketMQ在优先级队列、延迟队列、死信队列、重试队列、消费模式、广播模式的区别
ActiveMQ、RabbitMQ、Kafka、RocketMQ这四款消息队列在优先级队列、延迟队列、死信队列、重试队列、消费模式、广播模式等方面各有其特点和差异。以下是对这些方面的详细比较: 1. 优先级队列 ActiveMQ:支持优先级队列,可以在发送消息时指定…...
首款会员制区块链 Geist 介绍
今天,Pixelcraft Studios 很高兴地宣布即将推出 Geist,这是一个由 Base、Arbitrum、Alchemy 以及 Aavegotchi 支持的全新 L3。 Geist 之前的代号为 “Gotchichain”,是首个专为游戏打造的会员专用区块链。 为什么选择 Geist? …...
CANoe软件中Trace窗口的筛选栏标题不显示(空白)的解决方法
文章目录 问题描述原因分析解决方案扩展知识总结问题描述 不知道什么情况,CANoe软件中Trace窗口的筛选栏标题突然不显示了,一片空白。现象如下: 虽然不影响CANoe软件的使用,但是观感上非常难受,对于强迫症患者非常不友好。 原因分析 按照常规思路,尝试了: 1、重启CAN…...
日期类代码实现-C++
一、目标 通过前面对类和对象的介绍我们可以自己通过C代码初步实现一个简单的日期类。 实现的主要操作有: 1.日期类的构造函数 2.日期类的拷贝构造函数(在头文件中实现) 3.日期类的比较运算符重载 4.日期类的计算运算符重载 5.流插入运…...
【问题记录+总结】VS Code Tex Live 2024 Latex Workshop Springer模板----更新ing
目录 Summary 道阻且长 少即是多 兵马未动粮草先行 没有万能 和一劳永逸 具体问题具体分析 心态 Detail 1、关于模板[官网] 2、settings.json 3、虫和杀虫剂 4、擦 换成Tex Studio都好了。。。 Summary 道阻且长 某中意期刊,只有Latex。之前只简单用过…...
Linux运维_Bash脚本_源码安装Go-1.21.11
Linux运维_Bash脚本_源码安装Go-1.21.11 Bash (Bourne Again Shell) 是一个解释器,负责处理 Unix 系统命令行上的命令。它是由 Brian Fox 编写的免费软件,并于 1989 年发布的免费软件,作为 Sh (Bourne Shell) 的替代品。 您可以在 Linux 和…...
ShareSDK Twitter
创建应用 1.登录Twitter控制台并通过认证 2.点击Developer Portal进入Twitter后台 3.点击Sign up for Free Account创建应用 4.配置应用信息 以下为创建过程示例,图中信息仅为示例,创建时请按照真实信息填写,否则无法正常使用。 权限申请…...
word2vec 如何用多个词表示一个句子
word2vec 模型通常用于将单词映射为固定大小的向量。为了使用多个词表示一个句子,我们可以采用以下几种方法: 词袋模型 (Bag of Words, BoW): 将句子中所有词的向量加起来,不考虑词的顺序。这种方法简单,但会丢失词序信息。 计算…...
IDEA中查看接口的所有实现类和具体实现类
1.IDEA中接口的所有实现类查看 1.CTRLH(hierarchy 结构) 我们选中要查看的接口 按住快捷键ctrlh 在界面右侧可以看到该接口的所有可能实现类 2.右击diagrams->show diagram 选中要查看的接口 右击选择diagrams->show diagram 即可以以图表的方式查看接口和所有实现类…...
DLL的导出和调用
动态链接库在C中非常重要,写了一个简单的例子用于DLL的导出和调用。 DLL的生成 头文件 #include<iostream> #include<stdexcept> using namespace std;#define TESTAPI __declspec(dllexport)// 函数定义 extern "C" {TESTAPI int add(in…...
vscode中调试cuda kernel
关于vscode中调试cpp可参考之前的博客:ubuntu vscode 基本设置 和 调试设置_ubuntu vscode 调试-CSDN博客 这篇我们来讲如何调试.cu的kernel,主要参考的是:https://www.zhihu.com/question/431782036/answer/2468791220 1、基本准备不多说&am…...
SQL的连接查询与pandas的对应关系
在SQL和Pandas中,连接查询(join)是处理数据集之间关系的重要工具。下面是SQL中的各种连接查询类型及其与Pandas中相应操作的对应关系: 1. INNER JOIN SQL: INNER JOIN 返回两个表中具有匹配值的行。 Pandas: merge() 方法的 how…...
【JS】中断和恢复任务序列
前言 封装processTasks函数,实现以下需求 /*** 依次顺序执行一系列任务* 所有任务全部完成后可以得到每个任务的执行结果* 需要返回两个方法,start用于启动任务,pause用于暂停任务* 每个任务具有原子性,即不可中断,只…...
CentOS系统下安装NVIDIA显卡驱动
一、安装显卡驱动 1.安装依赖项 yum -y install gcc pciutils yum -y install gcc yum -y install gcc-c yum -y install make2.查看内核版本 uname -a3.查看显卡版本 lspci | grep -i nvidia4.屏蔽系统自带的nouveau (1)查看nouveau lsmod | grep nouveau (2)打开blackl…...
Linux 与 Windows 服务器操作系统 | 全面对比
在服务器操作系统的领域,Linux 和 Windows 一直是两个备受关注的选择。 首先来看 Windows 操作系统。它由 Microsoft Corporation 开发,在桌面领域占据显著份额,其中 Windows 10 是使用最广泛的版本,广泛应用于个人计算机和企业桌…...
给既有exe程序添加一机一码验证
原文地址:李浩的博客 lihaohello.top 本科期间开发过一款混凝土基本构件设计程序,该程序是一个独立的exe可执行文件,采用VC静态链接MFC库编制而成。近期,需要为该程序添加用户注册验证的功能,从而避免任何用户获取该程…...
【Datawhale X 魔搭 】AI夏令营第四期大模型方向,Task2:头脑风暴会,巧灵脑筋急转弯(持续更新)
队伍名称:巧灵脑筋急转弯 队伍技术栈:python,LLM,RAG,大模型,nlp,Gradio,Vue,java 队友:知唐(队长),我真的敲不动…...
mysql 多个外键
在MySQL中,一个表可以有多个外键约束,它们分别关联到不同的主表。在创建表时,可以在每个外键约束上指定不同的外键名称。以下是一个简单的例子,演示如何在创建表时定义多个外键: CREATE TABLE orders (order_id INT AU…...
解决方案上新了丨趋动科技推出基于银河麒麟操作系统的异构算力池化解决方案
趋动科技携手麒麟软件打造基于银河麒麟操作系统的异构算力池化解决方案,共同探索AI领域新场景。 人工智能技术作为数字经济发展的重要推手,在各行业业务场景中落地需要大量AI算力资源的有效保障。在IT基础设施普遍云化的今天,AI算力一方面需…...
14.创建一个实战maven的springboot项目
项目核心主要部分 pom.xml文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://mave…...
docker部署LNMP
docker部署LNMP nginx 1.22 172.111.0.10 docker-nginx mysql 8.0.30 172.111.0.20 docker-mysql php 8.1.27 172.111.0.30 docker-php docker:单节点部署,只能在一台机器上部署,如果跨机器容器无法操作,无法通信。 做高可用…...
在Spring Boot应用中,如果你希望在访问应用时加上项目的名称或者一个特定的路径前缀
在Spring Boot应用中,如果你希望在访问应用时加上项目的名称或者一个特定的路径前缀 在Spring Boot应用中,如果你希望在访问应用时加上项目的名称或者一个特定的路径前缀,你可以通过配置server.servlet.context-path属性来实现。这通常在app…...
东南大学:Wi-Fi 6搭档全光以太,打造“数智东南”信息高速路
东南大学:Wi-Fi 6搭档全光以太,打造“数智东南”信息高速路 - 华为企业业务 打好ICT底座,平台和应用层面就会非常通畅了。首先,出海企业的需求既有普遍性,也有垂直性行业的特性需求。普遍性需求需要通信、沟通数据和传…...
C++:stack类(vector和list优缺点、deque)
目录 前言 数据结构 deque vector和list的优缺点 push pop top size empty 完整代码 前言 stack类就是数据结构中的栈 C数据结构:栈-CSDN博客 stack类所拥有的函数相比与string、vector和list类都少很多,这是因为栈这个数据结构是后进先出的…...
负载均衡、高可用
负载均衡 负载均衡(Load Balance):可以利用多个计算机和组合进行海量请求处理,从而获得很高的处理效率,也可以用多个计算机做备份(高可用),使得任何一个机器坏了整个系统还是能正常…...
从Retrofit支持suspend协程请求说开去
在现代Android开发中,异步请求已经成为不可或缺的一部分。传统的异步请求往往涉及大量的回调逻辑,使代码难以维护和调试。随着Kotlin协程的引入,异步编程得到了极大的简化。而作为最流行的网络请求库之一,Retrofit早在Kotlin协程的…...
深入浅出:你需要了解的用户数据报协议(UDP)
文章目录 **UDP概述****1. 无连接性****2. 尽最大努力交付****3. 面向报文****4. 多种交互通信支持****5. 较少的首部开销** **UDP报文的首部格式****详细解释每个字段** **UDP的多路分用模型****多路分用的实际应用** **检验和的计算方法****伪首部的详细内容****检验和计算步…...
C++的Magic Static
什么是“Magic Static”? C 中,函数内部的静态变量只会在第一次执行该函数时被初始化,而且这种初始化在 C11 标准之后是线程安全的。这意味着即使多个线程同时第一次调用该函数,静态变量也只会被初始化一次,并且在初始…...
vscode添加宏定义
1 起因 在用vscode看项目代码时,如果源文件中的代码块被某个宏定义给包裹住了,则在vscode的默认配置下,不会高亮显示这块被包裹住的代码,如下图中229行开始的代码被STM32F40_41xxx所控制,没有高亮显示。 由于STM32F4…...
Postman接口关联
接口关联 接口之间存在依赖关系,接口B要依赖于接口A的返回值。 例如:现在有两个接口,接口1:获取接口统一鉴权码token接口,接口2:创建标签接口。接口2里的请求参数需要依赖接口1返回的值,即需要…...
用Python制作开心消消乐游戏|附源码
制作一个完整的“开心消消乐”风格的游戏在Python中是一个相对复杂的项目,因为它涉及到图形界面、游戏逻辑、动画效果以及用户交互等多个方面。不过,我可以为你提供一个简化的版本和概念框架,帮助你理解如何开始这个项目,并提供一…...
ArcGIS10.8 安装教程
目录 一、环境及安装包准备 二、安装流程 1、解压安装包ArcGIS_108.rar 2、安装 三、汉化 四、激活 五、自定义菜单(可选) 六、打开软件按查看 七、安装过程中出现的报错 八、其他 一、环境及安装包准备 安装环境:win7 安装包下载…...
2024网络安全学习路线,最全保姆级教程,学完直接拿捏!
关键词: 网络安全入门、渗透测试学习、零基础学安全、网络安全学习路线 首先咱们聊聊,学习网络安全方向通常会有哪些问题 前排提示:文末有CSDN独家网络安全资料包! 1、打基础时间太长 学基础花费很长时间,光语言都有…...
Apache Doris 中Compaction问题分析和典型案例
说明 此文档主要说明一些常见compaction问题的排查思路和临时处理手段。这些问题包括 Compaction socre高Compaction失败compaction占用资源多Compaction core 如果问题紧急,可联系社区同学处理 如果阅读中有问题,可以反馈给社区同学。 1 compaction …...
redis面试(十七)MultiLock加锁和释放锁
MultiLock MultiLock,英语直译为多个锁。 redisson分布式锁中的MultiLock这个机制,可以将多个锁合并为一个大锁,对一个大锁进行统一的申请加锁以及释放锁 一次性锁定多个资源,再去处理一些事情,然后事后一次性释放所…...
电脑开机LOGO修改教程_BIOS启动图片替换方法
准备工具:刷BIOS神器和change logo,打包下载地址:https://download.csdn.net/download/baiseled/89374686 一.打开刷BIOS神器,点击备份BIOS,保存到桌面 二.打开change logo,1.点击load image,选…...
微前端架构的持续集成与持续部署实践
在软件开发中,持续集成(Continuous Integration, CI)和持续部署(Continuous Deployment, CD)是实现高效、自动化软件交付的关键实践。微前端架构通过将应用拆分为多个自治的子应用,带来了开发和部署上的灵活…...
【STM32 FreeRTOS】事件标志组
事件标志组简介 事件标志组:用一个比特位来表示事件是否发生 事件标志组是一组事件标志位的集合,可以简单理解为事件标志组就是一个整数。 事件标志组的特点: 它的每一位表示一个事件(高八位不算)每一位事件的含义…...
【启动centos报错】另一个程序已锁定文件的一部分,进程无法访问,打不开磁盘.
启动centos报错 另一个程序已锁定文件的一部分,进程无法访问打不开磁盘“D:\Program2\CentOS\CentOS7\CentOS7.vmdk”或它所依赖的某个快照磁盘。模块“Disk”启动失败。未能启动虚拟机。解决方法 删除.lck文件...
基于YOLOv8-pose的手部关键点检测(3)- 实现实时手部关键点检测
目录 前言 1.扩大检测框区域 2.先检测手部,后检测手部关键点 3.正面视角检测 4.侧面视角检测 5.摄像头视角检测 6.遮挡视角检测 7.结论 前言 使用YOLOv8-m对图像进行手部检测,然后扩大检测框区域,并对该区域使用YOLOv8-s-pose使用关键…...
kylin系统永久关闭iptables
1 关闭iptables, 并且相关规则写入文件firewall.rules sudo iptables-save > /root/firewall.rules iptables -X iptables -t nat -F iptables -t nat -X iptables -t mangle -F iptables -t mangle -X iptables -P INPUT ACCEPT iptables -P FORWARD ACCEPT iptables -P …...