0

0

利用NumPy frombuffer 高效转换字节序列为 uint8 数组

霞舞

霞舞

发布时间:2025-10-30 08:56:07

|

1017人浏览过

|

来源于php中文网

原创

利用NumPy frombuffer 高效转换字节序列为 uint8 数组

本文详细介绍了如何高效地将包含大量字节序列元组的python列表转换为numpy的`uint8`多维数组。针对千万级别的数据量,传统迭代方法性能瓶颈明显。教程重点演示了如何结合使用`np.array`和`np.frombuffer`,将原始字节数据快速转换为目标形状的数值数组,从而实现高性能数据处理,避免了python层面的显式循环,大幅提升了处理速度。

引言:大规模字节数据转换的挑战

在数据处理任务中,我们经常会遇到需要将原始字节数据转换为数值型数组的情况。特别是当数据量达到千万级别,且每个数据点包含多个固定长度的字节序列时,如何高效地完成这一转换成为一个关键问题。例如,一个典型的场景是:存在一个包含数百万个元组的列表,每个元组又包含多个固定长度(如450字节)的字节序列(bytes类型)。我们的目标是将其转换为一个形如 (N, M, L) 的 numpy.uint8 数组,其中 N 是元组的数量,M 是每个元组中字节序列的数量,L 是每个字节序列的长度,且数组中的每个 uint8 元素对应原始字节序列中的一个字节值。

传统上,使用Python的 for 循环或 numpy.fromiter 结合 np.frompyfunc 进行逐个转换,对于小规模数据尚可接受,但面对千万级别的数据量时,其性能会迅速下降,导致处理时间过长。因此,寻找一种能够充分利用NumPy底层优化、避免Python循环的解决方案至关重要。

解决方案:利用 numpy.frombuffer 进行高效转换

NumPy库提供了一个名为 np.frombuffer 的函数,它能够将一个支持缓冲区协议的对象(如 bytes 或 bytearray)直接解释为一个新的NumPy数组,而无需进行数据拷贝。这是实现高效字节数据转换的关键。其核心思想是:将所有字节序列扁平化为一个连续的字节流,然后 np.frombuffer 可以直接将这个字节流解析为 uint8 数组,最后通过 reshape 操作恢复到期望的多维结构。

实现步骤与示例

以下是将一个由字节序列元组组成的列表转换为目标 numpy.uint8 数组的具体步骤和示例代码:

阿里云AI平台
阿里云AI平台

阿里云AI平台

下载
  1. 准备原始数据:假设我们有一个列表 data,其中包含多个元组,每个元组又包含固定数量和长度的字节序列。

    import numpy as np
    
    # 模拟原始数据,实际数据量可能达到千万级别
    data = [
        (
            b'\n\x0f\n\t\x0c\x00\x00\x01\x07\x06', # 10 bytes series for example
            b'\x00\x0e\x00\x06\x07\x0c\n\x0e\x07',
            b'\x05\x0e\x07\t\x04\x01\x05\x07\x08',
        ),
        (
            b'\x0a\x0f\x0a\x09\x0c\x00\x00\x01\x07\x06',
            b'\x00\x0e\x00\x06\x07\x0c\x0a\x0e\x07',
            b'\x05\x0e\x07\x09\x04\x01\x05\x07\x08',
        ),
    ]
    # 假设每个字节序列长度为 L (这里是10)
    # 假设每个元组包含 M 个字节序列 (这里是3)
    # 假设列表包含 N 个元组 (这里是2)
    N = len(data)
    M = len(data[0])
    L = len(data[0][0])
  2. 将数据扁平化为NumPy字节字符串数组: 首先,我们需要将 data 列表转换为一个NumPy数组。关键在于指定 dtype=np.bytes_。这将创建一个对象数组,其中每个元素是一个字节字符串。虽然这本身仍是一个Python对象数组,但它为下一步使用 frombuffer 提供了基础。reshape(-1) 操作将其扁平化为一维数组,方便后续处理。

    data_flat_bytes_array = np.array(data, dtype=np.bytes_).reshape(-1)
    # 此时 data_flat_bytes_array 看起来是这样的:
    # array([b'\n\x0f\n\t\x0c\x00\x00\x01\x07\x06',
    #        b'\x00\x0e\x00\x06\x07\x0c\n\x0e\x07',
    #        b'\x05\x0e\x07\t\x04\x01\x05\x07\x08',
    #        b'\x0a\x0f\x0a\x09\x0c\x00\x00\x01\x07\x06',
    #        b'\x00\x0e\x00\x06\x07\x0c\x0a\x0e\x07',
    #        b'\x05\x0e\x07\t\x04\x01\x05\x07\x08'], dtype='|S10')
    # 注意 dtype='|S10' 表示这是一个固定长度为10的字节字符串数组。
  3. 使用 frombuffer 解析字节数据: 现在,我们可以对 data_flat_bytes_array 使用 np.frombuffer。np.frombuffer 期望一个字节缓冲区作为输入。尽管 data_flat_bytes_array 是一个NumPy数组,但其内部的每个 np.bytes_ 元素都支持缓冲区协议。更重要的是,NumPy在处理 dtype=np.bytes_ 时,会将其内部的字节数据在内存中连续排列(如果可能的话),或者 frombuffer 可以遍历这些字节字符串的缓冲区。最直接和高效的方式是,如果我们将整个 data_flat_bytes_array 视为一个连续的内存块,并指定 dtype=np.uint8,frombuffer 将会逐字节地将其解释为 uint8 整数。

    # 关键步骤:将整个字节字符串数组的内存视为一个大的字节缓冲区
    # 注意:这里实际上是利用了 numpy.array(..., dtype=np.bytes_).tobytes() 的隐式行为
    # 或者更直接地,将所有字节序列连接起来形成一个大的字节对象
    # 然而,原始答案的简洁方法是直接对 data_flat_bytes_array 进行操作,
    # 这依赖于 numpy 内部对 np.bytes_ 数组的 frombuffer 处理方式。
    # 为了确保兼容性和明确性,更安全的方式可能是先将所有字节序列连接起来:
    # combined_bytes = b''.join(data_flat_bytes_array.tolist())
    # result_flat = np.frombuffer(combined_bytes, dtype=np.uint8)
    
    # 按照原始答案的简洁方式:
    # 这种方式能够工作的关键在于,当 np.frombuffer 接收到一个 dtype 为 np.bytes_ 的 NumPy 数组时,
    # 它能够有效地访问其底层数据缓冲区,并将其视为连续的字节流。
    result_flat = np.frombuffer(data_flat_bytes_array, dtype=np.uint8)
    # result_flat 此时是一个一维的 numpy.uint8 数组,包含了所有字节序列中的所有字节值。
    # 它的长度将是 N * M * L (2 * 3 * 10 = 60)
  4. 重塑数组至目标维度: 最后一步是将扁平化的 uint8 数组重塑为我们期望的三维形状 (N, M, L)。

    final_array = result_flat.reshape(N, M, L)
    
    print("最终的 NumPy 数组形状:", final_array.shape)
    print("最终的 NumPy 数组类型:", final_array.dtype)
    print("最终的 NumPy 数组内容:\n", final_array)

完整示例代码:

import numpy as np

# 模拟原始数据,实际数据量可能达到千万级别
# 每个元组包含3个450字节的序列,这里为了示例简化为10字节
data = [
    (
        b'\n\x0f\n\t\x0c\x00\x00\x01\x07\x06', # 10 bytes series
        b'\x00\x0e\x00\x06\x07\x0c\n\x0e\x07',
        b'\x05\x0e\x07\t\x04\x01\x05\x07\x08',
    ),
    (
        b'\x0a\x0f\x0a\x09\x0c\x00\x00\x01\x07\x06',
        b'\x00\x0e\x00\x06\x07\x0c\x0a\x0e\x07',
        b'\x05\x0e\x07\x09\x04\x01\x05\x07\x08',
    ), # 更多元组,例如10M个
]

# 确定目标数组的维度
N = len(data) # 元组数量 (例如 10M)
M = len(data[0]) # 每个元组中的字节序列数量 (例如 3)
L = len(data[0][0]) # 每个字节序列的长度 (例如 450)

# 步骤1&2:将数据扁平化为NumPy字节字符串数组,并使用 frombuffer 解析
# np.array(data, dtype=np.bytes_) 会创建固定长度字节字符串数组,
# 之后 np.frombuffer 能够高效地将其内部的字节数据解析。
data_flat_bytes_array = np.array(data, dtype=np.bytes_).reshape(-1)
result_flat_uint8 = np.frombuffer(data_flat_bytes_array, dtype=np.uint8)

# 步骤3:重塑数组至目标维度
final_uint8_array = result_flat_uint8.reshape(N, M, L)

print("原始数据示例 (第一个元组的第一个字节序列):", data[0][0])
print("转换后数组的形状:", final_uint8_array.shape)
print("转换后数组的dtype:", final_uint8_array.dtype)
print("转换后数组的第一个元素 (对应原始数据的第一个字节序列):\n", final_uint8_array[0, 0, :])

# 验证转换是否正确
# 例如,b'\n\x0f\n\t' 对应 [10, 15, 10, 9]
# 原始数据 b'\n\x0f\n\t\x0c\x00\x00\x01\x07\x06' 
# 对应的十进制是 [10, 15, 10, 9, 12, 0, 0, 1, 7, 6]
expected_first_series = np.array([10, 15, 10, 9, 12, 0, 0, 1, 7, 6], dtype=np.uint8)
print("\n验证第一个字节序列是否正确转换:", np.array_equal(final_uint8_array[0, 0, :], expected_first_series))

注意事项

  • np.frombuffer 的高效性:np.frombuffer 之所以高效,是因为它直接操作内存缓冲区,避免了Python对象和NumPy数组之间的数据复制和类型转换开销。它将内存中的原始字节流直接解释为指定 dtype 的NumPy数组。
  • dtype=np.bytes_ 的作用:在第一步中,将 data 转换为 np.array(data, dtype=np.bytes_) 至关重要。这会创建一个NumPy数组,其中每个元素是固定长度的字节字符串(例如 |S450)。NumPy内部优化了这种类型数组的存储,使得其底层字节数据能够被 np.frombuffer 有效地访问和解释。
  • 内存连续性:np.frombuffer 要求输入数据在内存中是连续的。通过 np.array(data, dtype=np.bytes_).reshape(-1),我们创建了一个由字节字符串组成的NumPy数组,NumPy能够确保这些字节字符串的数据在底层是可被 frombuffer 连续访问的。
  • 数据完整性:此方法假设所有字节序列的长度是相同的,并且每个元组包含相同数量的字节序列。如果长度不一致,reshape 操作将失败或导致数据错位。
  • 性能提升:与基于Python循环的解决方案相比,这种方法能够将处理速度提升几个数量级,尤其适用于处理大规模字节数据。

总结

本文介绍了一种在NumPy中高效地将大量字节序列列表转换为 uint8 多维数组的方法。通过巧妙地结合 np.array(..., dtype=np.bytes_) 和 np.frombuffer,我们能够直接操作底层字节缓冲区,避免了Python层面的循环开销,从而实现了卓越的性能。这种方法对于需要处理大规模原始字节数据(如网络包、传感器数据、二进制文件内容等)的数据科学家和工程师来说,是一个非常实用的工具。掌握这一技巧,可以显著提升数据预处理阶段的效率。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

761

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

221

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1568

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

651

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

1228

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

1204

2024.04.29

go语言字符串相关教程
go语言字符串相关教程

本专题整合了go语言字符串相关教程,阅读专题下面的文章了解更多详细内容。

193

2025.07.29

c++字符串相关教程
c++字符串相关教程

本专题整合了c++字符串相关教程,阅读专题下面的文章了解更多详细内容。

131

2025.08.07

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.5万人学习

Django 教程
Django 教程

共28课时 | 5万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号