如何在 .NET 中使用 SIMD
什么是 SIMD
SIMD(Single Instruction, Multiple Data) 译为 单指令多数据,是一种并行计算技术,允许单条指令同时对多个数据元素进行操作,从而提高计算效率。
与 SIMD 相对的是 SISD(Single Instruction, Single Data,单指令单数据),即每条指令只处理一个数据元素。
现在的大多数 CPU 都支持 SIMD 指令集,例如 Intel 的 SSE 和 AVX,ARM 的 NEON 等。
如果我们要对两组数组进行加法运算,传统方法(SISD)是逐个元素相加,而使用 SIMD 技术,可以一次性将多个元素加载到向量寄存器中,并执行单一的加法指令,从而显著提高计算效率。

下面我们通过一个简单的示例,对比传统的数组加法和使用 SIMD 优化后的数组加法在性能上的差异。例子中会对两个浮点数组进行加法运算,把结果存储在第三个数组中。
using System.Runtime.Intrinsics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
[MemoryDiagnoser]
public class SimdBenchmark
{
private float[] _arrA;
private float[] _arrB;
private float[] _resultArray;
private readonly int _dataSize = 1_000_000;
[GlobalSetup]
public void Setup()
{
var random = new Random();
_arrA = new float[_dataSize];
_arrB = new float[_dataSize];
_resultArray = new float[_dataSize];
for (int i = 0; i < _dataSize; i++)
{
_arrA[i] = (float)random.NextDouble() * 10f;
_arrB[i] = (float)random.NextDouble() * 10f;
}
}
[Benchmark]
public void NormalAdd()
{
for (int i = 0; i < _dataSize; i++)
{
_resultArray[i] = _arrA[i] + _arrB[i];
}
}
[Benchmark]
public void SimdAdd()
{
// 每次处理 4 个元素
int simdLength = Vector128<float>.Count; // 4
int i = 0;
// 处理可被 SIMD 整除的部分
for (; i <= _dataSize - simdLength; i += simdLength)
{
var va = Vector128.Create(_arrA[i], _arrA[i + 1], _arrA[i + 2], _arrA[i + 3]);
var vb = Vector128.Create(_arrB[i], _arrB[i + 1], _arrB[i + 2], _arrB[i + 3]);
(va + vb).CopyTo(_resultArray, i);
}
// 处理尾部不足 4 个的元素
for (; i < _dataSize; i++)
{
_resultArray[i] = _arrA[i] + _arrB[i];
}
}
}
public class Program
{
public static void Main(string[] args)
{
BenchmarkRunner.Run<SimdBenchmark>();
}
}
BenchmarkDotNet v0.15.6, macOS Sequoia 15.7.2 (24G325) [Darwin 24.6.0]
Apple M2 Max, 1 CPU, 12 logical and 12 physical cores
.NET SDK 9.0.100
[Host] : .NET 9.0.0 (9.0.0, 9.0.24.52809), Arm64 RyuJIT armv8.0-a
DefaultJob : .NET 9.0.0 (9.0.0, 9.0.24.52809), Arm64 RyuJIT armv8.0-a
| Method | Mean | Error | StdDev | Allocated |
|---------- |---------:|--------:|--------:|----------:|
| NormalAdd | 880.4 us | 9.28 us | 7.75 us | - |
| SimdAdd | 568.5 us | 4.18 us | 3.70 us | - |
笔者在 MacBook Pro M2 Max 上测试,使用 SIMD 优化后的数组加法运算相比传统方法提升了约 35% 的性能。
此处所使用的例子可能会受到结果需要拷贝到结果数组的影响,实际应用中如果能直接在向量上进行更多计算,性能提升会更加显著。
此例子也可以在 Windows 和 Linux 上运行,有兴趣的读者可以自行测试不同平台的性能差异。
SIMD 基础 API
System.Runtime.Intrinsics 命名空间
.NET 为我们提供了下面三个命名空间来使用 SIMD 技术:
- System.Runtime.Intrinsics :包含用于创建和传递各种大小和格式的寄存器状态的类型。
- System.Runtime.Intrinsics.X86 :包含特定于 x86/x64 架构的 SIMD 指令集的类型。
- System.Runtime.Intrinsics.Arm :包含特定于 ARM 架构的 SIMD 指令集的类型。
System.Runtime.Intrinsics 命名空间中定义了表示不同大小向量的结构体和提供创建及操作这些向量的静态类。
结构体
| 类型 | 描述 |
|---|---|
| Vector64<T> | 表示指定数值类型的 64 位向量,该向量适用于并行算法的低级别优化。 |
| Vector128<T> | 表示指定数值类型的 128 位向量,该向量适用于并行算法的低级别优化。 |
| Vector256<T> | 表示指定数值类型的 256 位向量,该向量适用于并行算法的低级别优化。 |
| Vector512<T> | 表示指定数值类型的 512 位向量,该向量适用于并行算法的低级别优化。 |
静态类
| 类型 | 描述 |
|---|---|
| Vector64 | 提供静态方法的集合,用于在 64 位向量上创建、操作和以其他方式操作。 |
| Vector128 | 提供静态方法集合,用于在 128 位向量上创建、操作和以其他方式操作。 |
| Vector256 | 提供静态方法集合,用于在 256 位向量上创建、操作和以其他方式操作。 |
| Vector512 | 提供静态方法的集合,用于在 512 位向量上创建、操作和以其他方式操作。 |
System.Runtime.Intrinsics.X86 和 System.Runtime.Intrinsics.Arm 命名空间中定义了特定于各自架构的 SIMD 指令集的类,这些类提供了访问底层硬件 SIMD 指令的能力。
常见的指令集类例如:
| 类型 | 描述 |
|---|---|
| Sse | 提供对 x86/x64 SSE 指令集的访问。 |
| Sse2 | 提供对 x86/x64 SSE2 指令集的访问。 |
| Avx | 提供对 x86/x64 AVX 指令集的访问。 |
| Avx2 | 提供对 x86/x64 AVX2 指令集的访问。 |
| AdvSimd | 提供对 ARM Advanced SIMD(NEON)指令集的访问。 |
更详细的列表可以参考官方文档:
System.Runtime.Intrinsics.X86 命名空间
System.Runtime.Intrinsics.Arm 命名空间
如何理解向量的大小
向量的大小(如 64 位、128 位、256 位、512 位)指的是向量寄存器能够容纳的数据总位数。每个向量寄存器可以存储多个数据元素,这些数据元素的类型和数量取决于向量的大小和数据类型的位数。
例如开头用到的 Vector128<float>,它表示一个 128 位的向量寄存器,可以存储 4 个 32 位的浮点数(因为 128 / 32 = 4)。
如果是用来存储 64 位的双精度浮点数(double),则 Vector128<double> 可以存储 2 个双精度浮点数(因为 128 / 64 = 2)。
using System.Runtime.Intrinsics;
// 创建一个 128 位的向量,存储 16 个 8 位的 字节
Vector128<byte> vectorByte = Vector128.Create((byte)1, (byte)2, (byte)3, (byte)4,
(byte)5, (byte)6, (byte)7, (byte)8,
(byte)9, (byte)10, (byte)11, (byte)12,
(byte)13, (byte)14, (byte)15, (byte)16);
// 创建一个 128 位的向量,存储 4 个 32 位的 浮点数
Vector128<float> vectorFloat = Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f);
// 创建一个 256 位的向量,存储 8 个 32 位的 浮点数
Vector256<float> vector256Float = Vector256.Create(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f);
// 创建一个 128 位的向量,存储 2 个 64 位的 双精度浮点数
Vector128<double> vectorDouble = Vector128.Create(1.0, 2.0);
// 创建一个 256 位的向量,存储 4 个 64 位的 双精度浮点数
Vector256<double> vector256Double = Vector256.Create(1.0, 2.0, 3.0, 4.0);
跨平台实现方式
.NET 的 SIMD 提供了跨平台的实现方式。无论是在 x86/x64 还是 ARM 架构上,.NET 都会根据运行时环境自动选择合适的 SIMD 指令集来执行向量化操作。
我们可以使用 VectorXXX.IsHardwareAccelerated 属性来检查当前平台是否支持特定大小的向量操作。例如:
Console.WriteLine(Vector128.IsHardwareAccelerated ? "128 位向量操作受支持" : "128 位向量操作不受支持");
但即使硬件不支持 SIMD,.NET 仍然会回退到非 SIMD 的实现方式,确保代码的兼容性。
VectorXXX 为我们提供了一组静态方法,用于创建和操作向量。例如,Vector128.Add 方法用于对两个 128 位向量执行加法运算。
我们也可以直接使用运算符号来进行向量运算,例如 +、-、*、/ 等。VectorXXX<T> 结构体重载了这些运算符,使得向量运算更加直观和简洁。
下面这个例子使用 Vector128<float> 来进行浮点数的 SIMD 运算:
using System.Runtime.Intrinsics;
// 创建两个 128 位的浮点向量
Vector128<float> vectorA = Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f);
Vector128<float> vectorB = Vector128.Create(5.0f, 6.0f, 7.0f, 8.0f);
// 执行加法运算
// 等效于 vectorA + vectorB
var result = Vector128.Add(vectorA, vectorB);
// 输出结果
Console.WriteLine($"Result: {result}");
Result: <6, 8, 10, 12>
SIMD 指令集的使用
在使用 SIMD 指令集之前,通常需要检查当前平台是否支持特定的指令集。可以通过调用指令集类的 IsSupported 属性来进行检查。例如:
using System.Runtime.Intrinsics.X86;
Console.WriteLine(Sse.IsSupported ? "SSE 指令集受支持" : "SSE 指令集不受支持");
一旦确认指令集受支持,就可以使用该指令集类提供的静态方法来执行 SIMD 操作。例如,使用 Sse 类的 Add 方法来对两个 128 位向量执行加法运算:
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
if (Sse.IsSupported)
{
// 创建两个 128 位的浮点向量
Vector128<float> vectorA = Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f);
Vector128<float> vectorB = Vector128.Create(5.0f, 6.0f, 7.0f, 8.0f);
// 使用 SSE 指令集执行加法运算
var result = Sse.Add(vectorA, vectorB);
// 输出结果
Console.WriteLine($"Result: {result}");
}
else
{
Console.WriteLine("SSE 指令集不受支持");
}
Result: <6, 8, 10, 12>
如果是在 ARM 架构上,可以使用 AdvSimd 类来执行类似的操作:
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;
if (AdvSimd.IsSupported)
{
// 创建两个 128 位的浮点向量
Vector128<float> vectorA = Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f);
Vector128<float> vectorB = Vector128.Create(5.0f, 6.0f, 7.0f, 8.0f);
// 使用 AdvSimd 指令集执行加法运算
var result = AdvSimd.Add(vectorA, vectorB);
// 输出结果
Console.WriteLine($"Result: {result}");
}
else
{
Console.WriteLine("AdvSimd 指令集不受支持");
}
Result: <6, 8, 10, 12>
System.Numerics 命名空间中的 SIMD 支持
基于 System.Runtime.Intrinsics,.NET 还提供了别的更高级别的 SIMD 支持。
比如在 System.Numerics 这个命名空间提供了一些易于使用的类型,如 Vector\<T> 和 Matrix4x4,简化了 SIMD 编程。
Vector<T> 结构体
Vector<T> 是一个通用的向量类型,支持多种数值类型(如 int、float、double 等)。它会根据硬件能力自动选择最佳的向量大小(如 128 位或 256 位),从而实现跨平台的 SIMD 优化。
下面是一个使用 Vector<T> 进行数组加法的示例:
var vectorSize = Vector<float>.Count; // 获取向量大小(元素个数)
float[] arrayA = new float[1000];
float[] arrayB = new float[1000];
float[] resultArray = new float[1000];
for (int i = 0; i <= arrayA.Length - vectorSize; i += vectorSize)
{
var va = new Vector<float>(arrayA, i);
var vb = new Vector<float>(arrayB, i);
(va + vb).CopyTo(resultArray, i);
}
Vector2、Vector3 和 Vector4 结构体
System.Numerics 命名空间还提供了 Vector2、Vector3 和 Vector4 结构体,分别表示二维、三维和四维向量,常用于图形和物理计算中。
using System.Numerics;
// 创建 Vector3 实例
Vector3 vector1 = new Vector3(1.0f, 2.0f, 3.0f);
Vector3 vector2 = new Vector3(4.0f, 5.0f, 6.0f);
// 向量加法
Vector3 resultAdd = Vector3.Add(vector1, vector2);
Console.WriteLine($"Addition: {resultAdd}"); // 输出: <5, 7, 9>
// 向量点乘
float dotProduct = Vector3.Dot(vector1, vector2);
Console.WriteLine($"Dot Product: {dotProduct}"); // 输出: 32
// 向量归一化
Vector3 normalized = Vector3.Normalize(vector1);
Console.WriteLine($"Normalized: {normalized}"); // 输出: <0.2672612, 0.5345225, 0.8017837>
如果我们去看 Vector3 结构体的源码实现,会发现它内部使用了 SIMD 技术来优化向量运算:
public struct Vector3 : IEquatable<Vector3>, IFormattable
{
/// <summary>The X component of the vector.</summary>
public float X;
/// <summary>The Y component of the vector.</summary>
public float Y;
/// <summary>The Z component of the vector.</summary>
public float Z;
public Vector3(float x, float y, float z) => this = Vector3.Create(x, y, z);
public static Vector3 Create(float x, float y, float z)
{
return Vector128.Create(x, y, z, 0.0f).AsVector3();
}
// 省略其他成员...
}
Matrix2x2、Matrix3x2 和 Matrix4x4 结构体
System.Numerics 还提供了 Matrix2x2、Matrix3x2 和 Matrix4x4 结构体,用于表示二维和三维空间中的矩阵,常用于变换和投影计算。
using System.Numerics;
// 创建一个 4x4 矩阵
Matrix4x4 matrix = Matrix4x4.CreateRotationX((float)(Math.PI / 4));
Vector3 point = new Vector3(1.0f, 0.0f, 0.0f);
// 使用矩阵变换点
Vector3 transformedPoint = Vector3.Transform(point, matrix);
Console.WriteLine($"Transformed Point: {transformedPoint}");
Transformed Point: <1, 0, 0>
其他 SIMD 的使用场景举例举例
字母大小写转换
在 ASCII 码表的设计中,大写字母和小写字母之间的差异仅在于第 6 位(从右往左数)。大写字母的第 6 位为 0,而小写字母的第 6 位为 1。因此,我们可以通过对字符的二进制表示进行按位操作来实现大小写转换。
示例:字母 A B C D 的 ASCII 编码对照
| 字符 | ASCII 十进制 | ASCII 十六进制 | 二进制表示 |
|---|---|---|---|
| A | 65 | 0x41 | 01000001 |
| B | 66 | 0x42 | 01000010 |
| C | 67 | 0x43 | 01000011 |
| D | 68 | 0x44 | 01000100 |
| a | 97 | 0x61 | 01100001 |
| b | 98 | 0x62 | 01100010 |
| c | 99 | 0x63 | 01100011 |
| d | 100 | 0x64 | 01100100 |
可以看到,对应的大写和小写之间,二进制的第 6 位(从右数,值为 32,即 0x20)状态不同:
- 大写:第 6 位为 0
- 小写:第 6 位为 1
这样,只需用 按位或(OR) 大写字母加上 0x20,就能得到对应的小写字母;
用 按位与(AND) 去掉 0x20,可以从小写变成大写。
在 System.Text 命名空间中,.NET 提供了 Ascii 类,里面包含了一些用于 ASCII 字符处理的静态方法。我们可以利用 SIMD 技术来实现高效的大小写转换。
public static class Ascii
{
public static OperationStatus ToUpper(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten);
public static OperationStatus ToLower(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten);
public static OperationStatus ToUpperInPlace(Span<byte> value, out int bytesWritten);
public static OperationStatus ToLowerInPlace(Span<byte> value, out int bytesWritten);
public static OperationStatus ToUpper(ReadOnlySpan<char> source, Span<char> destination, out int charsWritten);
public static OperationStatus ToLower(ReadOnlySpan<char> source, Span<char> destination, out int charsWritten);
public static OperationStatus ToUpperInPlace(Span<char> value, out int charsWritten);
public static OperationStatus ToLowerInPlace(Span<char> value, out int charsWritten);
}
public enum OperationStatus
{
/// <summary>The entire input buffer has been processed and the operation is complete.</summary>
Done,
/// <summary>The input is partially processed, up to what could fit into the destination buffer. The caller can enlarge the destination buffer, slice the buffers appropriately, and retry.</summary>
DestinationTooSmall,
/// <summary>The input is partially processed, up to the last valid chunk of the input that could be consumed. The caller can stitch the remaining unprocessed input with more data, slice the buffers appropriately, and retry.</summary>
NeedMoreData,
/// <summary>The input contained invalid bytes which could not be processed. If the input is partially processed, the destination contains the partial result. This guarantees that no additional data appended to the input will make the invalid sequence valid.</summary>
InvalidData,
}
参数的重载有 byte 和 char 两种,分别用于处理保存为 byte 和 char 类型的 ASCII 字符数据。
如果是从 IO 流中读取数据进行大小写转换,可以使用 Span<byte> 版本;如果是处理 char 数组或字符串,则使用 Span<char> 版本。
ToUpper 和 ToLower 方法会将源数据转换为目标数据,并返回一个 OperationStatus 枚举值,指示操作的状态。
ToUpperInPlace 和 ToLowerInPlace 方法则会直接在原始数据上进行大小写转换。
需要注意的是,这些方法仅处理 ASCII 范围内的字符(0-127),对于非 ASCII 字符不会进行任何转换。
这些方法并不能替代 string.ToUpper 等方法来获取 string 的大写或小写形式。需考虑 string 和 Span<byte>, Span<char> 之间的转换开销,最终性能并不一定优于直接使用 string.ToUpper 等方法。
可以参考微软的开源项目 Garnet 中的使用场景 AsciiUtils.cs
Ascii.ToUpperInPlace(Span<char> value, out int charsWritten) 的核心实现经整理后大致如下:
void ToUpperInPlace(Span<char> value)
{
// 将 Span<char> 转换成 Span<ushort>(char 占 2 字节,方便 SIMD 处理)
var buffer = MemoryMarshal.Cast<char, ushort>(value);
// 获取元素数量(ushort 数量)
var elementCount = (uint)buffer.Length;
// 每个向量能处理多少个 ushort
var numElementsPerVector = (uint)(Unsafe.SizeOf<Vector128<byte>>() / sizeof(ushort));
// 如果支持 SIMD 且数据足够,否则走普通循环
if (Vector128.IsHardwareAccelerated && elementCount >= numElementsPerVector)
{
// 有符号最小值 (0x8000) 用于偏移正确比较
ushort sourceSignedMinValue = (ushort)(1 << (8 * sizeof(ushort) - 1));
// 'a' 的基准向量(所有元素都是 0x8000 + 'a')
var subtractionVector = Vector128.Create((ushort)(sourceSignedMinValue + 'a'));
// 26 个字母范围偏移向量(所有元素都是 0x8000 + 26)
var comparisonVector = Vector128.Create((ushort)(sourceSignedMinValue + 26));
// 大小写差值向量(0x20,所有元素都是这个值)
var caseConversionVector = Vector128.Create((ushort)0x20);
// 向量化循环索引
uint i = 0;
// 可以整除的元素个数
uint n = elementCount - (elementCount % numElementsPerVector);
// 向量化批量处理
for (; i < n; i += numElementsPerVector)
{
// 加载当前批次的向量数据
var srcVector = Vector128.LoadUnsafe(ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), (int)i));
// 计算 src - 'a' 基准,并判断是否小于 26(即在 a..z 范围)
var matches = SignedLessThan(srcVector - subtractionVector, comparisonVector);
// 对匹配的小写字母执行大小写转换(异或 0x20 得到大写)
srcVector ^= matches & caseConversionVector;
// 存回修改后的向量数据
srcVector.StoreUnsafe(ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), (int)i));
}
// 处理剩余不足一个向量大小的元素
for (; i < elementCount; i++)
{
// 读取当前字符
ushort c = Unsafe.Add(ref MemoryMarshal.GetReference(buffer), (int)i);
// 如果是小写字母则转为大写
if (c is >= 'a' and <= 'z')
{
c = (ushort)(c - 0x20);
}
// 写回结果
Unsafe.Add(ref MemoryMarshal.GetReference(buffer), (int)i) = c;
}
}
else
{
// 非向量化处理,每个元素单独判断
for (int i = 0; i < buffer.Length; i++)
{
ushort c = buffer[i];
if (c is >= 'a' and <= 'z')
{
c = (ushort)(c - 0x20);
}
buffer[i] = c;
}
}
}
// 有符号比较(用于判断 a..z 范围)
Vector128<ushort> SignedLessThan(Vector128<ushort> left, Vector128<ushort> right)
{
// 将 ushort 当成 short 做有符号比较,然后再转换回 ushort 掩码
return Vector128.LessThan(left.AsInt16(), right.AsInt16()).AsInt16().AsUInt16();
}
调用上述方法可以高效地将 ASCII 字符串转换为大写形式:
string input = "Hello World! This is a Test String.";
Span<char> span = input.ToCharArray();
ToUpperInPlace(span);
string result = new string(span);
Console.WriteLine(result); // 输出: "HELLO WORLD! THIS IS A TEST STRING."
实际的 Ascii 类实现要复杂得多,包含了更多的边界检查和错误处理逻辑,上述代码仅仅是为了说明核心的 SIMD 思路的简化版本。
使用 Ascii.UpperInPlace 的示例:
using System.Text;
string input = "Hello World! This is a Test String. 这部分不会被转换。";
Span<char> span = input.ToCharArray();
Ascii.ToUpperInPlace(span, out int charsWritten);
// charsWritten 表示实际转换的字符数,非 ASCII 字符不会被转换
string result = new string(span[..charsWritten]);
Console.WriteLine(result); // 输出: "HELLO WORLD! THIS IS A TEST STRING."
二进制/位操作
SIMD 技术非常适合处理大量的二进制数据或位操作。例如,BinaryPrimitives.ReverseEndianness 方法利用 SIMD 来高效地反转字节序:
Span<ushort> data = [0x1234, 0xABCD, 0x5678, 0xEF01];
Span<ushort> reversedData = stackalloc ushort[4];
BinaryPrimitives.ReverseEndianness(data, reversedData);
foreach (var value in reversedData)
{
Console.WriteLine(value.ToString("X4"));
}
3412
CDAB
7856
01EF
总结
SIMD 技术在 .NET 中提供了强大的并行计算能力,能够显著提升处理大量数据时的性能。通过 System.Runtime.Intrinsics 和 System.Numerics,我们可以方便地利用 SIMD 指令集进行高效的向量化运算。
很多基础库已经内置了 SIMD 优化,开发者在日常编程中可以通过这些库间接受益于 SIMD 技术,而无需深入了解底层实现细节。当然,对于性能敏感的应用场景,理解和直接使用 SIMD 指令集仍然是非常有价值的。
如何在 .NET 中使用 SIMD的更多相关文章
- 我是如何在SQLServer中处理每天四亿三千万记录的
首先声明,我只是个程序员,不是专业的DBA,以下这篇文章是从一个问题的解决过程去写的,而不是一开始就给大家一个正确的结果,如果文中有不对的地方,请各位数据库大牛给予指正,以便我能够更好的处理此次业务. ...
- 如何在SpringBoot中使用JSP ?但强烈不推荐,果断改Themeleaf吧
做WEB项目,一定都用过JSP这个大牌.Spring MVC里面也可以很方便的将JSP与一个View关联起来,使用还是非常方便的.当你从一个传统的Spring MVC项目转入一个Spring Boot ...
- 如何在latex 中插入EPS格式图片
如何在latex 中插入EPS格式图片 第一步:生成.eps格式的图片 1.利用visio画图,另存为pdf格式的图片 利用Adobe Acrobat裁边,使图片大小合适 另存为.eps格式,如下图所 ...
- 如何正确的使用json?如何在.Net中使用json?
什么是json json是一种轻量级的数据交换格式,由N组键值对组成的字符串,完全独立于语言的文本格式. 为什么要使用json 在很久很久以前,调用第三方API时,我们通常是采用xml进行数据交互,但 ...
- [原创]如何在Parcelable中使用泛型
[原创]如何在Parcelable中使用泛型 实体类在实现Parcelable接口时,除了要实现它的几个方法之外,还另外要定义一个静态常量CREATOR,如下例所示: public static cl ...
- 如何在springMVC 中对REST服务使用mockmvc 做测试
如何在springMVC 中对REST服务使用mockmvc 做测试 博客分类: java 基础 springMVCmockMVC单元测试 spring 集成测试中对mock 的集成实在是太棒了!但 ...
- 如何在tomcat中如何部署java EE项目
如何在tomcat中如何部署java EE项目 1.直接把项目复制到Tomcat安装目录的webapps目录中,这是最简单的一种Tomcat项目部署的方法,也是初学者最常用的方法.2.在tomcat安 ...
- 【转】我是如何在SQLServer中处理每天四亿三千万记录的
原文转自:http://blog.jobbole.com/80395/ 首先声明,我只是个程序员,不是专业的DBA,以下这篇文章是从一个问题的解决过程去写的,而不是一开始就给大家一个正确的结果,如果文 ...
- 如何在JAVA中实现一个固定最大size的hashMap
如何在JAVA中实现一个固定最大size的hashMap 利用LinkedHashMap的removeEldestEntry方法,重载此方法使得这个map可以增长到最大size,之后每插入一条新的记录 ...
- 如何在HTML中加载Flash(2种实现方法)_HTML/Xhtml_网页制作
点评:如何在HTML中加载Flash,为网页添加更多的色彩,普通的网页以无法满足用户的需求,接下来为大家介绍下2种在HTML中加载Flash的方法,感兴趣的各位可以适当参考下,希望对你有所帮助 第一种 ...
随机推荐
- lens 添加 k8s 集群
官方下载地址:https://k8slens.dev/github:https://github.com/lensapp/lens 1.找到k8s中的config文件文件路径 ~/.kube/conf ...
- RTSP、GB28181监控摄像头实现WEB、微信直播的服务
引言 全民直播时代已经到来.现在的直播必然需要支持手机直播,如果能够嵌入到全面宠儿微信中,那就能深度民心了.从技术上而言,微信直播,自然需要使用H5页面.目前手机浏览器不支持RTSP.RTMP,想要在 ...
- Python 中的 __init__.py
本文参考 python跨文件夹调用别的文件夹下py文件或参数方式详解 第一章 运行另一个py文件 (1)在file_A.py中运行file_B.py文件,注意这里是运行,不是引用 1 2 3 4 ...
- 案例分享,由于研发的代码逻辑问题,导致redis内存急速飙升
群582337768分享的一个实际案例 由于研发的逻辑处理不当,导致内存急速飙升. ps: 研发排查了N天,没查出来,群友三下五除二就搞定了.你确定不加一下吗 研发[想法]的代码逻辑-客户端请求: 请 ...
- tabBar选择不同item设置标题不同颜色
转载请注明出处!!! 在开发中常常遇到带tabBar的应用,这些应用一般都是只有一个选中颜色,但在开发中也会遇到选择不同item选中颜色不一致的情况,这时要如何解决呢?方法一:不设置item titl ...
- Python selenium webdriver
元素定位 第一步,导入selenium模块的webdrivier包 import time from selenium import webdriver from selenium.webdriver ...
- ISSCC2024 Computing-In-Memory Session 趋势整理
ISSCC2024 Computing-In-Memory Session 趋势整理 今天上午ISSCC2024远东区推介会,主要关注了一下Computing-In-Memory Session.CI ...
- 对Python序列切片操作的理解之二
序列切片操作缺省值理解 s[i] 索引,返回序列s索引为i的元素.(1)i是0基序号(正序)或负数序号(逆序),开头索引为0(或者 -len(s) ).末尾索引为 len(s) - 1(或者 -1) ...
- VMware报错 无法创建新虚拟机: 无法打开配置文件“D:\vmware\workspace\unbuntu 20.04.6\Ubun
VMware报错 无法创建新虚拟机: 无法打开配置文件"D:\vmware\workspace\unbuntu 20.04.6\Ubuntu 解决:右击VMware管理员方式打开
- vc入门宝典四(常用控件)
常用控件 何志丹 主要内容: 1, 按钮 (1),位图按钮及动态按钮 (2),自画按钮 2,列表框 3,编辑控件和CRichEdit 4,CSliderCtrl 按钮的使用十分简单,拖一个到对话框, ...