1. 前言

在深度学习模型中,Tensor是最基本的运算单元。本文将深入探讨PyTorch中两个核心概念:

  • Tensor的广播机制(Broadcasting)
  • 自动求导(Autograd)机制

这些知识点不仅让你更加灵活地操作数据,还为后续搭建神经网络打下坚实基础!

2. Tensor广播(Broadcasting)详解

2.1 什么是广播?

广播(Broadcasting)是一种在不同形状的Tensor之间进行数学运算的机制。当我们对两个形状不同的Tensor进行运算时,PyTorch会自动将较小的Tensor扩展到较大Tensor的形状,使它们能够进行元素级的运算。

广播机制的优势在于:

  • 无需创建冗余的内存副本
  • 代码更简洁高效
  • 计算性能更好

2.2 广播规则总结

PyTorch中的广播规则遵循以下原则:

  1. 维度对齐:从最后一维开始对齐,向前比较
  2. 自动扩展:当一个Tensor的某维度为1时,它会被自动扩展以匹配另一个Tensor的对应维度
  3. 无法匹配时报错:如果两个Tensor的对应维度既不相等,也不存在为1的情况,则广播失败

2.3 广播常见案例

让我们通过代码示例来理解广播机制:

import torch

# 示例1:小Tensor加大Tensor
a = torch.rand(3, 1) # 形状为[3,1]
b = torch.rand(1, 4) # 形状为[1,4]
c = a + b # 广播后结果是[3,4]
print(f"a shape: {a.shape}, b shape: {b.shape}, c shape: {c.shape}") # 示例2:行向量与列向量相加
row = torch.rand(1, 5) # 形状为[1,5]的行向量
col = torch.rand(4, 1) # 形状为[4,1]的列向量
out = row + col # 结果是[4,5]的矩阵
print(f"row shape: {row.shape}, col shape: {col.shape}, out shape: {out.shape}") # 示例3:标量与矩阵运算
matrix = torch.rand(2, 3)
scalar = torch.tensor(5.0)
result = matrix * scalar # 标量会广播到矩阵的每个元素
print(f"matrix shape: {matrix.shape}, result shape: {result.shape}")

让我们分析一下为什么能这样广播:

对于第一个示例:

  • a的形状是[3,1]
  • b的形状是[1,4]
  • 最后一维:1和4不相等,但其中一个是1,所以a在这一维被扩展为4
  • 倒数第二维:3和1不相等,但其中一个是1,所以b在这一维被扩展为3
  • 最终两者都被广播为[3,4]的形状,然后进行元素级加法

2.4 广播的使用场景

广播在深度学习中有很多实用场景:

  1. 批量数据处理:对一批数据应用相同的变换
  2. 添加偏置项:将一维的偏置向量添加到二维矩阵的每一行
  3. 归一化操作:使用均值和标准差对数据进行归一化
  4. 掩码操作:使用布尔掩码对数据进行过滤
# 批量归一化例子
batch_data = torch.rand(32, 10) # 32个样本,每个10个特征
batch_mean = batch_data.mean(dim=0, keepdim=True) # 形状[1,10]
batch_std = batch_data.std(dim=0, keepdim=True) # 形状[1,10]
normalized_data = (batch_data - batch_mean) / batch_std # 广播操作

3. PyTorch自动求导(Autograd)详解

3.1 什么是Autograd?

PyTorch的Autograd是一个自动微分系统,它能够自动计算神经网络中所有参数的梯度。这个功能是深度学习框架的核心,因为反向传播算法依赖于对每个参数计算梯度。

简单来说,Autograd可以:

  • 自动构建计算图
  • 执行反向传播(backward)
  • 计算梯度

你只需专注于前向计算,梯度求导PyTorch帮你自动完成!

3.2 Tensor的requires_grad属性

在PyTorch中,每个Tensor都有一个requires_grad属性,它决定了这个Tensor是否需要计算梯度:

import torch

# 默认情况下,requires_grad为False
x = torch.tensor([2.0])
print(f"默认requires_grad: {x.requires_grad}") # 创建需要梯度的Tensor
x = torch.tensor([2.0], requires_grad=True)
print(f"设置requires_grad=True: {x.requires_grad}") # 也可以后续修改
x = torch.tensor([2.0])
x.requires_grad_(True) # 注意有下划线
print(f"后续修改requires_grad: {x.requires_grad}")

requires_grad=True时:

  • Tensor会开始追踪所有与它相关的操作
  • 执行backward()时,会自动计算梯度
  • 梯度值存储在.grad属性中

3.3 计算图与反向传播

当我们对设置了requires_grad=True的Tensor进行操作时,PyTorch会自动构建一个计算图

import torch

# 创建叶子节点
x = torch.tensor(2.0, requires_grad=True)
y = torch.tensor(3.0, requires_grad=True) # 构建计算图
z = x * y + torch.log(x) # 查看计算图
print(f"z.grad_fn: {z.grad_fn}")
print(f"z的创建者: {z.grad_fn.__class__.__name__}")

执行反向传播计算梯度:

import torch

# 创建需要求导的Tensor
x = torch.tensor(2.0, requires_grad=True) # 定义函数: y = x² + 3x + 1
y = x**2 + 3*x + 1 # 执行反向传播
y.backward() # 查看x的梯度
print(f"x的梯度: {x.grad}") # 输出应该是 dy/dx = 2x + 3,当x=2时,结果是7

3.4 梯度累积与清零

PyTorch中的梯度是累积的,这意味着多次调用.backward()会导致梯度累加,而不是覆盖:

import torch

x = torch.tensor(2.0, requires_grad=True)

# 第一次前向传播和反向传播
y = x**2
y.backward()
print(f"第一次反向传播后 x.grad: {x.grad}") # 输出: 4 # 第二次前向传播和反向传播(梯度会累加)
y = x**2
y.backward()
print(f"第二次反向传播后 x.grad: {x.grad}") # 输出: 8 (4+4) # 清零梯度
x.grad.zero_()
print(f"清零后 x.grad: {x.grad}") # 输出: 0 # 再次计算
y = x**2
y.backward()
print(f"清零后再计算 x.grad: {x.grad}") # 输出: 4

在训练神经网络时,每次更新参数前都需要清零梯度,否则会导致梯度累积:

optimizer.zero_grad()  # 清零所有参数的梯度
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新参数

3.5 高阶梯度和链式法则

PyTorch支持高阶导数计算,这对于某些优化算法和研究很有用:

import torch

x = torch.tensor(2.0, requires_grad=True)

# 计算函数 y = x^3
y = x**3 # 计算一阶导数 dy/dx = 3x^2
y.backward(create_graph=True) # 设置create_graph=True以计算高阶导数
print(f"一阶导数 dy/dx: {x.grad}") # 当x=2时,输出应该是12 # 计算二阶导数 d²y/dx² = 6x
x.grad.backward()
print(f"二阶导数 d²y/dx²: {x.grad.grad}") # 当x=2时,输出应该是6

PyTorch自动处理链式法则,使得复杂函数的求导变得简单:

import torch

x = torch.tensor(2.0, requires_grad=True)

# 复合函数: y = sin(x²)
y = torch.sin(x**2) # 计算导数: dy/dx = cos(x²) * 2x
y.backward()
print(f"dy/dx: {x.grad}") # 当x=2时,输出应该接近 cos(4) * 4

4. 实战案例

4.1 使用广播实现批量归一化

import torch

# 创建一批数据
batch_size = 100
features = 20
data = torch.randn(batch_size, features) # 计算每个特征的均值和标准差
mean = data.mean(dim=0, keepdim=True) # shape: [1, features]
std = data.std(dim=0, keepdim=True) # shape: [1, features] # 使用广播进行归一化
normalized_data = (data - mean) / std print(f"均值接近0: {normalized_data.mean(dim=0)}")
print(f"标准差接近1: {normalized_data.std(dim=0)}")

4.2 手写函数求导例子

让我们计算一个更复杂函数的导数:y = x² + 3x + 1

import torch

# 创建一个需要求导的Tensor
x = torch.tensor(2.0, requires_grad=True) # 定义函数
y = x**2 + 3*x + 1 # 执行反向传播
y.backward() # 查看x的梯度
print(f"x的梯度: {x.grad}") # 输出应该是 dy/dx = 2x + 3,当x=2时,结果是7 # 理论结果验证
theoretical_grad = 2*x.item() + 3
print(f"理论计算的梯度: {theoretical_grad}")

4.3 使用自动求导训练简单线性回归

import torch
import torch.nn as nn
import matplotlib.pyplot as plt # 生成一些带有噪声的数据
x = torch.linspace(0, 10, 100)
y_true = 2*x + 1 + torch.randn(100) * 0.5 # 准备数据
x = x.view(-1, 1)
y_true = y_true.view(-1, 1) # 定义模型
class LinearRegression(nn.Module):
def __init__(self):
super(LinearRegression, self).__init__()
self.linear = nn.Linear(1, 1) # 输入和输出维度都是1 def forward(self, x):
return self.linear(x) # 初始化模型、损失函数和优化器
model = LinearRegression()
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 训练模型
epochs = 100
losses = [] for epoch in range(epochs):
# 前向传播
y_pred = model(x) # 计算损失
loss = criterion(y_pred, y_true)
losses.append(loss.item()) # 反向传播
optimizer.zero_grad()
loss.backward() # 更新参数
optimizer.step() if (epoch+1) % 10 == 0:
print(f'Epoch {epoch+1}/{epochs}, Loss: {loss.item():.4f}') # 获取参数
w, b = model.linear.weight.item(), model.linear.bias.item()
print(f'学习到的参数: y = {w:.4f}x + {b:.4f}') # 可视化结果
plt.figure(figsize=(10, 6))
plt.scatter(x.numpy(), y_true.numpy(), label='原始数据')
plt.plot(x.numpy(), model(x).detach().numpy(), 'r-', linewidth=2, label=f'拟合线: y = {w:.2f}x + {b:.2f}')
plt.legend()
plt.title('线性回归结果')
plt.show() # 可视化损失下降
plt.figure(figsize=(10, 6))
plt.plot(losses)
plt.title('训练损失')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.show()

4.4 记录中间梯度进行可视化

import torch
import numpy as np
import matplotlib.pyplot as plt # 创建函数 f(x) = x^3 - 3x^2 + 2
def f(x):
return x**3 - 3*x**2 + 2 # 创建导数函数 f'(x) = 3x^2 - 6x
def df(x):
return 3*x**2 - 6*x # 准备数据点进行可视化
x_plot = np.linspace(-1, 3, 100)
y_plot = f(torch.tensor(x_plot)).numpy()
dy_plot = df(torch.tensor(x_plot)).numpy() # 选择几个点计算梯度
x_points = torch.tensor([-0.5, 0.5, 1.0, 2.0], requires_grad=True, dtype=torch.float)
y_points = f(x_points) # 计算每个点的梯度
gradients = []
for i in range(len(x_points)):
if i > 0: # 清除之前的梯度
x_points.grad.zero_() # 只对一个点的输出调用backward
y = f(x_points[i:i+1])
y.backward() # 存储梯度
gradients.append(x_points.grad[i].item()) # 可视化函数和导数
plt.figure(figsize=(12, 8)) # 绘制函数
plt.subplot(2, 1, 1)
plt.plot(x_plot, y_plot, 'b-', label='f(x) = x^3 - 3x^2 + 2')
plt.scatter(x_points.detach().numpy(), f(x_points).detach().numpy(), color='red', s=50, label='选中的点') # 绘制切线
for i, x_val in enumerate(x_points):
x_v = x_val.item()
y_v = f(torch.tensor(x_v)).item()
slope = gradients[i] # 绘制切线 (使用点斜式方程)
x_tangent = np.array([x_v - 0.5, x_v + 0.5])
y_tangent = slope * (x_tangent - x_v) + y_v
plt.plot(x_tangent, y_tangent, 'g--') plt.grid(True)
plt.legend()
plt.title('函数及其在选定点的切线') # 绘制导数
plt.subplot(2, 1, 2)
plt.plot(x_plot, dy_plot, 'r-', label='f\'(x) = 3x^2 - 6x')
plt.scatter(x_points.detach().numpy(), np.array(gradients), color='blue', s=50, label='计算的梯度')
plt.grid(True)
plt.legend()
plt.title('导数函数及通过autograd计算的梯度') plt.tight_layout()
plt.show()

5. 注意事项和最佳实践

5.1 自动求导注意事项

  1. 只有标量(单个数)才能直接执行backward()

    # 如果输出是向量,需要提供gradient参数
    vector_output = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
    vector_output.backward(torch.ones_like(vector_output))
  2. .grad属性是累计的

    # 每次使用backward()前清零梯度
    optimizer.zero_grad()
    # 或者
    x.grad.zero_()
  3. 中断梯度流

    # 使用detach()中断梯度流
    x = torch.tensor([2.0], requires_grad=True)
    y = x * 2
    z = y.detach() # z不会追踪与x的关系
    z = z * 3
    z.backward() # 这不会影响x.grad
  4. with torch.no_grad()上下文

    x = torch.tensor([2.0], requires_grad=True)
    with torch.no_grad():
    # 在这个上下文中的操作不会被追踪
    y = x * 2

5.2 广播机制最佳实践

  1. 在使用广播前了解张量形状

    print(f"Tensor shapes: {a.shape}, {b.shape}")
  2. 避免创建不必要的大型中间张量

    # 避免这样
    a = torch.rand(10000, 1)
    b = a.expand(10000, 10000) # 创建大矩阵 # 更好的方式是直接利用广播
    a = torch.rand(10000, 1)
    c = a + 1 # 广播,不创建中间张量
  3. 利用unsqueeze和view管理维度

    # 添加维度以便广播
    a = torch.rand(5)
    b = torch.rand(3)
    c = a.unsqueeze(0) + b.unsqueeze(1) # 结果形状为[3, 5]

6. 可视化案例代码

tensor_visualizer.py


import streamlit as st
import torch
import numpy as np
import matplotlib.pyplot as plt
import plotly.graph_objects as go
import plotly.express as px
import matplotlib.font_manager as fm
import matplotlib # 指定中文字体路径(macOS)
font_path = "/System/Library/Fonts/PingFang.ttc" # macOS 中文字体
my_font = fm.FontProperties(fname=font_path) # 设置 matplotlib 默认字体
matplotlib.rcParams['font.family'] = my_font.get_name()
matplotlib.rcParams['axes.unicode_minus'] = False # 设置页面标题
st.title(" PyTorch Tensor可视化工具")
st.caption("作者:何双新 | 环境:Mac M1 + PyTorch")
# st.set_page_config(page_title="PyTorch Tensor可视化", layout="wide") # 侧边栏选项
st.sidebar.header("Tensor设置")
tensor_dim = st.sidebar.radio("选择Tensor维度", [0, 1, 2, 3, 4], index=2) # 根据维度提供不同选项
if tensor_dim == 0: # 标量
scalar_value = st.sidebar.slider("标量值", -10.0, 10.0, 5.0, 0.1) st.header("0维Tensor (标量)")
tensor = torch.tensor(scalar_value)
st.code(f"tensor = torch.tensor({scalar_value})")
st.write(f"值: {tensor.item()}")
st.write(f"形状: {tensor.shape}") # 可视化
st.write("可视化: 一个点")
fig, ax = plt.subplots(figsize=(3, 3))
ax.scatter([0], [0], s=100, c=[scalar_value], cmap='viridis')
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)
ax.set_xticks([])
ax.set_yticks([])
st.pyplot(fig) elif tensor_dim == 1: # 向量
vector_size = st.sidebar.slider("向量大小", 2, 20, 10)
vector_type = st.sidebar.selectbox("向量类型", ["随机", "线性", "正弦波"]) st.header("1维Tensor (向量)") if vector_type == "随机":
tensor = torch.rand(vector_size)
elif vector_type == "线性":
tensor = torch.linspace(0, 10, vector_size)
else: # 正弦波
tensor = torch.sin(torch.linspace(0, 6.28, vector_size)) st.code(f"tensor.shape = {tensor.shape}")
st.write("Tensor值:")
st.write(tensor) # 可视化
st.write("可视化:")
fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(tensor.numpy(), marker='o')
ax.set_title("1维Tensor可视化")
ax.set_xlabel("索引")
ax.set_ylabel("值")
ax.grid(True)
st.pyplot(fig) elif tensor_dim == 2: # 矩阵
rows = st.sidebar.slider("行数", 2, 10, 5)
cols = st.sidebar.slider("列数", 2, 10, 5)
tensor_type = st.sidebar.selectbox("矩阵类型", ["随机", "单位矩阵", "对角矩阵"]) st.header("2维Tensor (矩阵)") if tensor_type == "随机":
tensor = torch.rand(rows, cols)
elif tensor_type == "单位矩阵":
tensor = torch.eye(max(rows, cols))[:rows, :cols]
else: # 对角矩阵
tensor = torch.diag(torch.linspace(1, min(rows, cols), min(rows, cols)))
if rows > cols:
tensor = torch.cat([tensor, torch.zeros(rows - cols, cols)], dim=0)
elif cols > rows:
tensor = torch.cat([tensor, torch.zeros(rows, cols - rows)], dim=1) st.code(f"tensor.shape = {tensor.shape}")
st.write("Tensor值:")
st.write(tensor) # 可视化为热力图
st.write("可视化:")
fig = px.imshow(tensor.numpy(),
labels=dict(x="列", y="行", color="值"),
color_continuous_scale='viridis')
fig.update_layout(width=600, height=500)
st.plotly_chart(fig) elif tensor_dim == 3: # 3D Tensor
depth = st.sidebar.slider("深度", 2, 5, 3)
height = st.sidebar.slider("高度", 2, 10, 5)
width = st.sidebar.slider("宽度", 2, 10, 5) st.header("3维Tensor")
tensor = torch.rand(depth, height, width) st.code(f"tensor.shape = {tensor.shape}") # 展示每个深度层
st.write("每个深度的切片可视化:") tabs = st.tabs([f"切片 {i}" for i in range(depth)])
for i, tab in enumerate(tabs):
with tab:
fig = px.imshow(tensor[i].numpy(),
labels=dict(x="宽度", y="高度", color="值"),
color_continuous_scale='viridis')
fig.update_layout(width=500, height=400)
st.plotly_chart(fig) # 3D可视化
st.write("3D可视化 (体素):")
# 创建网格
X, Y, Z = np.mgrid[0:depth, 0:height, 0:width]
values = tensor.numpy().flatten() fig = go.Figure(data=go.Volume(
x=X.flatten(),
y=Y.flatten(),
z=Z.flatten(),
value=values,
opacity=0.1,
surface_count=15,
colorscale='viridis'
))
fig.update_layout(
scene=dict(xaxis_title='深度', yaxis_title='高度', zaxis_title='宽度'),
width=700, height=700
)
st.plotly_chart(fig) elif tensor_dim == 4: # 4D Tensor
batch = st.sidebar.slider("批量大小", 1, 5, 2)
channels = st.sidebar.slider("通道数", 1, 3, 3)
height = st.sidebar.slider("高度", 4, 12, 8)
width = st.sidebar.slider("宽度", 4, 12, 8) st.header("4维Tensor (批量图像)")
tensor = torch.rand(batch, channels, height, width) st.code(f"tensor.shape = {tensor.shape}")
st.write(f"这个Tensor可以表示{batch}张{channels}通道的{height}x{width}图像") # 可视化每个批次的图像
batch_tabs = st.tabs([f"批次 {i}" for i in range(batch)]) for b, batch_tab in enumerate(batch_tabs):
with batch_tab:
if channels == 3:
# 针对RGB图像的特殊处理
img = tensor[b].permute(1, 2, 0).numpy() # 转换为HWC格式
st.image(img, caption=f"批次 {b} 的RGB图像", use_column_width=True)
else:
# 展示每个通道
channel_tabs = st.tabs([f"通道 {i}" for i in range(channels)])
for c, channel_tab in enumerate(channel_tabs):
with channel_tab:
fig = px.imshow(tensor[b, c].numpy(),
color_continuous_scale='viridis')
fig.update_layout(width=400, height=400)
st.plotly_chart(fig) # 添加信息部分
st.sidebar.markdown("---")
st.sidebar.info("""
这个应用程序帮助您可视化不同维度的PyTorch Tensor。
- 0维:标量(一个点)
- 1维:向量(一条线)
- 2维:矩阵(一个平面)
- 3维:3D张量(一个立方体)
- 4维:4D张量(批量图像)
""") # 添加代码说明
with st.expander("如何运行这个应用"):
st.code("""
# 保存代码为tensor_visualizer.py后运行:
streamlit run tensor_visualizer.py
""")



7. 总结

在本篇博客中,我们深入探讨了PyTorch中的两个核心概念:

  • Tensor广播机制 - 使不同形状的张量能够进行运算,避免不必要的内存复制,提高代码效率
  • 自动求导机制 - 自动构建计算图并执行反向传播,计算各参数的梯度,是深度学习优化的基础

8. 参考资料

第2讲、Tensor高级操作与自动求导详解的更多相关文章

  1. 『PyTorch』第三弹_自动求导

    torch.autograd 包提供Tensor所有操作的自动求导方法. 数据结构介绍 autograd.Variable 这是这个包中最核心的类. 它包装了一个Tensor,并且几乎支持所有的定义在 ...

  2. PyTorch官方中文文档:自动求导机制

    自动求导机制 本说明将概述Autograd如何工作并记录操作.了解这些并不是绝对必要的,但我们建议您熟悉它,因为它将帮助您编写更高效,更简洁的程序,并可帮助您进行调试. 从后向中排除子图 每个变量都有 ...

  3. [Pytorch框架] 1.4 Autograd:自动求导

    文章目录 Autograd: 自动求导机制 张量(Tensor) 梯度 Autograd: 自动求导机制 PyTorch 中所有神经网络的核心是 autograd 包. 我们先简单介绍一下这个包,然后 ...

  4. Autograd: 自动求导

    Pytorch中神经网络包中最核心的是autograd包,我们先来简单地学习它,然后训练我们第一个神经网络. autograd包为所有在tensor上的运算提供了自动求导的支持,这是一个逐步运行的框架 ...

  5. [深度学习] pytorch学习笔记(1)(数据类型、基础使用、自动求导、矩阵操作、维度变换、广播、拼接拆分、基本运算、范数、argmax、矩阵比较、where、gather)

    一.Pytorch安装 安装cuda和cudnn,例如cuda10,cudnn7.5 官网下载torch:https://pytorch.org/ 选择下载相应版本的torch 和torchvisio ...

  6. Pytorch Tensor, Variable, 自动求导

    2018.4.25,Facebook 推出了 PyTorch 0.4.0 版本,在该版本及之后的版本中,torch.autograd.Variable 和 torch.Tensor 同属一类.更确切地 ...

  7. PytorchZerotoAll学习笔记(三)--自动求导

    Pytorch给我们提供了自动求导的函数,不用再自己再推导计算梯度的公式了 虽然有了自动求导的函数,但是这里我想给大家浅析一下:深度学习中的一个很重要的反向传播 references:https:// ...

  8. 从零开始学习MXnet(四)计算图和粗细粒度以及自动求导

    这篇其实跟使用MXnet的关系不大,但对于我们理解深度学习的框架设计还是很有帮助的. 首先还是对promgramming models的一个简单介绍,这个东西实际上是在编译里面经常出现的东西,我们在编 ...

  9. pytorch的自动求导机制 - 计算图的建立

    一.计算图简介 在pytorch的官网上,可以看到一个简单的计算图示意图, 如下. import torchfrom torch.autograd import Variable x = Variab ...

  10. Pytorch Autograd (自动求导机制)

    Pytorch Autograd (自动求导机制) Introduce Pytorch Autograd库 (自动求导机制) 是训练神经网络时,反向误差传播(BP)算法的核心. 本文通过logisti ...

随机推荐

  1. 质疑了ChatGPT,结果他居然...

    小编其实是想挑战下ChatGPT,指出目前像他这种AI,通过如此高维向量的方式代表一个事物特征,是算力上的巨大浪费. 质疑这种方式可能不是最优解,冥冥之中应该有更好的方式. 结果发现他居然大方承认,而 ...

  2. 阿里云Windows server 2016服务器Antimalware Service Executable进程占比高,cpu接近100%,强制关闭该进程实测

    问题描述:阿里云Windows server 2016服务器Antimalware Service Executable进程占比高,cpu接近100%,需要强制关闭该进程,排查问题,进入系统服务关闭, ...

  3. 百万架构师第四十六课:并发编程的原理(一)|JavaGuide

    百万架构师系列文章阅读体验感更佳 原文链接:https://javaguide.net 并发编程的原理 课程目标 JMM 内存模型 JMM 如何解决原子性.可见性.有序性的问题 Synchronize ...

  4. C# 委托Action和Func

    Action和Func是微软已经定义好的的两种委托类型,区别是Action是没有返回值的,而Func是需要返回值的. 1 //Action内置委托的实例化及调用 2 //不带参数 3 Action m ...

  5. 多版本Java 配置记录

    来自 https://blog.csdn.net/zdl177/article/details/105246997 起因是为了启动MC 目录结构 Java总目录下放置多个jdk目录(jdk16.0.2 ...

  6. js 时间转时间戳

    前言 有时候我们用时间插件,选择好时间后,需要把日期格式转化为时间戳,再传到后台 时间转时间戳 let time = Math.floor(new Date("2014-04-23 18:5 ...

  7. go module基本使用

    前提 go版本为1.13及以上 官方文档 如果你想更深层次的了解GO MODULE的意义及开发者们的顾虑,可以直接访问官方文档(EN) https://github.com/golang/go/wik ...

  8. 如何写自己的springboot starter?自动装配原理是什么?

    如何写自己的springboot starter?自动装配原理是什么? 官方文档地址:https://docs.spring.io/spring-boot/docs/2.6.13/reference/ ...

  9. MD5加密BASE64加解密

    MD5需要引入system.Hash,BASE64需要引入System.NetEncoding,这两个单元应该只有高版本的DELPHI IDE才有 (貌似XE5以上版本才有).如果是D7的话,找第三方 ...

  10. Redis 是什么?

    Redis 的定义?   百度百科: Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.K ...