Android --- 消息机制与异步任务

news2024/11/18 1:28:29

在Android中,只有在UIThread(主线程)中才能直接更新界面,

在Android中,长时间的工作联网都需要在workThread(分线程)中执行

在分线程中获取服务器数据后,需要立即到主线程中去更新UI来显示数据,

所以,如何实现线程间的通信(消息机制)

消息机制原理

消息机制原理 尚硅谷

handler发送消息到 MessageQueue 消息队列中,消息队列内部是一个链表的结构,Looper会取出消息队列中待处理的消息,调用handler的dispatchMessage()分发消息,handler中处理消息的方法有三种,最常用的是第三种

Message的callback

handler的callback

handler的handleMessage方法

消息机制相关API

Message 消息

可理解为线程间通信的数据单元,可通过message携带需要的数据

创建对象:Message.obtain()、new Message()

Message.obtain()使用了Message中的消息池,比直接new一个对象更高效

常用参数:

  • what: id标识,用以区分来自哪个线程
  • arg1/arg2: 子线程需要向主线程传递整型参数
  • obj: Object 任意对象

Handler 处理器

handler并不是只能用在处理message上,定时循环调度等工作也能使用它。

Handler 是Message 的处理器,也负责消息的发送和移除工作。

  • 发送即时消息 
public final boolean sendMessage(@NonNull Message msg),实际上调用了sendMessageDelayed()发送一个延迟时间为0的消息
public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
  • 发送延时消息,并不是指延时发送,而是延时处理
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis)
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
  • 发送空消息
public final boolean sendEmptyMessage(int what)
    {
        return sendEmptyMessageDelayed(what, 0);
    }

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
 }

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

不管是发送什么消息,最后都要调用 sendMessageAtTime 方法

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
 private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
  • 处理消息 (回调方法) 在主线程中执行
public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        boolean handleMessage(@NonNull Message msg);
    }
  • 移除未处理消息,比如发送的延时消息:让消息队列调用 removeMessages 方法移除消息
    public final void removeCallbacks(@NonNull Runnable r) {
        mQueue.removeMessages(this, r, null);
    }

MessageQueue 消息队列

它是一个按Message的when(被处理时间)排序的优先级队列,用来存放通过 Handler 发送的消息。

实例

创建Handler对象,并重写 handleMessage 方法

在主/分线程创建Message对象,利用handler对象发送消息

在 handleMessage 中处理消息

  • 创建Handler对象,并重写 handleMessage 方法 

只要 handle 发了消息,就一定会触发 handleMessage 方法,并传入一个 Message 对象,根据Message 对象的what属性判断属于哪个线程。

public class HandleActivity extends AppCompatActivity {

    TextView textView ;
    String httpDate = "";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handle);
    }


    // 利用 handller 处理
    // 1.实例化一个 Handler
   Handler handler = new Handler(
      // 只要 handle 发了消息,就一定会触发 handleMessage 方法,并传入一个 Message 对象
            new Handler.Callback() {
                @Override
                // 3.由handle对象接收消息并处理,在Handler的内部类中处理
                public boolean handleMessage(@NonNull Message msg) {
                    Log.e("handler 处理消息", "这是handler发送的消息,此时是空");

                    // 根据 Message 的 what 属性,区分来源于哪个线程
                    if (msg.what == 1) {
                        textView1 = findViewById(R.id.t1);
                        textView1.setText(httpDate);
                    } else if (msg.what == 2) {
                        textView2 = findViewById(R.id.t2);
                        textView2.setText(httpDate);
                    } else if (msg.what == 3) {
                        textView3 = findViewById(R.id.t3);
                        textView3.setText("msg.what = "+msg.what+"\n"+msg.obj.toString());
                    }
                    return false;
                }
            }
    );
}
  • 在主/分线程创建Message对象,利用handler对象发送消息

当消息没有包含数据时,可以使用handler.sendEmptyMessage(int what) 发送一个空消息

new Thread() {
    @Override
    public void run() {
       super.run();
       getHttp();
       // 当 handler 需要传送数据时,需要使用到 Message,使用 sendMessage 方法
                   
       Message message = new Message();
       message.what = 3;
       DataBean dataBean = new DataBean(1,"157181@qq.com","ybr","scl","asiudh");
       message.obj = dataBean;
       handler.sendMessage(message);
       }
   }.start();
  • 设置一个网络操作方法 
 // 网络操作
    private void getHttp() {
        try {
            // 1. 实例化一个 URL 对象
            URL url = new URL("https://reqres.in/api/users");
            // 2. 获取 HttpURLConnection 实例,使用URL的
            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
            // 3. 设置请求相关属性
            httpURLConnection.setRequestMethod("GET"); // 请求方法
            httpURLConnection.setConnectTimeout(6000); // 超时时间
            // 4. 获取响应码 200 成功 404 未请求到指定资源 500 服务器异常(此时已经发起请求)
            // 5. 判断响应码并获取响应数据(响应的正文)
            if (httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                InputStream inputStream = httpURLConnection.getInputStream(); // 获取了服务器返回的输入流
                byte[] bytes = new byte[1024]; // bytes 数组,每次最多可以存放 1024 个字节
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();// 存储从输入流中读取的数据
                int len = 0;
                while ((len = inputStream.read(bytes)) > -1) {
                    // 将字节数组中的内容写入到缓存流
                    /* 参数1:要存入的字节数组
                     * 参数2:起点
                     * 参数3:要存入的长度*/
                    byteArrayOutputStream.write(bytes, 0, len);
                }
                httpDate = new String(byteArrayOutputStream.toByteArray());
                Log.e("GET返回的数据", httpDate);
            }
        } catch (ProtocolException ex) {
            throw new RuntimeException(ex);
        } catch (MalformedURLException ex) {
            throw new RuntimeException(ex);
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

 全部代码

package com.example.androidstudiostudy;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.example.androidstudiostudy.data.DataBean;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;

public class HandleActivity extends AppCompatActivity {

    TextView textView1, textView2, textView3;
    String httpDate = "";

    // 利用 handller 处理
    // 1.实例化一个 Handler
    Handler handler = new Handler(
            // 只要 handle 发了消息,就一定会触发 handleMessage 方法,并传入一个 Message 对象
            new Handler.Callback() {
                @Override
                // 3.由handle对象接收消息并处理,在Handler的内部类中处理
                public boolean handleMessage(@NonNull Message msg) {
                    Log.e("handler 处理消息", "这是handler发送的消息,此时是空");

                    // 根据 Message 的 what 属性,区分来源于哪个线程
                    if (msg.what == 1) {
                        textView1 = findViewById(R.id.t1);
                        textView1.setText(httpDate);
                    } else if (msg.what == 2) {
                        textView2 = findViewById(R.id.t2);
                        textView2.setText(httpDate);
                    } else if (msg.what == 3) {
                        textView3 = findViewById(R.id.t3);
                        textView3.setText("msg.what = "+msg.what+"\n"+msg.obj.toString());
                    }
                    return false;
                }
            }
    );

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handle);
    }

    // 此时有两个线程,这两线程发送的消息都能被 Handle接收,但如何区分不同线程发送的消息从而做不同处理呢?
    public void getDate(View view) {
        int id = view.getId();
        // 第一个线程
        if (id == R.id.handle1) {
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    getHttp();
                    // 此时会报错:Only the original thread that created a view hierarchy can touch its views.
                    /*TextView textView = findViewById(R.id.t1);
                    textView.setText(httpDate);*/

                    // 解决办法 1 runOnUiThread 方法 ==> 相当于在主线程中跑 (初学阶段)
                    /*runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            TextView textView = findViewById(R.id.t1);
                            textView.setText(httpDate);
                        }
                    });*/
                    // 解决办法 2 利用 Handle 处理
                    // 2.在子线程中发送(空)消息,此时发送的是空消息
                    handler.sendEmptyMessage(1);
                }
            }.start();
        }
        // 第二个线程
        else if (id == R.id.handle2) {
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    getHttp();
                    httpDate = httpDate + "\n第2个线程";
                    handler.sendEmptyMessage(2);
                }
            }.start();
        }
        // 第三个线程
        else if (id == R.id.handle3) {
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    getHttp();
                    // 当 handler 需要传送数据时,需要使用到 Message,使用 sendMessage 方法
                    /* 1. 实例化一个 Message
                     * 2. 参数
                     ** what:用于区分 handler 发送消息的不同线程来源
                     ** arg1/arg2: 子线程需要向主线程传递整型参数
                     ** obj: Object 任意对象*/
                    Message message = new Message();
                    message.what = 3;

                    DataBean dataBean = new DataBean(1,"157181@qq.com","ybr","scl","asiudh");
                    message.obj = dataBean;
                    handler.sendMessage(message);
                }
            }.start();
        }
    }

    // 网络操作
    private void getHttp() {
        try {
            // 1. 实例化一个 URL 对象
            URL url = new URL("https://reqres.in/api/users");
            // 2. 获取 HttpURLConnection 实例,使用URL的
            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
            // 3. 设置请求相关属性
            httpURLConnection.setRequestMethod("GET"); // 请求方法
            httpURLConnection.setConnectTimeout(6000); // 超时时间
            // 4. 获取响应码 200 成功 404 未请求到指定资源 500 服务器异常(此时已经发起请求)
            // 5. 判断响应码并获取响应数据(响应的正文)
            if (httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                InputStream inputStream = httpURLConnection.getInputStream(); // 获取了服务器返回的输入流
                byte[] bytes = new byte[1024]; // bytes 数组,每次最多可以存放 1024 个字节
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();// 存储从输入流中读取的数据
                int len = 0;
                while ((len = inputStream.read(bytes)) > -1) {
                    // 将字节数组中的内容写入到缓存流
                    /* 参数1:要存入的字节数组
                     * 参数2:起点
                     * 参数3:要存入的长度*/
                    byteArrayOutputStream.write(bytes, 0, len);
                }
                httpDate = new String(byteArrayOutputStream.toByteArray());
                Log.e("GET返回的数据", httpDate);
            }
        } catch (ProtocolException ex) {
            throw new RuntimeException(ex);
        } catch (MalformedURLException ex) {
            throw new RuntimeException(ex);
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }
}

Looper 循环器

  • 负责循环取出 MessageQueue 中当前需要处理的Message
  • 交给对应的handler进行处理
  • 处理完后,将Message缓存到消息池中,以备复用

每个线程都可以有一个关联的Looper对象,用于处理该线程的消息队列。主要功能是接收来自消息队列的消息并将其分发给对应的Handler处理。

在Android中,主线程已经自动创建了一个Looper对象,并启动了消息循环,我们可以在主线程中方便地使用Handler来处理UI事件。

而对于其他线程,如果需要处理消息,就需要手动创建一个Looper对象,并调用Looper.loop()方法来启动消息循环。

  • 必须确保在创建Handler对象之前,在线程中调用了Looper.prepare()方法创建Looper对象并初始化。
  • 通过调用Looper.loop()方法,线程会进入一个无限循环,不断地从消息队列中取出消息,并将其分发给对应的Handler进行处理。当调用Looper.quit()方法时,消息循环会停止,线程会退出循环。
 Handler handler2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handle);

        // 开一个新的线程,用于接收主线程向子线程传递消息
        new Thread(new Runnable() {
            @Override
            public void run() {

                // 系统会自动为主线程开启消息循环(自动调用这句代码),与此同时创建一个 Looper 对象,不断的从 MessageQue中读取消息,交给主线程处理
                Looper.prepare(); // 准备开启一个消息循环

                handler2 = new Handler(new Handler.Callback() {
                    @Override
                    public boolean handleMessage(@NonNull Message msg) {
                        Log.e("主线程向子线程中发送消息", "" + msg.what + "" + msg.arg1);
                        return false;
                    }
                });

                Looper.loop(); //开始消息循环,相当于 while(true) ,在这里等待消息
            }
        }).start();
    }

    // 此时有两个线程,这两线程发送的消息都能被 Handle接收,但如何区分不同线程发送的消息从而做不同处理呢?
    public void getDate(View view) {
        int id = view.getId();
        // 第一个线程
        if (id == R.id.handle1) {
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    getHttp();
                    // 此时会报错:Only the original thread that created a view hierarchy can touch its views.
                    /*TextView textView = findViewById(R.id.t1);
                    textView.setText(httpDate);*/

                    // 解决办法 1 runOnUiThread 方法 ==> 相当于在主线程中跑 (初学阶段)
                    /*runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            TextView textView = findViewById(R.id.t1);
                            textView.setText(httpDate);
                        }
                    });*/
                    // 解决办法 2 利用 Handle 处理
                    // 2.在子线程中发送(空)消息,此时发送的是空消息
                    handler.sendEmptyMessage(1);
                }
            }.start();
        }
        // 第二个线程
        else if (id == R.id.handle2) {
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    getHttp();
                    httpDate = httpDate + "\n第2个线程";
                    handler.sendEmptyMessage(2);
                }
            }.start();
        }
        // 第三个线程
        else if (id == R.id.handle3) {
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    getHttp();                  
                    Message message = new Message();
                    message.what = 3;

                    DataBean dataBean = new DataBean(1, "157181@qq.com", "ybr", "scl", "asiudh");
                    message.obj = dataBean;
                    handler.sendMessage(message);
                }
            }.start();
        }
        // 第四个线程- 主线程向子线程发送消息
        else if (id == R.id.handle4) {
            Message message2 = new Message();
            message2.what = 999;
            message2.arg1 = 1234;
            handler2.sendMessage(message2);
        }
    }

线程中更新UI

runOnUiThread 方法

==> 相当于在主线程中跑 (初学阶段)

public void getDate() {
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    getHttp();
                    // 此时会报错:Only the original thread that created a view hierarchy can touch its views.
                    /*TextView textView = findViewById(R.id.t1);
                    textView.setText(httpDate);*/

                    // 解决办法 1 runOnUiThread 方法 ==> 相当于在主线程中跑 (初学阶段)
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            TextView textView = findViewById(R.id.t1);
                            textView.setText(httpDate);
                        }
                    });
                }
            }.start();
        }

利用Handler 

 new Thread() {
     @Override
     public void run() {
       super.run();
       getHttp();
       // 此时会报错:Only the original thread that created a view hierarchy can touch its views.
       // 2.在子线程中发送(空)消息,此时发送的是空消息
       handler.sendEmptyMessage(1);
       }
}.start();
Handler handler = new Handler(
     new Handler.Callback() {
     @Override
     // 3.由handle对象接收消息并处理,在Handler的内部类中处理
     public boolean handleMessage(@NonNull Message msg) {
             Log.e("handler 处理消息", "这是handler发送的消息,此时是空");
             textView = findViewById(R.id.t1);
             textView.setText(httpDate);
             return false;
        }
      }
    );

什么是异步任务?

逻辑上:以多线程的方式完成的功能需求

API上:指AsyncTask类

在没有 AsyncTask 前,我们可以使用 Thread+Handler 实现异步任务,而 AsyncTask 是对Thread+Handler 功能的封装,使用更简洁,效率更高效

AsyncTask 是一个抽象类,需要指定3个泛型参数

public abstract class AsyncTask<Params, Progress, Result>
  • Params: 这个泛型指定的是我们传递给异步任务执行时的参数的类型。
  • Progress: 这个泛型指定的是我们的异步任务在执行的时候将执行的进度返回给UI线程的参数的类型。
  • Result: 这个泛型指定的异步任务执行完后返回给UI线程的结果的类型。

而 AsyncTask目前已被弃用

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

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

相关文章

50. 【Android教程】xml 数据解析

xml 是一种标记扩展语言&#xff08;Extension Mark-up Language&#xff09;&#xff0c;学到这里大家对 xml 语言一定不陌生&#xff0c;但是它在 Android 中的运用其实只是冰山一角。抛开 Android&#xff0c;XML 也被广泛运用于各种数据结构中。在运用 xml 编写 Android 布…

Docker创建镜像之--------------基于Dockerfile创建

目录 一、在编写 Dockerfile 时&#xff0c;有严格的格式需要遵循 二、Dockerfile 操作常用的指令 2.1ENTRYPOINT和CMD共存的情形 2.2ENTRYPOINT和CMD的区别 2.3ADD 与COPY的区别 三、Dockerfile案例 3.1构建apache镜像 3.1.1 创建镜像目录方便管理 3.1.2创建编写dock…

基于Springboot的音乐翻唱与分享平台

基于SpringbootVue的音乐翻唱与分享平台设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页 音乐资讯 音乐翻唱 在线听歌 后台登录 后台首页 用户管理 音乐资讯管理…

基础安全:CSRF攻击原理与防范

CSRF的概念 CSRF(Cross-Site Request Forgery)中文名为“跨站请求伪造”。这是一种常见的网络攻击手段,攻击者通过构造恶意请求,诱骗已登录的合法用户在不知情的情况下执行非本意的操作。这种攻击方式利用了Web应用程序中用户身份验证的漏洞,即浏览器在用户完成登录后会自…

JavaEE 初阶篇-深入了解网络原理中传输层的端口号与 UDP 协议报文格式

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 端口号概述 1.1 端口号的作用 1.2 端口号不能重复被多个进程绑定 2.0 传输层协议 - UDP 2.1 UDP 的特性 2.2 UDP 的报文格式 1.0 端口号概述 端口号是计算机网络中…

进一步了解android studio 里 AGP,gradle等关系

目录 &#xff08;1&#xff09; gradle是什么 &#xff08;2&#xff09; 工程的jdk版本&#xff0c;及引用包的编译版本的关系 实践 问题与解决 编译成功与运行成功 编译成功 运行成功 &#xff08;1&#xff09; gradle是什么 Gradle是一个构建工具&#xff0c;它是…

1.6 Java全栈开发前端+后端(全栈工程师进阶之路)-前置课程Jdbc编程,使用Java通过Jdbc对数据库进行基础操作

原理图 用java代码实现连接数据库&#xff08;mysql&#xff09;的操作 因为数据库连接需要使用到API和URL&#xff0c;下面简单介绍下API和URL的概念&#xff0c; API&#xff1a; Application Programming Interface应用程序编程接口&#xff0c;就是一套类库 Java中的AP…

2024中国绿电制氢技术趋势分析报告

来源&#xff1a;ATC & 大东时代 国家级规划《氢能产业发展中长期规划&#xff08;2021-2035&#xff09;》出台 • 主要宗旨&#xff1a;明确“能源”的角色定位以及在绿色低碳转型中的作用&#xff0c;为产业发展构建清晰的蓝图。 • 阶段目标设立&#xff1a; • 2025/…

如何不使用代理服务从hugging face上下载大模型?

前言&#xff1a;中国大陆的朋友会发现hugging face经常无法访问了&#xff0c;特别是在服务器上下载大型模型/数据集&#xff0c;如果先在电脑上下载完再传输到服务器上&#xff0c;对于大模型来说会非常麻烦&#xff0c;这篇博客一共提供了三种有效的方法不使用代理服务从hug…

【Java】何为JShell?——有趣的Java学习小工具

前言&#xff1a;上一篇中我们已经看到了如何编译和运行一个Java程序。Java1.9&#xff08;即Java9&#xff09;中引入了另一种使用Java的方式。JShell(Java Shell)程序提供了一个“读取-计算-打印循环”&#xff08;Read-Evaluate-Print Loop,REPL&#xff09;。当你键入一个J…

【综述】多核处理器芯片

文章目录 前言 Infineon处理器 AURIX™系列 TC399XX-256F300S 典型应用 开发工具 参考资料 前言 见《【综述】DSP处理器芯片》 Infineon处理器 AURIX™系列&#xff0c;基于TriCore内核&#xff0c;用于汽车和工业领域。 XMC™系列&#xff0c;基于ARM Cortex-M内核&…

基于 Evan_song1234 开发,MoonSpaceCat 增补的2D 我的世界,增加双缓冲实现 cmd控制台窗口或 Powershell 流畅运行

游戏玩法&#xff1a; awsd移动 1234567890 各有功能 t 是命令行 q 是刷新 e 是重开 z 是挖 其他还没来及探索代码 代码来源 C我的世界2D控制台版_cminecraft-CSDN博客 其中解决颜色被双缓冲刷新没的方法 参考于自己的博客 用ReadConsoleOutput 解决双缓冲ReadConsol…

短视频素材哪个App最好?短视频素材哪里有免费的?

在数字媒体的黄金时代&#xff0c;富有创意的视频内容已成为吸引观众的关键。高质量的视频素材不仅能增强视觉效果&#xff0c;还能提升整体叙述的力度。以下列出了一系列全球顶尖的视频素材提供网站&#xff0c;它们将为你的广告制作、社交媒体或任何视频项目提供极具影响力的…

Python制作精美表格——plottable

plottable是一个基础matplotlib的绘制精美图形表格的库。他将表格内容美化并转为一张图片 使用前提&#xff1a; 1、原始数据数量较少&#xff0c;可以一屏展示。这个库会将原始表格的所有数据都放到一个图片里&#xff0c;数据太多展示效果较差。 2、pandas读取时会将index列…

vue3步骤条带边框点击切换高亮

如果是div使用clip-path: polygon(0% 0%, 92% 0%, 100% 50%, 92% 100%, 0% 100%, 8% 50%);进行裁剪加边框没实现成功。目前这个使用svg完成带边框的。 形状可自行更改path 标签里的 :d“[num ! 1 ? ‘M 0 0 L 160 0 L 176 18 L 160 38 L 0 38 L 15.5 18 Z’ : ‘M 0,0 L 160,0…

飞腾D2000+X100 TYPE6全国产核心板

飞腾D2000X100 TYPE6核心板 产品概述 飞腾D2000X100 TYPE6核心板为增强型自主控制器核心板&#xff0c;其核心芯片CPU采用飞腾D2000/8核工业版CPU、飞腾桥片X100、双通道DDR4L插槽、PHY芯片等。 产品特点 l 基于飞腾D2000X100桥片 l 丰富的PCIE扩展资源&#xff0c;一路PCIE…

Java设计模式 _结构型模式_过滤器模式

一、过滤器模式 1、过滤器模式 过滤器模式&#xff08;Filter Pattern&#xff09;是这一种结构型设计模式。过滤器&#xff0c;顾名思义&#xff0c;就是对一组数据进行过滤&#xff0c;从而最终获取到我们预期的数据。 2、实现思路 &#xff08;1&#xff09;、定义过滤器的…

图搜索算法详解与示例代码

在计算机科学领域&#xff0c;图搜索算法是一类用于在图数据结构中查找特定节点或路径的算法。图搜索算法在许多领域都有着广泛的应用&#xff0c;包括网络路由、社交网络分析、游戏开发等。本文将详细介绍几种常见的图搜索算法&#xff0c;包括深度优先搜索&#xff08;DFS&am…

数据结构四:线性表之带头结点的单向循环链表的设计

前面两篇介绍了线性表的顺序和链式存储结构&#xff0c;其中链式存储结构为单向链表&#xff08;即一个方向的有限长度、不循环的链表&#xff09;&#xff0c;对于单链表&#xff0c;由于每个节点只存储了向后的结点的地址&#xff0c;到了尾巴结点就停止了向后链的操作。也就…

LeetCode 98.验证二叉搜索树

题目描述 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左 子树 只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。 示例 1&#xff…