可视化学习:如何生成简单动画让图形动起来

· 浏览次数 : 0

小编点评

本文介绍了如何生成简单动画让图形动起来,包括动画的三种形式:固定帧动画、增量动画和时序动画。文章详细阐述了每种动画形式的实现方法,并提供了具体的HTML/CSS和Shader示例。此外,还讨论了插值与缓动函数在实现不规则运动中的应用,以及顶点着色器和片元着色器在实现旋转动画时的不同之处。 1. **动画形式介绍**: - **固定帧动画**:使用预先生成的静态图像,按顺序播放形成动画。 - **增量动画**:在动画的每一帧给属性一个增量,实现旋转方块等简单动画。 - **时序动画**:通过定义初始时间和周期,计算当前经过的时间和进度,精确控制动画效果。 2. **HTML/CSS实现示例**: - **固定帧动画**:使用雪碧图和CSS精灵,通过修改background-position实现动态效果。 - **增量动画**:通过修改rotation值实现旋转方块动画。 - **时序动画**:使用计时器和动画类,通过更新属性实现旋转方块动画。 3. **Shader实现示例**: - **固定帧动画**:在片元着色器中通过纹理坐标读取像素信息,计算插值。 - **非固定帧动画**:在顶点着色器中改变顶点坐标,通过旋转矩阵处理映射到新坐标。 4. **插值与缓动函数**: - 插值方法如线性插值和贝塞尔曲线缓动,用于实现不规则运动。 - 缓动函数用于控制动画的细节,如匀加速、匀减速运动。 5. **着色器选择**: - 根据动画需求选择顶点着色器或片元着色器。 - 顶点着色器适用于简单动画,片元着色器适用于复杂效果和大量重复、随机、噪声。 总的来说,本文通过详细的示例和解释,为读者提供了一套完整的动画实现指南,帮助读者理解和实践动画制作。

正文

大家好,本文分享的是如何生成简单动画让图形动起来。

在可视化展现中,动画它是强化数据表达,吸引用户的重要技术手段。

在具体实现动画之前,我们先来了解一下动画的三种形式,分别是固定帧动画、增量动画和时序动画。

graph LR A[动画的三种形式] --> B[固定帧动画] A --> D[增量动画] A --> E[时序动画] B --> F[使用已生成的静态图像,将图像依次播放] D --> C[动态绘制图像] E --> C

固定帧动画的实现,是使用已生成的静态图像,然后将这些图像依次播放,而后面两种,增量动画和时序动画,都是需要动态绘制图像。可想而知,后面这两种动画形式会更灵活一些。

接下来,我们就来了解如何在HTML/CSS和Shader中实现动画效果。

HTML/CSS

首先,我们来了解如何在HTML/CSS中实现动画。

固定帧动画

先来看固定帧动画的一个例子,这个代码实现的是一个飞动的小鸟。

e.g.动态的小鸟

<!-- 固定帧动画 -->
<div v-show="checkedTab === 0" style="position: relative;">
  <div class="fixed-frame"></div>
</div>
/*固定帧动画*/
.fixed-frame {
  position: absolute;
  left: 100px;
  top: 100px;
  width: 86px;
  height: 60px;
  zoom: 0.5;
  background-repeat: no-repeat;
  background-image: url("@/assets/bird.png");
  background-position: -178px -2px;
  animation: flappy .5s step-end infinite;
}
@keyframes flappy {
  0%  {background-position: -178px -2px;}
  33% {background-position: -90px -2px;}
  66% {background-position: -2px -2px;}
}

image

很显然,在实现这个固定帧动画之前,我们需要预先准备好静态图片,这个例子中我们使用的是雪碧图,也叫CSS精灵,是将小图合并在一起形成的图片,在这里我们设置background-image来指定背景图,然后通过animation动态修改background-position来逐帧切换,最终形成一个动态的效果。当然如果我们使用的是多张图片,直接切换background-image也是可以的。

其中step-end 会使 keyframes 动画到了定义的关键帧处直接突变,没有变化的过程。

通过这个例子我们能发现,固定帧动画实现起来非常简单,比较适合的场景是提供现成图片的动画帧图像,如果要去动态绘制图像,就不太合适。如果要生成动态绘制的图像,也就是非固定帧动画,通常会使用另外两种方式。

增量动画

先来看增量动画,其实从名称上看,我们就能有一个大致的概念,增量嘛,就是增加数量,所以增量动画就是在动画的每一帧给属性一个增量。

下面是一个简单的旋转方块的动画例子,是一个旋转的蓝色方块。

<!-- 增量动画 -->
<div style="position: relative;">
  <div class="increase-frame" ref="increaseRef"></div>
</div>
/*增量动画*/
.increase-frame {
  position: absolute;
  left: 100px;
  top: 100px;
  width: 100px;
  height: 100px;
  background-color: blue;
  transform-origin: 50% 50%;
}
let rotation = 0;
requestAnimationFrame(function update() {
  increaseRef.value.style.transform = `rotate(${rotation ++}deg)`;
  requestAnimationFrame(update);
});

image

以上动画实现的关键逻辑就在于修改rotation的值,在每次绘制的时候将它加1。

这种绘制方式实现起来也比较简单,但是它不太容易去控制动画的细节,比如动画周期、变化率、轨迹等等;而且它定义的是状态变化,也就是根据上一刻的状态来计算得到下一刻的状态,这种方式在Shader中实现起来并不太方便,需要像上篇所提到的那样,去使用后期通道来进行处理,很显然,这样做会比较繁琐。

所以如果是比较复杂的动画,我们一般通过定义时间和动画函数来实现,也就是通过时序动画的方式来实现动画效果。

时序动画

关于如何去实现时序动画,我们也直接来看个例子。

e.g.旋转的蓝色方块

const startAngle = 0;
const T = 2000; // 周期。旋转这一周的时间
let startTime = null;
function update() {
  startTime = startTime === null ? Date.now() : startTime;
  const p = (Date.now() - startTime) / T;
  const angle = startAngle + p * 360;
  timeOrderRef.value.style.transform = `rotate(${angle}deg)`;
  requestAnimationFrame(update);
}
update();

这段代码中,我们定义了三个变量,startAngle是起始旋转角度,T是旋转周期,代表完成一次动画、一次旋转需要的时间,startTime表示每一次动画的开始时间。

在update函数中,我们通过Date.now() - startTime去得到当前经过的时间,然后除以周期T,就能得到旋转进度 p ,最后根据起始旋转角度和进度 p,计算得到旋转角度angle,并且赋值给transform属性,这样就实现了旋转动画。

image

根据这个例子,我们可以将时序动画的实现总结为三个步骤:

第一步,定义初始时间和周期;

第二步,在update中计算当前经过的时间和进度;

第三步,通过进度来更新动画元素的属性。

时序动画的优点是,可以更直观、精确地控制动画的周期(也是速度)等参数;它的缺点就是写法相对比较复杂,但是因为它的优点、可以更好控制动画的效果,所以在动画实现中最为常用。

标准动画模型

既然时序动画是最常用的动画实现形式,那么我们可以把它的三个步骤抽象成标准的动画模型,来方便后续的动画实现。

  • 首先,定义一个类、Timing用于处理时间

    /**
     * 用于处理动画的时间
     */
    export class Timing {
        constructor({duration, iterations = 1} = {}) {
            this.startTime = Date.now();
            this.duration = duration; // 周期
            this.iterations = iterations; // 重复次数
        }
    
        /**
         * 动画经过的时间
         * @returns {number}
         */
        get time() {
            return Date.now() - this.startTime;
        }
    
        /**
         * 动画进度
         * @returns {number|number}
         */
        get p() {
            // 动画持续了几个周期
            const progress = Math.min(this.time / this.duration, this.iterations);
            // 动画已结束:进度1
            // 动画未结束:0~1
            return this.isFinished ? 1 : progress % 1;
        }
    
        /**
         * 动画是否已结束
         * @returns {boolean}
         */
        get isFinished() {
            // 动画持续了几个周期是否已达到指定次数
            return this.time / this.duration >= this.iterations;
        }
    }
    

    这几个方法都很容易理解。

  • 然后,实现一个Animator类,用于控制动画过程。

    export class Animator {
        constructor({duration, iterations}) {
            this.timingParam = {duration, iterations};
        }
    
        /**
         * 执行动画
         * @param target
         * @param update
         * @returns {Promise<unknown>}
         */
        animate(target, update) {
            let frameIndex = 0; // 帧序号
            const timing = new Timing(this.timingParam);
    
            return new Promise(resolve => {
                function next() {
                    // 通过执行update更新动画
                    if(update({target, frameIndex, timing}) !== false
                        && !timing.isFinished) {
                        requestAnimationFrame(next);
                    } else {
                        resolve(timing);
                    }
                    frameIndex ++;
                }
                next();
            })
        }
    }
    

    animate方法,会在执行时创建一个timing对象,最后返回一个promise对象。这里通过执行update更新动画,在动画结束时,resolve这个promise。

现在我们就可以使用这个模型,来尝试实现动画效果了。来看下面这个例子。

在这个例子中,我们让每个方块转动的周期是1秒,一共旋转1.5个周期(也就是540度)。

<div class="container">
  <div class="block"></div>
  <div class="block"></div>
  <div class="block"></div>
  <div class="block"></div>
</div>
const blocks = document.querySelectorAll('.block');
const animator = new Animator({duration: 1000, iterations: 1.5});
(async function() {
  let i = 0;
  while(true) {
    await animator.animate(blocks[i++ % 4], ({target, timing}) => {
      target.style.transform = `rotate(${timing.p * 360}deg)`;
    });
  }
}());
.container {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  width: 300px;
}
.block {
  width: 100px;
  height: 100px;
  margin: 20px;
  flex-shrink: 0;
  transform-origin: 50% 50%;
  &:nth-child(1) {background-color: red;}
  &:nth-child(2) {background-color: blue;}
  &:nth-child(3) {background-color: green;}
  &:nth-child(4) {background-color: orange;}
}

image

可以看到,这个效果我们很方便地通过前面定义的Animator实现了。

插值与缓动函数

在前面的例子中,我们看到的动画效果都是匀速运动的,图像是匀速变化的,显然在实际中这是不够满足需求的,既然时序动画可以让我们更容易地控制动画的细节,所以它也可以让我们实现一些不规则的运动。

假设已知元素的起始状态、结束状态和运动周期,如果想要让它进行不规则运动,我们可以使用插值的方式来控制每一帧的展现。

下面我们来看一个动画:这是一个匀速运动的方块,我们用Animator实现,让这个方块从100px处匀速运动到400px。

const block = document.querySelector('.block');
const animator = new Animator({duration: 3000});
document.addEventListener('click', () => {
  animator.animate({el: block, start: 100, end: 400}, ({target: {el, start, end}, timing: {p}}) => {
      const left = start * (1 - p) + end * p;
      el.style.left = `${left}px`;
    });
});

image

这里我们用了一个线性插值方法:left = start * (1 - p) + end * p线性插值可以很方便地实现属性的均匀变化,所以用它来让方块做匀速运动是非常简单的。

如果要让方块进行非匀速运动,比如匀加速运动,我们仍然可以用线性插值的方式,只不过要对参数 p 做一个函数映射。比如要让方块做初速度为0的匀加速运动,我们可以将 p 映射为p 的平方;如果要让方块做末速度为0的匀减速运动,可以将p映射为p*(2-p)。那为什么是这样映射呢?

这就要提到匀加速和匀减速的物理计算公式了。有些小伙伴很久没接触物理公式,可能会有些遗忘,这里简单回顾一下。

假设,某个物体在做初速度为0的匀加速运动,运动的总时间为T,总位移为S。那么,它的加速度和在 t 时刻的位移的计算公式是这样的:

\[a = \frac{2S}{T^2} \\ S_t = \frac{1}{2}at^2 = S(\frac{t}{T})^2 = Sp^2 \]

所以在匀加速运动中,我们把 p 映射为 p 的平方。

同样的,如果物体在做匀减速运动,那么,它的加速度和在 t 时刻的位移的计算公式是这样的:

\[a = -\frac{2S}{T^2} \\ S_t = \frac{2S}{T}t - S(\frac{t}{T})^2 = Sp(2 - p) \]

所以在匀减速运动中,我们把 p 映射为 p*(2 - p)。

在实际应用中,我们还可以对p 应用更多映射,来实现不同的动画效果,为了方便实现更多的效果,我们可以抽象出一个函数来专门处理p的映射,这个函数就叫做缓动函数

我们可以在Timing类中直接增加一个缓动函数easing,在获取p 的时候,直接用 this.easing(progress % 1)取代progress %1

现在我们可以来尝试使用下缓动函数。

const animator2 = new Animator({duration: 3000, easing: p => p ** 2});
document.addEventListener('click', () => {
  animator2.animate({el: block, start: 100, end: 400}, ({target: {el, start, end}, timing: {p}}) => {
    const left = start * (1 - p) + end * p;
    el.style.left = `${left}px`;
  });
});

image

缓动函数有很多种,实际中比较常用的是贝塞尔曲线缓动,我们可以使用现成的JavaScript库bezier-easing来生成贝塞尔缓动函数,比如:

const animator3 = new Animator({duration: 3000, easing: BesizerEasing(0.5, -1.5, 0.5, 2.5)});
document.addEventListener('click', () => {
  animator3.animate({el: block, start: 100, end: 400}, ({target: {el, start, end}, timing: {p}}) => {
    const left = start * (1 - p) + end * p;
    el.style.left = `${left}px`;
  });
});

image

贝塞尔缓动函数有很多种,大家可以参考easing.net这个网站,尝试利用里面提供的缓动函数。

看到这里,关于如何去实现动画,相信大家都有一定的思路了。那么现在我们也可以尝试在Shader中去实现动画效果。

Shader

固定帧动画

首先我们还是先来看固定帧动画的实现。

直接来看具体的例子,还是之前那个飞动的小鸟的例子。

// 片元着色器
varying vec2 vUv;
uniform sampler2D tMap;
uniform float fWidth;
uniform vec2 vFrames[3]; // 3个二维向量,二维向量表示每一帧动画的图片起始x和结束x坐标
uniform int frameIndex;

void main() {
  vec2 uv = vUv;
  for (int i = 0; i < 3; i ++) {
    // 纹理坐标ux.x的取值范围
    // 第0帧:[2/272, 88/272] 约等于 [0.007,0.323]
    // 第1帧:[90/272, 176/272] 约等于 [0.330,0.647]
    // 第2帧:[178/272, 264/272] 约等于 [0.654,0.970]
    uv.x = mix(vFrames[i].x, vFrames[i].y, vUv.x) / fWidth; // vUv 到 uv的映射
    if(float(i) == mod(float(frameIndex), 3.0)) break; // frameIndex除3的余数:0-循环一次;1-循环两次;2-循环三次。(渲染第几帧)
  }

  vec4 color = texture2D(tMap, uv); // 按照uv坐标取色值

  gl_FragColor = color;
}

我们在片元着色器中获取纹理,通过纹理坐标读取图像上的像素信息。

vFrames是一个重要的参数,包含3个二维向量,每一个二维向量表示一帧图片的起始x和结束x坐标。

for循环是main函数中的关键部分,在循环内部,我们用二维向量中的两个坐标,来计算插值,最后除以图片的总宽度,得到一个 vUv 到 uv 坐标映射。

在对纹理进行采样时,我们就用这个uv的坐标值去进行颜色提取。

然后看JavaScript部分的代码:

(async function() {
  renderer.uniforms.tMap = await renderer.loadTexture(birdpng);
  renderer.uniforms.vFrames = [2, 88, 90, 176, 178, 264];
  renderer.uniforms.fWidth = 272;
  renderer.uniforms.frameIndex = 0;
  setInterval(() => {
    renderer.uniforms.frameIndex ++;
  }, 200);
  // 顶点坐标(WebGL画布绘制范围)
  const x = 43 / glRef.value.width; // 每帧的宽度(86/2)
  const y = 30 / glRef.value.height; // 每帧的高度(60/2)
  renderer.setMeshData([{
    positions: [
      [-x, -y],
      [-x, y],
      [x, y],
      [x, -y]
    ],
    attributes: {
      uv: [
        [0, 0],
        [0, 1],
        [1, 1],
        [1, 0]
      ]
    },
    cells: [
      [0, 1, 2],
      [2, 0, 3]
    ]
  }]);
  renderer.render();
}());

我们按照每帧图片的宽高比例设置了顶点坐标的范围,vFrames数组存储的是每一帧图像对应的x坐标范围,动画切换的关键代码就是setInterval中的frameIndex ++。

可以看到在Shader中实现固定帧动画也是比较简单的。

非固定帧动画

对于非固定帧动画,因为时序动画是最常用的实现形式,所以我们直接看时序动画。

大家都知道,在WebGL中有两类着色器,那么对动画的实现应该写在哪类着色器中呢?答案是,两个都可以。

顶点着色器

我们先来看顶点着色器的例子。

attribute vec2 a_vertexPosition;
attribute vec2 uv;
uniform float rotation;

void main() {
  gl_PointSize = 1.0;
  float c = cos(rotation);
  float s = sin(rotation);
  mat3 transformMatrix = mat3(
    c,  s, 0,
    -s, c, 0,
    0,  0, 1
  );
  vec3 pos = transformMatrix * vec3(a_vertexPosition, 1); // 映射新的坐标
  gl_Position = vec4(pos, 1);
}

这段代码中我们要实现的是一个旋转的红色方块。在这里我们用到了旋转矩阵,对于transform不熟悉的小伙伴可以参考我之前的文章《CSS transform与仿射变换》

在Shader中会绘制出一个红色的正方形,然后三维的齐次矩阵会让这个红色方块旋转起来。我们可以直接通过下面这段JavaScript去动态更新旋转的角度rotation,就能看到动画效果了:

// ...
renderer.uniforms.rotation = 0.0;
requestAnimationFrame(function update() {
  renderer.uniforms.rotation += 0.05;
  requestAnimationFrame(update);
});
// ...

我们也可以使用前面定义的Animator对象去更精确地控制图形的旋转效果。

// ...
renderer.uniforms.rotation = 0.0;
const animator = new Animator({duration: 2000, iterations: Infinity});
animator.animate(renderer, ({target, timing}) => {
  target.uniforms.rotation = timing.p * 2 * Math.PI;
});
// ...

可以看到,这里更新uniform属性和前面更新HTML元素的属性,这两种操作从代码上看很相似。

片元着色器

接着我们来看片元着色器的例子。

varying vec2 vUv;
uniform vec4 color;
uniform float rotation;

void main() {
  vec2 st = 2.0 * (vUv - vec2(0.5));
  float c = cos(rotation);
  float s = sin(rotation);
  mat3 transformMatrix = mat3(
    c, s, 0,
    -s, c, 0,
    0, 0, 1
  );
  vec3 pos = transformMatrix * vec3(st, 1.0); // 坐标系旋转
  float d1 = 1.0 - smoothstep(0.5, 0.505, abs(pos.x)); // abs(x)<0.5 d1=1
  float d2 = 1.0 - smoothstep(0.5, 0.505, abs(pos.y)); // abs(y)<0.5 d2=1
  gl_FragColor = d1 * d2 * color;
}

这段代码中,我们通过距离场着色的方式绘制了正方形,同样传递了rotation来控制方块的旋转角度。

我们能很明显的发现,片元着色器和前面顶点着色器的实现,最终实现的效果上,两个方块的旋转方向不一致。顶点着色器中是逆时针旋转,片元着色器中是顺时针旋转,这是因为在顶点着色器中,我们是直接改变了顶点坐标,通过旋转矩阵的处理映射到了新的顶点,而在片元着色器中的坐标变换,相当于是把坐标系做了旋转,最终绘图的图形是相对于新的坐标系去计算距离场的距离,所以最终就呈现了相反的旋转效果。

选择

那么既然两类着色器都能实现动画效果,在实际使用中我们要怎么选择呢?一般来说,动画如果能使用顶点着色器实现,会尽量在顶点着色器中实现。因为在绘制一帧画面的时候,顶点着色器的运算量会大大少于片元着色器,所以使用顶点着色器消耗的性能更少。

但是假如我们需要绘制更复杂的效果,比如运用大量的重复、随机、噪声,那么使用片元着色器更合适。

所以具体的,还是要根据我们最终想要达到的效果、去选择合适的实现方式。

Shader缓动函数

和HTML/CSS中的例子一样,如果我们想要在Shader中实现非匀速运动,也可以直接使用Animator对象,在JavaScript中使用缓动函数,但是在WebGL中除了这种方式之外,我们也可以选择直接把缓动函数写在Shader中,比如下面这个例子:

// vertex
attribute vec2 a_vertexPosition;
uniform vec4 uFromTo;
uniform float uTime;

float easing(in float p) {
  // return smoothstep(0.0, 1.0, p);
  // return clamp(p * p, 0.0, 1.0); // 匀加速

  return clamp(p * (2.0 - p), 0.0, 1.0); // 0->1->0 // 先减速后加速

  // if(p < 1.0) return clamp(p * (2.0 - p), 0.0, 1.0);
  // else return 1.0;
}

void main() {
  gl_PointSize = 1.0;
  vec2 from = uFromTo.xy;
  vec2 to = uFromTo.zw;
  float p = easing(uTime / 2.0);
  vec2 translation = mix(from, to, p);
  mat3 transformMatrix = mat3(
    1, 0, 0,
    0, 1, 0,
    translation, 1
  );
  vec3 pos = transformMatrix * vec3(a_vertexPosition, 1);
  gl_Position = vec4(pos, 1);
}

可以用smoothstep(0.0, 1.0, p)来让方块做平滑变速运动;也可以替换缓动函数,使用比如clamp(p*p, 0.0, 1.0)clamp(p*(2.0-p), 0.0, 1.0)来实现匀加速、匀减速的运动效果。

总结

以上就是关于动画实现的分享,主要介绍了动画的三种实现形式和具体操作,本文中都是比较简单的一些动画例子,希望能给到大家一些启发,去实现更复杂、更有意思的动画效果。

参考代码-HTML/CSS
参考代码-Shader
效果预览-HTML/CSS
效果预览-Shader

与可视化学习:如何生成简单动画让图形动起来相似的内容:

可视化学习:如何生成简单动画让图形动起来

在可视化展现中,动画是强化数据表达,吸引用户的重要技术手段,本文将介绍动画的三种实现形式,以及如何具体地在HTML/CSS和Shader中去实现动画。

[转帖]Jmeter学习笔记(二十三)——生成HTML性能报告

https://www.cnblogs.com/pachongshangdexuebi/p/11759316.html 有时候我们写性能报告的时候需要一些性能分布图,JMeter是可以生成HTML性能报告的。这篇博客,简单介绍下在利用jmeter进行性能测试时,是如何生成HTML的可视化测试报告的

可视化学习 | 如何使用噪声生成纹理

什么是噪声呢?在自然界中,离散的随机是比较常见的,比如蝉鸣突然响起又突然停下,比如雨滴随机落在一个位置,但是随机和连续并存是更常见的情况,比如山脉的走向是随机的,但山峰之间的高度又是连续的,比如天上的云朵、水面的波纹等等。这种把随机和连续结合起来,就形成了噪声。通过利用噪声,我们就可以去模拟真实自然...

[转帖]Oracle如何重启mmon/mmnl进程(AWR自动采集)

https://www.cnblogs.com/jyzhao/p/10119854.html 学习一下 环境:Oracle 11.2.0.4 RAC现象:sysaux空间满导致无法正常生成快照,清理空间后,手工生成快照可以成功,但是观察自动生成快照依然是不成功。之前了解到awr对应的相关后台进程是m

动手学Avalonia:基于硅基流动构建一个文生图应用(一)

文生图 文生图,全称“文字生成图像”(Text-to-Image),是一种AI技术,能够根据给定的文本描述生成相应的图像。这种技术利用深度学习模型,如生成对抗网络(GANs)或变换器(Transformers),来理解和解析文本中的语义信息,并将其转化为视觉表现。文生图可以用于创意设计、图像编辑、虚

Unity热更学习toLua使用--[1]toLua的导入和默认加载执行lua脚本

[0]toLua的导入 下载toLua资源包,访问GitHub项目地址,点击下载即可。 将文件导入工程目录中: 导入成功之后会出现Lua菜单栏,如未成功生成文件,可以点击Generate All 重新生成(注意很可能是路径问题导致的生成失败!) 之后就可以开始编写脚本执行第一个lua程序了! [1]

利用代码生成工具快速生成基于SqlSugar框架的Winform界面项目

我们接触一个新事物的时候,如果一个事物能够给我们带来非常直观的感官认识,那么我们就很容易接受,反之可能需要很长时间的潜移默化的了解认识才能接受。万物化繁为简,透过本质看表象,往往也是一个认知迭代深入的过程。在我介绍很多篇随笔《SqlSugar开发框架》,能够看完的肯定不会是一开始就学习的人员,毕竟技术的陈述是比较枯燥无味的,而最好的认识来自于一些快速的项目演示,本篇随笔介绍利用《代码生成工具Dat

算法学习笔记(30):Kruskal 重构树

Kruskal 重构树 这是一种用于处理与最大/最小边权相关的一个数据结构。 其与 kruskal 做最小生成树的过程是类似的,我们考虑其过程: 按边权排序,利用并查集维护连通性,进行合并。 如果我们在合并时,新建一个节点,其权值为当前处理的边的权值,并将合并的两个节点都连向新建的节点,那么就可以得

学习文章:“浅析数据安全之密态化计算”

本文学习文章:“浅析数据安全之密态化计算” 数据安全的必要性 数据安全应保证数据产生、存储、传输、访问、使用、销毁、公开等全生命周期安全,并且需要做到保证数据处理过程的保密性、完整性、可用性。如何安全采集用户数据,并且实现安全地对用户数据进行使用,主要包括在整个数据周期中保证安全,即在数据的生产、传

JVM启动参数脚本的再学习与研究

JVM启动参数脚本的再学习与研究 摘要 学无止境 前段时间一直再研究JVM参数调优. 但是最近也在想不应该仅研究如何调优. 因为不管怎么设置, 总有猪队友会把环境搞崩. 所以应该想办法在无人值守的情况下能够启动服务. systemd这种主流方式有时候不靠谱, 进程可能判断生死不准确. 所以还是想通过