如何无需循环将int16_t数组写入文本格式PLY文件?
问题
我需要将int16_t数据写入带特殊头部的ASCII格式PLY文件。目前用循环实现的代码功能正常,但速度极慢(循环执行921600次,是性能瓶颈)。尝试直接用ofs.write写入point_cloud_image_data指针数据,速度大幅提升,但写入的是二进制内容(文件出现¥ûú这类乱码字符)。请问如何不用耗时循环就能把数组写入文本文件?
已尝试的方案
功能正常但速度慢的代码
int width = point_cloud_image.get_width_pixels(); int height = point_cloud_image.get_height_pixels(); int i_max = width * height; // 获取数据 int16_t* point_cloud_image_data = (int16_t*)(void*)point_cloud_image.get_buffer(); std::stringstream ss; for (int i = 0; i < i_max; i++) // 执行921600次——这是性能瓶颈 { ss << point_cloud_image_data[3 * i + 0] << " " << point_cloud_image_data[3 * i + 1] << " " << point_cloud_image_data[3 * i + 2] << "\n"; } // 保存到PLY文件 std::ofstream ofs("myfile.ply", std::ios::out | std::fstream::binary); // 先使用文本模式 ofs << "ply\n" << "format ascii 1.0\n" << "element vertex" << " " << i_max << "\n" << "property float x\n" << "property float y\n" << "property float z\n" << "end_header\n" << std::endl; ofs.write(ss.str().c_str(), (std::streamsize)ss.str().length()); ofs.close();
快速但生成二进制内容的代码
int width = point_cloud_image.get_width_pixels(); int height = point_cloud_image.get_height_pixels(); int i_max = width * height; // 获取数据 int16_t* point_cloud_image_data = (int16_t*)(void*)point_cloud_image.get_buffer(); // 保存到PLY文件 std::ofstream ofs("myfile.ply", std::ios::out | std::fstream::binary); // 先使用文本模式 ofs << "ply\n" << "format ascii 1.0\n" << "element vertex" << " " << i_max << "\n" << "property float x\n" << "property float y\n" << "property float z\n" << "end_header\n" << std::endl; ofs.write((char*)(char16_t*)point_cloud_image_data, i_max); ofs.close();
解决方案
ASCII格式的PLY必须存储文本形式的数值,直接写二进制数据必然会出现乱码——你看到的特殊字符就是int16_t的二进制值被当作ASCII字符解析的结果。要高效完成转换,核心是减少循环内的IO和字符串拼接开销,以下是几种可行方案:
1. 批量格式化+预分配缓冲区
避免每次循环都给字符串流追加内容,预先估算所需缓冲区大小,用C风格的snprintf批量写入到一块大缓冲区,最后一次性写入文件,能大幅减少内存分配和IO次数。
示例代码:
int width = point_cloud_image.get_width_pixels(); int height = point_cloud_image.get_height_pixels(); int i_max = width * height; int16_t* point_cloud_image_data = (int16_t*)(void*)point_cloud_image.get_buffer(); // 估算每个顶点的字符串长度:int16_t范围是-32768到32767,最多6位,加上空格和换行,每个顶点约22字节 size_t buffer_size = i_max * 22; char* buffer = new char[buffer_size]; char* ptr = buffer; for (int i = 0; i < i_max; i++) { int16_t x = point_cloud_image_data[3*i]; int16_t y = point_cloud_image_data[3*i+1]; int16_t z = point_cloud_image_data[3*i+2]; // 写入缓冲区,返回写入的字符数并移动指针 ptr += snprintf(ptr, buffer + buffer_size - ptr, "%d %d %d\n", x, y, z); } // ASCII模式不需要binary标记 std::ofstream ofs("myfile.ply", std::ios::out); ofs << "ply\n" << "format ascii 1.0\n" << "element vertex " << i_max << "\n" << "property float x\n" << "property float y\n" << "property float z\n" << "end_header\n"; ofs.write(buffer, ptr - buffer); ofs.close(); delete[] buffer;
2. 优化文件流缓冲区大小
默认文件流的缓冲区很小,频繁写入会触发多次系统调用。手动设置更大的缓冲区,能减少IO开销:
// 打开文件后设置64KB缓冲区 char file_buffer[65536]; ofs.rdbuf()->pubsetbuf(file_buffer, sizeof(file_buffer));
3. 预分配字符串流空间
如果不想用C风格函数,可以给std::ostringstream预分配足够空间,避免循环中频繁的内存扩容:
std::ostringstream ss; ss.reserve(i_max * 22); // 预分配估算的空间 for (int i = 0; i < i_max; i++) { ss << point_cloud_image_data[3*i] << " " << point_cloud_image_data[3*i+1] << " " << point_cloud_image_data[3*i+2] << "\n"; }
4. 多线程批量处理(可选)
如果数据量极大,可以将数组拆分为多个块,用多线程分别格式化每个块的字符串,最后合并写入文件。这种方法复杂度较高,适合超大规模数据场景。
内容的提问来源于stack exchange,提问作者Breadman10
相关产品推荐
相关产品推荐

