这篇教你搞定Android内存优化分析总结

news2024/11/6 7:30:06

一、内存优化概念

1.1 为什么要做内存优化?

内存优化一直是一个很重要但却缺乏关注的点,内存作为程序运行最重要的资源之一,需要运行过程中做到合理的资源分配与回收,不合理的内存占用轻则使得用户应用程序运行卡顿、ANR、黑屏,重则导致用户应用程序发生 OOM(out of memory)崩溃。在你认真跟踪下来可能会发现内存出现问题的地方仅仅只是一个表现的地方,并非深层次的原因,因为内存问题相对比较复杂,它是一个逐渐挤压的过程,正好在你出现问题的代码那里爆了,所以针对应用的内存问题开发者必须多加关注。

1.2 内存问题表现形式

  • 内存抖动:锯齿状、GC频繁导致卡顿

  • 内存泄露:可用内存逐渐减少、频繁GC

  • 内存溢出:OOM、程序异常

二、常用内存分析工具

要解决内存问题,就必须要有强大的内存分析工具,让我们更快更方便的定位内存问题。目前主流的内存分析工具主要有 LeakCanary、Memory Profiler、MAT。

2.1 LeakCanary

LeakCanary是 Square 开源的一个内存泄露监控框架,在应用运行时出现的内存泄露会被 LeakCanary 监控记录。

上图是 LeakCanary 内存泄漏的 trace 分析,主要看 Leaking:NO 到 Leaking:YES 这段的 trace,可以发现 TextView 出现了内存泄漏,因为它持有了被销毁的 Activity 的上下文 Context。

更具体的 trace 分析,具体可以查看官方文档 Fixing a memory leak。

使用 LeakCanary 虽然很方便,但是也有一定弊端:

  • 直接引用依赖使用的 LeakCanary 一般用于线下调试,应用发布到线上时需要关闭

  • 应用调试时有时候会引起卡顿

所以 一般使用 LeakCanary 只是一种简便定位内存泄露的方式,但如果需要更好的做内存优化,比如定位内存抖动、Bitmap 优化等还是需要其他的分析工具,主要常用的有 Memory Profiler 和 MAT。

2.2 NativeSize、Shallow Size、Retained Size、Depth

后续说明 Memory Profiler 和 MAT 时,会经常出现几个比较重要的指标:Shallow Size 和 Retained Size。在 Memory Profiler 还会提供 Native Size 和 Depth。

当您拿到一段 Heap Dump 之后,Memory Profiler 会展示出类的列表。对于每个类,Allocations 这一列显示的是它的实例数量。而在它右边则依次是 Native Size、Shallow Size 和 Retained Size:

我们用下图来表示某段 Heap Dump 记录的应用内存状态。注意红色的节点,在这个示例中,这个节点所代表的对象从我们的工程中引用了 Native 对象;这种情况不太常见,但在 Android 8.0 之后,使用 Bitmap 便可能产生此类情景,因为 Bitmap 会把像素信息存储在原生内存中来减少 JVM 的内存压力。

先从 Shallow Size 讲起,这列数据其实非常简单,就是 对象本身消耗的内存大小,即为红色节点自身所占内存:

Native Size 同样也很简单,它是类对象所引用的 Native 对象 (蓝色节点) 所消耗的内存大小:

Retained Size 稍复杂些,它是下图中所有橙色节点的大小:

由于一旦删除红色节点,其余的橙色节点都将无法被访问,这时候它们就会被 GC 回收掉。从这个角度上讲,它们是被红色节点所持有的,因此被命名为 Retained Size。

还有一个前面没有提到的数据维度。当您点击某个类名,界面中会显示这个类实例列表,这里有一列新数据 —— Depth

Depth 是从 GC Root 到达这个实例的最短路径,图中的这些数字就是每个对象的深度 (Depth)。

一个对象离 GC Root 越近,它就越有可能与 GC Root 有多条路径相连,也就越可能在垃圾回收中被保存下来

以红色节点为例,如果从其左边来的任何一个引用被破坏,红色节点就会变成不可访问的状态并且被垃圾回收回收掉。而对于右边的蓝色节点来说,如果您希望它被垃圾回收,那您需要把左右两边的路径都破坏才行。

值得警惕的是,如果您看到某个实例的 Depth 为 1 的话,这意味着它直接被 GC Root 引用,同时也意味着它永远不会被自动回收

下面是一个示例 Activity,它实现了 LocationListener 接口,高亮部分代码 requestLocationUpdates() 将会使用当前 Activity 实例来注册 locationManager。如果您忘记注销,这个 Activity 就会泄漏。它将永远都待在内存里,因为位置管理器是一个 GC Root,而且永远都存在:

您能在 Memory Profiler 中查看这一情况。点击一个实例,Memory Profiler 将会打开一个面板来显示谁正在引用这个实例:

我们可以看到位置管理器中的 mListener 正在引用这个 Activity。您可以更进一步,通过引用面板导航至堆的引用视图,它可以让您验证这条引用链是否是您所预期的,也能帮您理解代码中是否有泄露以及哪里有泄露。

2.3 Memory Profiler

Memory Profiler 是内置在 Android Studio 适用于查看实时内存情况 的内存分析工具。

2.3.1 Memory Profiler 界面说明

官方文档:使用 Memory Profiler 查看 Java 堆和内存分配

2.3.2 Memory Profiler 查找内存抖动

查找内存抖动还是比较简单的,运行的程序在 Memory Profiler 会呈现为在短时间内内存上下波动频繁触发 GC 回收

内存抖动比较常见的地方:

  • 自定义View的 onMeasure()、onLayout()、onDraw() 直接 new 创建对象

  • 列表比如 RecyclerView 的 onBindViewHolder() 直接 new 创建对象

  • 有循环的代码中创建对象

用一个简单的案例模拟内存抖动:

publicclassMainActivityextendsAppCompatActivity {
​
    @SuppressWarnings("HandlerLeak")
    privateHandler mHandler = newHandler() {
        @Override
        publicvoidhandleMessage(Message msg) {
            // 模拟内存抖动
            for (int i = 0; i < 100; i++) {
                String[] args = newString[100000];
            }
​
            mHandler.sendEmptyMessageDelayed(0, 30);
        }
    };
​
    @Override
    protectedvoidonCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
​
        findViewById(R.id.button).setOnClickListener(newView.OnClickListener() {
            @Override
            publicvoidonClick(View v) {
                mHandler.sendEmptyMessage(0);
            }
        });
    }
}
复制代码

案例非常简单,就是点击按钮时频繁的创建对象。在真机上运行上面的程序也许不会出现锯齿状的内存波动,但是会有非常频繁的 GC 回收,如下图:

那应该怎么具体的定位到是哪里发生的内存抖动呢?

按照上面的步骤操作:

  • 位置①:在程序处于运行时,点击 Record 按钮录制内存情况,然后点击 Stop 停止录制,会显示上图

  • 位置②:我们可以点击 Allocations 按降序从大到小或升序从小到大查看分配对象的数量,一般我们会选择降序从大到小看数量最多的对象。上图对象数量最多的是 String 对象

  • 位置③:在 Instance View 随便选择一个 String 对象,会显示下面的 Allocation Call Stack,它会显示这个对象的调用栈位置

  • 位置④: 从 Allocation Call Stack 可以看到,String 对象是在 MainActivity 的第 18 行 handleMessage() 创建的,从而也定位到内存抖动的位置

上面的操作还有一些小技巧:

  • 在位置①操作前,为了排除干扰,一般在录制前会先手动 GC 后再录制变化的内存;在 Android 8.0 以上的设备可以实时的拖动 Memory Profiler 选择查看的内存波动范围

  • 位置②上例是直接查看 Arrange by class,但实际项目中更多的会是选择 Arrange by package 查看自己项目包名下的类

2.3.3 Memory Profiler 查找内存泄露

上面讲到内存泄露的表现是会出现内存抖动,因为出现内存泄露时可用内存不断减少,系统需要内存时获取内存不足就会 GC,所以产生内存抖动。

出现内存泄露时 Memory Profiler 会呈现一个类似阶梯型的内存上升趋势,而且内存没有降下来:

上图的内存泄漏比较明显,实际项目开发中出现内存泄漏时可能不会特别明显,运行时间比较久才能发现内存是在缓慢上升的。这时候就需要 dump heap 帮助定位。

接下来会使用 Handler 内存泄露的案例简单讲解怎么使用 Memory Profiler 分析内存泄露。

public classHandlerLeakActivityextendsAppCompatActivity{
    private static finalStringTAG = HandlerLeakActivity.class.getSimpleName();
​
    privateHandler handler = newHandler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 0) {
                Log.i(TAG, "handler receive msg");
            }
        }
    };
​
    @Override
    protected void onCreate(@NullableBundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        handler.sendEmptyMessageDelayed(0, 10 * 1000);
    }
}
复制代码

上面代码非常简单,就是启动 app 后,每次进入 HandlerLeakActivity 就使用 Handler 延迟 10s 发送消息,在 10s 内退出界面,不断重复操作。

1、重复多次可能内存泄露的操作,Memory Profiler 堆转储出 hprof 文件(建议在操作前先 GC 排除干扰):

2、在 Memory Profiler 查看查看堆转储文件 hprof:

可以发现经过手动 GC 后,Allocations 显示有 5 个 HandlerLeakActivity,堆转储 Instance View 下也仍显示有多个 Activity 实例,说明已经内存泄露,具体的内存泄露定位可以在 Instance View 泄露的实例类对象中点击查看,Instance View 下面的 Reference 会显示具体的引用链。

在新版本的 Memory Profiler 提供了 Activity/Fragment Leaks 复选框,选中它可以直接找到可能内存泄露的位置:

2.4 MAT

2.4.1 MAT简介

  • 强大的Java Heap分析工具,查找内存泄漏及内存占用

  • 生成整体报告、分析问题等

  • 线下深入使用

官网下载地址:www.eclipse.org/mat/downloa… ,这个地址是不是有你熟悉的单词,嗯,没错啦,MAT是Eclipse中的一个插件,因为现在开发过程中很多人都使用了IDEA或者Android Studio,所以你不想下载Eclipse的话呢,你可以去下载MAT的独立版,解压之后里面有一个MemoryAnalyzer.exe的可执行文件,直接点击就可以使用了。

这个工具很多时候我们需要结合Android Studio的堆转储能力配合使用,但是需要注意,AS3.0之后生成的hprof文件不是标准的hprof文件了,需要使用命令转换一下:hprof-conv 原文件路径 转换后文件路径

2.4.2 MAT用法简介

①、Overview:概览信息

Top Consumers

  • 通过图表展示出占用内存比较多的对象,此栏在做降低内存占用时比较有帮助

  • Biggest Objects:相对详细的信息

Leak Suspects

  • 快速查看内存泄露的可疑点

②、 Histogram:直方图

  • Class Name:具体检索某一个类

  • Objects:某一个具体的Class有多少实例

  • Shallow Heap:某单一实例自己占了多少内存

  • Retained Heap:在这个引用链之上这些对象总共占了多少内存

Group by packge:将类对象以包名形式展示

List objects

  • with outgoing references:自身引用了哪些类

  • with incoming references:自身被哪些类引用

③、dominator_tree

  • 每个对象的支配树

  • percentage:占所有对象的百分比

在条目上右键它也有List objects,它和Histogram之间有啥区别呢?主要区别就是下面两点:

  • Histogram:基于类的角度分析

  • dominator_tree:基于实例的角度分析

④、OQL:对象查询语言,类似于从数据库中检索内容

⑤、thread_overview:详细的展示线程信息,可以查看出当前内存中存在多少线程

三、实战内存抖动解决

3.1 内存抖动简介

  • 定义:内存频繁分配和回收导致内存不稳定

  • 表现:频繁GC、内存曲线呈锯齿状

  • 危害:导致卡顿、严重时会导致OOM

3.2 内存抖动导致OOM

  • 频繁创建对象,导致内存不足及碎片(不连续)

  • 不连续的内存片无法被分配,导致OOM

3.3 实战分析

这一部分我会模拟一次内存抖动,并通过Profiler分析内存情况,定位到具体内存抖动的代码。

首先先来创建一个布局文件activity_memory.xml,里面就一个按钮,用来触发模拟内存抖动的那部分代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/btn_memory"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="模拟内存抖动"/>
</LinearLayout>
复制代码

然后定义一个MemoryShakeActivity页面,加载刚才的布局,并且在页面中定义一个Handler,当点击模拟内存抖动的按钮时,我们定时执行handleMessage中的模拟抖动的代码,整个代码都是很容易能看懂的那种:

/**
 * 说明:模拟内存抖动页面
 */publicclassMemoryShakeActivityextendsAppCompatActivityimplementsView.OnClickListener {
 
    @SuppressLint("HandlerLeak")
    privatestaticHandler mHandler = newHandler(){
        @Override
        publicvoidhandleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            //模拟内存抖动的场景,每隔10毫秒执行一次,循环执行100次,每次通过new分配大内存
            for (int i=0;i<100;i++){
                String[] obj = newString[100000];
            }
            mHandler.sendEmptyMessageDelayed(0,10);
        }
    };
 
    @Override
    protectedvoidonCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory);
        findViewById(R.id.btn_memory).setOnClickListener(this);
    }
 
    @Override
    publicvoidonClick(View view) {
        if (view.getId() == R.id.btn_memory){
            mHandler.sendEmptyMessage(0);
        }
    }
 
    @Override
    protectedvoidonDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}
复制代码

然后跑起来,我截了两张图给大家看一下,第一张是没有执行模拟抖动的代码之前的,第二张是执行之后的:

从上面两张图中可以清晰的看到第一张内存比较平稳,第二张内存图有锯齿状出现,突然出现了频繁的GC,看到下面好多小垃圾桶了没,这个时候可以初步判定应该是出现了内存抖动现象,因为比较符合它的特征,然后在面板上拖动一段距离它就会将这段时间内的内存分配情况给我们展示出来:

首先双击Allocations,然后将这一列按照从大到小的顺序排列好,然后你会发现String数组居然有这么多,它占用的内存大小也是最高的(值得关注的点我都用矩形标出了),此时我们就应该锁定这个目标,为什么String类型的数组会有这么多,这里很有可能是有问题的。然后排查究竟是哪里导致的这个问题,很简单点击String[]这一行,在右侧Instance View面板中随便点击一行,下方Allocation Call Stack面板中就出现了对应的堆栈信息,上面也列出了具体哪个类的哪一行,右键jupm to source就可以跳转到指定的源码位置,这样就找到了内存抖动出现的位置,然后我们分析代码作相应的修改即可。

流程总结

  1. 使用Memory Profiler初步排查;

  1. 使用Memory Profiler或CPU Profiler结合代码排查

内存抖动解决技巧:找循环或者频繁调用的地方

四、实战内存泄露解决

4.1 内存泄露简介

定义:内存中存在已经没有用的对象

表现:内存抖动、可用内存逐渐变少

危害:内存不足、GC频繁、OOM

4.2 实战分析

这里还是通过代码来真实的模拟一次内存泄露的场景,对于一般的APP程序来说,最大的问题往往都是在Bitmap上,因为它消耗的内存比较多,拿它来模拟会看的比较明显。好首先来看布局文件activity_memoryleak.xml,里面就一个ImageView控件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <ImageView
        android:id="@+id/iv_memoryleak"
        android:layout_width="50dp"
        android:layout_height="50dp" />
</LinearLayout>
复制代码

然后定义了一个模拟处理某些业务的Callback回调接口,和一个统一管理这些回调接口的Manager类:

//模拟回调处理某些业务场景publicinterfaceCallBack {
    voiddpOperate();
}
 
//统一管理CallbackpublicclassCallBackManager {
    publicstatic ArrayList<CallBack> sCallBacks = new ArrayList<>();
 
    publicstaticvoidaddCallBack(CallBack callBack) {
        sCallBacks.add(callBack);
    }
 
    publicstaticvoidremoveCallBack(CallBack callBack) {
        sCallBacks.remove(callBack);
    }
}
复制代码

然后在我们的模拟内存泄露的页面上设置Bitmap,并设置回调监听:

/**
 * 说明:模拟内存泄露页面
 */
public classMemoryLeakActivityextendsAppCompatActivityimplementsCallBack{
    @Override
    protected void onCreate(@NullableBundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memoryleak);
        ImageView imageView = findViewById(R.id.iv_memoryleak);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.big_bg);
        imageView.setImageBitmap(bitmap);
        CallBackManager.addCallBack(this);
    }
 
    @Override
    public void dpOperate() {
 
    }
}
复制代码

OK,我们的代码就写完了,现在来实际运行一下,然后将这个页面连续打开关闭多次,看看这段代码会不会造成内存泄露呢?

这是我用Profiler截取的内存图片,可以看到整个内存在经过了我的反复开关页面之后虽然有的地方出现了一个小抖动,但是整体是呈阶梯状上升的,可用内存在逐渐减少,此时基本上可以断定这个界面出现了内存泄露。Profiler工具虽然可以初步帮我们断定出现了内存泄露,但是它却无法断定具体是哪里出现了内存泄露,意思就是我们还是不知道该修改哪里的代码,所以此时需要用到强大的Java Heap工具了,来有请MAT出场。

首先需要在Profiler中点击Dump Java Heap按钮,使用堆转储功能转换成一个文件,然后点击保存按钮将文件保存到本地目录下,比如我这里保存为H盘中的memoryleak.hprof文件,然后使用hprof-conv命令将其转换为标准的hprof文件,我这里是转换后的名称是:memoryleak_transed.hprof,如下所示:

然后打开MAT工具,导入刚刚生成的转换后的文件:

点击Histogram查看内存中所有存活的对象,然后我们在Class Name中可以输入内容搜索想要查找的对象:

然后可以看到该对象的具体信息,以及数量和所占用的内存大小,我这里发现内存中居然存在6个MemoryLeakActivity对象:

然后右键List objects---->with incoming references找到所有引向它的强引用:

然后右键Path To GC Roots----->with all references,将所有引用都计算在内然后算出来这个对象和GCRoot之间的路径:

来看结果,最后是到了sCallBacks这里,而且它左下角有个小圆圈,这就是我们真正要找的位置,也就是说MemoryLeakActivity是被CallBackManager这个类的sCallBacks这个对象引用了:

根据上面找的结果到代码中去找CallBackManager的sCallBacks看看这里究竟是做了什么引发的?

publicstaticArrayList<CallBack> sCallBacks = new ArrayList<>();
复制代码

MemoryLeakActivity是被sCallBacks这个静态变量引用着,由于被static关键字修饰的变量的生命周期是和App的整个生命周期一样长的,所以当MemoryLeakActivity这个页面关闭时,我们应该将变量的引用关系给释放掉,否则就出现了上面的内存泄露的问题。所以解决这个问题也很简单了,添加如下几行代码:

@OverrideprotectedvoidonDestroy() {
    super.onDestroy();
    CallBackManager.removeCallBack(this);
}
复制代码

流程总结

  1. 使用Memory Profiler初步观察(可用内存逐渐减少);

  1. 通过Memory Analyzer结合代码确认

五、线上内存监控方案

线上内存问题最大的就是内存泄露,对于内存抖动和内存溢出它们一般都和内存泄露导致的内存无法释放相关,如果能够解决内存泄露,则线上内存问题就会减少很多。线上内存监控其实还是比较困难的,因为我们无法使用线下的这些工具来直观的发现分析问题。

5.1 常规方案

①、设定场景线上Dump

比如你的App已经占用到单个App最大可用内存的较高百分比,比如80%,通过:Debug.dumpHprofData();这行代码可以实现将当前内存信息转化为本地文件。

整个流程如下超过内存80%——>内存Dump——>回传文件(注意文件可能很大,保持在wifi状态回传)——>MAT手动分析

总结:

  • Dump文件太大,和对象数正相关,可裁剪

  • 上传失败率高、分析困难

②、LeakCanary线上使用

  • LeakCanary带到线上

  • 预设泄露怀疑点

  • 发现泄露回传

总结:

  • 不适合所有情况,必须预设怀疑点,限制了全面性

  • 分析比较耗时,也容易OOM(实践发现LeakCanary分析过程较慢,很有可能自己在分析的过程中自身发生OOM)

5.2 LeakCanary定制

  • 预设怀疑点——》自动找怀疑点(谁的内存占用大就怀疑谁,大内存对象出现问题的概率更大)

  • 分析泄露链路慢(分析预设对象的每一个对象)——》分析Retain Size大的对象(减少它的分析工作量,提高分析速度)

  • 分析OOM(将内存堆栈生成的所有文件全部映射到内存中,比较占用内存)——》对象裁剪,不全部加载到内存

5.3 线上监控完整方案

  • 待机内存、重点模块内存、OOM率

  • 整体及重点模块GC次数、GC时间

  • 增强的LeakCanary自动化内存泄露分析

六、内存优化技巧

优化大方向:

  • 内存泄露

  • 内存抖动

  • Bitmap

优化细节:

  • LargeHeap属性(虽然有点耍流氓,但还是应该向系统申请)

  • onTrimMemory、onLowMemory(系统给的低内存的回调,可以根据不同的回调等级去处理一些逻辑)

  • 使用优化过的集合:SparseArray

  • 谨慎使用SharedPreference(一次性load到内存中)

  • 谨慎使用外部库(尽量选择经过大规模验证的外部库)

  • 业务架构设计合理(加载的数据是你能用到的,不浪费内存加载无用数据)

Android核心知识点笔记(其他Android知识点):

Android开发核心知识点笔记

Android Framework核心知识点笔记

音视频开发笔记,入门到高级进阶

Android Flutter核心知识点笔记与实战详解

性能调优核心知识点笔记

Android开发高频面试题,25个知识点整合

Android开发核心架构知识点笔记

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

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

相关文章

QT之OpenGL帧缓冲

QT之OpenGL帧缓冲1. 概述1.1 帧缓冲的创建与删除1.2 帧缓冲的数据来源1.2.1 数据源与帧缓冲的关系1.2.2 纹理Attachment1.2.3 渲染缓冲对象Attachment1.2.4 两者的区别1.2.5 关于两者的使用场景2. Demo3. 后期处理4. 参考1. 概述 OpenGL管线的最终渲染目的地被称作帧缓冲(fram…

【代码随想录训练营】【Day28】第七章|回溯算法|93.复原IP地址|78.子集|90.子集II

复原IP地址 题目详细&#xff1a;LeetCode.93 这道题与上一道练习题分割回文字符串十分详细&#xff0c;一样是涉及到分割字符串、判断字符串、递归与回溯的问题&#xff0c;所以这道题要解决的难点在于&#xff1a; 如何分割IP地址字符串如何判断分割的IP地址是否合法递归的…

Kafka基本概念

什么是Kafka Kafka是一个消息系统。它可以集中收集生产者的消息&#xff0c;并由消费者按需获取。在Kafka中&#xff0c;也将消息称为日志(log)。 一个系统&#xff0c;若仅有一类或者少量的消息&#xff0c;可直接进行发送和接收。 随着业务量日益复杂&#xff0c;消息的种类…

2.单例模式

基本概念 单例模式&#xff1a;保证一个类只有一个实例&#xff0c;并提供一个访问该实例的全局访问点 常见应用场景 读取配置文件的类一般设计为单例模式网站计数器应用程序的日志应用&#xff0c;因为共享日志文件一直处于打开状态&#xff0c;只能有一个实例去操作Spring…

新C++(11):unordered_map\set的封装

"假如可以让音乐停下来"一、unordered_map\unordered_set简介在C98中&#xff0c;STL底层提供了以红黑树封装的关联式容器map\set&#xff0c;其查询效率可以达到LogN(以2为底)。而在C11中&#xff0c;STL又提供了unordered(无序)容器&#xff0c;其使用方式与map\se…

企业对不同形态CRM系统价格需求不同

很多企业在选型时关心CRM客户管理系统的价格&#xff0c;有人对CRM的价格完全没有概念&#xff0c;也有的人先问价格再看其他。CRM价格在系统选型中到底有多重要&#xff1f;不同类型CRM系统的价格是否有所不同&#xff1f; CRM的不同产品形态也会影响价格 通常情况下&#x…

十五、MyBatis使用PageHelper

1.limit分页 limit分页原理 mysql的limit后面两个数字&#xff1a; 第一个数字&#xff1a;startIndex&#xff08;起始下标。下标从0开始。&#xff09; 第二个数字&#xff1a;pageSize&#xff08;每页显示的记录条数&#xff09; 假设已知页码pageNum&#xff0c;还有每页…

移动端笔记

目录 一、移动端基础 二、视口 三、二倍图/多倍图 四、移动端开发 &#xff08;一&#xff09;开发选择 &#xff08;二&#xff09;常见布局 &#xff08;三&#xff09;移动端技术解决方案 五、移动WEB开发之flex布局 六、移动WEB开发之rem适配布局 #END&#xff08…

嘀嗒出行再闯IPO:千军万马我无懈

羽扇纶巾笑谈间&#xff0c;千军万马我无懈。 在激烈竞争中再度冲刺港交所IPO的嘀嗒出行&#xff0c;闪露出一丝歌词里的气魄。交通运输部下属网约车监管信息交互系统的数据显示&#xff0c;截至2023年1月31日&#xff0c;全国共有300家网约车平台公司取得网约车平台经营许可。…

如何使用COM-Hunter检测持久化COM劫持漏洞

关于COM-Hunter COM- Hunter是一款针对持久化COM劫持漏洞的安全检测工具&#xff0c;该工具基于C#语言开发&#xff0c;可以帮助广大研究人员通过持久化COM劫持技术来检测目标应用程序的安全性。 关于COM劫持 微软在Windows 3.11中引入了(Component Object Model, COM)&…

2月第4周榜单丨飞瓜数据B站UP主排行榜(哔哩哔哩平台)发布!

飞瓜轻数发布2023年2月20日-2月26日飞瓜数据UP主排行榜&#xff08;B站平台&#xff09;&#xff0c;通过充电数、涨粉数、成长指数三个维度来体现UP主账号成长的情况&#xff0c;为用户提供B站号综合价值的数据参考&#xff0c;根据UP主成长情况用户能够快速找到运营能力强的B…

基频估计算法简介

基频估计算法 F0 estimate methods 估计F0的方法可以分为三类:基于时域、基于频域、或混合方法。本文详细介绍了这些方法。 所有的算法都包含如下三个主要步骤&#xff1a; 1.预处理&#xff1a;滤波&#xff0c;加窗分帧等 2.搜寻&#xff1a;可能的基频值F0&#xff08;候选…

chatgpt到底颠覆了什么 第二部分

以第二个理由就是两个字&#xff0c;垄断。 现在谈到范式转变&#xff0c;如果首先谈的还是算法&#xff0c;那说明还没有透彻理解范式改变范式改变&#xff0c;首先要改的是什么。是什么&#xff1f;是参赛资格。 过去我相信大企业大团队聚拢了许多聪明的脑袋&#xff0c;但我…

基于神经网络补偿的主动悬架自适应控制

目录 前言 1. 1/4悬架模型 2.仿真分析 2.1仿真模型 2.2仿真结果 2.1 形① 2.2 形② 3. 总结 前言 上两篇博客我们介绍了神经网络补偿控制律的仿真测试&#xff0c;从仿真结果我们可以得知神经网络具有逼近扰动&#xff0c;并将其补偿的作用。 上两篇文章链接&#xf…

在nestjs中进行typeorm cli迁移(migration)的配置

在nestjs中进行typeorm cli迁移(migration)的配置 在学习nestjs过程中发现typeorm的迁移配置十分麻烦,似乎许多方法都是旧版本的配置&#xff0c;无法直接使用. 花了挺长时间总算解决了这个配置问题. db.config.ts 先创建db.config.ts, 该文件export了两个对象&#xff0c;其…

AcWing算法提高课-3.1.2信使

宣传一下算法提高课整理 <— CSDN个人主页&#xff1a;更好的阅读体验 <— 题目传送门点这里 题目描述 战争时期&#xff0c;前线有 nnn 个哨所&#xff0c;每个哨所可能会与其他若干个哨所之间有通信联系。 信使负责在哨所之间传递信息&#xff0c;当然&#xff0c;…

CPU 偏高,和linux常用命令

CPU 偏高&#xff0c;和linux常用命令一、1、常用命令2、top 查看整体的cpu占有率&#xff08;哪个进程cpu占用过高&#xff09;3、top -Hp 8779 查看该pid 下哪个进程占用过高4、打印dump日志 重点关注&#xff08;RUNNABLE、BLOCKED&#xff09;一、 1、常用命令 整机 top …

k8s ConfigMap 中 subPath 字段和 items 字段

Kubernetes中什么是subPath 有时&#xff0c;在单个 Pod 中共享卷以供多方使用是很有用的。volumeMounts.subPath 属性可用于指定所引用的卷内的子路径&#xff0c;而不是其根路径。 这句话理解了&#xff0c;基本就懂subPath怎么用了&#xff0c;比如我们要替换nginx.cnf, 挂…

map和set的使用

文章目录关联式容器树形结构的关联式容器setinsert增减erase删除multiset修改mappair<key,value>insertoperator[] 的引入insert和operator[]的区别multimap小结map的使用统计最喜欢吃的前几种水果前K个高频单词&#xff0c;返回单词的频率由高到低,频率相同时&#xff0…

Isaac-gym(9):项目更新、benchmarks框架梳理

一、项目更新 近期重新git clone isaac gym的强化部分&#xff08;具体见系列第5篇&#xff09;时发现官方的github库有跟新&#xff0c;git clone下来后发现多了若干个task&#xff0c;在环境配置上也有一定区别。 例如新旧两版工程项目的setup.py区别如下&#xff1a; git …