jjzjj

AssetBundle详解

夜槿笙歌 2024-03-26 原文

1.什么是AssetBundle

AssetBundle可以理解为一种包文件,它可以将特定平台的非代码资源(模型、纹理、预制体、音频等)包含在内,并在运行时由Unity加载。通过AssetBundle技术,可以在游戏中实现资源的热更新。通过热更新,一方面可以避免每次更新资源都需要用户重新下载整个游戏,另一方面也可以减小游戏安装包的大小,将资源文件延迟到更新时下载。另外,开发者还可以根据实际情况,选择内置的压缩算法来压缩AssetBundle,从而提高网络传输效率。

AssetBundle的组成

首先要明确一点,AssetBundle是一种容器,一个容器中可以包含其他文件。这些包含的文件一般分为两种:

  • 一个序列化文件。如模型、预制体等被拆解为一个个单独的对象,然后统一写入到一个文件中。
  • 资源文件。如图片、音频等二进制资源被单独存放,以便快速加载。

2.如何使用AssetBundle

构建AssetBundle

首先我们在场景中创建一个Cube,并将其添加为预制体。此时在预制体的Inspector面板中就可以看到AssetBundle的选项

点击这个下拉菜单就可以为其新建一个AssetBundle名称。注意这个名称是支持目录形式的,比如prefab/cube,它表示的是在prefab文件夹下创建名为cube的AssetBundle。如果两个不同的资源选择了同一个AssetBundle名称,则这两个资源会被一同打包到该AssetBundle包中。

另外,左侧的下拉菜单可以为AssetBundle自定义一个文件后缀,上图的AssetBundle构建出来后会以.ab结尾。(这里吐槽一句,一旦名称命名错了,只能将当前AssetBundle名称置空,然后选择Remove Unused Names才能删除,十分反人类!)

为AssetBundle分配好了资源,就可以进行构建了。不过Unity似乎并未内置Build的快捷选项,因此我们需要自己动手。代码也很简单,就是使用BuildPipeline提供的BuildAssetBundlesAPI。代码如下:

[MenuItem("Framework/AssetBundleHelper/Build", false, 1)]
private static void BuildAssetBundle()
{
	string path = "Assets/AssetBundles";
	if (!Directory.Exists(path))
	{
		Directory.CreateDirectory(path);
	}

	BuildPipeline.BuildAssetBundles(path, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);
	AssetDatabase.Refresh();
}

AssetDatabase.Refresh的作用是刷新工程目录的缓存,让新增的文件立刻显示在project面板中。

经过一次构建,我们就会发现在设定的目录下生成了几个文件:

很显然,这些文件是成对出现的,一种是不带任何后缀(包括自己定义后缀)的文件,另一种是.manifest文件。其中cube.ab文件是我们构建出来的AssetBundle本体,cube.ab.manifest是这个AssetBundle对应的清单文件。除此之外,还生成了一个与根目录同名的AssetBundle文件及其.manifest文件。这些文件具体的作用,在后面会讲到。

加载并使用AssetBundle

在AssetBundle构建完成后,我们就可以在运行时通过代码获取到对应的资源。这一步的核心API是AssetBundle.LoadFromFileAssetBundle.LoadAsset。其用法如下:

private static void LoadAssetBundle()
{
	string path = "Assets/AssetBundles";
	AssetBundle assetBundle = AssetBundle.LoadFromFile(path + "/prefab/cube.ab");
	if (assetBundle is null)
	{
		Debug.Log("指定AssetBundle不存在");
		return;
	}
	GameObject cube = assetBundle.LoadAsset<GameObject>("Cube");
	Instantiate(cube);
}

上面用到的这两个API也有对应的异步加载方式,后面会详细说明。

3.AssetBundle分组策略

还记得在打包之前,不同的资源可以选择相同的AssetBundle名称吗?这其实就是AssetBundle的分组。我们可以按照某种策略,将符合条件的一组资源分配进同一个AssetBundle。下面列举几个常见的分组策略:

  • 逻辑实体分组
    • 一个UI界面或所有UI界面一个包(贴图、布局信息)
    • 一个角色或所有角色一个包(模型、动画)
    • 场景之间共享的资源一个包
  • 类型分组
    • 所有音频资源一个包
    • 所有模型资源一个包
    • 所有材质资源一个包
  • 并发内容分组
    • 同一时间内需要用到的资源一个包
    • 一个关卡所需的资源一个包
    • 一个场景所需的资源一个包

分组策略需要根据具体的项目来定制,但分组的核心逻辑都是尽可能的减少资源更新时所需的代价。举个栗子,假如将需要频繁更新的资源A与不需要频繁更新的资源B打包在一起,那么每次A资源更新,就需要下载AB组成的整个包,而B又不需要更新,也就是说每次下载都会有一部分数据是毫无必要的。Unity的官方文档中给出了一些建议可供参考:

  • 将频繁更新的资源与较少更新的资源拆分到不同的AssetBundle
  • 将可能同时加载的对象分到一组。例如模型及其纹理和动画
  • 将其他包共享的资源放到一个单独的包中。
  • 如果不可能同时加载两组对象(例如标清资源和高清资源),请确保它们位于各自的 AssetBundle 中。
  • 如果一个 AssetBundle 中只有不到 50% 的资源经常同时加载,请考虑拆分该AssetBundle
  • 考虑将多个小型的(少于 5 到 10 个资源)但经常同时加载内容的 AssetBundle 组合在一起
  • 如果一组对象只是同一对象的不同版本,可以考虑通过后缀加以区分

依赖打包

上面提到了将其他包共享的资源放到一个单独的包中,我们可以通过一个具体的实例来验证为什么要这样做。

首先创建两个游戏物体,让他们依赖同一个材质,然后制成预制体

将这两个预制体打包进不同的AssetBundle

打包后的结果如下,可以看到两个包加起来大概有1M的大小

那么如果将这两个预制体依赖的材质资源单独打一个包,结果又会如何呢?

打包的结果如下

可以看到,三者的总体积要比之前小了很多。这是因为Unity在默认情况下,不会对依赖资源打包策略进行优化。Cube依赖了Material,那么cube包中也会包含Material;如果另外有一个Sphere也依赖了Material,那么sphere包中同样会包含Material。如果将Material单独包含进一个AssetBundle,在打包时,Unity就会识别出包之间的依赖,从而避免将依赖项一同打入包中。

但这样做也会存在风险。将材质提取到单个AssetBundle后,如果只使用预制体实例化游戏物体,就会出现材质丢失的情况

解决方案是在使用依赖资源之前,先加载依项的AssetBundle。(其实在cube实例化后再加载依赖包,其材质也会成功加载,只不过在依赖包加载完成之前,cube还是材质丢失的状态)

private static void LoadDependencyAssetBundle()
{
	string path = "Assets/AssetBundles";
	AssetBundle cubeAb = AssetBundle.LoadFromFile(path + "/cube");
	AssetBundle materialAb = AssetBundle.LoadFromFile(path + "/material");

	if (cubeAb is null)
	{
		Debug.Log("指定AssetBundle不存在");
		return;
	}
	GameObject cube = cubeAb.LoadAsset<GameObject>("Cube");
	Instantiate(cube);
}

加载正常

4.AssetBundle压缩

在Build AssetBundle的代码中,有一个BuildAssetBundleOptions的枚举类型参数,它主要控制的是Build时的一些可选项,且支持逻辑运算。

BuildPipeline.BuildAssetBundles(path, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);

这个枚举类型也列举出了Build时要采用的压缩算法:

  • BuildAssetBundleOptions.None 这是默认采用的压缩策略。使用LZMA压缩算法,压缩的包会比较小,但是加载时间会比较长,在使用前需要进行整体解压。一旦被解压,这些包就会重新采用LZ4算法重新进行压缩。LZ4是基于块的压缩算法,当Unity需要从LZ4压缩的包中访问资源时,只需要解压缩并读取包含所请求资源的字节块即可。优点是加载时间短,但相应的包的体积也会比较大。
  • BuildAssetBundleOptions.UncompressedAssetBundle不进行压缩。包占用空间很大,但加载速度会很快。
  • BuildAssetBundleOptions.ChunkBasedCompression使用LZ4进行压缩,包比较大,但加载速度也会比较快。

5.Manifest文件

.manifest文件就是在打包时生成的清单文件,可以用文本编辑器打开。打开后会有如下所示的内容

ManifestFileVersion: 0
CRC: 1610761843
Hashes:
  AssetFileHash:
    serializedVersion: 2
    Hash: 521947ff3d4497aceb05856a22ce7e4c
  TypeTreeHash:
    serializedVersion: 2
    Hash: 4db82f4e618e4630b2fa8228e9c6d8d1
HashAppended: 0
ClassTypes:
- Class: 1
  Script: {instanceID: 0}
- Class: 4
  Script: {instanceID: 0}
- Class: 21
  Script: {instanceID: 0}
- Class: 23
  Script: {instanceID: 0}
- Class: 33
  Script: {instanceID: 0}
- Class: 43
  Script: {instanceID: 0}
- Class: 65
  Script: {instanceID: 0}
SerializeReferenceClassIdentifiers: []
Assets:
- Assets/01_AssetBundlePractice/AssetBundle/Cube.prefab
Dependencies:
- [工程路径]/Assets/AssetBundles/material

其中比较重要的是最下面的两个路径。Assets记录了这个AssetBundle包含了哪个资源,Dependencies记录了这个AssetBundle依赖于哪些其他的AssetBundle。除了每个AssetBundle对应的.manifest文件,在根目录中还有一个以跟文件夹命名的.manifest文件,它打开后会有如下所示内容

ManifestFileVersion: 0
CRC: 3248957812
AssetBundleManifest:
  AssetBundleInfos:
    Info_0:
      Name: cube
      Dependencies:
        Dependency_0: material
    Info_1:
      Name: sphere
      Dependencies:
        Dependency_0: material
    Info_2:
      Name: material
      Dependencies: {}

可以看到,它主要记录了所有AssetBundle包的名称及其依赖信息。这个清单文件可以帮助我们获取到任意一个AssetBundle的依赖项。要获取到Manifest只需要像一般的AssetBundle一样进行加载,然后载入其中的AssetBundleManifest即可(其实这种方式加载的是AssetBundle包中的manifest文件)

string path = "Assets/AssetBundles";
AssetBundle assetBundle = AssetBundle.LoadFromFile(path + "/AssetBundles");
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

// 获取指定AssetBundle的所有依赖
string[] cubeAllDependencies = manifest.GetAllDependencies("cube");
// 获取指定AssetBundle的直接依赖
string[] sphereDirectDependencies = manifest.GetDirectDependencies("sphere");
// 获取所有AssetBundle
string[] assetBundles = manifest.GetAllAssetBundles();

6.加载AssetBundle

加载AssetBundle有四种方式,分别是从本地文件加载、从流加载、从内存加载以及从远程服务器加载。它们分别对应如下几个API:

  • AssetBundle.LoadFromFile从本地加载
  • AssetBundle.LoadFromMemory从内存中加载
  • AssetBundle.LoadFromStream从流中加载
  • UnityWebRequestAssetBundle.GetAssetBundle从远程服务器加载(也可以从本地加载)

AssetBundle.LoadFromFile

这是加载AssetBundle最快的方法。如果AssetBundle未压缩或采用了数据块(LZ4)压缩方式,LoadFromFile 将直接从磁盘加载AssetBundle。使用此方法加载完全压缩 (LZMA) 的AssetBundle将首先解压缩AssetBundle,然后再将其加载到内存中。它的使用方法如下:

// 同步方式
private static void LoadMethodSync()
{
	string path = "Assets/AssetBundles/cube";
	AssetBundle assetBundle = AssetBundle.LoadFromFile(path);
	GameObject cube = assetBundle.LoadAsset<GameObject>("Cube");
	Instantiate(cube);
}
// 异步方式
IEnumerator LoadMethodAsync()  
{  
    string path = "Assets/AssetBundles/cube";  
    AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(path);  
    yield return request;  
    var cube = request.assetBundle.LoadAsset<GameObject>("Cube");  
    Instantiate(cube);  
}

AssetBundle.LoadFromMemory

该方法传入一个包含AssetBundle数据的字节数组。也可以根据需要传递CRC校验码。如果捆绑包采用的是LZMA压缩方式,将在加载时解压缩AssetBundle。LZ4 压缩包则会以压缩状态加载。当下载的是加密数据并需要从未加密的字节创建AssetBundle时会用到。它的使用方法如下:

// 同步方法
private static void LoadMethodSync()  
{  
    string path = "Assets/AssetBundles/cube";  
    AssetBundle assetBundle = AssetBundle.LoadFromMemory(File.ReadAllBytes(path));  
    Instantiate(cube);  
}
// 异步方法
IEnumerator LoadMethodAsync()  
{  
    string path = "Assets/AssetBundles/cube";  
    AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path));  
    yield return request;  
    var cube = request.assetBundle.LoadAsset<GameObject>("Cube");  
    Instantiate(cube);  
}

AssetBundle.LoadFromStream

从托管Stream加载AssetBundle。如果是LZMA压缩,则将数据解压缩到内存。如果是未压缩或使用块压缩的捆绑包,则直接从Stream读取。需要注意的是,在加载AssetBundle或其中的资源时,不应该释放Stream资源,应该在AssetBundle.Unload之后再释放。相比于从内存加载,从流加载的优势是加载加密资源时,占用的内存较小。它的用法如下:

// 同步方法
private static void LoadMethodSync()
{
	AssetBundle.UnloadAllAssetBundles(true);
	string path = "Assets/AssetBundles/cube";
	
	FileStream stream = new FileStream(path, FileMode.Open,FileAccess.Read);
	AssetBundle assetBundle = AssetBundle.LoadFromStream(stream);
	GameObject cube = assetBundle.LoadAsset<GameObject>("Cube");
	Instantiate(cube);
	assetBundle.Unload(false);
	stream.Close();
}
// 异步方法
IEnumerator LoadMethodAsync()
{
	string path = "Assets/AssetBundles/cube";
	FileStream stream = new FileStream(path, FileMode.Open,FileAccess.Read);
	AssetBundleCreateRequest request = AssetBundle.LoadFromStreamAsync(stream);
	yield return request;
	var cube = request.assetBundle.LoadAsset<GameObject>("Cube");
	Instantiate(cube);
	request.assetBundle.Unload(false);
	stream.Close();
}

UnityWebRequestAssetBundle.GetAssetBundle

该方法会创建经过优化的 UnityWebRequest,以通过 HTTP GET 下载AssetBundle。请求成功后,使用DownloadHandlerAssetBundle方法将数据流式传输到缓冲区并解压缩。与一次性下载所有数据相比,这种方式节省了很多内存。需要注意,如果使用UnityWebRequestAssetBundle.GetAssetBundle加载本地数据,需要在路径中加上file://前缀。其用法如下:

IEnumerator LoadMethodAsync()
{
	string path = @"[远程资源路径]";

	UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(path);
	yield return request.SendWebRequest();
	AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(request);
	GameObject cube = assetBundle.LoadAsset<GameObject>("Cube");
	Instantiate(cube);

}

7.从AssetBundle加载资源

AssetBundle包加载完成后,就可以加载包中的资源。前面已经多次用到,这里只罗列出对应的API

string path = "Assets/AssetBundles/cube";
AssetBundle assetBundle = AssetBundle.LoadFromFile(path);

// 同步加载指定资源
GameObject cube = assetBundle.LoadAsset<GameObject>("Cube");
// 异步加载指定资源
AssetBundleRequest request1 = assetBundle.LoadAssetAsync<GameObject>("Cube");
// 同步加载所有资源
UnityEngine.Object[] objs = assetBundle.LoadAllAssets();
// 异步加载所有资源
AssetBundleRequest request2 = assetBundle.LoadAllAssetsAsync();

8.AssetBundle卸载

在AssetBundle的资源使用完成后,需要对其进行卸载,以释放其占用的内存空间。AssetBundle的卸载主要靠AssetBundle.Unload这个API实现。该方法需要传入一个bool类型的参数,如果传入的是true,则会卸载AssetBundle本身及从AssetBundle加载的全部资源。如果传入的是false,则会保留已经加载的资源。
在大多数情况下都推荐使用AssetBundle.Unload(true),因为如果传入false会造成如下问题(图片来自Unity官方文档):

当参数为false时,会中断材质资源M与当前AssetBundle的联系

即便再次加载 AB 并且调用 AB.LoadAsset(),Unity也不会将现有 M 副本重新链接到新加载的材质

如果现在创建了一个预制体的实例并引用了材质M,也不会使用现有的M,而是会加载一个新的M副本

这样就导致了M在内存中存在两份,造成内存资源的浪费。因此在大多数情况下应该使用AssetBundle.Unload(true)来保证对象不会重复。两种常用的方式是:

  • 在应用程序生命周期中具有明确定义的卸载AssetBundle的时间点,例如在关卡之间或在加载期间。
  • 维护单个对象的引用计数,仅当未使用所有组成对象时才卸载 AssetBundle。这允许应用程序卸载和重新加载单个对象,而无需复制内存。

如果不得不使用AssetBundle.Unload(false),则只能用以下两种方式卸载单个对象:

  • 在场景和代码中消除对不需要的对象的所有引用。完成此操作后,调用Resources.UnloadUnusedAssets
  • 以非附加方式加载场景。这样会销毁当前场景中的所有对象并自动调用Resources.UnloadUnusedAssets

8.AssetBundle浏览工具

Unity官方提供了AssetBundle的浏览工具插件,但并没有默认集成,需要我们手动进行安装。安装方式如下:

  • 在项目中打开 Unity Package Manager(菜单:Windows > Package Manager)。
  • 单击窗口左上角的 +(添加)按钮。
  • 选择 Add package from git URL…
  • 输入https://github.com/Unity-Technologies/AssetBundles-Browser.git 作为 URL
  • 单击 Add。

安装完成后,通过Window > AssetBundle Browser打开工具窗口。通过该工具可以查看项目中所有AssetBundle信息,也可以进行Build操作。操作比较简单,这里不再赘述。



参考文献:
[1]Siki学院.AssetBundle(创建打包)入门学习(基于Unity2017)[DB/OL].https://www.sikiedu.com/my/course/74.
[2]Unity官方手册[DB/OL].https://docs.unity3d.com/cn/current/Manual/AssetBundlesIntro.html.

有关AssetBundle详解的更多相关文章

  1. 物联网MQTT协议详解 - 2

    一、什么是MQTT协议MessageQueuingTelemetryTransport:消息队列遥测传输协议。是一种基于客户端-服务端的发布/订阅模式。与HTTP一样,基于TCP/IP协议之上的通讯协议,提供有序、无损、双向连接,由IBM(蓝色巨人)发布。原理:(1)MQTT协议身份和消息格式有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。MQTT传输的消息分为:主题(Topic)和负载(payload)两部分Topic,可以理解为消息的类型,订阅者订阅(Su

  2. Tcl脚本入门笔记详解(一) - 2

    TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是

  3. 【详解】Docker安装Elasticsearch7.16.1集群 - 2

    开门见山|拉取镜像dockerpullelasticsearch:7.16.1|配置存放的目录#存放配置文件的文件夹mkdir-p/opt/docker/elasticsearch/node-1/config#存放数据的文件夹mkdir-p/opt/docker/elasticsearch/node-1/data#存放运行日志的文件夹mkdir-p/opt/docker/elasticsearch/node-1/log#存放IK分词插件的文件夹mkdir-p/opt/docker/elasticsearch/node-1/plugins若你使用了moba,直接右键新建即可如上图所示依次类推创建

  4. 【Elasticsearch基础】Elasticsearch索引、文档以及映射操作详解 - 2

    文章目录概念索引相关操作创建索引更新副本查看索引删除索引索引的打开与关闭收缩索引索引别名查询索引别名文档相关操作新建文档查询文档更新文档删除文档映射相关操作查询文档映射创建静态映射创建索引并添加映射概念es中有三个概念要清楚,分别为索引、映射和文档(不用死记硬背,大概有个印象就可以)索引可理解为MySQL数据库;映射可理解为MySQL的表结构;文档可理解为MySQL表中的每行数据静态映射和动态映射上面已经介绍了,映射可理解为MySQL的表结构,在MySQL中,向表中插入数据是需要先创建表结构的;但在es中不必这样,可以直接插入文档,es可以根据插入的文档(数据),动态的创建映射(表结构),这就

  5. 最强Http缓存策略之强缓存和协商缓存的详解与应用实例 - 2

    HTTP缓存是指浏览器或者代理服务器将已经请求过的资源保存到本地,以便下次请求时能够直接从缓存中获取资源,从而减少网络请求次数,提高网页的加载速度和用户体验。缓存分为强缓存和协商缓存两种模式。一.强缓存强缓存是指浏览器直接从本地缓存中获取资源,而不需要向web服务器发出网络请求。这是因为浏览器在第一次请求资源时,服务器会在响应头中添加相关缓存的响应头,以表明该资源的缓存策略。常见的强缓存响应头如下所述:Cache-ControlCache-Control响应头是用于控制强制缓存和协商缓存的缓存策略。该响应头中的指令如下:max-age:指定该资源在本地缓存的最长有效时间,以秒为单位。例如:Ca

  6. IDEA 2022 创建 Spring Boot 项目详解 - 2

    如何用IDEA2022创建并初始化一个SpringBoot项目?目录如何用IDEA2022创建并初始化一个SpringBoot项目?0. 环境说明1.  创建SpringBoot项目 2.编写初始化代码0. 环境说明IDEA2022.3.1JDK1.8SpringBoot1.  创建SpringBoot项目        打开IDEA,选择NewProject创建项目。        填写项目名称、项目构建方式、jdk版本,按需要修改项目文件路径等信息。        选择springboot版本以及需要的包,此处只选择了springweb。        此处需特别注意,若你使用的是jdk1

  7. 详解Unity中的粒子系统Particle System (二) - 2

    前言上一篇我们简要讲述了粒子系统是什么,如何添加,以及基本模块的介绍,以及对于曲线和颜色编辑器的讲解。从本篇开始,我们将按照模块结构讲解下去,本篇主要讲粒子系统的主模块,该模块主要是控制粒子的初始状态和全局属性的,以下是关于该模块的介绍,请大家指正。目录前言本系列提要一、粒子系统主模块1.阅读前注意事项2.参考图3.参数讲解DurationLoopingPrewarmStartDelayStartLifetimeStartSpeed3DStartSizeStartSize3DStartRotationStartRotationFlipRotationStartColorGravityModif

  8. VMware虚拟机与本地主机进行磁盘共享(详解) - 2

    VMware虚拟机与本地主机进行磁盘共享前提虚拟机版本为Windows10(专业版,不是可能有问题)本地主机为家庭版或学生版(此版本会有问题,但有替代方式)最好是专业版VMware操作1.关闭防火墙,全部关闭。2.打开电脑属性3.点击共享-》高级共享-》权限4.如果没有everyone,就添加权限选择完全控制,然后应用确定。5.打开cmd输入lusrmgr.msc(只有专业版可以打开)如果不是专业版,可以跳过这一步。点击用户-》administrator密码要复杂密码,否则不行。推荐admaiN@1234类型的密码。设置完密码,点击属性,将禁用解开。6.如果虚拟机的windows不是专业版,可

  9. ElasticSearch之 ik分词器详解 - 2

    IK分词器本文分为简介、安装、使用三个角度进行讲解。简介倒排索引众所周知,ES是一个及其强大的搜索引擎,那么它为什么搜索效率极高呢,当然和他的存储方式脱离不了关系,ES采取的是倒排索引,就是反向索引;常见索引结构几乎都是通过key找value,例如Map;倒排索引的优势就是有效利用Value,将多个含有相同Value的值存储至同一位置。分词器为了配合倒排索引,分词器也就诞生了,只有合理的利用Value,才会让倒排索引更加高效,如果一整个Value不进行任何操作直接进行存储,那么Value和key毫无区别。分词器Analyzer通常会对Value进行操作:一、字符过滤,过滤掉html标签;二、分

  10. Educational Codeforces Round 146 (Rated for Div. 2)(B,E详解) - 2

    题外话:抑郁场,开局一小时只出A,死活想不来B,最后因为D题出锅ura才保住可怜的分。但咱本来就写不到DB-LongLegs(数论)本题题解法一学自同样抑郁的知乎作者幽血魅影的题解,有讲解原理。法二来着知乎巨佬cup-pyy(大佬说《不难发现》呜呜)题意三种操作:向上走mmm步向右走mmm步给自己一次走的步数加111,即使得m=m+1m=m+1m=m+1问从(0,0)(0,0)(0,0)走到(a,b)(a,b)(a,b)的最小操作次数,值得注意的是操作三不可逆。解析假设我们最终一步的大小增长到mmm,那么在这个过程中我能以[1,m][1,m][1,m](当步数增长到该数时)之间的任何数字向上或

随机推荐