C# Byte 数组与字符串互转:实用方法详解
在 C# 编程中,我们经常需要在 byte
数组(byte[]
)和字符串(string
)之间进行转换。这种转换在处理文件、网络流、加密解密、编码转换等场景中尤为常见。本文将深入探讨 C# 中 byte
数组与字符串互转的各种实用方法,并详细分析每种方法的原理、适用场景和注意事项。
1. 理解 Byte 数组和字符串
在深入探讨转换方法之前,我们需要先理解 byte
数组和字符串在 C# 中的本质。
1.1 Byte 数组 (byte[])
- 定义:
byte
数组是 C# 中用于存储一系列无符号 8 位整数(0-255)的集合。每个byte
代表一个字节,是计算机存储数据的基本单位。 - 用途:
byte
数组通常用于表示二进制数据,例如:- 文件的原始内容
- 网络传输的数据包
- 图像、音频等媒体数据
- 加密后的密文
1.2 字符串 (string)
- 定义:
string
类型在 C# 中表示一个不可变的 Unicode 字符序列。 - 内部表示: 字符串在内部使用 UTF-16 编码存储,每个字符占用 2 个字节(或 4 个字节,对于某些罕见字符)。
- 用途: 字符串主要用于表示文本数据。
1.3 编码的概念
在 byte
数组和字符串之间转换时,编码是一个至关重要的概念。编码是指定如何将字符序列表示为字节序列的规则集。常见的编码包括:
- ASCII: 使用 7 位表示 128 个字符(主要包括英文字母、数字和标点符号)。
- UTF-8: 一种可变长度编码,使用 1 到 4 个字节表示 Unicode 字符。它是互联网上最常用的编码。
- UTF-16: 使用 2 个字节(或 4 个字节)表示 Unicode 字符。C# 字符串内部使用 UTF-16 编码。
- UTF-32: 使用 4 个字节表示 Unicode 字符。
- GB2312/GBK/GB18030: 中文编码标准,用于表示简体中文字符。
不同的编码方式对同一个字符可能会有不同的字节表示。因此,在进行 byte
数组和字符串转换时,必须明确指定使用的编码,否则可能会导致乱码或数据丢失。
2. 字符串转换为 Byte 数组
将字符串转换为 byte
数组的过程,实际上是将字符串中的每个字符按照指定的编码转换为对应的字节序列。
2.1 使用 Encoding 类
C# 的 System.Text
命名空间提供了 Encoding
类,它包含了各种编码的实现。Encoding
类提供了 GetBytes()
方法,可以将字符串转换为指定编码的 byte
数组。
“`csharp
using System.Text;
public static byte[] StringToBytes(string str, Encoding encoding)
{
return encoding.GetBytes(str);
}
// 使用示例
string str = “Hello, 世界!”;
// 使用 UTF-8 编码
byte[] utf8Bytes = StringToBytes(str, Encoding.UTF8);
// 使用 ASCII 编码 (注意:ASCII 无法正确处理非 ASCII 字符)
byte[] asciiBytes = StringToBytes(str, Encoding.ASCII);
// 使用 UTF-16 编码
byte[] utf16Bytes = StringToBytes(str, Encoding.Unicode); // 或 Encoding.BigEndianUnicode
// 使用 GB2312 编码
byte[] gb2312Bytes = StringToBytes(str, Encoding.GetEncoding(“GB2312”));
“`
说明:
Encoding.UTF8
、Encoding.ASCII
、Encoding.Unicode
等是Encoding
类提供的静态属性,分别代表 UTF-8、ASCII、UTF-16(小端序)等常用编码。Encoding.GetEncoding("GB2312")
可以通过编码名称获取对应的Encoding
对象。- 如果使用 ASCII 编码转换包含非 ASCII 字符的字符串,非 ASCII 字符会被替换为 ‘?’ (ASCII 码为 63)。
2.2 使用 Convert.FromBase64String()
如果字符串是 Base64 编码的,可以使用 Convert.FromBase64String()
方法直接将其转换为 byte
数组。
“`csharp
using System;
public static byte[] Base64StringToBytes(string base64String)
{
return Convert.FromBase64String(base64String);
}
// 使用示例
string base64String = “SGVsbG8sIOS9oOWlveS4lueVjA==”; // “Hello, 世界!” 的 Base64 编码
byte[] bytes = Base64StringToBytes(base64String);
“`
说明: Base64 是一种将二进制数据编码为 ASCII 字符串的编码方式,常用于在文本协议中传输二进制数据。
2.3.手动转换 (不推荐,仅用于理解原理)
“`C#
public static byte[] StringToBytesManual(string str)
{
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
``
sizeof(char)
**说明:**
这种方法利用了.NET字符串在内存中以UTF-16格式存储的事实。在C#中总是2(表示UTF-16中每个字符的字节数)。
Buffer.BlockCopy`是一个高效的内存复制方法。
注意:这种方法假设字符串只包含可以用两个字节表示的UTF-16字符。对于包含代理项对(surrogate pairs)的字符串(即占用4个字节的字符),这种方法会产生不正确的结果.
3. Byte 数组转换为字符串
将 byte
数组转换为字符串的过程,实际上是根据指定的编码将字节序列还原为对应的字符序列。
3.1 使用 Encoding 类
与字符串转换为 byte
数组类似,Encoding
类也提供了 GetString()
方法,可以将 byte
数组转换为指定编码的字符串。
“`csharp
using System.Text;
public static string BytesToString(byte[] bytes, Encoding encoding)
{
return encoding.GetString(bytes);
}
// 使用示例
byte[] utf8Bytes = { 72, 101, 108, 108, 111, 44, 32, 228, 184, 150, 231, 149, 140, 33 }; // “Hello, 世界!” 的 UTF-8 编码
// 使用 UTF-8 编码
string strUtf8 = BytesToString(utf8Bytes, Encoding.UTF8);
// 使用 ASCII 编码 (结果会是乱码,因为 UTF-8 字节序列不符合 ASCII 编码规则)
string strAscii = BytesToString(utf8Bytes, Encoding.ASCII);
// 使用 UTF-16 编码 (结果也会是乱码)
string strUtf16 = BytesToString(utf8Bytes, Encoding.Unicode);
“`
说明:
- 必须使用与
byte
数组原始编码相同的编码来转换,否则会导致乱码。 GetString()
方法有多个重载,可以指定要转换的字节数组的起始索引和长度。例如:encoding.GetString(bytes, startIndex, count);
3.2 使用 Convert.ToBase64String()
如果需要将 byte
数组转换为 Base64 编码的字符串,可以使用 Convert.ToBase64String()
方法。
“`csharp
using System;
public static string BytesToBase64String(byte[] bytes)
{
return Convert.ToBase64String(bytes);
}
// 使用示例
byte[] bytes = { 72, 101, 108, 108, 111, 44, 32, 228, 184, 150, 231, 149, 140, 33 }; // “Hello, 世界!” 的 UTF-8 编码
string base64String = BytesToBase64String(bytes); // 结果为 “SGVsbG8sIOS9oOWlveS4lueVjA==”
“`
3.3.使用 BitConverter 类 (用于数值类型)
BitConverter
类可以将 byte
数组转换为各种基本数值类型(如 int
、float
、double
等),也可以将这些数值类型转换为 byte
数组。虽然这并不是严格意义上的字符串转换,但在某些场景下可能会用到。
“`csharp
using System;
using System.Text;
public class Example
{
public static string GetString(byte[] bytes)
{
char[] chars = new char[bytes.Length / sizeof(char)];
System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
return new string(chars);
}
}
// 使用示例
int intValue = 12345;
byte[] intBytes = BitConverter.GetBytes(intValue); // 将 int 转换为 byte 数组
int convertedInt = BitConverter.ToInt32(intBytes, 0); // 将 byte 数组转换回 int
double doubleValue = 3.14159;
byte[] doubleBytes = BitConverter.GetBytes(doubleValue); // 将 double 转换为 byte 数组
double convertedDouble = BitConverter.ToDouble(doubleBytes, 0); // 将 byte 数组转换回 double
“`
注意:BitConverter
转换的字节顺序取决于系统的字节序(endianness)。可以使用 BitConverter.IsLittleEndian
属性来判断当前系统的字节序。
3.4.手动转换
C#
public static string BytesToStringManual(byte[] bytes)
{
char[] chars = new char[bytes.Length / sizeof(char)];
System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
return new string(chars);
}
与字符串到字节数组的手动转换相对应,此方法依赖于UTF-16编码和Buffer.BlockCopy
。同样,它不处理代理项对。
4. 常见场景与最佳实践
4.1 文件读写
在读写文本文件时,需要根据文件的编码进行 byte
数组和字符串之间的转换。
“`csharp
using System.IO;
using System.Text;
// 写入文件 (使用 UTF-8 编码)
string text = “Hello, 世界!”;
File.WriteAllText(“output.txt”, text, Encoding.UTF8);
// 读取文件 (使用 UTF-8 编码)
string readText = File.ReadAllText(“output.txt”, Encoding.UTF8);
// 也可以使用 StreamReader/StreamWriter,它们可以自动处理编码
using (StreamWriter writer = new StreamWriter(“output.txt”, false, Encoding.UTF8))
{
writer.WriteLine(text);
}
using (StreamReader reader = new StreamReader(“output.txt”, Encoding.UTF8))
{
string line = reader.ReadLine();
}
“`
最佳实践:
始终明确指定文件的编码,建议优先使用UTF-8编码。
4.2 网络通信
在网络通信中,数据通常以字节流的形式传输,需要进行byte
数组和字符串之间的转换。
“`C#
using System.Net.Sockets;
using System.Text;
//...
public void SendAndReceive(string server, string message)
{
//连接到服务器
TcpClient client = new TcpClient(server, 13);
//将消息转换为字节数组
byte[] data = Encoding.ASCII.GetBytes(message);
NetworkStream stream = client.GetStream();
//向服务器发送消息
stream.Write(data, 0, data.Length);
Console.WriteLine("发送: {0}", message);
//接收来自服务器的响应,最多256字节
data = new byte[256];
string responseData = string.Empty;
int bytes = stream.Read(data, 0, data.Length);
responseData = Encoding.ASCII.GetString(data, 0, bytes);
Console.WriteLine("接收: {0}", responseData);
//关闭连接
stream.Close();
client.Close();
}
“`
4.3 加密解密
加密算法通常操作的是 byte
数组,因此在加密前需要将字符串转换为 byte
数组,解密后需要将 byte
数组转换回字符串。
“`C#
using System;
using System.Security.Cryptography;
using System.Text;
using System.IO;
//…
public class Example
{
public static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
{
//检查参数
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException(“plainText”);
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException(“Key”);
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException(“IV”);
byte[] encrypted;
//创建Aes对象
//使用指定的密钥Key和初始化向量IV
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
//创建一个加密器来执行流转换
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
//创建一个用于加密的流
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//将所有数据写入流中
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
//返回来自内存流的加密字节
return encrypted;
}
public static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
{
//检查参数
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("IV");
//声明一个字符串来保存
//解密文本
string plaintext = null;
//创建一个Aes对象
//使用指定的密钥Key和初始化向量IV
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
//创建一个解密器来执行流转换
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
//创建一个用于解密的流
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
//从解密流中读取解密字节
//并将它们放在字符串中
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
}
“`
4.4 数据库操作
在与数据库交互时,如果数据库字段存储的是二进制数据(如 VARBINARY
、BLOB
等),则需要进行 byte
数组和字符串之间的转换。具体的转换方式取决于数据库驱动程序和数据类型。
4.5 最佳实践总结
- 明确编码: 在进行
byte
数组和字符串转换时,始终明确指定编码,避免使用默认编码(Encoding.Default
),因为它可能因系统而异。 - 优先使用 UTF-8: UTF-8 是互联网上最常用的编码,具有良好的兼容性和广泛的支持,建议优先使用。
- 处理异常: 在转换过程中可能会出现异常,例如
ArgumentException
(无效的 Base64 字符串)或DecoderFallbackException
(无法解码的字节序列),应妥善处理这些异常。 - 避免不必要的转换: 如果数据已经是
byte
数组形式,并且不需要以文本形式处理,则应避免将其转换为字符串,以减少开销和潜在的错误。 - 当处理Unicode字符时,倾向于使用
Encoding
类的方法:Encoding
类提供了对不同编码的良好支持,并能正确处理代理项对。如果你不确定或预期字符串可能包含来自多个代码页的字符,使用Encoding
类是更安全和推荐的方法。 - 对于简单的ASCII字符串或当你确定字符串不包含任何扩展的Unicode字符时,手动方法可以作为一种优化手段:如果你对性能有严格的要求,并且确定你的字符串只包含标准的ASCII字符(0-127),那么手动方法可能稍微快一些,因为它避免了
Encoding
类的一些内部检查。
5. 总结
byte
数组和字符串之间的转换是 C# 编程中常见的任务。本文详细介绍了使用 Encoding
类、Convert
类和 BitConverter
类进行转换的各种方法,并分析了每种方法的原理、适用场景和注意事项。通过理解这些方法并遵循最佳实践,我们可以有效地处理各种涉及 byte
数组和字符串转换的场景,编写出更健壮、更可靠的代码。