深度解读并发安全集合的原理及源码

embedded/2024/9/19 19:13:52/ 标签: 安全, java, 开发语言

本节主要介绍J.U.C包中的几种并发安全集合:ConcurrentHashMap,ConcurrentLinkedQueue,和ConcurrentLinkedDeque。所谓并发安全集合,相对于普通集合来说,能够保证在多线程环境下向集合中添加数据时的线程安全性。主要讲ConcurrentHashMap在实现线程安全性方面对性能和安全性的合理平衡。

并发安全集合ConcurrentHashMap

ConcurrentHashMap是JDK1.5引入的一个并发安全且高效的HashMap,简单来说,我们可以认为他在HashMap的基础上增加了线程安全性的保障。

实现上,关于HashMap采用数组+链表的结构来存储数据,在多个线程并发执行扩容时,可能造成环形链进而导致死循环和数据丢失;在JDK1.8中,HashMap采用数组+链表+红黑树的数据结构来存储数据,优化了JDK1.7中数据扩容的方案,解决了死循环和丢失的问题,但是在并发场景下调用put(),还会存在数据覆盖的问题。

为了解决线程安全性问题带来的影响,我们可以采用一些具备线程安全性的集合,比如HashTable,它使用synchronized关键字来保障线程的安全性;还有Conllections.synchronizeMap,它可以把一个线程不安全的Map,通过synchronized(互斥锁)的方式转换成安全的。但是这些方法有一个问题,在线程竞争激烈的情况下,效率非常低。原因是因为使用synchronized实现锁机制,会导致所有线程在操作数据的时候不管是put还是get操作都需要去竞争同一把锁。

ConcurrentHashMap在性能和安全方面的设计和实现都比较巧妙,技能保证线程的安全性,在性能方面也远远优于HashTable等集合。

正确理解ConcurrentHashMap的线程安全

ConcurrentHashMap本身就是一个HashMap,因此在实际应用上,只需要考虑当前场景是否存在多线程并发访问同一个Map实例,如果存在,则采用ConcurrentMap。需要注意的是,ConcurrentHashMap的线程安全性,只能保证多线程并发执行时,容器中的数据不会被破坏,但是涉及到多个线程的复合操作,ConcurrentHashMap无法保证业务行为的正确性。

public class ConCurrentHahMapExample {private static final ConcurrentMap<String, Long> USER_ACCESS_COUNT = new ConcurrentHashMap<>(64);public static void main(String[] args) {Long accessCount = USER_ACCESS_COUNT.get("Liang");if (null == accessCount) {USER_ACCESS_COUNT.put("Liang", 1L);} else {USER_ACCESS_COUNT.put("Liang", accessCount + 1);}}
}

上述代码属于一个复合操作,也就是“读-修改-写”,这三个操作不是原子的,所以当多个线程去访问同一个用户的时候可能会导致覆盖相互操作的结果,造成记录的数据少于实际的次数。

虽然ConcurrentHashMap是线程安全的,但是对于ConcurrentHash的复合操作,我们需要特别关注。当然上述问题有很多解决方案,比如我们针对于复合操作进行枷锁。ConcurrentHashMap提供了另外一个解决方案,就是使用ConcurrentMap接口定义。

java">public interface ConcurrentMap<K, V> extends Map<K, V> {V putIfAbsent(K key, V value);boolean remove(Object key, Object value);boolean replace(K key, V oldValue, V newValue);V replace(K key, V value);}     
  • putIfAbsent():向ConcurrentHashMap集合插入数据,如果插入的key不存在集合中,则保存当前数据并返回null。如果key已经存在,则返回key对于的value值。
  • remove():根据key和value来删除ConcurrentHashMap集合中的数据,该删除操作必须保证key和value完全匹配,如果匹配成功则返回true,否则返回false。
  • replace(K,V,V):根据key和oldValue来替换ConcurrentHashMap中已经存在的值,新的值是newValue,该提壶按操作必须保证key和oldValue完全匹配,替换成功则返回地面true,否则返回false。
  • replace(K,V):少了对oldValue的判断,如果替换成功,则返回替换之前的value,否则返回null。
public class ConCurrentHahMapExample {private static final ConcurrentMap<String, Long> USER_ACCESS_COUNT = new ConcurrentHashMap<>(64);public static void main(String[] args) {while (true) {Long accessCount = USER_ACCESS_COUNT.get("Liang");if (null == accessCount) {if (null == USER_ACCESS_COUNT.putIfAbsent("Liang", 1L)) {break;}} else {if (USER_ACCESS_COUNT.replace("Liang", accessCount, accessCount + 1)) {break;}}}}
}

JDK1.8提供了支持lambda表达式的原子操作:

public interface ConcurrentMap<K, V> extends Map<K, V> {@Overridedefault V computeIfAbsent(K key,Function<? super K, ? extends V> mappingFunction) {Objects.requireNonNull(mappingFunction);V v, newValue;return ((v = get(key)) == null &&(newValue = mappingFunction.apply(key)) != null &&(v = putIfAbsent(key, newValue)) == null) ? newValue : v;}@Overridedefault V computeIfPresent(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction) {Objects.requireNonNull(remappingFunction);V oldValue;while((oldValue = get(key)) != null) {V newValue = remappingFunction.apply(key, oldValue);if (newValue != null) {if (replace(key, oldValue, newValue))return newValue;} else if (remove(key, oldValue))return null;}return oldValue;}@Overridedefault V compute(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction) {Objects.requireNonNull(remappingFunction);V oldValue = get(key);for(;;) {V newValue = remappingFunction.apply(key, oldValue);if (newValue == null) {// delete mappingif (oldValue != null || containsKey(key)) {// something to removeif (remove(key, oldValue)) {// removed the old value as expectedreturn null;}// some other value replaced old value. try again.oldValue = get(key);} else {// nothing to do. Leave things as they were.return null;}} else {// add or replace old mappingif (oldValue != null) {// replaceif (replace(key, oldValue, newValue)) {// replaced as expected.return newValue;}// some other value replaced old value. try again.oldValue = get(key);} else {// add (replace if oldValue was null)if ((oldValue = putIfAbsent(key, newValue)) == null) {// replacedreturn newValue;}// some other value replaced old value. try again.}}}}@Overridedefault V merge(K key, V value,BiFunction<? super V, ? super V, ? extends V> remappingFunction) {Objects.requireNonNull(remappingFunction);Objects.requireNonNull(value);V oldValue = get(key);for (;;) {if (oldValue != null) {V newValue = remappingFunction.apply(oldValue, value);if (newValue != null) {if (replace(key, oldValue, newValue))return newValue;} else if (remove(key, oldValue)) {return null;}oldValue = get(key);} else {if ((oldValue = putIfAbsent(key, value)) == null) {return value;}}}}
}

computeIfAbsent()方法详解

computeIfAbsent()方法是通过判断传入key是否存在来对ConcurrentMap集合进行数据初始化操作,如果存在,则不做任何处理;如果不存在则调用remappingFunction计算出balue值,然后把key=value存入ConcurrentHashMap中。由于remappingFunction时一个函数式接口,所以他的返回值也会影响存储结果。

  • 如果mappingFunction返回的value不为null,则存储key=value。
  • 如果mappingFunction返回的value为null,由于ConcurrentHashMap不允许value为null,所以不会存储,返回null。
USER_ACCESS_COUNT.computeIfAbsent("Liang",v->10L);

computeIfPresent()方法详解

和computeIfAbsent()方法的作用正好相反,computeIfPresent()方法对已经存在的key对应的value值进行修改。如果key不存在,则返回null;如果key存在,则调用remappingFunction进行运算,根据返回的value的情况做出不同的处理。

  • 如果remappingFunction返回的value不为null,则修改当前key的value为remappingFunction的值。
  • 如果remappingFunction的返回值为null,则删除当前的key,相当于调用了remove(key)方法。
  • 如果remappingFunction抛出异常,则原本key对应的value值不会发生变化。
java">USER_ACCESS_COUNT.computeIfPresent("Liang",(k,v)->v+1L);

compute()方法详解

compute()方法相当于computeIfAbsent()和computeIfPresent()方法的结合体,它不管key是否存在,都会调用remappingFunction进行计算。如果key存在,则调用remappingFunction对value进行修改;如果key不存在,则调用remappingFunction进行初始化。

java">USER_ACCESS_COUNT.compute("Liang",(k,v)->(null==v)?1L:v+1);

merge()方法详解

将ConcurrentHashMap中相同的key和value合并。

  • ConcurrentHashMap不存在指定的key时,则把传入的value设置key的值。
  • ConcurrentHashMap存在指定的key时,就将传入的新旧value值性自定义逻辑处理,返回最终的结果并设置为key的值。分不同的场景
    • 如果写为(oldValue,newValue)->newValue,把当前key的value修改成newValue。
    • 如果写为(oldValue,newValue)->oldValue,表示保留oldValue,不做修改。
    • 如果写为(oldValue,newValue)->oldValue+newValue,表示对新老两个值的合并。
    • 如果写为(oldValue,newValue)->null,删除当前的key。
ConcurrentMap<Integer,Integer> cm = new ConcurrentHashMap<>();
Stream.of(1,2,8,2,5,6,5,8,3,8).forEach(v->{cm.merge(v,2,Integer::sum);
});
System.out.println(cm);

ConcurrentHashMap的数据结构

在JDK1.8中,ConcurrentHashMap采用数组+链表+红黑树的方式来实现数据的存储,数据结构相比于JDK1.7,做了如下改造:

  • 取消了segment分段设计,直接使用Node数组来保存数据,并且采用Node数组元素作为锁的范围,进一步减小了并发冲突的范围和概率。
  • 引入红黑树的设计,降低了极端情况下插叙某个节点数据的时间复杂度,从O(n)降低到了O(logn),提高了查找性能。

ConcurrentHashMap在性能和安全性方面也做好了平衡,使用了一些巧妙的设计,主要体现在以下几个方面:

  • 分段锁的设计
  • 多线程协助实现并发扩容
  • 高低位迁移设计
  • 链表转红黑树,红黑树转链表
  • 降低锁的粒度

ConcurrentHashMap数据存储的相关定义

java">transient volatile Node<K,V>[] table;private static final sun.misc.Unsafe U;// 初始化容量大小 ---> 初始化前
// sizeCtl = -1 表示正在初始化中
// 如果另一个线程进入发现sizeCtl = -1 ,他要让出CPU资源片  --->初始化中
// 代表扩容阈值  ---> 初始化后
private transient volatile int sizeCtl;//	通过反射去获取sizeCtl的值
private static final long SIZECTL;private static final long TRANSFERINDEX;
private static final long BASECOUNT;
private static final long CELLSBUSY;
private static final long CELLVALUE;
private static final long ABASE;
private static final int ASHIFT;static {try {U = sun.misc.Unsafe.getUnsafe();Class<?> k = ConcurrentHashMap.class;SIZECTL = U.objectFieldOffset(k.getDeclaredField("sizeCtl"));TRANSFERINDEX = U.objectFieldOffset(k.getDeclaredField("transferIndex"));BASECOUNT = U.objectFieldOffset(k.getDeclaredField("baseCount"));CELLSBUSY = U.objectFieldOffset(k.getDeclaredField("cellsBusy"));Class<?> ck = CounterCell.class;CELLVALUE = U.objectFieldOffset(ck.getDeclaredField("value"));// 该对象在内存中所占有的初始化偏移量Class<?> ak = Node[].class;ABASE = U.arrayBaseOffset(ak);int scale = U.arrayIndexScale(ak);if ((scale & (scale - 1)) != 0)throw new Error("data type scale not a power of two");//	在内存中占有的容量ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);} catch (Exception e) {throw new Error(e);}
}// 当前的i在tab中所占有的偏移量的大小
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
java">static class Node<K,V> implements Map.Entry<K,V> {//	表示key对应的hash值final int hash;//	实际存储的keyfinal K key;//	实际存储的valuevolatile V val;//	表示链表结构,next表示的只想下一个node节点的指针volatile Node<K,V> next;Node(int hash, K key, V val, Node<K,V> next) {this.hash = hash;this.key = key;this.val = val;this.next = next;}//	切记:hashCode方法和equals方法是配套使用的,如果重写其中的一个,那么另外一个也需要重写public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }public final boolean equals(Object o) {Object k, v, u; Map.Entry<?,?> e;return ((o instanceof Map.Entry) &&(k = (e = (Map.Entry<?,?>)o).getKey()) != null &&(v = e.getValue()) != null &&(k == key || k.equals(key)) &&(v == (u = val) || v.equals(u)));}}
java">// 最大值:二进制:01000000 00000000 00000000 00000000
private static final int MAXIMUM_CAPACITY = 1 << 30;//	链表长度的阈值
static final int TREEIFY_THRESHOLD = 8;// 扩容的数组最小的值
static final int MIN_TREEIFY_CAPACITY = 64;//	获取当前可用线程数
static final int NCPU = Runtime.getRuntime().availableProcessors();
public ConcurrentHashMap() {
}
java">public ConcurrentHashMap(int initialCapacity) {if (initialCapacity < 0)throw new IllegalArgumentException();int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?MAXIMUM_CAPACITY :// 设置成距离1.5倍的initialCapacity距离最左边的2的倍数的数字 再扩大2倍// 原因:避免在多线程过程中,频繁扩容造成的性能开销---膨胀阈值设计tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));// 把计算好的容量大小赋值给sizeCtlthis.sizeCtl = cap;
}
java">public V put(K key, V value) {return putVal(key, value, false);
}
java">final V putVal(K key, V value, boolean onlyIfAbsent) {// 在多线程环境下不允许存在一些歧义现象。if (key == null || value == null) throw new NullPointerException();// int hash = spread(key.hashCode());int binCount = 0;for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;if (tab == null || (n = tab.length) == 0)tab = initTable();//	(n-1)&hash ----> 如果n为2的整数倍时,这个式子等同于hash%nelse if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {// 更改数组偏移量-->保存这个元素if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))break;                   // no lock when adding to empty bin}else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);//	要么替换,要么解决hash冲突else {V oldVal = null;synchronized (f) {if (tabAt(tab, i) == f) {if (fh >= 0) {binCount = 1;for (Node<K,V> e = f;; ++binCount) {K ek;// 	如果hash,key相同,说明做新旧值替换if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {oldVal = e.val;if (!onlyIfAbsent)e.val = value;break;}// hash相同,维护链表Node<K,V> pred = e;if ((e = e.next) == null) {pred.next = new Node<K,V>(hash, key,value, null);break;}}}else if (f instanceof TreeBin) {Node<K,V> p;binCount = 2;if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value;}}}}//	如果链表长度大于等于8,进行转换处理逻辑if (binCount != 0) {if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal != null)return oldVal;break;}}}addCount(1L, binCount);return null;
}
java">//	减少碰撞,进一步降低hash冲突的几率。
//	使用异或运算 保留高低位特性 而且分布均匀
//	与操作是为让最高位为0,消除符号位,等到的都是正数。因为负的hashCode在ConcurrentHashMap中有特殊的含义,因此我们需要得到一个正的hashCode。
static final int spread(int h) {return (h ^ (h >>> 16)) & HASH_BITS;
}// 01111111 11111111 11111111 11111111
static final int HASH_BITS = 0x7fffffff;
java">private final Node<K,V>[] initTable() {Node<K,V>[] tab; int sc;// 防止在多线程环境下同时存在着多个线程进行初始化 while ((tab = table) == null || tab.length == 0) {if ((sc = sizeCtl) < 0)// 将线程改变成就绪状态,释放CUP资源Thread.yield(); else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {try {if ((tab = table) == null || tab.length == 0) {int n = (sc > 0) ? sc : DEFAULT_CAPACITY;@SuppressWarnings("unchecked")Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];table = tab = nt;sc = n - (n >>> 2);}} finally {// 设置扩容阈值的大小sizeCtl = sc;}break;}}return tab;
}

当链表长度大于或等于8时,ConcurrentHashMap认为链表已经有点长了,需要考虑优化,有两种方式:

  • 对数组进行扩容,当数组长度小于等于64,并且链表的长度大于等于8,优先选择对数组进行扩容。
  • 把链表转换为红黑树,当数组长度大于64,并且链表的长度大于等于8,会把链表转化为红黑树。
java">private final void treeifyBin(Node<K,V>[] tab, int index) {Node<K,V> b; int n, sc;if (tab != null) {//	如果当前数组的大小小于64,则进行扩容。if ((n = tab.length) < MIN_TREEIFY_CAPACITY)tryPresize(n << 1);else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {synchronized (b) {if (tabAt(tab, index) == b) {TreeNode<K,V> hd = null, tl = null;for (Node<K,V> e = b; e != null; e = e.next) {TreeNode<K,V> p =new TreeNode<K,V>(e.hash, e.key, e.val,null, null);if ((p.prev = tl) == null)hd = p;elsetl.next = p;tl = p;}setTabAt(tab, index, new TreeBin<K,V>(hd));}}}}
}
java">private final void tryPresize(int size) {int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :tableSizeFor(size + (size >>> 1) + 1);int sc;while ((sc = sizeCtl) >= 0) {Node<K,V>[] tab = table; int n;if (tab == null || (n = tab.length) == 0) {n = (sc > c) ? sc : c;if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {try {if (table == tab) {@SuppressWarnings("unchecked")Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];table = nt;sc = n - (n >>> 2);}} finally {sizeCtl = sc;}}}else if (c <= sc || n >= MAXIMUM_CAPACITY)break;else if (tab == table) {00000000 00000000 00000000 000100001700000000 00000000 00000000 0001000100000000 00000000 10000000 0000000000000000 00000000 10000000 0001000110000000 00010001 00000000 00001000// 获取rs的值int rs = resizeStamp(n);if (sc < 0) {Node<K,V>[] nt;if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||transferIndex <= 0)break;if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))transfer(tab, nt);}// 第一次扩容,走这个方法。else if (U.compareAndSwapInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2))transfer(tab, null);}}
}// (rs << RESIZE_STAMP_SHIFT) 扩容戳
// SIZECTL:低16位,记录参与扩容的线程数
java">// ******** ******** 1******* ********
static final int resizeStamp(int n) {// 返回无符号整数n最高位非0位前面的0的个数return Integer.numberOfLeadingZeros(n) |//	10000000 00000000(1 << (RESIZE_STAMP_BITS - 1));
}
java">private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {int n = tab.length, stride;// 计算每个线程处理的区间长度,默认是16if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)stride = MIN_TRANSFER_STRIDE; // 初始化nextTable,第二步初始化transferIndex,默认是老的数组长度if (nextTab == null) {            // initiatingtry {@SuppressWarnings("unchecked")Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];nextTab = nt;} catch (Throwable ex) {      // try to cope with OOMEsizeCtl = Integer.MAX_VALUE;return;}nextTable = nextTab;transferIndex = n;}int nextn = nextTab.length;//	表示一个正在被迁移的NodeForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);//	用来判断是否还有待处理的数据迁移工作boolean advance = true;boolean finishing = false; // to ensure sweep before committing nextTabfor (int i = 0, bound = 0;;) {Node<K,V> f; int fh;// 	计算迁移数据的区间while (advance) {int nextIndex, nextBound;if (--i >= bound || finishing)advance = false;else if ((nextIndex = transferIndex) <= 0) {i = -1;advance = false;}else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex,nextBound = (nextIndex > stride ?nextIndex - stride : 0))) {bound = nextBound;i = nextIndex - 1;advance = false;}}if (i < 0 || i >= n || i + n >= nextn) {int sc;if (finishing) {nextTable = null;table = nextTab;sizeCtl = (n << 1) - (n >>> 1);return;}if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)return;finishing = advance = true;i = n; // recheck before commit}}//	else if ((f = tabAt(tab, i)) == null)advance = casTabAt(tab, i, null, fwd);else if ((fh = f.hash) == MOVED)advance = true; // already processedelse {synchronized (f) {if (tabAt(tab, i) == f) {Node<K,V> ln, hn;if (fh >= 0) {int runBit = fh & n;Node<K,V> lastRun = f;for (Node<K,V> p = f.next; p != null; p = p.next) {int b = p.hash & n;if (b != runBit) {runBit = b;lastRun = p;}}if (runBit == 0) {ln = lastRun;hn = null;}else {hn = lastRun;ln = null;}for (Node<K,V> p = f; p != lastRun; p = p.next) {int ph = p.hash; K pk = p.key; V pv = p.val;if ((ph & n) == 0)ln = new Node<K,V>(ph, pk, pv, ln);elsehn = new Node<K,V>(ph, pk, pv, hn);}setTabAt(nextTab, i, ln);setTabAt(nextTab, i + n, hn);setTabAt(tab, i, fwd);advance = true;}else if (f instanceof TreeBin) {TreeBin<K,V> t = (TreeBin<K,V>)f;TreeNode<K,V> lo = null, loTail = null;TreeNode<K,V> hi = null, hiTail = null;int lc = 0, hc = 0;for (Node<K,V> e = t.first; e != null; e = e.next) {int h = e.hash;TreeNode<K,V> p = new TreeNode<K,V>(h, e.key, e.val, null, null);if ((h & n) == 0) {if ((p.prev = loTail) == null)lo = p;elseloTail.next = p;loTail = p;++lc;}else {if ((p.prev = hiTail) == null)hi = p;elsehiTail.next = p;hiTail = p;++hc;}}ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :(hc != 0) ? new TreeBin<K,V>(lo) : t;hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :(lc != 0) ? new TreeBin<K,V>(hi) : t;setTabAt(nextTab, i, ln);setTabAt(nextTab, i + n, hn);setTabAt(tab, i, fwd);advance = true;}}}}}
}

http://www.ppmy.cn/embedded/107309.html

相关文章

一个平台重要的规则改了!

大家好&#xff0c;我是凡人小哥。 是一个不黑、不吹、不跟风、有知识、有骨气的五好小号主。 现在是凌晨1点13分&#xff0c;就在昨天微信公众平台又又又调整了&#xff0c;可能朋友们还在想是不是又要严格了&#xff1f;这次恰恰相反&#xff0c;腾讯把注册微信公众号的门槛…

7、Django Admin删除默认应用程序

admin文件 from django.contrib.auth.models import User, Groupadmin.site.unregister(User) admin.site.unregister(Group) 显示效果&#xff1a; 前 后

数学基础 -- 线性代数之矩阵的秩

矩阵的秩&#xff1a;概念与应用 1. 概述 矩阵的秩&#xff08;Rank&#xff09;是线性代数中的一个基本概念&#xff0c;它衡量了矩阵中行或列向量的线性无关性。矩阵的秩在解线性方程组、矩阵分解、确定线性变换的维度等方面起着重要作用。 2. 矩阵的秩的定义 矩阵的秩可…

Android Camera系列(二):TextureView+Camera

两岸猿声啼不住&#xff0c;轻舟已过万重山—李白 Android Camera系列&#xff08;一&#xff09;&#xff1a;SurfaceViewCamera Android Camera系列&#xff08;二&#xff09;&#xff1a;TextureViewCamera Android Camera系列&#xff08;三&#xff09;&#xff1a;GLS…

IP地址是怎么实现HTTPS访问的?

首先&#xff0c;需要明确的是&#xff0c;IP地址&#xff08;Internet Protocol Address&#xff09;是互联网上设备&#xff08;如服务器、路由器等&#xff09;的唯一标识符&#xff0c;它允许数据包在网络中正确地路由和传输。然而&#xff0c;IP地址本身并不直接支持HTTPS…

union_collinear_contours_xld 算子介绍一下,条件关系

1&#xff0c;条件参数 MaxDistAbs,最大投影距离 MaxDistRel,两条临近线之间相互最大比例值&#xff08;这个不好预测&#xff0c;尽量设大一点&#xff09; MaxShift,被并入直线离基线最远垂直向最远点距离&#xff08;决定最后的直线整体的宽度&#xff09; MaxAngle,被并…

2024 年高教社杯全国大学生数学建模竞赛题目-A 题 “板凳龙” 闹元宵

“板凳龙”&#xff0c;又称“盘龙”&#xff0c;是浙闽地区的传统地方民俗文化活动。人们将少则几十条&#xff0c; 多则上百条的板凳首尾相连&#xff0c;形成蜿蜒曲折的板凳龙。盘龙时&#xff0c;龙头在前领头&#xff0c;龙身和龙尾 相随盘旋&#xff0c;整体呈圆盘状。一…

【MySQL】批量插入数据造数-存储过程

日常工作中可能有针对需要对某个表进行造数&#xff0c;如何批量插入呢&#xff1f; 可以使用存储过程循环结构。下面是一个存储过程以插入100条&#xff0c;while语句后的<控制循环次数。 concat是一个拼接语句&#xff0c;拼接后是test_1-100&#xff0c;这种也适用于ID/…

如何构建Java SpringBoot传统文化网,实现信息展示,传承文化精髓

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

深度学习_数据读取到model模型存储

概要 应用场景&#xff1a;用户流失 本文将介绍模型调用预测的步骤&#xff0c;这里深度学习模型使用的是自定义的deepfm&#xff0c;并用机器学习lgb做比较 代码 导包 import pandas as pd import numpy as npimport matplotlib.pyplot as plt import seaborn as sns from…

磁盘加密工具 | VeraCrypt v1.26.15 绿色版

VeraCrypt 是一个开源项目&#xff0c;旨在提供强大的加密解决方案&#xff0c;以创建和管理加密的磁盘分区和加密容器。它继承了著名的加密软件 TrueCrypt 的特性&#xff0c;并在此基础上进行了扩展和改进。 主要特性 1. 高级加密算法 VeraCrypt 支持多种加密算法&#xf…

2024 高教社杯 数学建模国赛 (C题)深度剖析|农作物的种植策略|数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题&#xff01; CS团队倾注了大量时间和心血&#xff0c;深入挖掘解…

2024开学必备好物合集大公开!开学带什么你都准备好了吗?

漫长的暑期已经结束&#xff0c;随之也迎来了2024年的秋季开学潮&#xff0c;对于学生党们来说&#xff0c;除了必备的生活用品外&#xff0c;还有哪些值得购买的开学好物值得推荐呢&#xff0c;下面我为大家梳理了一份清单&#xff0c;它们几乎都是大学生活中必备的刚需好物&a…

自学网络安全的三个必经阶段(含路线图)

一、为什么选择网络安全&#xff1f; 这几年随着我国《国家网络空间安全战略》《网络安全法》《网络安全等级保护2.0》等一系列政策/法规/标准的持续落地&#xff0c;网络安全行业地位、薪资随之水涨船高。 未来3-5年&#xff0c;是安全行业的黄金发展期&#xff0c;提前踏入…

区块链ARC如何能让节点能够大规模处理交易数据

​​发表时间&#xff1a;2024年8月7日 TAAL技术主管Michael Bckli表示&#xff0c;TAAL公司一直在对ARC进行测试&#xff0c;并准备在今年年底全面发布。因TAAL在区块链交易处理方面具备深厚的专业知识&#xff0c;BSV区块链委托TAAL进行ARC开源参考落地方案的开发。 ARC是一个…

Avalonia 播放 VLC 视频(Windows / Linux)

【演示效果】 一、开发步骤 1. 版本与引用类库 Avalonia 版本:11.0.11 Windows上只需要安装以下类库: LibVLCSharp 3.8.5 LibVLCSharp.Avalonia 3.8.5 VideoLAN.LibVLC.Windows 3.0.20 引用截图: 2. 前端代码 <UserControl xmlns="https://github.com/avaloni…

P1597 语句解析

[题目通道](语句解析 - 洛谷) #include<bits/stdc.h> using namespace std; int n,a0,s0,d0,k,num0,z1; string q; int main() {cin>>q;for (int i0;i<q.size();i5){if (q[i]a){if(q[i3]a)aa;elseif(q[i3]b)as;elseif(q[i3]c)ad;elseaq[i3]-0;}if (q[i]b){if(q…

Vue初学-简易计算器

最近在学习Vue的指令&#xff0c;做了一个简易计算器&#xff0c;比较适合刚入门的人参考学习。用到的知识点有&#xff1a; 1.插值表达式 2.v-model&#xff0c;双向绑定、-、*、/、**等操作符 3.v-show&#xff0c;控制操作数2是否显示&#xff0c;乘方时不显示操作数2 4.met…

【系统架构设计师-2012年】综合知识-答案及详解

更多内容请见&#xff1a; 备考系统架构设计师-核心总结索引 文章目录 【第1~2题】【第3~4题】【第5题】【第6题】【第7题】【第8题】【第9题】【第10~11题】【第12~13题】【第14~19题】【第20~21题】【第22~24题】【第25~26题】【第27~31题】【第32~33题】【第34~36题】【第37…

24 初识C++

目录 一、C概念 &#xff08;一&#xff09;C是什么 &#xff08;二&#xff09;C的历史 &#xff08;三&#xff09;C参考文档 二、第一个C程序 三、C的域 四、命名空间 namespace &#xff08;一&#xff09;namespace 的作用 &#xff08;二&#xff09;namespace 的…