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 数组的具体步骤和示例代码:

LangChain
LangChain

一个开源框架,用于构建基于大型语言模型(LLM)的应用程序。

下载
  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层面的循环开销,从而实现了卓越的性能。这种方法对于需要处理大规模原始字节数据(如网络包、传感器数据、二进制文件内容等)的数据科学家和工程师来说,是一个非常实用的工具。掌握这一技巧,可以显著提升数据预处理阶段的效率。

相关专题

更多
python开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

706

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

624

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

734

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

616

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1234

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

547

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

573

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

694

2023.08.11

苹果官网入口直接访问
苹果官网入口直接访问

苹果官网直接访问入口是https://www.apple.com/cn/,该页面具备0.8秒首屏渲染、HTTP/3与Brotli加速、WebP+AVIF双格式图片、免登录浏览全参数等特性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

10

2025.12.24

热门下载

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

精品课程

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

共4课时 | 0.6万人学习

Django 教程
Django 教程

共28课时 | 2.4万人学习

SciPy 教程
SciPy 教程

共10课时 | 0.9万人学习

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

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