2024年3月

近日,开发 ChatGPT 的 OpenAI 公司又放出王炸 Sora,一个可以根据文本生成视频的 AI 模型。

上图就是 OpenAI 公布的 Sora 生成的视频片段,可以毫不夸张地说 Sora 直接将视频生成技术推向了新的高度,这也标志着人工智能视频生成技术迈入了新的时代。此项技术,可以广泛应用于电影、动画、游戏、广告等领域,为内容创作者提供更加便捷、高效的创作工具。

虽然 Sora 没有开源,但我在 GitHub 上发现了 Colossal-AI 刚开源的完整 Sora 复现架构方案 Open-Sora,它提供了完整的 Sora 复现架构解决方案,包括从数据处理到训练、部署的全流程。Open-Sora 不仅可以降低 46% 复现成本,而且还将模型训练输入序列长度扩充至 819K patches(模型处理视频的最小单元)。

GitHub 地址:
https://github.com/hpcaitech/Open-Sora

既然 Sora 没有开源,那这个复现方案从何而来呢?接下来,就让我们一起来看看已公布的 Sora 技术原理以及 Open-Sora 到底有没有真东西!

Sora 算法复现方案

与 Sora 视频一起发布的,还有一份 OpenAI 关于 Sora 的技术报告,在这份 Sora 的技术报告中,介绍了 Sora 是使用了一个视频压缩网络,将不同尺寸的视频压缩成一个隐空间(latent space)的时空块序列(temporal patch),然后使用了 Diffusion Transformer 进行去噪,最后进行解码生成视频。

根据上面描述的技术原理,Open-Sora 将 Sora 可能使用的训练流程归纳为下图。

综上所述,Open-Sora 作为 Sora 技术的复现框架,用 Python 实现了将原视频转化成通用型大模型能处理的最小单元 patches 的模块(patches 类似文本的 token),然后在去噪步骤提供了 3 种常见的多模态模型结构。

目前 Open-Sora 提供的功能,如下:

  • 完整的 Sora 复现架构
    :包含从数据处理到训练推理全流程。
  • 动态分辨率
    :训练时可直接训练任意分辨率的视频,无需进行缩放。
  • 多种模型结构
    :由于 Sora 实际模型结构未知,我们实现了 adaLN-zero、cross attention、in-context conditioning(token concat)等 3 种常见的多模态模型结构。
  • 多种视频压缩方法
    :用户可自行选择使用原始视频、VQVAE(视频原生的模型)、SD-VAE(图像原生的模型)进行训练。
  • 多种并行训练优化
    :支持结合 Colossal-AI 的 AI 大模型系统优化能力,及 Ulysses 和 FastSeq 的混合序列并行。

众所周知,模型训练需要的资源是恐怖的,哪怕是 1% 的优化都可以节约极大的训练成本,而 Open-Sora 直接节省了近一半 46% 的复现成本,而且在模型训练输入方面更是将序列扩充至近百万。到底 Open-Sora 是如何做的性能优化,从而实现类 Sora 视频生成模型的低成本开发流程呢?跟着小编我们一探究竟。

性能优化

不同于 LLM 的大模型、大激活,Sora 类训练任务的特点是
模型本体不大(如在 10B 以下)
,但是由于视频复杂性带来的序列长度特别长。

在此情况下,
PyTorch 数据并行已无法运行,而传统的模型并行、零冗余数据并行带来的收益有限
。因此,在支持 AMP(FP16/BF16)、Flash Attention、Gradient checkpointing、ZeRO-DP 等场景优化策略的基础上,Open-Sora 进一步引入两种不同的序列并行方法实现,可以 ZeRO 一起使用实现混合并行:

  1. 通用性较强的 Ulysses,对小规模或长序列表现可能更好。
  2. FastSeq 能将 qkv projection 的计算和 all-gather 通信重叠,只需多占用一点内存就可更进一步提升训练效率。

这两种序列并行方案,都可以轻松与 ZeRO2 共同使用来实现混合并行。

以在单台 H800 SXM 8*80GB GPU 上使用
DiT-XL/2
模型的性能测试为例。

在 600K 的序列长度时,Open-Sora 的方案比基线方案有
40% 以上的性能提升和成本降低

在保证更快训练速度的情况下,Open-Sora 还能
训练 30% 更长的序列,达到 819K+

最后

在这个视频流行的时代,AI 生成视频技术成为了当下备受瞩目的热门技术之一,Sora 的出现更是引爆了这个领域。

今天介绍的 Open-Sora 才刚刚开源,并未提供训练好的类 Sora 模型,还不能直接拿来生成视频。但它提供了一套经过优化后的类 Sora 视频生成模型低成本开发框架,为视频生成提供方便易用、成本低廉、质量可靠的开源解决方案。更多的技术细节,篇幅问题这里就不展开了,如果你对 Open-Sora 感兴趣,可深入了解下它的源码(Python)或者参与贡献代码,复现 Sora 赶超 Sora!

GitHub 地址:
https://github.com/hpcaitech/Open-Sora

写在开头

在过去的2023年双11活动中,天猫的累计访问人次达到了8亿,京东超60个品牌销售破10亿,直播观看人数3.0亿人次,订单支付频率1分钟之内可达百万级峰值,这样的瞬间高并发活动,给服务端带来的冲击可想而知,就如同医院那么多医生,去看病挂号时,有时候都需要排队,对于很多时间就是金钱的场景来说,是不可忍受的。

为什么要使用多线程并发

在上述这种场景下我们就不得不去学习多线程下的并发处理,我们先来了解一下并发与线程的概念

并发:
并发指在某时刻只有一个事件在发生,某个时间段内由于 CPU 交替执行,可以发生多个事件,存在对 CPU 资源进行抢占。

线程:
是进程的子任务,因此它本身不会独立存在,系统不会为线程分配内存,线程组之间只能共享所属进程的资源,而线程仅仅是CPU 调度和分派的基本单位,当前线程 CPU 时间片用完后,会让出 CPU 等下次轮到自己时候在执行。


有了这两个概念后,我们再来聊一聊并发多线程的必要性或者它所具有的优势

ASP.NET Core MVC的“模块化”设计使我们可以构成应用的基本单元Controller定义在任意的模块(程序集)中,并在运行时动态加载和卸载。《
设计篇
》介绍了这种为“飞行中的飞机加油”的方案的实现原理?本篇我们将演示将介绍“分散定义Controller”的N种实现方案。源代码从
这里
下载。

一、标注ApplicationPartAttribute特性
二、标注RelatedAssemblyAttribute特性
三、注册ApplicationPartManager
四、添加ApplicationPart到现有ApplicationPartManager

一、标注ApplicationPartAttribute特性

接下来我们就通过几个简单的实例来演示如何将Controller类型定义在非入口应用所在的项目中。我们创建如图1所示的解决方案,其中App是一个MVC应用类型的项目,而Foo则是一个普通的类库项目,App具有针对Foo的项目引用。我们希望将部分Controller类型定义在Foo这个类库项目中。

image_thumb[12]
图1 将部分Controller类型定义在Foo项目中

我们在App项目中定义了如下这个HomeController。如代码片段所示,我们在构造函数中注入了
ApplicationPartManager
对象,并利用它得到当前应用范围内所有有效Controller类型。在执行应用根路径的Action方法Index中,我们将得到的有效Controller类型名称呈现出来。如下所示的FooController类型是我们在Foo项目中定义的Controller类型。

public class HomeController : Controller
{
    private readonly IEnumerable<Type> _controllerTypes;
    public HomeController(ApplicationPartManager manager)
    {
        var feature = new ControllerFeature();
        manager.PopulateFeature(feature);
        _controllerTypes = feature.Controllers;
    }

    [HttpGet("/")]
    public string Index()
    {
        var lines = _controllerTypes.Select(it => it.Name);
        return string.Join(Environment.NewLine, lines.ToArray());
    }
}

public class FooController
{
    public void Index() => throw new NotImplementedException();
}

在启动这个演示程序后,如果利用浏览器通过根路径访问定义在HomeController类型中的Action方法Index,我们会得到如图2所示的输出结果。从输出结果可以看出,定义在非MVC应用项目Foo中的Controller类型在默认情况下是不会被解析的。

image_thumb[14]
图2 默认只解析MVC应用所在项目定义的Controller

如果希望MVC应用在进行Controller类型解析的时候将项目Foo编译后的程序集(默认为Foo.dll)包括进来,我们可以在应用所在项目中标注
ApplicationPartAttribute
特性将程序集Foo作为应用的组成部分。所以我们在Program.cs中针对ApplicationPartAttribute特性进行了如下的标记。

[assembly:ApplicationPart("Foo")]

修改后的程序集启动之后,再次利用浏览器按照按照相同的路径对它发起请求,我们将得到如图3所示的输出结果。由于程序集Foo成为了当前应用的有效组成部分,定义在它里面的BarController自然也成为了当前应用有效的Controller类型。

image_thumb[16]
图3 解析ApplicationPartAttribute特性指向程序集中的Controller类型

二、标注RelatedAssemblyAttribute特性

除了在入口程序集上标注ApplicationPartAttribute特性将某个程序集作为当前应用的有效组成部分之外,我们也可以通过标注
RelatedAssemblyAttribute
达到相同的目的。根据前面的介绍,我们知道RelatedAssemblyAttribute特性只能标注到入口程序集或者ApplicationPartAttribute特性指向的程序集中,所以我们可以将RelatedAssemblyAttribute特性标注到Foo项目中将另一个程序集包含进行。为此我们在解决方案中添加了另一个类库项目Bar(如图4所示),并为App添加针对Bar的项目引用,然后在Bar项目中定义一个类似于FooController的BarController类型。

image_thumb[19]
图4 将部分Controller类型定义在Foo和Bar项目中

为了将项目Bar编译后生成的程序集(默认为Bar.dll)作为当前应用的组成部分,我们可以选择在App或者Foo项目中标注一个指向它的
RelatedAssemblyAttribute
特性。对于我们演示的实例来说,我们选择在FooController.cs中以如下形式标注一个指向程序集Bar的RelatedAssemblyAttribute特性。

[assembly: RelatedAssembly("Bar")]

修改后的程序集启动之后,再次利用浏览器按照按照相同的路径对它发起请求,我们将得到如图5所示的输出结果。由于程序集Bar成为了当前应用的有效组成部分,定义在它里面的BazController自然也成为了当前应用有效的Controller类型。

image_thumb[21]
图5 解析RelatedAssemblyAttribute特性指向程序集中的Controller类型

三、注册ApplicationPartManager

由于针对有效Controller类型的解析是利用注册的ApplicationPartManager对象实现的,所以我们完全可以通过注册一个ApplicationPartManager对象的方式达到相同的目的。接下来我们将上一个演示实例中标注的ApplicationPartAttribute和RelatedAssemblyAttribute特性删除,并将承载程序修改为如下的形式。

var manager = new ApplicationPartManager();
var entry = Assembly.GetEntryAssembly()!;
var foo = Assembly.Load(new AssemblyName("Foo"));
var bar = Assembly.Load(new AssemblyName("Bar"));

manager.ApplicationParts.Add(new AssemblyPart(entry));
manager.ApplicationParts.Add(new AssemblyPart(foo));
manager.ApplicationParts.Add(new AssemblyPart(bar));
manager.FeatureProviders.Add(new ControllerFeatureProvider());


var builder = WebApplication.CreateBuilder(args);
builder.Services
    .AddSingleton(manager)
    .AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();

如上面的代码片段所示,我们创建了一个ApplicationPartManager对象,并在其ApplicationParts属性中显式添加了指向入口程序集以及Foo和Bar程序集的AssemblyPart对象。为了能够让这个ApplicationPartManager对象具有解析Controller类型的能力,我们在其FeatureProviders中添加了一个ControllerFeatureProvider对象。在后续的应用承载程序中,我们将这个ApplicationPartManager对象作为服务注册到依赖注入框架中。修改后的程序集启动之后,再次利用浏览器按照按照相同的路径对它发起请求,我们依然会得到如图5所示的输出结果。

四、添加ApplicationPart到现有ApplicationPartManager

其实我们没有必要注册一个新的,按照如下的方式将Foo、Bar程序集转换成AssemblyPart并将其添加到现有的ApplicationPartManager之中也可以达到相同的目的。

var builder = WebApplication.CreateBuilder(args);
builder.Services
    .AddControllers()
    .AddApplicationPart(Assembly.Load(new AssemblyName("Foo")))
    .AddApplicationPart(Assembly.Load(new AssemblyName("Bar")));
var app = builder.Build();
app.MapControllers();
app.Run();

深入解析ASP.NET Core MVC的模块化设计[上篇]
深入解析ASP.NET Core MVC的模块化设计[下篇]
如何实现运行时动态定义Controller类型?

环境说明

ZLMediaKit编译需要的软件

visual studio 2022
cmake 3.29.0-rc2
OpenSSL 1.1.1w(不想踩坑的话安装这个版本)
libsrtp 2.6.0

ZLMediaKit编译后运行需要

libsrtp 编译后且配置环境变量
ZLMediaKit 编译后文件

visual studio

Snipaste_2024-02-28_17-33-25

Snipaste_2024-02-28_17-34-19

Snipaste_2024-02-28_17-35-53

cmake

visual stuido 20222,自带cmake

cmake可以到这两个地方下载:
下载1

下载2

Snipaste_2024-02-28_09-48-44

Snipaste_2024-02-27_13-02-04

Snipaste_2024-02-28_09-49-07

Snipaste_2024-02-28_09-49-24

Snipaste_2024-02-29_15-12-23

Snipaste_2024-02-29_15-12-30

测试是否成功

C:\Users\XXX>cmake
Usage

  cmake [options] <path-to-source>
  cmake [options] <path-to-existing-build>
  cmake [options] -S <path-to-source> -B <path-to-build>

Specify a source directory to (re-)generate a build system for it in the
current working directory.  Specify an existing build directory to
re-generate its build system.

Run 'cmake --help' for more information.

OpenSSL

可以到
OpenSSL
安装window编译版本, 记得
要下载是exe或msi文件,不要light的文件!!!!!

Snipaste_2024-02-27_13-02-04

按步骤安装,安装地址为:C:\Program Files\OpenSSL-Win64

Snipaste_2024-02-27_15-49-25

配置环境变量

一定要配置正确!!!

Snipaste_2024-02-27_09-46-14

测试

C:\Users\XXX>openssl -v
OpenSSL 3.2.1 30 Jan 2024 (Library: OpenSSL 3.2.1 30 Jan 2024)

可以不重启电脑,但必须关闭已经打开cmd窗口,否则会显示'openssl' 不是内部或外部命令,也不是可运行的程序
或批处理文件。

libsrtp

libsrtp
下载后解压,解压路径地址为: D:\XXX\soft\libsrtp-2.6.0

有两种编译方式:

第一种:
visual stuido

cmake
结合使用

  1. 首先在D:\XXX\soft\libsrtp-2.6.0\libsrtp-2.6.0新建build和install两个文件夹

  2. 打开cmake软件
    Snipaste_2024-02-29_14-26-41

  3. 完成后出现红色配置,勾选上BUILD_SHARED_LIBS、ENABLE_OPENSSL、以及CMAKE_INSTALL_PREFIX的路径,然后点生成。
    注意这里有个问题
    Could NOT find OpenSSL

    Snipaste_2024-02-29_14-29-54

  4. 如果3顺利的话,完成后的结果(
    换回1.1.1的测试结果
    ),然后点open project

    Snipaste_2024-02-29_16-54-09

  5. 会打开vs

    Snipaste_2024-02-29_17-13-25

  6. 生成后会出现一些错误(但无影响)

    Snipaste_2024-02-29_17-14-51

  7. install文件夹生成的目录

    install目录│文件列表:
     ├ include
     │  └ srtp2
     │     │ auth.h
     │     │ cipher.h
     │     │ crypto_types.h
     │     └ srtp.h
     └ lib
        │ srtp2.dll
        │ srtp2.lib
        └ cmake
           └ libSRTP
              │ libSRTPConfig.cmake
              │ libSRTPConfigVersion.cmake
              │ libSRTPTargets-debug.cmake
              └ libSRTPTargets.cmake
    
  8. 配置环境变量到path

    D:\XXX\soft\libsrtp-2.6.0\libsrtp-2.6.0\install
    D:\XXX\soft\libsrtp-2.6.0\libsrtp-2.6.0\install\lib
    

编译ZLMediaKit

在使用VS打开ZLMediaKit项目时,如果未开启webrtc, 会出现

[CMake] -- set_file_group D:/XXX/project/ZLMediaKit/src 205
1> [CMake] -- srt 功能已开启
1> [CMake] CMake Warning at webrtc/CMakeLists.txt:28 (message):
1> [CMake]   srtp 未找到, WebRTC 相关功能打开失败

要开启webrtc,需要保证以下正确

  • 开启了webrtc

    Snipaste_2024-02-29_17-51-14

  • libsrtp编译正确且配置环境变量

开启成功的话, 会出现下面,然后安装ZLMediaKit

> [CMake] -- Enable wepoll
1> [CMake] -- set_file_group D:/XXX/project/ZLMediaKit/3rdpart/ZLToolKit/src 80
1> [CMake] -- set_file_group D:/XXX/project/ZLMediaKit/src 205
1> [CMake] -- srt 功能已开启
1> [CMake] -- found library: D:/XXX/soft/libsrtp-2.6.0/libsrtp-2.6.0/install/lib/srtp2.lib
1> [CMake] -- SCTP LIBRARIES: SCTP_LIBRARIES-NOTFOUND
1> [CMake] -- SCTP INCLUDE DIRS: SCTP_INCLUDE_DIRS-NOTFOUND
1> [CMake] -- WebRTC 功能已开启

image-20240229175901339

如果不行先把out和release文件夹删除,再重新打开vs,然后安装ZLMediaKit

Snipaste_2024-02-22_16-33-28

测试webrtc

打开编译目录,双击MediaServer.exe

image-20240306666664721947

先推流,用obs推流

Snipaste_2024-03-01_18-08-44

chrome浏览器打开

Snipaste_2024-03-01_18-05-58

点确定

Snipaste_2024-03-01_18-06-14

点开始

Snipaste_2024-03-01_18-09-45

问题

  • MediaServer.exe打开后,由于找不到 srtp2.dll,无法继续执行代码。重新安装程序可能会解决此问题

    检查srtp2.dll的环境变量是否正确,如:D:\XXX\soft\libsrtp-2.6.0\libsrtp-2.6.0\install\lib

    image-20240306666662912727

  • Could NOT find OpenSSL

CMake Error at C:/Program Files/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/share/cmake-3.28/Modules/FindPackageHandleStandardArgs.cmake:230 (message):
  Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the
  system variable OPENSSL_ROOT_DIR (missing: OPENSSL_CRYPTO_LIBRARY
  OPENSSL_INCLUDE_DIR) (Required is at least version "1.1.0")		C:/Program Files/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/share/cmake-3.28/Modules/FindPackageHandleStandardArgs.cmake	230		

开始以为是安装openssl步骤有问题,改为第一个选项,结果还是这样的接口

Snipaste_2024-02-28_10-13-28

然后看了报错

try to set the path to OpenSSL root folder in the system variable OPENSSL_ROOT_DIR

添加了环境变量

```
OPENSSL_ROOT_DIR	C:\Program Files\OpenSSL-Win64
OPENSSL_CRYPTO_LIBRARY	C:\Program Files\OpenSSL-Win64\lib
OPENSSL_INCLUDE_DIR	C:\Program Files\OpenSSL-Win64\include
```

Snipaste_2024-02-27_13-07-31

完成后重启,清除缓存生成依然没有效果

image-20240229160034606

解决方式

  • 换版本OpenSSL:
    Win64OpenSSL-1_1_1w.msi
    , 省很多事

    C:\Users\XXX>openssl version
    OpenSSL 1.1.1w  11 Sep 2023
    

    安装完成它的lib目录结构为:

    lib目录│文件列表:
     │ capi.lib
     │ dasync.lib
     │ ec_internal_test.lib
     │ libapps.lib
     │ libcrypto.def
     │ libcrypto.lib
     │ libcrypto_static.lib
     │ libssl.def
     │ libssl.lib
     │ libssl_static.lib
     │ libtestutil.lib
     │ openssl.lib
     │ ossltest.lib
     │ padlock.lib
     │ uitest.lib
     └ VC
        │ libcrypto64MD.lib
        │ libcrypto64MDd.lib
        │ libcrypto64MT.lib
        │ libcrypto64MTd.lib
        │ libssl64MD.lib
        │ libssl64MDd.lib
        │ libssl64MT.lib
        │ libssl64MTd.lib
        └ static
           │ libcrypto64MD.lib
           │ libcrypto64MDd.lib
           │ libcrypto64MT.lib
           │ libcrypto64MTd.lib
           │ libssl64MD.lib
           │ libssl64MDd.lib
           │ libssl64MT.lib
           └ libssl64MTd.lib
    

在 Java 中,
java.util.Arrays
类提供的多种数组操作功能,可以有效地执行各种数组相关的操作,使得数组处理变得简单和高效。

打印数组

String[] arr = new String[] {"a", "b", "c", "d"};
System.out.println(Arrays.toString(arr)); // 输出 [a, b, c, d]

Arrays.toString(arr)
,不过是简单的一个调用,却让你的数组输出从一串地址,变成了清晰可辨的字符。

复制数组

要复制一个新的数组么?
copyOf

copyOfRange
给你全方位的选择:

String[] arr = new String[] {"a", "b", "c", "d"};
String[] copyOf2 = Arrays.copyOf(arr, 2); // [a, b]
System.out.println(Arrays.toString(copyOf2));

String[] copyOfRange = Arrays.copyOfRange(arr, 1, 3); // [b, c]
System.out.println(Arrays.toString(copyOfRange));

String[] copyOf10 = Arrays.copyOf(arr, 10); // [a, b, c, d, null, null, null, null, null, null]
System.out.println(Arrays.toString(copyOf10));


copyOf

copyOfRange
,无异于为数组之尺,想要复制前 N 个元素,使用
copyOf
,想要取其中一段,使用
copyOfRange

注意:若范围大于原数组,多出位置以 null 填充。

填充数组

String[] arr = new String[5];
Arrays.fill(arr, "java");
System.out.println(Arrays.toString(arr)); // [java, java, java, java, java]

如若数组空空如也,可用
Arrays.fill(arr, "str")
将它填满。每一个元素,都被赋予相同的值。

也可以使用
Arrays.setAll
填充,它允许传入一个函数,生成填充值。

// 生成 100以内的 随机数
IntFunction<Integer> intFunction = i -> new Random().nextInt(100);
Integer[] intArr = new Integer[5];
Arrays.setAll(intArr, intFunction);
System.out.println(Arrays.toString(intArr));
// output:[29, 17, 18, 11, 85]

数组比较

数据比较有两种方式,一是使用
Arrays.equals
,二是使用
Arrays.hashCode

Arrays.equals

String[] arr = new String[] {"a", "b", "c", "d"};
Object[] arr1 = new Object[] {arr, new String[] {"a", "b", "c", "d"}};
Object[] arr2 = new Object[] {arr, arr};

System.out.println(Arrays.equals(arr1, arr2));     // false
System.out.println(Arrays.deepEquals(arr1, arr2)); // tue

如果是一维数组比较,直接
Arrays.equals
比较没有问题。如果是多维数组,
Arrays.equals
并不会比较子数组中的元素值,此时需要使用
deepEquals
比较。

Arrays.hashCode

String[] arr = new String[] {"a", "b", "c", "d"};
Object[] arr2 = new Object[] {arr, arr};
System.out.println(Arrays.hashCode(arr2));
System.out.println(Arrays.deepHashCode(arr2));

arr[0] = null;
System.out.println(Arrays.hashCode(arr2));
System.out.println(Arrays.deepHashCode(arr2));

元素可能会变,如何捕捉这一瞬间的改变?
hashCode()

deepHashCode()
,便是这捕风捉影的法宝。
hashCode
方法会根据数组元素值计算出一个数字,如果元素值发生改变,则数字改变。但是同样对于多维数字,在计算数字时不会计算子数组中的元素,这种情况应该使用
deepHashCode
判断。

输出:

-2117384671
125140001

-2117384671
32668737

数组排序

// 生成 100以内的 随机数
IntFunction<Integer> intFunction = i -> new Random().nextInt(100);
Integer[] intArr = new Integer[5];
Arrays.setAll(intArr, intFunction);
System.out.println(Arrays.toString(intArr));
// output: [18, 31, 86, 59, 43]

Arrays.sort(intArr);
System.out.println(Arrays.toString(intArr));
// output: [18, 31, 43, 59, 86]

Arrays.sort
方法可以将元素排序。而
parallelSort()
可以并发排序,但是在数据量比较小的情况下,
Arrays.sort
可能效率更高。

二分查找

二分搜索可以快速找到数组中的指定元素的利器。

Integer[] intArr = new Integer[] {2, 3, 4, 5, 6, 7, 8, 9};
int index = Arrays.binarySearch(intArr, 3);
System.out.println("index:"+index);
System.out.println(intArr[index]);

注意事项:使用
binarySearch()
前确保数组是排序过的,否则结果将不可预知。

数组转 Stream

Java 8 引入的 Stream API 让数组操作更加现代化,可以链式操作。使用
Arrays.stream
可以让数组共享这一便利。

Integer[] intArr = new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
System.out.println(Arrays.stream(intArr).count()); // 10
ToIntFunction toIntFunction = i -> (int)i;
System.out.println(Arrays.stream(intArr).mapToInt(toIntFunction).sum());// 45

数组转换

如果你想把数组转成 List 怎么办?
Arrays.asList()
就是你需要的。

String[] arr = new String[] {"a", "b", "c", "d"};
List<String> list = Arrays.asList(arr);
System.out.println(list); // 输出 [a, b, c, d]
// list.add("e"); 报错

注意事项:
asList()
返回的列表
不支持增删操作
,它和原始数组是共享数据的。

并行前缀计算

使用
parallelPrefix
函数并行地累积给定数组中的每个元素。对于大型数组,并行前缀计算通常比顺序循环更有效。

示例:每一个元素转换成当前以及之前所有元素的和。

Integer[] intArr = new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
Arrays.parallelPrefix(intArr, (left, right) -> left + right);
System.out.println(Arrays.toString(intArr));

输出:
[0, 1, 3, 6, 10, 15, 21, 28, 36, 45]

总结

文章介绍了Java中
java.util.Arrays
类提供的多种数组操作功能,展示了其强大且多样的用途。可以有效地执行各种数组相关的操作,使得数组处理变得简单和高效。

本文
Github.com/niumoo/JavaNotes
仓库已经收录。
本文原发于网站:
Java Arrays 教程
本文原发于公众号:
程序猿阿朗