看图学源码之 Atomic 类源码浅析二(cas + 分治思想的原子累加器)

原子累加器

相较于上一节看图学源码 之 Atomic 类源码浅析一(cas + 自旋操作的 AtomicXXX原子类)说的的原子类,原子累加器的效率会更高

在这里插入图片描述

XXXXAdderXXXAccumulator 区别就是 Adder只有add 方法,Accumulator是可以进行自定义运算方法的

始于 Striped64
abstract class Striped64 extends Number {// cpu 运行核数, 控制数组的大小static final int NCPU = Runtime.getRuntime().availableProcessors();// 当非空时,大小是 2 的幂。 transient volatile Cell[] cells;// 表初始化竞争期间的后备值  通过  CAS 更新  就是 valuetransient volatile long base;// 锁的 标志位 调整单元大小和/或创建单元时使用自旋锁(通过 CAS 锁定)transient volatile int cellsBusy;//Base的cas 操作final boolean casBase(long cmp, long val) {return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);}// CellsBusy的cas操作final boolean casCellsBusy() {return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);}// 主要是给不同的线程找到数组中不同的下标// 当前线程的探测值static final int getProbe() {return UNSAFE.getInt(Thread.currentThread(), PROBE);}// 给定线程的给定探测值static final int advanceProbe(int probe) {probe ^= probe << 13;   // xorshiftprobe ^= probe >>> 17;probe ^= probe << 5;UNSAFE.putInt(Thread.currentThread(), PROBE, probe);return probe;}......// Unsafe mechanics// Unsafe 的获取 和 偏移量的获取private static final sun.misc.Unsafe UNSAFE;private static final long BASE;private static final long CELLSBUSY;private static final long PROBE;static {try {UNSAFE = sun.misc.Unsafe.getUnsafe();Class<?> sk = Striped64.class;BASE = UNSAFE.objectFieldOffset(sk.getDeclaredField("base"));CELLSBUSY = UNSAFE.objectFieldOffset(sk.getDeclaredField("cellsBusy"));Class<?> tk = Thread.class;PROBE = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomProbe"));} catch (Exception e) {throw new Error(e);}}}
内部类

@sun.misc.Contended——解决伪共享,进行字节填充

@sun.misc.Contended static final class Cell {// 操作的数volatile long value;// 构造器Cell(long x) { value = x; }// 进行 cas final boolean cas(long cmp, long val) {return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);}
}
LongAdder

可能会出现一边正在进行 累加操作,一边又在执行求和操作,所以就导致了不是 强一致性,而是最终一致性

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

public class LongAdder extends Striped64 implements Serializable {private static final long serialVersionUID = 7249069246863182397L;public LongAdder() {}public void add(long x) {Cell[] as; long b, v; int m; Cell a;// 数组是不是 null(判断有没有发生竞争,因为只有竞争发生才会初始化数组)// 没有初始化(就是没有竞争) 直接对 base 的值+x 失败if ((as = cells) != null || !casBase(b = base, b + x)) {//有竞争的时候boolean uncontended = true;// 数组还是没有初始化 || 数组初始化,但是数组的长度  < 0 || 数组中的该位置的值是 null (表示这个下标没有初始化)|| cas的方式把当前位置的值 + x ,cas 失败if (as == null || (m = as.length - 1) < 0 ||(a = as[getProbe() & m]) == null ||!(uncontended = a.cas(v = a.value, v + x)))// 发生冲突都会走这里longAccumulate(x, null, uncontended);}}public void increment() {add(1L);}public void decrement() {add(-1L);}// 返回当前总和。返回的值不是原子快照;// 在没有并发更新的情况下调用会返回准确的结果,但计算总和时发生的并发更新可能不会被合并// 所以不是 强一致性的public long sum() {Cell[] as = cells; Cell a;long sum = base;//数组不是 nullif (as != null) {//遍历数组,for (int i = 0; i < as.length; ++i) {//数组中的槽位不是 null,对槽位的数据进行运算,赋值加到base中if ((a = as[i]) != null)sum += a.value;}}//返回总的值return sum;}// 将保持总和为零的变量重置。此方法可能是创建新加法器的有用替代方法,但仅在没有并发更新时才有效。// 由于此方法本质上是活泼的,因此仅应在  已知没有线程同时更新时才使用它。public void reset() {Cell[] as = cells; Cell a;base = 0L;if (as != null) {// 数组存在,遍历数组,将数组中所有的值设置为 0 for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)a.value = 0L;}}}// 相当于sum后跟reset // 该方法可以应用于例如 多线程计算之间的静止点期间。// 如果此方法同时有更新,则不能保证返回值是重置之前发生的最终值。public long sumThenReset() {Cell[] as = cells; Cell a;long sum = base;base = 0L;if (as != null) {// 数组存在,遍历数组,先求和  后把数组中所有的值设置为 0 for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null) {sum += a.value;a.value = 0L;}}}return sum;}// 返回sum的字符串表示形式public String toString() {return Long.toString(sum());}// 返回sumpublic long longValue() {return sum();}//缩小基元转换后以 int 形式返回sum public int intValue() {return (int)sum();}// 加宽基元转换后以float形式返回sum public float floatValue() {return (float)sum();}//加宽基元转换后以 double 形式返回sum public double doubleValue() {return (double)sum();}private static class SerializationProxy implements Serializable {private static final long serialVersionUID = 7249069246863182397L;private final long value;// sum() 返回的当前值。SerializationProxy(LongAdder a) {value = a.sum();}// 返回一个LongAdder对象,其初始状态由该代理保存。private Object readResolve() {LongAdder a = new LongAdder();a.base = value;return a;}}private Object writeReplace() {return new SerializationProxy(this);}private void readObject(java.io.ObjectInputStream s)throws java.io.InvalidObjectException {throw new java.io.InvalidObjectException("Proxy required");}}
striped64中的 longAccumulate

在这里插入图片描述

final void longAccumulate(long x, LongBinaryOperator fn,boolean wasUncontended) {int h;// 拿 hash 值,拿不到强制获取if ((h = getProbe()) == 0) {ThreadLocalRandom.current(); // force initializationh = getProbe();// 将 wasUncontended 的值设为 true,表示当前线程是未争用的。wasUncontended = true;}boolean collide = false;                // True if last slot nonemptyfor (;;) {Cell[] as; Cell a; int n; long v;// 分支 1//数组已经初始化,出现了竞争if ((as = cells) != null && (n = as.length) > 0) {// 分支1.1// 当前位置的值是nullif ((a = as[(n - 1) & h]) == null) {// 锁的标志位 == 0 ,没有加锁 if (cellsBusy == 0) {       // Try to attach new CellCell r = new Cell(x);   // Optimistically create// 加锁if (cellsBusy == 0 && casCellsBusy()) {boolean created = false;   try {               // Recheck under lockCell[] rs; int m, j;//  加锁之后再次检查指定位置是否为空 //  数组初始化过了 && 当前位置的值不是nullif ((rs = cells) != null &&(m = rs.length) > 0 &&rs[j = (m - 1) & h] == null) {// 给数组的指定位置设置为  之前设置过的cell对象rs[j] = r;// 创建成功created = true;}} finally {// 解锁cellsBusy = 0;}if (created)break;continue;           // Slot is now non-empty}}// 有人加锁了,发生了冲突 避免在当前位置发生碰撞的情况下继续进行操作,将 collide 标志位设置为 false。collide = false;}// 分支1.2// 没有发生竞争else if (!wasUncontended)       // CAS already known to fail// 此时是发生了竞争wasUncontended = true;      // Continue after rehash// 分支1.3// cas 的方式更新 此位置的值, cas 失败表示有线程正在此位置执行操作else if (a.cas(v = a.value, ((fn == null) ? v + x :fn.applyAsLong(v, x))))break;// 分支1.4// n > cpu 的个数  当前分段数组的长度是否已经达到或超过了处理器的数量。//如果是,说明分段数组已经达到了最大的容量或者已经很大了,不再需要继续进行扩容操作。// 或者 cells  发生了变化,当前线程获取到的分段数组引用是否与共享变量中的引用相等。// 如果不相等,说明在当前线程获取到分段数组的过程中,有其他线程进行了修改,即分段数组已经发生了变化。else if (n >= NCPU || cells != as)collide = false;            // At max size or stale// 分支1.5// 此时是发生了碰撞的 collide 被设置为 true else if (!collide)collide = true;// 分支1.6 // 扩容// 没有被锁 && cas  的方式 成功加锁 else if (cellsBusy == 0 && casCellsBusy()) {try {// 数组没有变化if (cells == as) {      // Expand table unless stale// as 数组长度扩大一倍Cell[] rs = new Cell[n << 1];// 元素直接赋值for (int i = 0; i < n; ++i)rs[i] = as[i];cells = rs;}} finally {// 解锁cellsBusy = 0;}//没有冲突collide = false;//扩容成功,继续循环continue;                   // Retry with expanded table}// 更新 hash 值h = advanceProbe(h);}
// 分支 2// 此处数组没有进行初始化,此时进行初始化// 锁的标志为 == 0  &&  数组没有改变(多线程情况下该线程没有被其他线程初始化)  && cas 成功的把锁的标志位 设置为 1(加锁流程)// 当前的 cells 数组没有被其他线程占用,并且成功获取了 cellsBusy 锁else if (cellsBusy == 0 && cells == as && casCellsBusy()) {boolean init = false;try {                           // Initialize table// 加完锁之后再次判断一次  cells 数组没有发生过变化if (cells == as) { // 数组 长度默认为2Cell[] rs = new Cell[2];// 给rs 赋值为 要加入的 xrs[h & 1] = new Cell(x);// 将 cells 数组变更为 rscells = rs;// 初始化成功init = true;}} finally {// 解锁cellsBusy = 0;}if (init)    //初始化成功break;    // 退出循环}
// 分支 3  cas 的方式 操作 base  , fn 函数式接口的方法  == null 默认加法,否则就是定义的方法else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x))))break;                          // Fall back on using base  退出循环}
}
LongAccumulator

在这里插入图片描述

在这里插入图片描述

image-20231207001843693

public class LongAccumulator extends Striped64 implements Serializable {private static final long serialVersionUID = 7249069246863182397L;private final LongBinaryOperator function;private final long identity;public LongAccumulator(LongBinaryOperator accumulatorFunction,long identity) {this.function = accumulatorFunction;base = this.identity = identity;}// 更新值public void accumulate(long x) {Cell[] as; long b, v, r; int m; Cell a;// 有竞争  || ( cas 运算 base 的值成功  && 对 base进行cas更新失败 )if ((as = cells) != null ||//  function.applyAsLong  函数式接口 (r = function.applyAsLong(b = base, x)) != b && !casBase(b, r)) {boolean uncontended = true;// 出现了竞争// 数组还是没有初始化 || 数组初始化,但数组的长度 < 0 || 数组中的该位置的值是 null (表示这个下标没有初始化)|| (cas的方式运算当前位置的值 失败 && cas 更新当前位置的值也失败)if (as == null || (m = as.length - 1) < 0 ||(a = as[getProbe() & m]) == null ||!(uncontended =(r = function.applyAsLong(v = a.value, x)) == v ||a.cas(v, r)))longAccumulate(x, function, uncontended);}}// 返回当前值。返回的值不是原子快照;// 在没有并发更新的情况下调用会返回准确的结果,但计算值时发生的并发更新可能不会被合并。public long get() {Cell[] as = cells; Cell a;long result = base;// 数组存在if (as != null) {// 遍历数组for (int i = 0; i < as.length; ++i) {//数组中的槽位不是 nullif ((a = as[i]) != null)//对槽位的数据进行运算,赋值加到base中result = function.applyAsLong(result, a.value);}}// 并返回总的值return result;}// 重置变量以维护对标识值的更新。// 此方法可能是创建新更新程序的有用替代方法,但仅在没有并发更新时才有效。// 由于此方法本质上是活泼的,因此仅应在已知没有线程同时更新时才使用它。 public void reset() {Cell[] as = cells; Cell a;base = identity;// 数组存在if (as != null) {// 遍历数组for (int i = 0; i < as.length; ++i) {//数组中的槽位不是 nullif ((a = as[i]) != null)//将槽位的值设置为  identitya.value = identity;}}}// 效果相当于get后面跟着reset 。// 该方法可以应用于例如多线程计算之间的静止点期间。// 如果此方法同时有更新,则不能保证返回值是重置之前发生的最终值。public long getThenReset() {Cell[] as = cells; Cell a;long result = base;base = identity;// 数组存在if (as != null) {// 遍历数组for (int i = 0; i < as.length; ++i) {//数组中的槽位不是 nullif ((a = as[i]) != null) {// 将槽位的值设置为  identity// 对槽位的数据进行运算,赋值加到base中long v = a.value;a.value = identity;result = function.applyAsLong(result, v);}}}return result;}public String toString() {return Long.toString(get());}public long longValue() {return get();}public int intValue() {return (int)get();}public float floatValue() {return (float)get();}public double doubleValue() {return (double)get();}/*** 序列化代理,用于避免以序列化形式引用非公共 Striped64 超类*/private static class SerializationProxy implements Serializable {private static final long serialVersionUID = 7249069246863182397L;private final long value;private final LongBinaryOperator function;private final long identity;SerializationProxy(LongAccumulator a) {function = a.function;identity = a.identity;value = a.get();}private Object readResolve() {LongAccumulator a = new LongAccumulator(function, identity);a.base = value;return a;}}private Object writeReplace() {return new SerializationProxy(this);}private void readObject(java.io.ObjectInputStream s)throws java.io.InvalidObjectException {throw new java.io.InvalidObjectException("Proxy required");}}

在Double 中会有 doubleToRawLongBits的操作,主要是检查数组越界的

DoubleAdder

在这里插入图片描述

public class DoubleAdder extends Striped64 implements Serializable {private static final long serialVersionUID = 7249069246863182397L;/*
请注意,我们必须使用“long”作为底层表示,因为 double 没有compareAndSet,因为任何 CAS 实现中使用的按位等于与双精度等于不同
然而,我们仅使用 CAS 来检测和缓解争用,无论如何,按位等于效果最好。
原则上,这里使用的 longdouble 转换在大多数平台上基本上应该是免费的,因为它们只是重新解释位。*/public DoubleAdder() {}public void add(double x) {Cell[] as; long b, v; int m; Cell a;// 数组存在 || 对 base 进行 cas运算操作失败  
if ((as = cells) != null ||!casBase(b = base,Double.doubleToRawLongBits(Double.longBitsToDouble(b) + x))) {// boolean uncontended = true;// 数组为 空 || 数组的长度 < 0  || 当前位置的值为 null  ||  对该位置的值进行cas 运算失败if (as == null || (m = as.length - 1) < 0 ||(a = as[getProbe() & m]) == null ||!(uncontended = a.cas(v = a.value,Double.doubleToRawLongBits(Double.longBitsToDouble(v) + x))))doubleAccumulate(x, null, uncontended);}}/**返回当前总和。返回的值不是原子快照;在没有并发更新的情况下调用会返回准确的结果,但计算总和时发生的并发更新可能不会被合并。由于浮点算术不是严格关联的,因此返回的结果不需要与在单个变量的一系列连续更新中获得的值相同。*/public double sum() {Cell[] as = cells; Cell a;double sum = Double.longBitsToDouble(base);if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)sum += Double.longBitsToDouble(a.value);}}return sum;}/**将保持总和为零的变量重置。此方法可能是创建新加法器的有用替代方法,但仅在没有并发更新时才有效。由于此方法本质上是活泼的,因此仅应在已知没有线程同时更新时才使用它。*/public void reset() {Cell[] as = cells; Cell a;base = 0L; // relies on fact that double 0 must have same rep as longif (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)a.value = 0L;}}}/**相当于sum后跟reset 。该方法可以应用于例如多线程计算之间的静止点期间。如果此方法同时有更新,则不能保证返回值是重置之前发生的最终值。*/public double sumThenReset() {Cell[] as = cells; Cell a;double sum = Double.longBitsToDouble(base);base = 0L;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null) {long v = a.value;a.value = 0L;sum += Double.longBitsToDouble(v);}}}return sum;}public String toString() {return Double.toString(sum());}public double doubleValue() {return sum();}public long longValue() {return (long)sum();}public int intValue() {return (int)sum();}public float floatValue() {return (float)sum();}private static class SerializationProxy implements Serializable {private static final long serialVersionUID = 7249069246863182397L;private final double value;SerializationProxy(DoubleAdder a) {value = a.sum();}private Object readResolve() {DoubleAdder a = new DoubleAdder();a.base = Double.doubleToRawLongBits(value);return a;}}private Object writeReplace() {return new SerializationProxy(this);}private void readObject(java.io.ObjectInputStream s)throws java.io.InvalidObjectException {throw new java.io.InvalidObjectException("Proxy required");}}
striped64中的 doubleAccumulate

和上面的 striped64中的 longAccumulate 几乎一模一样,只有doubleToRawLongBits部分的细微差别

final void doubleAccumulate(double x, DoubleBinaryOperator fn,boolean wasUncontended) {int h;// 拿 hash 值,拿不到强制获取if ((h = getProbe()) == 0) {ThreadLocalRandom.current(); // force initializationh = getProbe();wasUncontended = true;}boolean collide = false;                // True if last slot nonemptyfor (;;) {Cell[] as; Cell a; int n; long v;
// 分支 1//数组已经初始化,出现了竞争if ((as = cells) != null && (n = as.length) > 0) {// 分支1.1// 当前位置的值是nullif ((a = as[(n - 1) & h]) == null) {// 锁的标志位 == 0 ,没有加锁 if (cellsBusy == 0) {       // Try to attach new CellCell r = new Cell(Double.doubleToRawLongBits(x));// 加锁if (cellsBusy == 0 && casCellsBusy()) {boolean created = false;try {               // Recheck under lockCell[] rs; int m, j;// 数组初始化过了 && 当前位置的值不是nullif ((rs = cells) != null &&(m = rs.length) > 0 &&rs[j = (m - 1) & h] == null) {// 给数组的位置设置为  之前设置过的cell对象rs[j] = r;// 创建成功created = true;}} finally {// 解锁cellsBusy = 0;}if (created)break;continue;           // Slot is now non-empty}}// 有人加锁了,发生了冲突collide = false;}// 分支1.2// 没有发生竞争else if (!wasUncontended)       // CAS already known to fail// 此时是发生了竞争wasUncontended = true;      // Continue after rehash// 分支1.3// cas 的方式更新 此位置的值, cas 失败表示有线程正在此位置执行操作else if (a.cas(v = a.value,((fn == null) ?Double.doubleToRawLongBits(Double.longBitsToDouble(v) + x) :Double.doubleToRawLongBits(fn.applyAsDouble(Double.longBitsToDouble(v), x)))))break;// 分支1.4// n > cpu 的个数 或者 cells  发生了变化,表示 之前没有发生碰撞,不能扩容else if (n >= NCPU || cells != as)collide = false;            // At max size or stale// 分支1.5// 此时是发生了碰撞的 collide 被设置为 true else if (!collide)collide = true;// 分支1.6 // 扩容// 没有被锁 && 成功加锁 else if (cellsBusy == 0 && casCellsBusy()) {try {// 数组没有变化if (cells == as) {      // Expand table unless stale// as 数组长度扩大一倍Cell[] rs = new Cell[n << 1];// 元素直接赋值for (int i = 0; i < n; ++i)rs[i] = as[i];cells = rs;}} finally {// 解锁cellsBusy = 0;}//没有冲突collide = false;//扩容成功,继续循环continue;                   // Retry with expanded table}// 获取hash 值h = advanceProbe(h);}
// 分支 2// 此处数组没有进行初始化,此时进行初始化// 锁的标志为 == 0  &&  数组没有改变(多线程情况下该线程没有被其他线程初始化)  && cas 成功的把锁的标志位 设置为 1(枷锁流程)else if (cellsBusy == 0 && cells == as && casCellsBusy()) {boolean init = false;try {   // Initialize table// 加完锁之后再次判断一次  cells 数组没有发生过变化if (cells == as) {// 数组 长度默认为2Cell[] rs = new Cell[2];// 给rs 赋值为 要加入的 xrs[h & 1] = new Cell(Double.doubleToRawLongBits(x));// 将 cells 数组变更为 rscells = rs;// 初始化成功init = true;}} finally {// 解锁cellsBusy = 0;}if (init)// 初始化成功break;    // 退出循环}
// 分支 3  cas 的方式 操作 base  , fn 函数式接口的方法  == null 默认加法,否则就是定义的方法else if (casBase(v = base,((fn == null) ?Double.doubleToRawLongBits(Double.longBitsToDouble(v) + x) :Double.doubleToRawLongBits(fn.applyAsDouble(Double.longBitsToDouble(v), x)))))break;                          // Fall back on using base// 退出循环}
}
DoubleAccumulator

在这里插入图片描述

public class DoubleAccumulator extends Striped64 implements Serializable {private static final long serialVersionUID = 7249069246863182397L;private final DoubleBinaryOperator function;private final long identity; // use long representationpublic DoubleAccumulator(DoubleBinaryOperator accumulatorFunction,double identity) {this.function = accumulatorFunction;base = this.identity = Double.doubleToRawLongBits(identity);}public void accumulate(double x) {Cell[] as; long b, v, r; int m; Cell a;// 数组存在 || (对 base 进行 cas的 运算操作成功  && 对base 进行cas 更新操作失败  )if ((as = cells) != null ||(r = Double.doubleToRawLongBits(function.applyAsDouble (Double.longBitsToDouble(b = base), x))) != b  && !casBase(b, r)) {boolean uncontended = true;
// 数组为 空 || 数组被初始化但是 数组的长度 < 0  || 当前位置的值为 null  ||  (对该位置的值进行cas 运算失败  || 对该值进行cas 更新失败)if (as == null || (m = as.length - 1) < 0 ||(a = as[getProbe() & m]) == null ||!(uncontended =(r = Double.doubleToRawLongBits(function.applyAsDouble(Double.longBitsToDouble(v = a.value), x))) == v ||a.cas(v, r)))doubleAccumulate(x, function, uncontended);}}public double get() {Cell[] as = cells; Cell a;double result = Double.longBitsToDouble(base);if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)result = function.applyAsDouble(result, Double.longBitsToDouble(a.value));}}return result;}public void reset() {Cell[] as = cells; Cell a;base = identity;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)a.value = identity;}}}public double getThenReset() {Cell[] as = cells; Cell a;double result = Double.longBitsToDouble(base);base = identity;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null) {double v = Double.longBitsToDouble(a.value);a.value = identity;result = function.applyAsDouble(result, v);}}}return result;}public String toString() {return Double.toString(get());}public double doubleValue() {return get();}public long longValue() {return (long)get();}public int intValue() {return (int)get();}public float floatValue() {return (float)get();}private static class SerializationProxy implements Serializable {private static final long serialVersionUID = 7249069246863182397L;private final double value;private final DoubleBinaryOperator function;private final long identity;SerializationProxy(DoubleAccumulator a) {function = a.function;identity = a.identity;value = a.get();}private Object readResolve() {double d = Double.longBitsToDouble(identity);DoubleAccumulator a = new DoubleAccumulator(function, d);a.base = Double.doubleToRawLongBits(value);return a;}}private Object writeReplace() {return new SerializationProxy(this);}private void readObject(java.io.ObjectInputStream s)throws java.io.InvalidObjectException {throw new java.io.InvalidObjectException("Proxy required");}}

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

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

相关文章

大数据技术5:OLAP引擎对比分析

前言&#xff1a;数据仓库建设&#xff0c;初级的理解就是建表&#xff0c;将业务数据、日志数据、消息队列数据等&#xff0c;通过各种调度任务写入到表里供OLAP引擎使用。但要想建好数仓也是一个复杂、庞大的工程&#xff0c;比如要考虑&#xff1a;数据清洗、数据建模&#…

001 LLM大模型之Transformer 模型

参考《大规模语言模型--从理论到实践》 目录 一、综述 二、Transformer 模型 三、 嵌入表示层&#xff08;位置编码代码&#xff09; 一、综述 语言模型目标是建模自然语言的概率分布&#xff0c;在自然语言处理研究中具有重要的作用&#xff0c;是自然 语言处理基础任务之一…

第 119 场 LeetCode 双周赛题解

A 找到两个数组中的公共元素 模拟 class Solution { public:vector<int> findIntersectionValues(vector<int> &nums1, vector<int> &nums2) {unordered_set<int> s1(nums1.begin(), nums1.end()), s2(nums2.begin(), nums2.end());vector<…

【基于大数据的人肥胖程度预测分析与可控策略】

基于大数据的人肥胖程度预测分析与可控策略 前言数据获取与清洗数据挖掘与分类建模1. K-means聚类2. 层次聚类3. DBSCAN4. 分类建模 数据可视化模型肥胖程度预测分析与可控策略结语 前言 随着现代生活方式的改变&#xff0c;肥胖问题逐渐成为全球性的健康挑战。为了更好地理解…

实用篇 | 3D建模中Blender软件的下载及使用[图文详情]

本文基于数字人系列的3D建模工具Blender软件的安装及使用&#xff0c;还介绍了图片生成3D模型的AI工具~ 目录 1.Blender的下载 2.Blender的使用 3.安装插件(通过压缩包安装) 4.实例 4.1.Blender使用MB-Lab插件快速人体模型建构 4.1.1.点击官网&#xff0c;进行下载 4.1.…

Java TCP(一对一)聊天简易版

客户端 import java.io.*; import java.net.Socket; import java.util.Date; import javax.swing.*;public class MyClient {private JFrame jf;private JButton jBsend;private JTextArea jTAcontent;private JTextField jText;private JLabel JLcontent;private Date data;p…

C语言 题目

1.写一个函数算一个数的二进制(补码)表示中有几个1 #include<stdio.h>//统计二进制数中有几个1 //如13:1101 //需要考虑负数情况 如-1 结果应该是32// n 1101 //n-1 1100 //n 1100 //n-1 1011 //n 1000 //n-1 0111 //n 0000 //看n的变化 int funca(int c){int co…

css:flex布局中子元素高度height没有达到100%

目录 问题flex布局示例解决办法方式一方式二 参考 问题 css中使用flex布局中子元素高度height没有达到100% flex布局示例 希望实现两个盒子左右分布&#xff0c;内容垂直居中对齐 <style>.box {display: flex;align-items: center;border: 1px solid #eeeeee;}.box-l…

react新旧生命周期钩子

以下的内容根据尚硅谷整理。 旧生命钩子 辅助理解&#xff1a; 红色框&#xff1a;挂载时生命钩子蓝色框&#xff1a;更新时生命钩子绿色框&#xff1a;卸载时生命钩子 挂载时 如图所示&#xff0c;我们可以看到&#xff0c;在组件第一次挂载时会经历&#xff1a; 构造器&a…

stateflow——如何查看状态机中参数变化及状态机断点调试

法一&#xff1a;使用Data Inspector 点击“符号图窗”和“属性”&#xff0c;如图&#xff1b;在选择变量n并右键点击inspector&#xff0c;最后在logging&#xff0c;如图 法二&#xff1a;log active state 和法一类似使用data inspector查看&#xff0c;类似的查看方法和…

【每周一测】Java阶段四第三周学习

目录 1、关于分布式锁的说法&#xff0c;错误的是&#xff08; &#xff09; 2、JDK动态代理产生的代理类和委托类的关系是 3、下列关于ElasticSearch中基本概念描述错误的是 4、Spring Cloud 中&#xff0c;Feign 是什么&#xff1f; 5、在JavaScript中&#xff0c;可以使…

玩转大数据13: 数据伦理与合规性探讨

1. 引言 随着科技的飞速发展&#xff0c;数据已经成为了现代社会的宝贵资产。然而&#xff0c;数据的收集、处理和利用也带来了一系列的伦理和合规性问题。数据伦理和合规性不仅关乎个人隐私和权益的保护&#xff0c;还涉及到企业的商业利益和社会责任。因此&#xff0c;数据…

韵达快递单号查询,以表格的形式导出单号的每一条物流信息

批量查询韵达快递单号的物流信息&#xff0c;并以表格的形式导出单号的每一条物流信息。 所需工具&#xff1a; 一个【快递批量查询高手】软件 韵达快递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;第一次使用的伙伴记得先注册…

SOP(标准作业程序)和WI(操作指导书)的联系和区别

目录 1.SOP&#xff08;标准作业程序&#xff09;&#xff1a;2.WI&#xff08;操作指导书&#xff09;&#xff1a;3.SOP和WI的区别&#xff1a; 1.SOP&#xff08;标准作业程序&#xff09;&#xff1a; SOP: 所谓SOP&#xff0c;是 Standard Operation Procedure三个单词中…

【计算机网络实验】实验三 IP网络规划与路由设计(头歌)

目录 一、知识点 二、实验任务 三、头歌测试 一、知识点 IP子网掩码的两种表示方法 32位IP子网掩码&#xff0c;特点是从高位开始连续都是1&#xff0c;后面是连续的0&#xff0c;它有以下两种表示方法&#xff1a; 传统表示法&#xff0c;如&#xff1a;255.255.255.0IP前…

【WebSocket】使用ws搭建一个简单的在线聊天室

前言 什么是WebSockets&#xff1f; WebSockets 是一种先进的技术。它可以在用户的浏览器和服务器之间打开交互式通信会话。使用此 API&#xff0c;你可以向服务器发送消息并接收事件驱动的响应&#xff0c;而无需通过轮询服务器的方式以获得响应。 webscokets 包括webscoket…

中科院分区和JCR分区有什么区别

文章目录 名词解释学科划分不同参考的影响因子不同期刊分区不同期刊分区阈值不同 名词解释 中科院分区&#xff1a;又称“中科院JCR分区”&#xff0c;是中国科学院文献情报中心世界科学前沿分析中心的科学研究成果&#xff0c;期刊分区表数据每年底&#xff08;每年12月中下旬…

Python爬虫-实现批量抓取王者荣耀皮肤图片并保存到本地

前言 本文是该专栏的第12篇,后面会持续分享python爬虫案例干货,记得关注。 本文以王者荣耀的英雄皮肤为例,用python实现批量抓取“全部英雄”的皮肤图片,并将图片“批量保存”到本地。具体实现思路和详细逻辑,笔者将在正文结合完整代码进行详细介绍。注意,这里抓取的图片…

数据结构和算法-单链表

数据结构和算法-单链表 1. 链表介绍 链表是有序的列表&#xff0c;但是它在内存中是存储如下 图1 单链表示意图 小结: 链表是以节点的方式存储每个节点包含data域&#xff0c;next域&#xff0c;指向下一个节点。如图&#xff1a;发现链表的各个节点不一定是连续存储。比如地…

滑动窗口练习(三)— 加油站问题

题目 测试链接 在一条环路上有 n 个加油站&#xff0c;其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车&#xff0c;从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发&#xff0c;开始时油箱为空。 给定两个整数数组…