Android Handler用法

Android Handler用法

  • 为什么要设计Handler机制?
  • Handler的用法
    • 1、创建Handler
    • 2、Handler通信
      • 2.1 sendMessage 方式
      • 2.2 post 方式
  • Handler常用方法
    • 1、延时执行
    • 2、周期执行
  • HandlerThread用法
    • 主线程-创建Handler
    • 子线程-创建Handler
  • FAQ
    • Message是如何创建
    • 主线程中Looper的轮询死循环为何没有阻塞主线程
    • Handler内存泄漏问题及解决方案
    • Handler为什么会持有Activity的引用?

参考文档:https://developer.android.google.cn/guide/components/processes-and-threads?hl=zh-cn#java
在这里插入图片描述

Android UI操作并非线程安全。因此,请不要在工作线程(即子线程)中操纵界面。您可以通过界面线程对界面进行所有操作。Android 的单线程模型有以下两条规则:

  • 请勿阻塞UI线程
  • 请勿从UI线程以外的线程进行UI操作

为什么要设计Handler机制?

一个耗时的操作,比如需要联网读取数据或者读取本地较大的一个文件或者数据库查询,如果把这些操作放在主线程中,界面会出现假死现象, 如果5秒钟还没有完成的话,阻塞了UI线程会收到Android系统的一个错误提示 “强制关闭”,这糟糕的体验会造成严重的损失,所以不能阻塞UI线程,以确保应用界面的响应能力。
这时候就需要把这些耗时的操作,放到子线程中去执行,但是在子线程执行完以后,又需要将结果更新到UI页面,此时就涉及到子线程UI更新的操作。那为什么安卓规定不能在子线更新UI? 最根本的原因是多线程并发的问题,假设在一个Activity中,有多个线程去更新UI,并且都没有加锁机制,就会产生更新界面错乱,所以子线程中更新UI是不安全的,而在一个线程中更新UI是变得比较合理,那自然就是主线程了,当然主线程也可以叫UI线程了。
综上所述,所以安卓应用需要这样的机制:

  1. 只能在主线程更新UI,并且所有更新UI的操作,都要在主线程的消息队列当中去轮询处理。
  2. 耗时的操作在子线程,更新UI的操作在主线程,他们之间的交互需要实现线程间通信。

Handler用于实现多线程通信和管理UI线程的消息处理。它为开发者提供了一种简单有效的方法来处理异步任务和更新UI界面。
当然,Android已经提供了多种从其他线程访问UI线程的方式。以下列出了几种有用的方法:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • Handler.post(Runnable)

Activity.runOnUiThread(Runnable)方法:如果在UI线程,直接更新UI;如果非UI线程,使用的是post。

new Thread(new Runnable() {
    @Override
    public void run() {
        // todo 在子线程中进行耗时操作
        doSomethings();
        
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // todo UI更新代码
                doSomethingsAboutUI();
            }
        });
    }
}).start();

View.post(Runnable) 方法:

new Thread(new Runnable() {
    @Override
    public void run() {
		// todo 在子线程中进行耗时操作
        doSomethings();
		
        findViewById(R.id.button_send).post(new Runnable() {
            @Override
            public void run() {
                // todo UI更新代码
                doSomethingsAboutUI();
            }
        });
    }
}).start();

Handler.post(Runnable)方法

private Handler mHandler =new Handler();  //默认主线程
...
new Thread(new Runnable() {
    @Override
    public void run() {
        // todo 在子线程中进行耗时操作
        doSomethings();
        
        mHandlers.post(new Runnable() {
            @Override
            public void run() {
                // todo UI更新代码
                doSomethingsAboutUI();
            }
        });
    }
}).start();

随着操作变得越来越复杂,这种代码也会变得复杂且难以维护。为了处理与工作线程(子线程)的更复杂的交互,建议在UI线程中使用 Handler 处理从子线程传送的消息。

工作原理:Handler运行在主线程(UI线程)中, 它与子线程可以通过Message对象来传递数据和消息,然后把这些消息放入主线程队列中,按照先进先出的原则配合主线程逐个进行更新UI的操作。另外,驱动这套机制运行的核心是Looper.loop() 里的死循环。

@Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare(); // 创建Looper的子线程,然后创建MessageQueue,最后进行绑定。
    synchronized (this) {
        mLooper = Looper.myLooper();  // 获取准备好的Looper对象
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();  // 启动Looper
    mTid = -1;
}

Handler的用法

下面的这种写法是可以实现刷新UI的功能,但是它违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中执行。但是如果与UI无关的操作如上传/下载,数据库,可以使用此种写法。

new Thread(new Runnable() {
    @Override
    public void run() {
        textviewCurrentStatus.invalidate();
    }
}).start();

优点:避免了创建新线程带来的线程切换开销。
缺点:Handler发送的消息会保存在消息队列中,如果一直发送大量的消息,将可能导致消息队列过长,影响应用的响应能力。LiveData和RxJava等现在比较流行的框架,能够替代Handler实现更优异的异步编程和UI更新。

1、创建Handler

一般在主线程中创建Handler如下:

private Handler mHandler = new Handler(Looper.getMainLooper()) {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                // todo 在主线程更新UI
                doSomethingsAboutUI();
                break;
        }
        super.handleMessage(msg);
    }
};

在子线程中创建Handler,最好使用HandlerThread。如果不使用HandlerThread,必须要手动启动Looper,具体如下:

private Handler mHandler;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    new Thread(new Runnable() {
        @Override
        public void run() {
        	// todo 在子线程进行耗时操作
        	doSomethings();
        	
            Looper.prepare();  // 准备Looper
            mHandler= new Handler(new Handler.Callback() {
                @Override
                public boolean handleMessage(@NonNull Message msg) {
                	// todo 实际还是在主线程更新UI
                	doSomethingsAboutUI();
                    return false;
                }
            });
            Looper.loop();    // 启动Looper
        }
    }).start();
}

2、Handler通信

使用Handler通信,有两种方法将消息加入消息队列中:post()方法和sendMessage()方法。
– sendMessage()方法是异步方式。即加入消息到消息队列中后,不会立即执行此消息,而是等待消息阻塞的处理程序返回。 — 存疑
– post()方法是同步方式。即加入消息到消息队列中后,会直接处理此消息,不必等待消息阻塞的处理程序返回。— 存疑

2.1 sendMessage 方式

首先,需要定义好handler需要处理的业务。

private Handler myHandler = new Handler(Looper.getMainLooper()) {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                // todo 在主线程更新UI
                doSomethingsAboutUI();
                break;
        }
        super.handleMessage(msg);
    }
};

在需要的时机,发送消息触发handler调用业务。

new Thread(new Runnable() {
    @Override
    public void run() {
        // todo 在子线程中进行耗时操作
        doSomethings();
        
        Message message = new Message(); //或者Message msg = mHandler.obtainMessage();
        message.what = 1;
        msg.arg1 = 100;
		msg.obj = "message content";
        myHandler.sendMessage(message);
    }
}).start();

另外一种常见写法,本质都是一样的:

private Handler mHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(@NonNull Message msg) {
        switch (msg.what){
            case 1:
                // todo 在主线程更新UI
                doSomethingsAboutUI();
                break;
        }
        return false;
    }
});
....
new Thread(new Runnable() {
     @Override
     public void run() {
         // todo 在子线程中进行耗时操作
         doSomethings();

        Message message = mHandler.obtainMessage(1);
        mHandler.sendMessage(message);
    }
}).start();

2.2 post 方式

通过调用 Handler 的 post() 方法,将 Runnable 对象通过 MessageQueue 发送到消息队列中,即可让主线程处理相应的操作。这种方式可以用于解决在子线程中不能进行 UI 操作的问题,例如我们可以在子线程中通过 post 方式将更新 UI 的任务传递到主线程来完成,这样就不会因为在非 UI 线程中更新 UI 而导致 ANR(Application Not Responding)了。

private Handler mHandler =new Handler();
...
mHandler.post(new Runnable() {
    @Override
    public void run() {
        // todo UI更新代码
        doSomethingsAboutUI();
    }
});

注意:post方法虽然发送的是一个实现了Runnable接口的类对象,但是它并非创建了一个新线程,而是执行了该对象中的run方法。也就是说,整个run中的操作和主线程处于同一个线程。这样对于那些简单的操作,似乎并不会影响。但是对于耗时较长的操作,就会出现“假死”。为了解决这个问题,就需要使得handler绑定到一个新开启线程的消息队列上,在这个处于另外线程的上的消息队列中处理传过来的Runnable对象和消息。

在主线程中使用Handler,可以直接使用getMainLooper()获取主线程Looper对象,并创建Handler实例。例如,在Activity中实现在子线程中更新UI:

private Handler mHandler = new Handler(Looper.getMainLooper());
...
new Thread(new Runnable() {
    @Override
    public void run() {
        // todo 在子线程中进行耗时操作
        doSomething();
        
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                // todo 在主线程更新UI
                doSomethingsAboutUI();
            }
        });
    }
}).start();

Handler常用方法

1、延时执行

3秒后执行UI更新的代码。

mHandler.postDelayed(new Runnable() {
    @Override
    public void run() {
        // todo UI更新代码
        doSomethingsAboutUI();
    }
},3000);

2、周期执行

有时候需要按时反复周期性的执行一些任务
2.1 使用Timer和TimerTask 实现

private Timer mTimer = new Timer();
private TimerTask mTimerTask = new TimerTask() {
    @Override
    public void run() {
        Message message = new Message();
        message.what = 1;
        mHandler.sendMessage(message);
    }
};

private Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                // todo 处理定时或周期性的业务
                doSomethings();
                
                break;
        }
        super.handleMessage(msg);
    }
};

在需要触发的时机,调用即可

mTimer.schedule(mTimerTask, 10000);

2.2 使用postDelayed和sendMessage实现

private int index = 0;

private Handler mHandler = new Handler(Looper.getMainLooper()) {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                updateOnTick();
                break;
        }
        super.handleMessage(msg);
    }
};

private void updateOnTick(){
    mHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            // todo 周期性执行
            doSomethings();
            Log.d(TAG, "run: " + (index++));
            mHandler.sendMessage(Message.obtain(mHandler,1));
        }
    }, 2000);  // 2秒执行一次
}

HandlerThread用法

HandlerThread常用于需要在后台执行耗时任务,并与UI线程进行交互的场景。比如,每隔6秒需要切换一下TextView的显示数据,虽然可以在UI线程中执行,但是这样的操作长时间占用UI线程,很容易让UI线程卡顿甚至崩溃,所以最好在子线程HandlerThread中调用这种业务。
HandlerThread能新建一个拥有Looper的线程。这个Looper能够用来新建其他的Handler。但需要注意的是,新建的HandlerThread需要及时回收,否则容易内存泄露。

非UI线程的业务也可以使用HandlerThread消息机制,因为不会干扰或阻塞UI线程,而且通过消息可以多次重复使用当前线程,也可以多个Handler也可以共享一个Looper,节省开支。

一个线程只能创建一个Looper,一个Looper个创建多个Handler。

使用HandlerThread可以实现以下功能和优势:

  1. 后台线程执行任务:HandlerThread在后台创建一个工作线程,可以在该线程中执行耗时任务,而不会阻塞UI线程,保证了应用的响应性和流畅性。
  2. 消息处理和线程间通信:HandlerThread内部封装了Looper和Handler,可以轻松地实现消息的发送和处理,以及线程间的通信。通过HandlerThread,可以将耗时任务的结果发送到UI线程进行更新,或者接收UI线程发送的消息进行处理。
  3. 简化线程管理:HandlerThread将线程的创建和管理进行了封装,开发人员只需要关注业务逻辑的实现,而无需手动创建和管理线程,减少了线程管理的复杂性。

主线程-创建Handler

private Handler mHandler;
private HandlerThread mHandlerThread;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mHandlerThread = new HandlerThread("子线程HandlerThread");
    mHandlerThread.start();
    mHandler= new Handler(mHandlerThread.getLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    Log.d(TAG, "handleMessage: " + mHandlerThread.getName());
                    // todo 在主线程更新UI
                    doSomethingsAboutUI();
                    break;
            }
        }
    };
}
...
private void sendMessages(){
    new Thread(new Runnable() {
        @Override
        public void run() {
            // todo 在子线程中进行耗时操作
            doSomethings();

            Message message = mHandler.obtainMessage(1);
            mHandler.sendMessage(message);
        }
    }).start();
}

@Override
protected void onPause() {
    super.onPause();
    // 防止退出界面后Handler还在执行
    mHandler.removeMessages(1);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    // 防止退出界面后Handler还在执行
    mHandler.removeMessages(1);
    // 释放资源
    mHandlerThread.quit();
}

子线程-创建Handler

private Handler mHandler;
private HandlerThread mHandlerThread;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
	new Thread(new Runnable() {
        @Override
        public void run() {
            mHandlerThread = new HandlerThread("子线程HandlerThread");
            mHandlerThread.start();
            mHandler= new Handler(mHandlerThread.getLooper()) {
                @Override
                public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
                    switch (msg.what){
                        case 1:
                            Log.d(TAG, "handleMessage: " + mHandlerThread.getName());
                            // todo 在主线程更新UI
                            doSomethingsAboutUI();
                            break;
                    }
                }
            };
        }
    }).start();
}
...
private void sendMessages(){
    new Thread(new Runnable() {
        @Override
        public void run() {
            // todo 在子线程中进行耗时操作
            doSomethings();
			Log.d(TAG, "sendMessages: 耗时操作");
			
            Message message = mHandler.obtainMessage(1);
            mHandler.sendMessage(message);
        }
    }).start();
}

@Override
protected void onPause() {
    super.onPause();
    // 防止退出界面后Handler还在执行
    mHandler.removeMessages(1);  // 删除所有消息 mHandler.removeCallbacksAndMessages(null);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    // 防止退出界面后Handler还在执行
    mHandler.removeMessages(1);   // 删除所有消息 mHandler.removeCallbacksAndMessages(null);
    // 释放资源
    mHandlerThread.quit();
}

注意:1、子线程中创建了Looper,当没有消息的时候子线程将会被block,无法被回收,所以我们需要手动调用quit 方法将消息删除并且唤醒looper,然后next方法返回null退出loop。
2、在主线程和子线程中,使用HandlerThread创建Handler,基本没有区别。但如果没有使用HandlerThread,在子线程中需要先创建Looper,再创建Handler。具体如下:

private Handler mHandler ;
...
new Thread(new Runnable() {
   @Override
   public void run() {
       Looper.prepare(); // 创建Looper的子线程,然后创建MessageQueue,最后进行绑定。
       mHandler = new Handler(new Handler.Callback() {
           @Override
           public boolean handleMessage(@NonNull Message msg) {
               return false;
           }
       });
       Looper.loop();  // 启动Looper
   }
}).start();

FAQ

Message是如何创建

首先考虑一个问题,屏幕刷新率 60Hz(即每秒刷新60次),每次刷新要用到 3 个 Message,也就是每秒钟至少要创建180个Message。这样不断的创建回收,就会出现内存抖动的问题,从而导致 GC、屏幕卡顿等问题。
为了解决上面的问题,采用 Message 了享元的设计模式,使用 obtain() 方法创建。在Handler 中创建两个线程池队列,一个是我们比较熟悉的 MessageQueue,另一个就是回收池 sPool(最大长度是 50 复用池)。MessageQueue 中 Message 回收时,我们将清空数据的 Message 放回到 sPool 队列中。创建 Manager,我们直接从 sPool 池中取出来就可以了。
应用场景:地图、股票、RecyclerView复用等对数据的处理都使用了享元模式。

主线程中Looper的轮询死循环为何没有阻塞主线程

Looper 轮询是死循环,但是当没有消息的时候他会 block(阻塞, 阻塞代码没有执行计时操作),ANR 是当我们处理点击事件的时候 5s 内没有响应,我们在处理点击事件的时候也是用的 Handler,所以一定会有消息执行,并且 ANR 也会发送 Handler 消息,所以不会阻塞主线程。Looper 是通过 Linux 系统的 epoll 实现的阻塞式等待消息执行(有消息执行无消息阻塞),而 ANR 是消息处理耗时太长导致无法执行剩下需要被执行的消息触发了 ANR。Handler底层为什么用epoll,为什么不用select和poll? Socket 非阻塞 IO 中 select 需要全部轮询不适合,poll 也是需要遍历和 copy,效率太低了。epoll 非阻塞式 IO,使用句柄获取 APP 对象,epoll 无遍历,无拷贝。还使用了红黑树(解决查询慢的问题)。

Handler内存泄漏问题及解决方案

内部类持有外部类的引用导致了内存泄漏,如果 Activity 退出的时候,MessageQueue中还有一个 Message 没有执行,这个 Message 持有了 Handler 的引用,而 Handler 持有了 Activity 的引用,导致 Activity 无法被回收,导致内存泄漏。这种问题可以使用 static 关键字修饰,在 onDestory 的时候将消息清除。
简单理解:当 Handler 为非静态内部类时,其持有外部类 Activity 对象,所以导致 static Looper -> mMainLooper -> MessageQueue -> Message -> Handler -> MainActivity,这个引用链中 Message 如果还在 MessageQueue 中等待执行,则会导致 Activity 一直无法被释放和回收。
根本原因:因为Looper需要循环处理消息,但一个线程只有一个Looper,而一个线程中可以有多个Handler,MessageQueue中消息Message 执行时不知道要通知哪个Handler执行任务,所以在Message创建时target引用了Handler对象,用于回调执行的消息。
如果Handler是Activity这种短生命周期对象的非静态内部类时,则创建出来的Handler对象会持有该外部类Activity的引用,当页面销毁时,还在队列的Message持有着Handler对象,而Handler正持有着外部类Activity,就会导致 Activity无法被gc回收,从而导致内存泄漏。
解决办法:
1、Handler不能是Activity这种短生命周期的对象类的内部类;
2、在 Activity销毁时,将创建的 Handler中的消息队列清空并结束所有任务。
3、将handler设置成static,static变量是全局变量,不能够自动引用外部类变量,这时Handler 就不再持有 Activity,Activity就可以正常释放。

Handler为什么会持有Activity的引用?

创建Handler时,采用的是匿名内部类或者成员内部类的方式,而内部类会默认持有外部类的引用,也就是Handler对象会默认持有Activity的引用。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微服务保护和分布式事务(Sentinel、Seata)笔记

一、雪崩问题的解决的服务保护技术了解 二、Sentinel 2.1Sentinel入门 1.Sentinel的安装 (1)下载Sentinel的tar安装包先 (2)将jar包放在任意非中文、不包含特殊字符的目录下,重命名为 sentinel-dashboard.jar &…

Docker容器---Harbor私有仓库部署与管理

一、搭建本地私有仓库 1、下载registry镜像 [rootlocalhost ~]#docker pull registry Using default tag: latest latest: Pulling from library/registry 79e9f2f55bf5: Pull complete 0d96da54f60b: Pull complete 5b27040df4a2: Pull complete e2ead8259a04: Pull comp…

vulnhub靶场之FunBox-1

一.环境搭建 1.靶场描述 Boot2Root ! This is a reallife szenario, but easy going. You have to enumerate and understand the szenario to get the root-flag in round about 20min. This VM is created/tested with Virtualbox. Maybe it works with vmware. If you n…

NASA数据集——NASA 标准二级(L2)暗目标(DT)气溶胶产品每 6 分钟在全球范围内对陆地和海洋上空的气溶胶光学厚度(AOT)产品

VIIRS/NOAA20 Dark Target Aerosol 6-Min L2 Swath 6 km 简介 NOAA-20(前身为联合极地卫星系统-1(JPSS-1))--可见红外成像辐射计套件(VIIRS)NASA 标准二级(L2)暗目标(D…

集合的基本操作

集合: 在java当中,含有着一些不同的存储数据的相关集合。分为单列集合(Collection)和双列集合(Map)。 Collection 首先学习Collection来进行展示: 以框框为例子,蓝色的代表的是接口,而红色的…

【Linux极简教程】常见实用命令不断更新中......

【Linux极简教程】常见实用命令不断更新中...... 常见问题1.Waiting for cache lock: Could not get lock /var/lib/dpkg/lock. It is held by process xxxx(dpkg) 常见问题 1.Waiting for cache lock: Could not get lock /var/lib/dpkg/lock. It is held by process xxxx(dp…

机器学习:基于Sklearn、XGBoost,使用逻辑回归、支持向量机和XGBClassifier预测股票价格

前言 系列专栏:机器学习:高级应用与实践【项目实战100】【2024】✨︎ 在本专栏中不仅包含一些适合初学者的最新机器学习项目,每个项目都处理一组不同的问题,包括监督和无监督学习、分类、回归和聚类,而且涉及创建深度学…

C语言——队列的实现

队列按照先进先出(FIFO,First In First Out)的原则管理数据。这意味着最先进入队列的元素会被最先移出,类似于排队等候服务的情况。队列通常有两个主要操作:入队(enqueue),将元素添加…

DSP实时分析平台设计方案:924-6U CPCI振动数据DSP实时分析平台

6U CPCI振动数据DSP实时分析平台 一、产品概述 基于CPCI结构完成40路AD输入,30路DA输出的信号处理平台,处理平台采用双DSPFPGA的结构,DSP采用TI公司新一代DSP TMS320C6678,FPGA采用Xilinx V5 5VLX110T-1FF1136芯片&#xff…

《QT实用小工具·五十》动态增删数据与平滑缩放移动的折线图

1、概述 源码放在文章末尾 该项目实现了带动画、带交互的折线图,包含如下特点: 动态增删数值 自适应显示坐标轴数值 鼠标悬浮显示十字对准线 鼠标靠近点自动贴附 支持直线与平滑曲线效果 自定义点的显示类型与大小 自适应点的数值显示位置 根据指定锚点…

java并发编程-AQS介绍及源码详解

介绍 AQS 的全称为 AbstractQueuedSynchronizer ,就是抽象队列同步器。 从源码上可以看到AQS 就是一个抽象类,它继承了AbstractOwnableSynchronizer,实现了java.io.Serializable接口。 public abstract class AbstractQueuedSynchronizere…

redis缓存详情

redis安装包及图形化软件: 百度链接:https://pan.baidu.com/s/1wljo7JzgrSQyqldv9d5HZA?pwdht1m 提取码:ht1m 目录 1.redis的下载及安装 1.1redis的启动与停止 1.2Redis服务启动与停止 2.redis数据类型及常用指令 2.1redis数据类型 2.2redis常用…

读天才与算法:人脑与AI的数学思维笔记15_声响的数学之旅

1. 音乐 1.1. 巴赫的作品以严格的对位著称,他十分中意对称的结构 1.2. 巴托克的作品很多都以黄金比例为结构基础,他非常喜欢并善于使用斐波纳契数列 1.3. 有时,作曲家是本能地或者不自知地被数学的模式和结构所吸引,而他们并没…

Golang | Leetcode Golang题解之第61题旋转链表

题目: 题解: func rotateRight(head *ListNode, k int) *ListNode {if k 0 || head nil || head.Next nil {return head}n : 1iter : headfor iter.Next ! nil {iter iter.Nextn}add : n - k%nif add n {return head}iter.Next headfor add > …

【项目构建】04:动态库与静态库制作

OVERVIEW 1.编译动态链接库(1)编译动态库(2)链接动态库(3)运行时使用动态库 2.编译静态链接库(1)编译静态库(2)链接静态库(3)运行时使…

matlab学习007-已知离散时间系统的系统函数并使用matlab绘制该系统的零极点图;判断系统的稳定性;幅频和相频特性曲线

目录 题目 离散时间系统的系统函数:H(z)(3*z^3-5*z^210z)/(z^3-3*z^27*z-5) 1,绘制该系统的零极点图 1)零极点图 2)代码 2,判断系统的稳定性 1)判断结果 2)代码 3,试用MATL…

C++的未来之路:探索与突破

在计算机科学的浩瀚星空中,C无疑是一颗璀璨的明星。自诞生以来,它以其强大的性能和灵活的特性,赢得了无数开发者的青睐。然而,随着技术的不断进步和应用的日益复杂,C也面临着前所未有的挑战和机遇。本文将探讨C的未来之…

腾锐D2000-8 MXM VPX,全国产,可广泛应用于边缘计算网关、入侵检测、VPN、网络监控等等应用领域

腾锐D2000-8 MXM VPX 1. 概述 XMVPX-108 是一款基于飞腾 D2000/8 处理器的低功耗逻辑运算和图形处理 VPX 刀片, 板贴 32GB DDR4 内存,搭载飞腾 X100 套片,满足通用 IO 接口功能。GPU 采用 MXM 小型插卡形式, 搭配 8GB 显卡。提供…

【16-降维技术:PCA与LDA在Scikit-learn中的应用】

文章目录 前言主成分分析(PCA)原理简介Scikit-learn中的PCA实现应用示例线性判别分析(LDA)原理简介Scikit-learn中的LDA实现应用示例总结前言 降维是机器学习中一种常见的数据预处理方法,旨在减少数据集的特征数量,同时尽量保留原始数据集的重要信息。这不仅有助于减少计…

开箱子咸鱼之王H5游戏源码_内购修复优化_附带APK完美运营无bug最终版__GM总运营后台_附带安卓版本

内容目录 一、详细介绍二、效果展示2.效果图展示 三、学习资料下载 一、详细介绍 1.包括原生打包APK,资源全部APK本地化,基本上不跑服务器宽带 2.优化后端,基本上不再一直跑内存,不炸服响应快! 3.优化前端&#xff0c…
最新文章