前言

有时我们希望将把数据保存在单个连续的数组中,以便快速、便捷地访问数据,但这需要调整数组大小或者对其扩展。Java 数组不能调整大小,只用数组不足以达成目标。可变长原始类型数组需要自己实现。本文将展示如何实现 Java 可变长数组。

为什么不用 ArrayList?

要满足文章开头的需求,为什么不使用 Java ArrayList?如果满足下面条件之一,可以使用 ArrayList:

  • 在数组中存储某种对象类型;
  • 在数组中存储原始类型,没有特别的性能或内存要求。

这是我的Java专栏《Java 进阶集中营》,里面会有很多优秀的java技术内容和工作分享,各位喜欢Java并且正在学习Java 的朋友可别错过了

JAVA 进阶集中营​zhuanlan.zhihu.com

Java ArrayList 类只适用于对象,不适合原始类型(byte、int、long等)。使用 ArrayList 存储原始类型数据,插入 ArrayList 时会自动装箱为对象中,从中取出时会进行拆箱转为原始类型。装箱和拆箱在插入元素和访问元素时会带来额外开销,在尝试针对性能进行优化时,应该避免这些开销(本文是 Java 性能跟踪的一部分)。

不仅如此,这种方式无法确定自动装箱对象在内存中的存储位置。很可能分散存储在堆中各个位置。因此,与原始类型数组相比访问速度要慢得多,前者在内存中连续存储。

另外,原始类型装箱会带来额外的内存开销,例如 long 会存到 Long 对象。

本文源代码

本文实现了 Java 变长数组源代码可以在 GitHub 下载:

http://github.com/jjenkov/java-resizable-array

代码包含三个 Java 类和两个单元测试。

可变长数组用例

假设有一台服务器接收大小不同的邮件。其中有些邮件很小(小于4KB),另一些很大(1MB 甚至更大)。

如果服务器同时从多个(10万多个)连接接收消息,那么需要为每个消息限制预分配内存。每个缓冲区不能只按最大值(1MB或16MB)分配内存。当连接和消息数量增加时,这种方式会快速耗尽服务器内存!100_000 x 1MB = 100GB(这是估计值,帮助问题理解)。

假设大多数消息比较小,一开始可以使用较小的缓冲区。如果消息超出缓存大小,则分配一个更大的新数组,并把数据拷贝到该数组中。如果消息超出分配的新数组,接着分配一个比之前更大的数组,并把消息复制到该数组。

使用这种策略,大多数消息通常只会存入小数组。这意味着服务器内存得到了更有效的利用。100_000 x 4KB (小缓冲) = 400MB大多数服务器应该能够正常处理。即使是 4GB (1_000_000 x 4KB),现在的服务器也能满足要求。

可变长数组设计

可变长数组包含两个组件:

  • ResizableArray
  • ResizableArrayBuffer

ResizableArrayBuffer 包含一个大数组。该数组被划分为三个部分。一段用作小数组,一段用作中数组,一段用作大数组。ResizableArray类表示一个可变长数组,底层数据存储在ResizableArrayBuffer中。

下图展示了数组分为三段,每段再分为小块:

通过为小、中、大不同类型数据预留空间,ResizableArrayBuffer 能够确保不会被某种大小的数据塞满。例如,小数据不会占用数组的所有内存,进而阻断中型和大数据存储。同样,接收大数据也不会占用所有内存,进而阻断小数据和中型数据存储。

由于底层存储以小数据开始,如果小数组存储空间耗尽,那么无论中数组或大数组是否还有空间,都无法分配新的数组。可以让使小数组足够大,减小发生这种情况的可能性。

即使小数组已经全部用完,仍然可以把小数据变成中型和大型数据。

优化方案

一种优化方案:只用一个存储块。需要的时候在待扩展的块后面直接分配新块。这样不需要把数据从旧数组拷贝到新数组,可以直接“扩展”存储块容纳旧数据和新数据,新数据直接写入新增的第二个扩展块即可。这样避免了拷贝所有数组数据的情况。

上述优化的缺点在于,如果无法扩展下一个内存块仍然需要拷贝数据。因此需要加入“可扩展”检查,这个操作开销不大。此外,如果存储块大小设置过小,在小数据、中等数据和大数据都存在的情况下会出现频繁扩展。

跟踪空闲块

ResizableArrayBuffer 内部的大数组同样分为三段。每段都被分为更小的存储块;每段中的存储块大小相同;小数组中的存储块大小相同;中型数组中的存储块大小相同;大数组中的存储块大小相同。

每段中的存储块大小相同可以更方便地追踪块使用状态。可以使用队列记录每个块的起始索引。还需要一个队列记录每段中的共享数组。最终,一个队列来跟踪空闲小数据块,一个队列用记录空闲的中型数据块,一个队列用于空闲的大数据块。

根据数据类型从响应队列获取下一个空闲块起始索引,可以实现从任意数据段分配存储块。把起始索引放回相应队列可以释放数据块。

这里我用简单的环形缓冲区实现队列。GitHub 仓库对应的代码为 QueueIntFlip。

扩展写

向数组写数据时,可变长数组自动扩展。如果尝试向数组写入的数据超出当前分配的存储空间,将分配一个新的更大的存储块并把所有数据拷贝到新块中,然后释放之前较小的存储块。

释放数组

一旦可变长数组完成了大小调整,应该对其进行释放以便可以接收其他消息。

使用 ResizableArrayBuffer

下面展示如何使用 GitHub 中 ResizableArrayBuffer。

创建一个 ResizableArrayBuffer

首先,必须创建一个 ResizableArrayBuffer。示例如下:

这个例子创建的 ResizableArrayBuffer 包含一个4KB小数组,128KB中数组和1MB大数组。ResizableArrayBuffer存储空间包含1024个小数组(共4MB)、32个中数组(共4MB)和4个大数组(共4MB),完整的共享数组大小总计12MB。

获取 ResizableArray 实例

要得到 ResizableArray 实例,调用 ResizableArrayBuffer的getArray() 方法,如下所示:

这里得到一个最小的 ResizableArray(之前设置为4KB)。

向 ResizableArray 写数据

调用 write() 方法向 ResizableArray 写数据。GitHub 中 ResizableArray 类只包含一个 write() 方法,其参数为 ByteBuffer。不过,可以根据需要自行添加更多 write() 方法。

下面是写数据示例:

上面的代码把 ByteBuffer 内容复制到 ResizableArray 数组中。Write() 返回从 ByteBuffer 拷贝的字节数。

如果 ByteBuffer 包含的数据超出了 ResizableArray 容量,这时 ResizableArray 会尝试扩展,为 ByteBuffer 中的数据留出空间。如果 ResizableArray 即使扩展到最大值也无法容纳 ByteBuffer 中的所有数据,则 write() 方法返回-1,并且不会复制任何数据!

从 ResizableArray 读数据

从 ResizableArray 中读数据时,可以直接从 ResizableArray 所有实例中直接读取共享数组。ResizableArray 包含以下 public 字段:

  • sharedArray 字段对应所有 ResizableArray 实例中的共享数组,即ResizableArrayBuffer 的内部数组。
  • offset 字段对应共享数组的起始索引,ResizableArray 在这里保存数据。
  • capacity 字段包含分配给 ResizableArray 实例中的块大小。
  • length 字段包含 ResizableArray 实际使用的块数量。

要读取 ResizableArray 的写入数据,只要读取从sharedArray[offset] 到sharedArray[offset+ length -1] 的字节即可。

释放 ResizableArray

一旦 ResizableArray 实例使用完毕应该释放。只要在 ResizableArray 上调用 free() 方法即可,如下所示:

无论分配给 ResizableArray 块大小如何,调用 free() 都能将使用的块正确返还队列。

变换设计

您可以根据自己的需要修改 ResizableArrayBuffer 设计。例如,可以在其中创建多于三个数据段。操作起来应该也很容易。

最后,如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。

.dat文件写入byte类型数组_不可不知的可变Java长数组相关推荐

  1. 数组字段查询不包含_不可不知的可变Java长数组

    前言 有时我们希望将把数据保存在单个连续的数组中,以便快速.便捷地访问数据,但这需要调整数组大小或者对其扩展.Java 数组不能调整大小,只用数组不足以达成目标.可变长原始类型数组需要自己实现.本文将 ...

  2. .dat文件写入byte类型数组_文件字节流、文件字符流、缓冲字节流、缓冲字符流字节数组流、数据流、转换流、对象流...

    一.实操名称: 描述如下流的基本作用:文件字节流.文件字符流.缓冲字节流.缓冲字符流字节数组流.数据流.转换流.对象流二.描述1.文件字节流:包括:FileInputStream,FileOutput ...

  3. .dat文件写入byte类型数组_深入浅出MATLAB数据处理之文件读写

    过冷水给大家讲了好几期实战案例.但是最基本的文件操作,读取数据的函数使用方法没有给大家讲,只是一个没有思想的代码操作工,今天和大家剖析一下最常见的fopen.fread函数使用方法,先来看看 file ...

  4. .dat文件写入byte类型数组_小师妹学JavaIO之:文件写入那些事

    简介 小师妹又对F师兄提了一大堆奇奇怪怪的需求,要格式化输出,要特定的编码输出,要自己定位输出,什么?还要阅后即焚?大家看F师兄怎么一一接招吧. 字符输出和字节输出 小师妹:F师兄,上次你的IO讲到了 ...

  5. .dat文件写入byte类型数组_《计算机导论》课程实验报告(文件)

    <计算机导论>课程实验报告 专业班级: 姓名: 学号: 实验类型: 设计型实验 时间: 实验题目:文件 实验目的: 1.掌握文件和文件指针的概念: 2.掌握文件打开和关闭的方法. 3.掌握 ...

  6. .dat文件写入byte类型数组_Go语言学习基础-读文件、写文件、行过滤器

    Reading File 读文件 读写文件是许多Go程序所需的基本任务.首先,我们将看一些读取文件的示例.读取文件需要检查是否出现调用错误. 最基本的文件读取任务是将文件的全部内容读到内存中iouti ...

  7. python读取dat文件写入表格_在python中从.dat文件读取和执行计算

    我需要用python读取一个.dat文件,它总共有12列,数百万行.我需要把第2.3和4栏和第1栏分开计算.所以在加载.dat文件之前,是否需要删除所有其他不需要的列?如果没有,如何有选择地声明列并要 ...

  8. js 如何将java list集合转换成var类型数组_零基础参加郑州Java培训 一定要注意Arrays.asList的用法...

    作为编程界的老大哥,Java一直是想要加入互联网行业的人的首选.为了能够更快更好地入行,很多零基础学员选择参加郑州Java培训班,跟着老师的脚步由浅入深的学习专业技术.今天千锋郑州小编就给大家分享使用 ...

  9. python读取dat文件写入表格_Pandas:外部文件数据导入/ 读取 (如 :csv、txt、tsv、dat、excel文件)、文件存储(to_csv、to_excel)...

    一.文本文件读取 文本文件是一种由若干行字符构成的计算机文件,它是一种典型的顺序文件. csv是一种逗号分隔的文件格式,因为其分隔符不一定是逗号,又被称为字符分隔文件,文件以纯文本形式存储表格数据(数 ...

  10. JAVA中两个char类型相加_【技术干货】Java 面试宝典:Java 基础部分(1)

    海牛学院的 | 第 616 期 本文预计阅读 |18 分钟 Java 基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语法,集合的语法,io 的语法, ...

最新文章

  1. Selenium 2.0的由来及设计架构(二)
  2. C语言位运算实现加法
  3. Scala入门到精通——第六节:类和对象(一)
  4. linux26内核,Linux26内核对象机制研究.pdf
  5. 博文视点Open Party ——漏洞分析
  6. linux 离线安装node.js,Linux上离线安装node.js、Newman、newman-reporter-html
  7. 扫码枪扫码直接提交ajax,js监听页面扫码枪
  8. 坚果pro2官方rom_坚果pro2线刷包_坚果pro2刷机包_坚果pro2固件包_坚果pro2救砖包 - 线刷宝ROM中心...
  9. 仿时钟表盘自定义view
  10. python下载谷歌地图瓦片_Python地图可视化之Folium更换地图瓦片(Map Tiles)
  11. Docker Desktop 错误:必须在BIOS中启用 硬件辅助虚拟化和数据执行保护
  12. python基础学习笔记——完结
  13. 2021 第五届“达观杯” 基于大规模预训练模型的风险事件标签识别】3 Bert和Nezha方案
  14. Git 学会git,探索GitHub,掌握新知识 (二)
  15. 2020年9月19日 晴
  16. 低代码平台在ERP软件开发中的作用
  17. 论文他引次数及ESI高被引论文查询方法
  18. 使用 DTrace 和 SystemTap 检测 CPython
  19. 如何快速批量生成SSCC-18条码
  20. 老男孩Day18作业:后台用户管理

热门文章

  1. WebSocket 时时双向数据,前后端(聊天室)
  2. Js中Array数组学习总结
  3. Linux下更改目录及其下的子目录和文件的访问权限
  4. SDL2源码分析1:初始化(SDL_Init())
  5. Java多线程:线程同步与关键字synchronized
  6. Mysql按时间段分组查询来统计会员的个数
  7. PHP的图片等比缩放
  8. 将帐套升级到百万用户纪念版实践教程
  9. PPC小问题,持续更新中...
  10. javascript-----日历控件