Unity AssetsBundle 详解

news2024/11/26 10:45:53

文章目录

  • 1.AssetBundle 概念
  • 2.AssetBundle 优势
  • 3.AssetBundle 特性
  • 4.AssetBundle 使用流程
    • 4.1 分组
    • 4.2 打包
    • 4.3 加载包
    • 4.4 加载资源
    • 4.5 卸载资源
  • 5.AssetBundleManifest
  • 6.AssetBundle的内存占用
  • 7.AB包资源加密

1.AssetBundle 概念

AssetBundle又称AB包,是Unity提供的一种用于存储资源的资源压缩包。

Unity中的AssetBundle系统是对资源管理的一种扩展,通过将资源分布在不同的AB包中可以最大程度地减少运行时的内存压力,可以动态地加载和卸载AB包,继而有选择地加载内容。

2.AssetBundle 优势

  1. AB包存储位置自定义,继而可放入可读可写的路径下便于实现热更新
  2. AB包自定义压缩方式,可以选择不压缩或选择LZMA和LZ4等压缩方式,减小包的大小,更快的进行网络传输。
  3. 资源可分布在不同的AB包中,最大程度减少运行时的内存压力, 可做到即用即加载,有选择的加载需要的内容。
  4. AB包支持后期进行动态更新,显著减小初始安装包的大小,非核心资源以AB包形式上传服务器,后期运行时动态加载,提高用户体验。

AssetBundle和Resources的比较

AssetBundleResources
资源可分布在多个包中所有资源打包成一个大包
存储位置自定义灵活必须存放在Resources目录下
压缩方式灵活(LZMA,LZ4)资源全部会压缩成二进制
支持后期进行动态更新打包后资源只读无法动态更改

3.AssetBundle 特性

  1. AB包可以存储绝大部分Unity资源但无法直接存储C#脚本,所以代码的热更新需要使用Lua或者存储编译后的DLL文件。
  2. AB包不能重复进行加载,当AB包已经加载进内存后必须卸载后才能重新加载。
  3. 多个资源分布在不同的AB包可能会出现一个预制体的贴图等部分资源不在同一个包下,直接加载会出现部分资源丢失的情况,即AB包之间是存在依赖关系的,在加载当前AB包时需要一并加载其所依赖的包。
  4. 打包完成后,会自动生成一个主包(主包名称随平台不同而不同),主包的manifest下会存储有版本号、校验码(CRC)、所有其它包的相关信息(名称、依赖关系)

4.AssetBundle 使用流程

AssetBundle的使用流程比较繁琐,大致上分为五个阶段:分组、打包、加载包、加载资源、卸载包

4.1 分组

在这里插入图片描述
分组策略:

  1. 按逻辑实体分组
    • 一个UI界面或者所有UI界面一个包(这个界面里面的贴图和布局信息一个包)
    • 一个角色或者所有角色一个包(这个角色里面的模型和动画一个包)
    • 所有的场景所共享的部分一个包(包括贴图和模型)
      2.按照类型分组
      所有声音资源打成一个包,shader打成一个包,模型打成一个包,材质打成一个包
      3.按照使用分组
      把在某一时间内使用的所有资源打成一个包。可以按照关卡分,一个关卡所需要的所有资源包括角色、贴图、声音等打成一个包。也可以按照场景分,一个场景所需要的资源一个包
  • 注意事项:
  1. 经常更新的资源放在一个单独的包里面,跟不经常更新的包分离
  2. 把需要同时加载的资源放在一个包里面
  3. 可以把其他包共享的资源放在一个单独的包里面
  4. 把一些需要同时加载的小资源打包成一个包
  5. 如果对于一个同一个资源有两个版本,可以考虑通过后缀来区分,例如v1、v2、v3

4.2 打包

1、代码打包

Using Unity.Editor;

[MenuItem("Assets/Build AssetBundles")]
static void BuildAllAssetBundles()
{
   string assetBundleDirectory = "Assets/AssetBundles";
   if (!Directory.Exists(assetBundleDirectory))
   {
      Directory.CreateDirectory(assetBundleDirectory);
   }
   //BuildTarget -- 打包平台,打包的资源只能在该平台下使用
   BuildPipeline.BuildAssetBundles(assetBundleDirectory,
                                        BuildAssetBundleOptions.None,
                                        BuildTarget.StandaloneWindows);
}

  此脚本将在 Assets 菜单底部创建一个名为 Build AssetBundles 的菜单项,该菜单项将执行与该标签关联的函数中的代码。单击 Build AssetBundles 时,将随构建对话框一起显示一个进度条。此过程将会获取带有 AssetBundle 名称标签的所有资源,并将它们放在 assetBundleDirectory 定义的路径中的文件夹中。
BuildAssetBundleOptions:

  • None: 默认构建AssetBundle的方式。使用LZMA算法压缩,此算法压缩包小,但是加载时间长,而且使用之前必须要整体解压。解压以后,这个包又会使用LZ4算法重新压缩,这样这种包就不要对其整体解压了。(也就是第一次解压很慢,之后就变快了。
  • UncompressedAssetBundle: 不压缩数据,包大,但是加载很快。
  • ChunkBaseCompression: 使用LZ4算法压缩,压缩率没有LZMA高,但是加载资源不必整体解压。这种方法中规中矩,比较常用。
  • 其它的参数请自行百度

2、AssetBundleBrowse 工具打包
AssetBundleBrowser插件的获取
Unity 2019版本可以直接在Windows —> PackageManager里面找到此插件并直接安装
或在https://github.com/Unity-Technologies/AssetBundles-Browser下载
在这里插入图片描述
2、AssetBundleBrowser面板的使用
通过 Windows --> AssetBundle Browser下打开AB包管理面板 一共有三个面板
Configure面板
查看当前AB包及其内部资源的基本情况(大小,资源,依赖情况等)
在这里插入图片描述
Build面板
负责AssetBundle打包的相关设置 按Build即可进行打包
在这里插入图片描述
三种压缩方式:

  • NoCompression:不压缩,解压快,包较大,不建议使用。
  • LZMA: 压缩最小,解压慢,用一个资源要解压包下所有资源。
  • LZ4: 压缩稍大,解压快,用什么解压什么,内存占用低,更建议使用。
    一般需要进行更改的设置即为图中勾选的相关选项设置。

Inspect面板
主要用来查看已经打包后的AB包文件的一些详细情况(大小,资源路径等)
在这里插入图片描述
打包后的 manifest 文件
打包完成后,会自动生成一个主包(主包名称随平台不同而不同),主包的manifest下会存储有版本号、校验码(CRC)、所有其它包的相关信息(名称、依赖关系)在这里插入图片描述
在这里插入图片描述

4.3 加载包

// 从文件中加载
AssetBundle.LoadFromFile(abPath);
AssetBundle.LoadFromFileAsync();
// 从内存中加载
AssetBundle.LoadFromMemory();
// 从网络下载
UnityWebRequestAssetBundle.GetAssetBundle();

① AssetBundle.LoadFromFile(Async)

最常用也是最推荐使用的方法。
这个方法可以高性能的加载没有压缩或者通过LZ4方法压缩的AssetBundle。
使用这个方法的时候,大部分平台只加载AssetBundle的包头,剩下的数据仍然在硬盘上。AssetBundles的对象会按需要进行加载,或者被间接引用到的时候加载。在这个情境下不会有额外的内存被消耗。

② AssetBundle.LoadFromMemory(Async)

不建议使用的API。常用于通过加密过的ab资源的加载。
如果你通过下载或者其他方法已经把Assetbundle的字节保存到了内存中的一个数组中,就能通过此函数进行加载。
使用这个方法的时候,底层会把内存中已经加载好的assetbundle数组byte[]拷贝到一个新分配的、相邻的地址上,如果使用了LZMA压缩方法,拷贝的时候还会顺便解压。造成的问题是内存使用的峰值至少是assetbundle的两倍。

③ UnityWebRequestAssetBundle.GetAssetBundle()

从远端服务器加载ab资源时推荐的方法。
UnityWebRequest类允许开发者定义unity应该怎么处理下载的数据,避免不必要的内存消耗。
使用DownloadHandlerAssetBundle 类可以对下载进行配置,使用一个worker线程流式下载数据,并存放在一个可变大小的buffer,或者某个临时的存储空间中。DownloadHandler 不会复制已下载的bytes。
对于LZMA压缩的Assetbundles,下载的时候会顺便解压,存放的时候通过LZ4再次压缩,可以通过Caching.CompressionEnabled 设置。
下载完成后,可以通过DownloadHandler的属性对assetbundle进行访问,这个行为类似于AssetBundle.LoadFromFile。

 UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(assetBundlePath, 0);
 var opt = request.SendWebRequest();
 opt.completed += (_) =>
 {
     AssetBundle = DownloadHandlerAssetBundle.GetContent(request);
 };

④ WWW.LoadFromCacheOrDownload
旧版API,因为性能问题已被废弃,建议使用UnityWebRequest。

4.4 加载资源

// 泛型加载
T obj = ab.LoadAsset<T>(ResourceName); 
// 参数指明资源类型 防止重名
Object obj = ab.LoadAsset(ResourceName, Type); 

// 异步加载,加载较大资源的时候使用
AssetBundle.LoadAssetAsync<GameObject>(assetName)  
// 参数指明资源类型 防止重名
Object obj = ab.LoadAssetAsync(resName,Type); 

// 加载AB包中所有的对象,不包含依赖的包
AssetBundle.LoadAllAssets();
// 异步加载全部资源
AssetBundle.LoadAllAssetsAsync();
// 加载资源及其子资源
AssetBundle.LoadAssetWithSubAssets();

示例:

// 加载AB包
AssetBundle loadedAssetBundle = AssetBundle.LoadFromFile(path);

//加载单个游戏对象:
GameObject gameObject = loadedAssetBundle.LoadAsset<GameObject>(assetName);
//加载所有资源:
Unity.Object[] objectArray = loadedAssetBundle.LoadAllAssets();
//异步加载资源:
AssetBundleRequest request = loadedAssetBundleObject.LoadAssetAsync<GameObject>(assetName);
yield return request;
var loadedAsset = request.asset;
//异步加载所有资源:
AssetBundleRequest request = loadedAssetBundle.LoadAllAssetsAsync();
yield return request;
var loadedAssets = request.allAssets;

4.5 卸载资源

为什么要卸载AssetBundle?

每次加载AB包都会有内存的占用,如果一直不卸载,随着游戏的运行不停加载使用到的新AB包,内存占用会越来越大。
哪些AssetBundle可以卸载?
当前运行时没有使用到的Assetbundle可以卸载。没有使用到的Assetbundle指的是内部的资源当前未被引用。
想要卸载AssetBundle,首先需要了解它在内存中的占用组成。AssetBundle的内存占用查看 5.AssetBundle的内存占用

// 会把 Bundle 卸载,但是已经从 Bundle 里加载出来的 资源 是不会被卸载的。
ab.Unload(false)

// 不但会卸载 Bundle,也会卸载已经从 Bundle 里加载出来的 所有资源,哪怕这些 资源 还被引用着。
ab.Unload(true)

常用的卸载策略

需要通过引用计数管理ab包。
推荐的卸载方式:
加载ab包中资源时,引用计数+1,释放已加载的资源时,引用计数-1;
当引用计数为0时,调用AssetBundle.Unload(true) 彻底卸载ab包资源和从ab包中加载出来的资源;

5.AssetBundleManifest

AssetBundleManifest 文件内容:
主包的manifest下会存储有版本号、校验码(CRC)、所有其它包的相关信息(名称、依赖关系)
在这里插入图片描述
获取AssetBundleManifest:

// 编辑器下:
AssetBundleManifest manifest = BuildPipeline.BuildAssetBundles(outputPath, options, buildTarget);

// 正式环境下:
AssetBundle ab = AssetBundle.LoadFromFile(abPath);
AssetBundleManifest assetBundleManifest = ab.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

AssetBundleManifest API:

方法说明
string[] GetAllAssetBundles ()获取清单中的所有 AssetBundle。
string[] GetAllDependencies (string assetBundleName)获取给定 AssetBundle 的所有依赖 AssetBundle。
Hash128 GetAssetBundleHash (string assetBundleName)获取给定 AssetBundle 的哈希值。
string[] GetDirectDependencies (string assetBundleName)获取给定 AssetBundle 的直接依赖 AssetBundle。
string[] GetAllAssetBundlesWithVariant ()返回所有使用给定扩展名的assetbundlenames

6.AssetBundle的内存占用

在这里插入图片描述
黑色部分:www类本身占用内存,通过www接口加载AssetBundle才会有这部分内存,www对象保留了一份对WebStream数据(粉色部分)的引用。使用www =null 或者 www.dispose()释放。
其中www.dispose()会立即释放,而www = null会等待垃圾回收。释放www后WebStream的引用计数会相应减一。

橙色部分:官方称为WebStream数据,是数据真正的存储区域。当AssetBundle被加载进来后,这部分内存就被分配了。
它包含3个内容:压缩后的AssetBundle本身、解压后的资源以及一个解压缓冲区(图绿)。无论www(黑色部分)还是后面会提到的AssetBundle对象(粉色部分),都只是有一个结构指向了WebStream数据,从而能对外部提供操作真正资源数据的方法。而当www对象和AssetBundle对象释放时,WebStream数据的引用计数也会相应减1。当WebStream数据引用计数为0时,系统会自动释放。
但为了不频繁地开辟和销毁解压Buffer,其中绿色Decompression解压缓冲区Unity会至少保留一份。例如同时加载3个AssetBundle时,系统会生成3个Decompression Buffer,当解压完成后,系统会销毁两个。

粉色部分:AssetBundle对象,引用了橙色WebStream数据部分,并提供了从WebStream数据中加载资源的接口。通过AssetBundle.Unload(bool unloadAllLoadedObjects)释放。
如果调用AssetBundle.Unload(false),将释放AssetBundle对象本身,其对WebStream引用也将减少,从而可能引起WebStream释放,我们也就无法再通过接口或依赖关系从该AssetBundle加载资源。但已加载的资源还可以正常使用。
如果调用的是AssetBundle.Unload(true),不仅会释放WebStream部分,所有被加载出来的资源将被释放。

无论true或false,AssetBundle.Unload()都将销毁AssetBundle,销毁后调用该AssetBundle对象的任何方法都不会生效或产生报错,也就是说这个接口只能被调用一次,不能先调用unload(false)再调用unload(true)。

7.AB包资源加密

能被AssetStudio等解密软件解密到的资源

  • Resources文件夹下的资源文件
  • 被场景直接引用的资源文件
  • StreamingAssets下的资源文件(包含未加密的ab资源)

加密思路:

  1. 在构建完AB包后,可以将AB包中的内容以byte[]形式读取。
  2. 之后选用任意加密方式对该byte[]加密。
  3. 加密完后重新写入AB包中。
  4. AB包加密完成。

这样对AB包加密之后,如果使用AssetBundle.LoadFromFile()来加载加密的AB包是会报错的,因为Unity已经无法识别加密过后的内容了,这样也就防止了别人随意对AB包进行的读取和加载,保证了资源的安全性。

解密思路:

  1. 先以byte[]形式读取AB包中的内容。
  2. 之后使用对应的解密算法对该byte[]进行解密。
  3. 解密过后的byte[]通过AssetBundle.LoadFromMemory()来进行加载。
  4. AB包加载完成。
using System.IO;
using System.Security.Cryptography;
using UnityEngine;

/// <summary>
/// AB资源加解密
/// </summary>
public static class AssetBundleEncryption
{
    #region 基于偏移量加密
    private const int OffsetSize = 32; // 插入的字节数

    // 加密AssetBundle
    public static void EncryptAssetBundle(string inputPath, string outputPath)
    {
        byte[] originalBytes = File.ReadAllBytes(inputPath);
        byte[] offsetBytes = new byte[OffsetSize];

        // 随机生成插入的二进制数据(或使用特定标记)
        System.Random random = new System.Random();
        random.NextBytes(offsetBytes);

        byte[] encryptedBytes = new byte[originalBytes.Length + OffsetSize];
        System.Buffer.BlockCopy(offsetBytes, 0, encryptedBytes, 0, OffsetSize);
        System.Buffer.BlockCopy(originalBytes, 0, encryptedBytes, OffsetSize, originalBytes.Length);

        File.WriteAllBytes(outputPath, encryptedBytes);
        Debug.Log("加密完成: " + outputPath);
    }

    // 解密AssetBundle
    public static AssetBundle DecryptAssetBundle(string encryptedPath)
    {
        return AssetBundle.LoadFromFile(encryptedPath, 0, OffsetSize);
    }
    #endregion

    #region 基于AES加密
    private static readonly byte[] Key = new byte[32]; // 256位密钥
    private static readonly byte[] IV = new byte[16];  // 128位初始化向量

    static void AssetBundleAesEncryption()
    {
        // 生成随机密钥和初始化向量
        using (var rng = new RNGCryptoServiceProvider())
        {
            rng.GetBytes(Key);
            rng.GetBytes(IV);
        }
    }

    // 使用AES加密AssetBundle
    public static void AESEncryptAssetBundle(string inputPath, string outputPath)
    {
        byte[] originalBytes = File.ReadAllBytes(inputPath);
        byte[] encryptedBytes;

        using (Aes aes = Aes.Create())
        {
            aes.Key = Key;
            aes.IV = IV;

            ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

            using (MemoryStream ms = new MemoryStream())
            {
                using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                {
                    cs.Write(originalBytes, 0, originalBytes.Length);
                }
                encryptedBytes = ms.ToArray();
            }
        }

        using (FileStream fs = new FileStream(outputPath, FileMode.Create, FileAccess.Write))
        {
            fs.Write(encryptedBytes, 0, encryptedBytes.Length);
        }

        Debug.Log("AES加密完成: " + outputPath);
    }

    // 解密并加载AES加密的AssetBundle
    public static AssetBundle AesEncryptedAssetBundle(string encryptedPath)
    {
        byte[] encryptedBytes = File.ReadAllBytes(encryptedPath);
        byte[] decryptedBytes;

        using (Aes aes = Aes.Create())
        {
            aes.Key = Key;
            aes.IV = IV;

            ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);

            using (MemoryStream ms = new MemoryStream(encryptedBytes))
            {
                using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                {
                    using (MemoryStream decryptedStream = new MemoryStream())
                    {
                        cs.CopyTo(decryptedStream);
                        decryptedBytes = decryptedStream.ToArray();
                    }
                }
            }
        }

        string tempFilePath = Path.GetTempFileName();
        File.WriteAllBytes(tempFilePath, decryptedBytes);

        AssetBundle bundle = AssetBundle.LoadFromFile(tempFilePath);
        File.Delete(tempFilePath);

        // AssetBundle bundle = AssetBundle.LoadFromMemory(decryptedBytes);
        Debug.Log("AES解密并加载完成: " + encryptedPath);
        return bundle;
    }
    #endregion
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1903757.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python视觉轨迹几何惯性单元超维计算结构算法

&#x1f3af;要点 &#x1f3af;视觉轨迹几何惯性单元超维计算结构算法 | &#x1f3af;超维计算结构视觉场景理解 | &#x1f3af;超维计算结构算法解瑞文矩阵 | &#x1f3af;超维矢量计算递归神经算法 &#x1f36a;语言内容分比 &#x1f347;Python蒙特卡罗惯性导航 蒙…

【漏洞复现】宏景eHR LoadOtherTreeServlet SQL注入漏洞

0x01 产品简介 宏景eHR人力资源管理软件是一款人力资源管理与数字化应用相融合&#xff0c;满足动态化、协同化、流程化、战略化需求的软件。 0x02 漏洞概述 宏景eHR LoadOtherTreeServlet接口处存在SQL注入漏洞&#xff0c;未经身份验证的远程攻击者除了可以利用 SQL 注入漏…

[Multi-Modal] MDETR 论文及代码学习笔记

代码地址&#xff1a;https://github.com/ashkamath/mdetr 论文地址&#xff1a;https://arxiv.org/abs/2104.12763 多模态推理系统依靠预先训练的目标检测器从图像中提取感兴趣区域&#xff08;边界框包围区域&#xff09;。然而&#xff0c;这个关键模块通常被用作黑匣子&…

MySQL高级----详细介绍MySQL中的锁

概述 锁是计算机协调多个进程或线程并发访问某一资源的机制&#xff0c;为了解决数据访问的一致性和有效性问题。在数据库中&#xff0c;除传统的计算资源(CPU、RAN、I/O&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、…

windows无法访问github

##一、如果发现windows无法访问github时 一般就是我们的dns出现了问题&#xff0c;此时我们需要更换一个dns访问 ##二、解决方法 首先我们访问ip查询地址&#xff0c; https://ipchaxun.com/github.com/ 可更换下面历史ip进行测试&#xff0c;在windows的cmd里面输入ping git…

【C++深度探索】:继承(定义赋值兼容转换作用域派生类的默认成员函数)

✨ 愿随夫子天坛上&#xff0c;闲与仙人扫落花 &#x1f30f; &#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;C学习 &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞…

pin是什么?管脚

1.平面分割 1)启动Allegro PCB design &#xff0c;打开.brd。深色部分属于一个net&#xff0c;要做一下修改&#xff0c;将上面的pin包含进shape中&#xff0c;i进行a&#xff0c;b两步操作&#xff0c;删除以前存在的Anti Etch下的line&#xff0c;再将其进行补齐 使它保住上…

MSPM0G3507——OPENMV给M0传数据(用数据包)互相通信(以循迹为例)

OPENMV端代码 # main.py -- put your code here! import pyb, sensor, image, math, time from pyb import UART import ustruct from image import SEARCH_DS, SEARCH_EX import time import sensor, displayuart UART(3, 115200, bits8, parityNone, stop1, timeout_char10…

Pogo-DroneCANPWM模块:可实现DroneCAN转PWM,DroneCAN转dshot,DroneCAN转bdshot

关键词&#xff1a;Ardupilot&#xff0c;Pixhawk&#xff0c;PWM&#xff0c;dshot&#xff0c;bdshot&#xff0c;DroneCANPWM&#xff0c;电调ESC&#xff0c;DroneCAN&#xff0c;UAVCAN&#xff0c;飞控&#xff0c;无人机&#xff0c;UAV Keywords&#xff1a;Ardupilot…

Xilinx FPGA:vivado串口输入输出控制fifo中的数据

一、实验要求 实现同步FIFO回环测试&#xff0c;通过串口产生数据&#xff0c;写入到FIFO内部&#xff0c;当检测到按键信号到来&#xff0c;将FIFO里面的数据依次读出。 二、信号流向图 三、状态转换图 四、程序设计 &#xff08;1&#xff09;按键消抖模块 timescale 1ns…

Python编程学习笔记(1)--- 变量和简单数据类型

1、变量 在学习编程语言之前&#xff0c;所接触的第一个程序&#xff0c;绝大多数都是&#xff1a; print("Hello world!") 接下来尝试使用一个变量。在代码中的开头添加一行代码&#xff0c;并对第二行代码进行修改&#xff0c;如下&#xff1a; message "…

Github 2024-07-07php开源项目日报 Top9

根据Github Trendings的统计,今日(2024-07-07统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目9Blade项目2JavaScript项目1Laravel:表达力和优雅的 Web 应用程序框架 创建周期:4631 天开发语言:PHP, BladeStar数量:75969 个Fork数…

什么时候考虑将mysql数据迁移到ES?

文章目录 对ES的一些疑问问题1:ES相比mysql本身有哪些优势&#xff1f;问题2:哪些场景适合用ES而不是mysql&#xff1f;问题3:mysql逐行扫描&#xff0c;根据过滤条件检查记录中对应字段是否满足要求属于正排索引&#xff0c;根据二叉树索引检索记录的方式属于正排索引还是倒排…

LeetCode 189.轮转数组 三段逆置 C写法

LeetCode 189.轮转数组 C写法 三段逆置 思路: 三段逆置方法:先逆置前n-k个 再逆置后k个 最后整体逆置 由示例1得&#xff0c;需要先逆置1,2,3,4 再逆置5,6,7&#xff0c;最后前n-k个与后k个逆置 代码 void reverse(int*num, int left, int right) //逆置函数 { while(left …

XLSX + LuckySheet + LuckyExcel + Web Worker实现前端的excel预览

文章目录 功能简介简单代码实现web worker 版本效果参考 功能简介 通过LuckyExcel的transformExcelToLucky方法&#xff0c; 我们可以把一个文件直接转成LuckySheet需要的json字符串&#xff0c; 之后我们就可以用LuckySheet预览excelLuckyExcel只能解析xlsx格式的excel文件&a…

mac|idea导入通义灵码插件

官方教程&#xff1a;通义灵码下载安装指南_智能编码助手_AI编程_云效(Apsara Devops)-阿里云帮助中心 下载插件&#xff1a; ⇩ TONGYI Lingma - JetBrains 结果如下&#xff1a; 选择apply、ok&#xff0c;会出现弹窗&#xff0c;点击登录 可以实现&#xff1a;生成单元测…

SQL Server特性

一、创建表 在sql server中使用create table来创建新表。 create table Customers( id int primary key identity(1,1), name varchar(5) ) 该表名为Customers其中包含了2个字段&#xff0c;分别为id&#xff08;主键&#xff09;以及name。 1、数据类型 整数类型&#xff…

【TB作品】51单片机 Proteus仿真 00002仿真-智能台灯色调倒计时光强

实验报告&#xff1a;基于51单片机的智能台灯控制系统 背景 本实验旨在设计一个基于51单片机的智能台灯控制系统&#xff0c;该系统可以通过按键进行手动控制&#xff0c;并能根据环境光强度自动调节台灯亮度。此外&#xff0c;系统还具备倒计时关灯功能。 器件连接 51单片…

latex英文转中文word,及一些latex相关工具分享

前言&#xff1a;想要转换latex生成的英文pdf文件为中文word文件 一、主要步骤 1、文字翻译&#xff1a;直接使用谷歌翻译等辅助将英文翻译成中文即可&#xff1b; 2、图片&#xff1a; 使用latex时一般保存的.png&#xff0c;.bmp格式图片可以直接插入word, 但是.eps或者 .p…

期末成绩发布方式

期末考试结束后&#xff0c;成绩单的发放总是让老师们头疼不已。想象一下&#xff0c;每个学生的成绩都需要老师一个个私信给家长&#xff0c;不仅耗时耗力&#xff0c;而且极易出错。 在传统的成绩单发放方式中&#xff0c;老师往往需要通过电子邮件、短信或者微信等方式&…