GB/T28181-2022之图像抓拍规范解读和设计实现

news2024/11/19 1:27:30

技术背景

GB/T28181-2022相对2016版,对图像抓拍有了明确的界定,图像抓拍在视频监控行业非常重要, Android平台GB28181设备接入端,无需实时上传音视频实时数据的情况下,就可以抓图上传到指定的图像存储服务器上。

图像抓拍基本要求如下:

  1. 源设备向目标设备发送图像抓拍配置命令,需要携带传输路径、会话ID等信息。
  2. 目标设备完成图像传输后,发送图像抓拍传输完成通知命令,采用IETF RFC3428中的MESSAGE方法实现。
  3. 图像文件命名规则宜采用“设备编码(20位)、图像编码(2位)、时间编码(17位)、序列码(2位)”的形式
  4. 图像格式宜使用JPEG,图像分辨率宜采用与主码流相同的分辨率。

需要注意的是,MESSAGE消息头Content-type头域为Content-type:Application/MANSCDP+xml,采用XML封装。设备收到图像抓拍配置命令后,发送配置响应命令,响应命令中包含执行结果信息。

图像抓拍流程如下:

技术实现

大牛直播SDK的SmartGBD已经完成GB28181设备接入侧的图像抓拍。

总体功能设计如下:

  •  [视频格式]H.264/H.265(Android H.265硬编码);
  •  [音频格式]G.711 A律、AAC;
  •  [音量调节]Android平台采集端支持实时音量调节;
  •  [H.264硬编码]支持H.264特定机型硬编码;
  •  [H.265硬编码]支持H.265特定机型硬编码;
  •  [软硬编码参数配置]支持gop间隔、帧率、bit-rate设置;
  •  [软编码参数配置]支持软编码profile、软编码速度、可变码率设置;
  •  支持横屏、竖屏推流;
  •  Android平台支持后台service推送屏幕(推送屏幕需要5.0+版本);
  • 支持纯视频、音视频PS打包传输;
  • 支持RTP OVER UDP和RTP OVER TCP被动模式(TCP媒体流传输客户端);
  • 支持信令通道网络传输协议TCP/UDP设置;
  • 支持注册、注销,支持注册刷新及注册有效期设置;
  • 支持设备目录查询应答;
  • 支持心跳机制,支持心跳间隔、心跳检测次数设置;
  • 支持移动设备位置(MobilePosition)订阅和通知;
  •  适用国家标准:GB/T 28181—2016;
  • 支持语音广播;
  • 支持语音对讲;
  • 支持图像抓拍;
  • 支持历史视音频文件检索;
  • 支持历史视音频文件下载;
  • 支持历史视音频文件回放;
  • 支持云台控制和预置位查询;
  •  [实时水印]支持动态文字水印、png水印;
  •  [镜像]Android平台支持前置摄像头实时镜像功能;
  •  [实时静音]支持实时静音/取消静音;
  •  [实时快照]支持实时快照;
  •  [降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测;
  •  [外部编码前视频数据对接]支持YUV数据对接;
  •  [外部编码前音频数据对接]支持PCM对接;
  •  [外部编码后视频数据对接]支持外部H.264数据对接;
  •  [外部编码后音频数据对接]外部AAC数据对接;
  •  [扩展录像功能]支持和录像SDK组合使用,录像相关功能。

图像抓拍相关信令处理如下:

/*
 * Author: daniusdk.com
 */
 
package com.gb.ntsignalling;
 
 
public interface GBSIPAgent {
    void setDeviceConfigListener(GBSIPAgentDeviceConfigListener deviceConfigListener);
 
 
    /*
     * 通知图像抓拍传输完成
     */
    boolean notifyUploadSnapShotFinished(String fromUserName, String fromUserNameAtDomain, String deviceID, String sessionID, java.util.List<String> snapShotList);
 
}

Device配置Listener如下:

package com.gb.ntsignalling;
 
public interface GBSIPAgentDeviceConfigListener {
    /*
     * 图像抓拍配置
     */
    void ntsOnDeviceSnapShotConfig(String from_user_name, String from_user_name_at_domain,
                                   String sn, String device_id, SnapShotConfig config,
                                   List<String> extra_info_list);
}

Snapshot配置接口如下:

public interface SnapShotConfig {
    int snap_num();
    int interval();
    String upload_url();
    String session_id();
}

图像抓拍JNI接口设计如下:

public class SmartPublisherJniV2 {
 
     /**
	 * 截图接口, 支持JPEG和PNG两种格式
	 * @param compress_format: 压缩格式, 0:JPEG格式, 1:PNG格式, 其他返回错误
	 * @param quality: 取值范围:[0, 100], 值越大图像质量越好, 仅对JPEG格式有效, 若是PNG格式,请填100
	 * @param file_name: 图像文件名, 例如:/dirxxx/test20231113100739.jpeg, /dirxxx/test20231113100739.png
	 * @param user_data_string: 用户自定义字符串
	 * @return {0} if successful
	 */
	 public native int CaptureImage(long handle, int compress_format, int quality, String file_name, String user_data_string);
	 
}

Device Snap Shot Listener 核心代码如下:

/*
 * Author: daniusdk.com
 */
 
public class GBDeviceSnapShotListenerImpl implements GBSIPAgentDeviceControlListener {
 
   @Override
    public void ntsOnDeviceSnapShotConfig(String from_user_name, final String from_user_name_at_domain,
                                          String sn, String device_id, final SnapShotConfig config,
                                          List<String> extra_info_list) {
        if (null == config)
            return;
 
        handler_.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG, "ntsOnDeviceSnapShotConfig device_id:" + device_id_ + " session_id:" + config.session_id()
                        + ", snap_num:" + config.snap_num() + ", interval:" + config.interval() + ", upload_url:" + config.upload_url());
 
                if (null == gb28181_agent_)
                    return;
 
                if (null == snap_shot_impl_) {
                    snap_shot_impl_ = new SnapShotGBImpl(image_path_, context_, handler_, lib_publisher_jni, snap_shot_publisher_);
                    snap_shot_impl_.start();
                }
 
                snap_shot_impl_.add_config(gb28181_agent_, from_user_name_, from_user_name_at_domain_, sn_,
                        device_id_, snap_shot_config_, extra_info_list_);
            }
 
            private String from_user_name_;
            private String from_user_name_at_domain_;
            private String sn_;
            private String device_id_;
            private SnapShotConfig snap_shot_config_;
            private List<String> extra_info_list_;
 
            public Runnable set(String from_user_name, String from_user_name_at_domain,
                                String sn, String device_id, SnapShotConfig config,
                                List<String> extra_info_list) {
                this.from_user_name_ = from_user_name;
                this.from_user_name_at_domain_ = from_user_name_at_domain;
                this.sn_ = sn;
                this.device_id_ = device_id;
                this.snap_shot_config_ = config;
                this.extra_info_list_ = extra_info_list;
                return this;
            }
 
        }.set(from_user_name, from_user_name_at_domain, sn, device_id, config, extra_info_list), 0);
    }
}
 
 
public class SnapShotGBImpl extends SnapShotImpl {
    private List<SnapConfig> config_list_ = new LinkedList<>();
  
    public SnapShotGBImpl(String dir, Context context, android.os.Handler handler,
                                      SmartPublisherJniV2 lib_sdk, LibPublisherWrapper publisher) {
        super(dir, context, handler, lib_sdk, publisher);
    }
 
    public boolean add_config(GBSIPAgent agent, String from_user_name, String from_user_name_at_domain, String sn,
                              String device_id, SnapShotConfig config, List<String> extra_info_list) {
        if (null == agent)
            return false;
 
        if (is_null_or_empty(device_id))
            return false;
 
        if (null == config)
            return false;
 
        if (config.snap_num() < 1)
            return false;
 
        if (config.interval() < 1)
            return false;
 
        if (is_null_or_empty(config.session_id()))
            return false;
 
        SnapConfig c = new SnapConfig(agent, from_user_name, from_user_name_at_domain, sn, device_id, config, extra_info_list);
        config_list_.add(c);
 
        return true;
    }
 
    public void on_captured_image(long result, String file_name, long file_date_time_ms, String user_data) {
        SnapConfig config = find_config(user_data);
        if (null == config) {
            super.on_captured_image(result, file_name, file_date_time_ms, user_data);
            return;
        }
 
        SnapItem item = config.find_capturing_item(file_name);
        if (null == item) {
            super.on_captured_image(result, file_name, file_date_time_ms, user_data);
            return;
        }
 
        if (result != 0) {
            item.set_status(SnapItem.ERROR_STATUS);
            item.set_error_info("capture failed");
            Log.e(TAG, "capture failed, file:" + file_name + ", session_id:" + user_data);
            return;
        }
 
        item.set_status(SnapItem.CAPTURE_COMPLETION_STATUS);
    }
 
    public void on_uploaded(boolean is_ok, String file_name, String session_id, String gb_name) {
        SnapConfig config = find_config(session_id);
        if (null == config) {
            Log.w(TAG, "on_uploaded cannot find config, session_id:" + session_id + ", gb_name:" + gb_name);
            return;
        }
 
        SnapItem item = config.find_uploading_item(gb_name);
        if (null == item) {
            Log.w(TAG, "on_uploaded cannot find item, session_id:" + session_id + ", gb_name:" + gb_name);
            return;
        }
 
        if (is_ok) {
            item.set_status(SnapItem.UPLOAD_COMPLETION_STATUS);
            Log.i(TAG, "on_uploaded ok, session_id:" + session_id + ", file:" + file_name);
        }else {
            item.set_status(SnapItem.ERROR_STATUS);
            item.set_error_info("upload failed");
            Log.e(TAG, "on_uploaded failed, session_id:" + session_id + ", file:" + file_name);
        }
    }
 
    @Override
    public void on_stop() {
        this.config_list_.clear();
        shutdown(200, TimeUnit.MILLISECONDS);
    }
 
    private void process_upload() {
        android.os.Handler app_handler = os_handler();
        for(SnapConfig c : config_list_)
            c.upload_files(app_handler, this);
    }
 
    private void process_finished() {
        List<String> notified_files = null;
 
        Iterator<SnapConfig> iterator = config_list_.iterator();
        while(iterator.hasNext()) {
            SnapConfig c = iterator.next();
            if (!c.is_can_notify_server())
                continue;
 
            iterator.remove();
 
            if (null == notified_files)
                notified_files = new LinkedList<>();
 
            c.notify_server(notified_files);
        }
		
        // 暂时删除这些文件, 根据业务需求随时调整就好
        if(notified_files != null && !notified_files.isEmpty())
            execute(new DeleteFilesTask(notified_files));
    }
 
    private static class SnapItem {
        private int status_ = INITIALIZATION_STATUS;
 
        private final String device_id_;
        private final int sn_; // 序列码, 40~41
        private final String dir_;
        private String file_name_;
    }
 
    private static class SnapConfig {
        private WeakReference<GBSIPAgent> agent_;
        private final String from_user_name_;
        private final String from_user_name_at_domain_;
        private final String sn_;
        private final String device_id_;
        private final String session_id_;
        private final int snap_num_;
        private final String upload_url_;
        private final int interval_sec_;
        private final List<String> extra_info_list_;
 
        private ArrayList<SnapItem> items_;
 
       
        public final String session_id() {
            return this.session_id_;
        }
 
        public void upload_files(android.os.Handler os_handler, SnapShotGBImpl snap) {
            if (null == items_)
                return;
 
            for (SnapItem i : items_) {
                if (i.is_capture_completion_status()) {
                    i.set_status(SnapItem.UPLOADING_STATUS);
 
                    BaseUploadTask upload_task = new MyUploadTask(upload_url_, i.file_name(), i.gb_snap_shot_file_id(),
                            session_id(), i.gb_name(), os_handler, snap);
 
                    if (!snap.submit(upload_task) ) {
                        i.set_status(SnapItem.ERROR_STATUS);
                        i.set_error_info("submit upload task failed");
                    }
                }
            }
        }
 
        public void notify_server(List<String> notified_files) {
            ArrayList<String> snap_shot_list = new ArrayList(items_.size());
            for (SnapItem i : items_) {
                if (SnapItem.UPLOAD_COMPLETION_STATUS == i.status())
                    snap_shot_list.add(i.gb_snap_shot_file_id());
 
                if (notified_files != null)
                    notified_files.add(i.file_name());
            }
 
            if (null == agent_)
                return;
 
            GBSIPAgent agent = agent_.get();
            if (null == agent)
                return;
 
            agent.notifyUploadSnapShotFinished(from_user_name_, from_user_name_at_domain_, device_id_, this.session_id(), snap_shot_list);
        }
    }
 
    private static class DeleteFilesTask implements Runnable {
        private List<String> file_name_list_;
 
        public DeleteFilesTask(List<String> file_name_list) {
            this.file_name_list_ = file_name_list;
        }
 
        @Override
        public void run() {
            if (null == file_name_list_)
                return;
 
            if (file_name_list_.isEmpty()) {
                file_name_list_ = null;
                return;
            }
 
            for (String i : file_name_list_) {
                try  {
                    File f = new File(i);
                    if (!f.exists()||!f.isFile() )
                        continue;
 
                    if (f.delete())
                        Log.i(TAG, "delete file:" + i);
                    else
                        Log.w(TAG, "delete file failed, " + i);
                }
                catch(Exception e) {
                    Log.e(TAG, "DeleteFilesTask.run Exception:", e);
                }
            }
 
            file_name_list_.clear();
            file_name_list_ = null;
        }
    }
 
    public static class BaseUploadTask extends CancellableTask {
        private final String upload_url_;
        private final String file_name_;
        private final String gb_snap_shot_file_id_;
        private final String session_id_;
        private final String gb_name_;
 
        private WeakReference<android.os.Handler> os_handler_;
        private WeakReference<SnapShotGBImpl> snap_;
 
        public BaseUploadTask(String upload_url, String file_name, String gb_snap_shot_file_id,
                              String session_id, String gb_name, android.os.Handler os_handler, SnapShotGBImpl snap) {
            this.upload_url_ = upload_url;
            this.file_name_ = file_name;
            this.gb_snap_shot_file_id_ = gb_snap_shot_file_id;
            this.session_id_ = session_id;
            this.gb_name_ = gb_name;
 
            if (os_handler !=null)
                this.os_handler_ = new WeakReference<>(os_handler);
 
            if (snap != null)
                this.snap_ = new WeakReference<>(snap);
        }
 
        protected final String upload_url() {
            return this.upload_url_;
        }
 
        protected final String file_name() {
            return this.file_name_;
        }
 
        protected final String gb_snap_shot_file_id() {
            return this.gb_snap_shot_file_id_;
        }
 
        protected final String session_id() {
            return this.session_id_;
        }
 
        protected final String gb_name() { return this.gb_name_; }
 
        protected final android.os.Handler os_handler() {
            if (os_handler_ != null)
                return os_handler_.get();
 
            return null;
        }
 
        protected final SnapShotGBImpl snap() {
            if (snap_ != null)
                return snap_.get();
 
            return null;
        }
 
        private static class ResultRunnable implements Runnable {
            private final boolean result_;
            private final String file_name_;
            private final String session_id_;
            private final String gb_name_;
            private WeakReference<SnapShotGBImpl> snap_;
 
            public ResultRunnable(boolean result, String file_name, String session_id,
                                  String gb_name, SnapShotGBImpl snap) {
                this.result_ = result;
                this.file_name_ = file_name;
                this.session_id_ = session_id;
                this.gb_name_ = gb_name;
 
                if (snap != null)
                    this.snap_ = new WeakReference<>(snap);
            }
 
            @Override
            public void run(){
                if (null == this.snap_)
                    return;
 
                SnapShotGBImpl snap = this.snap_.get();
                if (null == snap)
                    return;
 
                snap.on_uploaded(result_, file_name_, session_id_, gb_name_);
            }
        }
 
        protected void post_result(boolean is_ok) {
            android.os.Handler handler = os_handler();
            if (null == handler)
                return;
 
            SnapShotGBImpl gb_snap = snap();
            if (null == gb_snap)
                return;
 
            handler.post(new ResultRunnable(is_ok, file_name_,session_id_, gb_name_, gb_snap));
        }
    }
}
 

总结

以上是GB28181图像抓拍大概的流程和设计参考,权当抛砖引玉,Android终端除支持常规的音视频数据接入外,还可以支持移动设备位置(MobilePosition)订阅和通知、图像抓拍、语音广播和语音对讲、历史视音频下载和回放。感兴趣的开发者,可以单独跟我探讨。

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

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

相关文章

Ubuntu20.04安装配置OpenCV-Python库并首次执行读图

一、选择三方提供的预编译包安装&#xff1a; 可以从官网下载 OpenCV 的安装包&#xff0c;编译后使用&#xff1b;也可以直接使用第三方提供的预编译包 安装。显然后者不需要执行编译步骤&#xff0c;更便捷。选择由 PyPI 提供的 OpenCV 安装包&#xff0c;可以在 https://py…

010-新手如何建立一个属于自己的图像处理FPGA/ZYNQ框架(自己的用着才舒服,内容非常全面!)

文章目录 前言一、图像处理框架二、图像采集输入1.常用视频流格式&#xff1a;Rgb565/Bayer1.RGB565数据流格式2.Bayer阵列数据流格式 2.图像预处理&#xff1a;时钟域同步/去马赛克/色彩空间转换/滤波1.时钟域同步2.图像去马赛克化3.色彩空间转换4.滤波 三、图像算法处理1.图像…

《世界之外》提前开测,网易打响国乙大战

1月18日&#xff0c;国乙市场迎来了一场大战。 原定于1月26日开服的网易新乙游《世界之外》&#xff0c;突然宣布在1月18日进行不删档、不限量测试&#xff0c;从某种意义上来说&#xff0c;其实就等同于提前公测。 而同一天开服的还有叠纸的全新3D乙游《恋与深空》&#xff…

【Redis】基于Token单点登录

基于Token单点登录 获取验证码 流程图 #mermaid-svg-DLGHgCofEYXVSmI5 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-DLGHgCofEYXVSmI5 .error-icon{fill:#552222;}#mermaid-svg-DLGHgCofEYXVSmI5 .error-text{f…

unocss+iconify技术在vue项目中使用20000+的图标

安装依赖 npm i unocss iconify/json配置依赖 vue.config.js文件 uno.config.js文件 main.js文件 使用 <i class"i-fa:user"></i> <i class"i-fa:key"></i>class名是 i- 开头&#xff0c;跟库名:图标名&#xff0c;那都有什么库…

Springboot+vue项目部署所有遇到的坑

https://flowus.cn/siriusx/share/0a818075-372b-4948-88af-c75c1d10ceab项目地址及笔记 Spring Boot3VUE3前后端分离项目基于Jwt的校验方案 环境搭建 安装jdk17 安装mysql server https://ubuntu.com/server/docs/databases-mysql 将服务器root账号由系统验证改为密码验证…

c语言-结构体内存对齐

文章目录 前言一、结构体内存对齐总结 前言 本篇文章介绍结构体内存对齐。 一、结构体内存对齐 定义两个结构体&#xff1a; struct S1 {char c1;int i;char c2; };struct S2 {char c1;char c2;int i; }; //输出结构体大小 int main() {printf("%u\n", sizeof(st…

提升开发效率,Fiddler Everywhere for Mac助您解决网络调试难题

在现代软件开发中&#xff0c;网络调试是一个不可或缺的环节。无论是前端开发还是后端开发&#xff0c;我们经常需要对网络请求进行监控和调试&#xff0c;以便及时发现并解决问题。而Fiddler Everywhere for Mac作为一款强大的网络调试工具&#xff0c;能够帮助开发者提升工作…

【01】mapbox js api加载arcgis切片服务

需求&#xff1a; 第三方的mapbox js api加载arcgis切片服务&#xff0c;同时叠加在天地图上&#xff0c;天地图坐标系web墨卡托。 效果图&#xff1a; 形如这种地址去加载http://zjq2022.gis.com:8080/demo/loadmapboxtdt.html 思路&#xff1a; 需要制作一个和天地图比例…

视觉检测系统:工厂生产零部件的智能检测

在工厂的生产加工过程中&#xff0c;工业视觉检测系统被广泛应用&#xff0c;并且起着重要的作用。它能够对不同的零部件进行多功能的视觉检测&#xff0c;包括尺寸和外观的缺陷。随着制造业市场竞争越来越激烈&#xff0c;对产品质检效率的要求不断提高&#xff0c;传统的人工…

EMQX安装和Java使用

一、EMQX介绍 EMQX是大规模分布式MQTT消息服务器&#xff0c;可以高效可靠连接海量物联网设备&#xff0c;实时处理分发消息与事件流数据&#xff0c;助力构建关键业务的物联网与云应用。EMQX 作为物联网应用开发和物联网平台搭建必须用到的基础设施软件&#xff0c;主要在边缘…

田园好比一幅美丽的画

一望无垠的麦田&#xff0c;清新的空气&#xff0c;茂密的小树林&#xff0c;这一切都呈现在田园里。 清晨&#xff0c;东方刚刚泛起了鱼肚白&#xff0c;一切都雾蒙蒙的&#xff0c;仿佛给大地披上了一层薄薄的轻纱&#xff0c;大地在沉睡当中&#xff0c;小花沉静在美梦中&am…

ARM64汇编01 - 环境搭建

arm官方手册 由于市面上几乎没有arm相关书籍&#xff0c;所以推荐看官方文档。虽然是英文的&#xff0c;看不下去也要硬看&#xff0c;毕竟搞这方面的还是得有啃英文文档/书籍的能力。 另外&#xff0c;再推荐一个翻译网站&#xff1a;https://www.deepl.com/zh/translator …

git报错:Failed to connect to github.com port 443: Timed out

git提交报错 git报错&#xff1a;Failed to connect to github.com port 443: Timed out解决办法 使用ping github.com 发现ping不通修改hosts文件&#xff0c;让主机跳过DNS服务器 直接根据hosts配置信息进行替换域名在ipaddress.com查询Github真实IP地址 打开hosts文件 添加…

【Java SE】基础知识回顾——【13.线程 | 同步】

【线程 同步】 第一章 Thread 1.1 Thread类 /*** author 270* 获取线程的名称&#xff1a;* 1.使用Thread中的方法getName()* String getName() 返回该线程的名称* 2.可以获取到当前正在执行的线程&#xff0c;使用线程中的方法getName()获…

【年度TikTok电商数据解析】火遍全网!出单率超高!这个产品会成为2024年卖家新宠吗?

随着大众对口腔健康越来越重视&#xff0c;消费者对漱口水、牙线、口腔清新剂等口腔卫生产品形成深度依赖。 当然&#xff0c;这一趋势对于我们TikTok爆款商机打造息息相关。 然而&#xff0c;在目前的口腔护理赛道&#xff0c;高露洁、宝洁、飞利浦等巨头盘踞&#xff0c;掌握…

Spring重要知识点

一、Spring中相关概念 1.IOC 控制反转 IoC&#xff08;Inverse of Control:控制反转&#xff09;是⼀种设计思想&#xff0c;就是将原本在程序中⼿动创建对象的控制权&#xff0c;交由Spring框架来管理。IoC 在其他语⾔中也有应⽤&#xff0c;并⾮ Spring 所独有。 IoC 容器…

政务服务场景为何要打造AI交互数字人?

随着Chat GPT兴起&#xff0c;越来越多服务场景通过AI交互数字人以多模态交互形式实现人机交互。如近日昆山市档案馆推出了AI交互数字人“昆兰”&#xff0c;数字人设置于市档案馆一楼大厅入口处&#xff0c;市民可以与AI交互数字人进行实时问答交流&#xff0c;了解总书记关于…

使用 Docker 部署 的WAF: 雷池社区版

Web应用防火墙&#xff08;WAF&#xff09;是保护网站不受恶意攻击的关键组件。 使用 Docker 部署雷池社区版&#xff0c;可以大大简化安全管理工作。 一、WAF 雷池社区版简介 雷池社区版是一种流行的开源 Web 应用防火墙&#xff0c;它提供基本的安全保护&#xff0c;如防止…

【C++】:STL序列式容器list源码剖析

一、list概述 总的来说&#xff1a;环形双向链表 特点&#xff1a; 底层是使用链表实现的&#xff0c;支持双向顺序访问 在list中任何位置进行插入和删除的速度都很快 不支持随机访问&#xff0c;为了访问一个元素&#xff0c;必须遍历整个容器 与其他容器相比&#xff0c;额外…