使用高德API和MapboxGL实现路径规划并语音播报

news2024/10/26 18:18:55

概述

本文使用高德API实现位置查询和路径规划,使用MapboxGL完成地图交互与界面展示,并使用Web Speech API实现行驶中路线的实时语音播报。

效果

image.png

image.png

Web Speech API简介

Web Speech API 使你能够将语音数据合并到 Web 应用程序中。Web Speech API 有两个部分:SpeechSynthesis 语音合成(文本到语音 TTS)和 SpeechRecognition 语音识别(异步语音识别)。

  • 语音识别通过 SpeechRecognition接口进行访问,它提供了识别从音频输入(通常是设备默认的语音识别服务)中识别语音情景的能力。一般来说,你将使用该接口的构造函数来构造一个新的 SpeechRecognition对象,该对象包含了一系列有效的对象处理函数来检测识别设备麦克风中的语音输入。SpeechGrammar 接口则表示了你应用中想要识别的特定文法。文法则通过 JSpeech Grammar Format (JSGF.) 来定义。

  • 语音合成通过SpeechSynthesis接口进行访问,它提供了文字到语音(TTS)的能力,这使得程序能够读出它们的文字内容(通常使用设备默认的语音合成器)。不同的声音类类型通过SpeechSynthesisVoice对象进行表示,不同部分的文字则由 SpeechSynthesisUtterance 对象来表示。你可以将它们传递给 SpeechSynthesis.speak()方法来产生语音。

SpeechSynthesisUtterance是HTML5中新增的API,用于将指定文字合成为对应的语音。它包含一些配置项,可以指定如何去阅读(如语言、音量、音调等)。

简单使用示例如下代码:

// 创建 SpeechSynthesisUtterance 对象
var utterance = new SpeechSynthesisUtterance();
// 可选:设置语言(例如,中文)
utterance.lang = "zh-CN";
// 可选:设置语音
utterance.voice = window.speechSynthesis.getVoices()[0];
// 可选:设置音量(0.0到1.0之间)
utterance.volume = 1.0;
// 可选:设置语速(正常速度为1.0)
utterance.rate = 1.0;
// 可选:设置语调(正常语调为1.0)
utterance.pitch = 1.0;
// 设置要朗读的文本
utterance.text = '设置要朗读的文本';
window.speechSynthesis.speak(utterance);

实现

实现思路

  1. 地图初始化的时候通过H5的geolocation接口获取当前位置;
  2. 调用rgeo接口,根据获取到的位置获取位置所在市;
  3. 调用inputtips接口完成关键词联想查询;
  4. 调用/direction/driving接口完成路径的规划;
  5. 用MapboxGL实现地图交互与路径展示;
  6. 根据当前位置判断是否进入对应的步骤,提示对应的语音。

实现代码

示例使用Vue作为演示,界面与地图初始化代码如下:

<template>
  <div class="container">
    <div class="query">
      <el-form :model="form" label-width="auto" class="query-form">
        <el-form-item label="起点">
          <el-autocomplete
            v-model="form.startPosition"
            :fetch-suggestions="querySearchStart"
            clearable
            placeholder="请输入起点位置"
            @select="handleSelectStart"
          >
            <template #default="{ item }">
              <div class="autocomplete-value">{{ item.value }}</div>
              <div class="autocomplete-address">{{ item.address }}</div>
            </template></el-autocomplete
          >
        </el-form-item>
        <el-form-item label="终点" style="margin-bottom: 0">
          <el-autocomplete
            v-model="form.endPosition"
            :fetch-suggestions="querySearchEnd"
            clearable
            placeholder="请输入终点位置"
            @select="handleSelectEnd"
          >
            <template #default="{ item }">
              <div class="autocomplete-value">{{ item.value }}</div>
              <div class="autocomplete-address">{{ item.address }}</div>
            </template></el-autocomplete
          >
        </el-form-item>
      </el-form>
      <div class="query-button">
        <el-button
          :disabled="!form.startPosition || !form.endPosition"
          class="query-button-inner"
          @click="queryRoute"
          >查询</el-button
        >
      </div>
    </div>
    <div class="map" id="map">
      <div class="map-button">
        <el-button
          v-show="path"
          class="map-button-inner"
          type="primary"
          @click="toggleAnimate"
          >{{ isPlay ? "结束导航" : "开始导航" }}</el-button
        >
      </div>
    </div>
  </div>
</template>

<script>
const AK = "你申请的key"; // 高德地图key
const BASE_URL = "https://restapi.amap.com/v3";
let map = null,
  animation = null;

import { ElMessage, ElMessageBox } from "element-plus";
import AnimationRoute from "./utils/route";

//封装请求
function request(url, params) {
  let fullUrl = `${BASE_URL}${url}?key=${AK}`;
  for (const key in params) {
    fullUrl += `&${key}=${params[key]}`;
  }
  return new Promise((resolve, reject) => {
    fetch(fullUrl)
      .then((res) => res.json())
      .then((res) => {
        if (res.status === "1") resolve(res);
        else {
          ElMessage.error("接口请求失败");
          reject(res);
        }
      });
  });
}

export default {
  data() {
    return {
      center: [113.94150905808976, 22.523881824251347],
      form: {
        startPosition: "",
        endPosition: "",
        startCoord: [],
        endCoord: [],
      },
      cityInfo: {},
      path: null,
      isPlay: false,
    };
  },
  mounted() {
    const that = this;
    function successFunc(position) {
      const { longitude, latitude } = position.coords;
      that.center = [longitude, latitude];
      that.initMap(true);
    }
    function errFunc() {
      that.initMap(false);
    }
    if (navigator.geolocation) {
      try {
        errFunc();
        navigator.geolocation.getCurrentPosition(successFunc, errFunc);
      } catch (e) {
        errFunc();
      }
    } else {
      errFunc();
    }
  },
  methods: {
    toggleAnimate() {
      if (!animation) return;

      if (this.isPlay) {
        animation.pause();
        ElMessageBox.confirm("确认取消导航吗?", "提示", {
          confirmButtonText: "确认",
          cancelButtonText: "取消",
          type: "warning",
        })
          .then(() => {
            animation.destory();
            animation = null;
            this.path = null;
            this.form = {
              startPosition: "",
              endPosition: "",
              startCoord: [],
              endCoord: [],
            };
            this.isPlay = false;
          })
          .catch(() => {
            animation.play();
            this.isPlay = true;
          });
      } else {
        animation.play();
        this.isPlay = true;
      }
    },
    initMap(isLocate) {
      map = new SFMap.Map({
        container: "map",
        center: this.center,
        zoom: 17.1,
      });
      map.on("load", (e) => {
        new SFMap.SkyLayer({
          map: map,
          type: "atmosphere",
          // 设置天空光源的强度
          atmosphereIntensity: 12,
          // 设置太阳散射到大气中的颜色
          atmosphereColor: "rgba(87, 141, 219, 0.8)",
          // 设置太阳光晕颜色
          atmosphereHaloColor: "rgba(202, 233, 250, 0.1)",
        });
        request("/geocode/regeo", {
          location: this.center.join(","),
        }).then((res) => {
          this.cityInfo = res.regeocode.addressComponent;
        });
      });
    },
    querySearchStart(str, cb) {
      if (str) {
        request("/assistant/inputtips", {
          keywords: str,
          city: this.cityInfo?.city,
        }).then((res) => {
          cb(
            res.tips.map((t) => {
              t.value = t.name;
              return t;
            })
          );
        });
      } else {
        cb([]);
      }
    },
    querySearchEnd(str, cb) {
      if (str) {
        request("/assistant/inputtips", {
          keywords: str,
          city: this.cityInfo?.city,
        }).then((res) => {
          cb(
            res.tips.map((t) => {
              t.value = t.name;
              return t;
            })
          );
        });
      } else {
        cb([]);
      }
    },
    handleSelectStart(item) {
      this.form.startCoord = item.location.split(",").map(Number);
    },
    handleSelectEnd(item) {
      this.form.endCoord = item.location.split(",").map(Number);
    },
    queryRoute() {
      const { startCoord, endCoord } = this.form;
      request("/direction/driving", {
        origin: startCoord.join(","),
        destination: endCoord.join(","),
        extensions: "all",
      }).then((res) => {
        const path = res.route.paths[0];
        let coordinates = [];
        this.path = path;
        path.steps.forEach((step) => {
          const polyline = step.polyline
            .split(";")
            .map((c) => c.split(",").map(Number));
          coordinates = [...coordinates, ...polyline];
        });
        const route = {
          type: "Feature",
          properties: { path },
          geometry: {
            type: "LineString",
            coordinates: coordinates,
          },
        };
        animation = new AnimationRoute(map, route, false);
      });
    },
  },
};
</script>

<style scoped lang="scss">
.container {
  width: 100%;
  height: 100vh;
  overflow: hidden;
}
.query {
  padding: 0.8rem;
  overflow: hidden;
  background: #ccc;
  .query-form {
    width: calc(100% - 4rem);
    float: left;
  }
  .query-button {
    width: 2rem;
    height: 4.7rem;
    float: right;
    display: flex;
    justify-content: flex-end;
    align-items: center;
    .query-button-inner {
      width: 3.8rem;
      height: 100%;
    }
  }
}
.map {
  height: calc(100% - 6.4rem);
}

.autocomplete-value,
.autocomplete-address {
  white-space: nowrap;
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.autocomplete-address {
  font-size: 0.8rem;
  color: #999;
  margin-top: -0.8rem;
  border-bottom: 1px solid #efefef;
}
.map-button {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  z-index: 99;
  padding: 0.8rem;
  box-sizing: border-box;
  &-inner {
    width: 100%;
  }
}
:deep .el-form-item {
  margin-bottom: 0.5rem;
}
</style>

示例中使用轨迹播放的方式演示了位置的变化,前文mapboxGL轨迹展示与播放已经有过分享,示例在前文的基础上做了一点改动,改动完代码如下:

const icon = "/imgs/car.png";
const arrow = "/imgs/arrow.png";

import * as turf from "@turf/turf";

class AnimationRoute {
  constructor(map, route, play = true, fit = true, speed = 60) {
    this._map = map;
    this._json = route;
    this._play = play;
    this._speed = speed;
    this._path = route.properties.path;
    this.init();

    if (fit) this._map.fitBounds(turf.bbox(route), { padding: 50 });
  }

  init() {
    const that = this;

    // 创建 SpeechSynthesisUtterance 对象
    var utterance = new SpeechSynthesisUtterance();
    // 可选:设置语言(例如,中文)
    utterance.lang = "zh-CN";
    // 可选:设置语音
    utterance.voice = window.speechSynthesis.getVoices()[0];
    // 可选:设置音量(0.0到1.0之间)
    utterance.volume = 1.0;
    // 可选:设置语速(正常速度为1.0)
    utterance.rate = 1.0;
    // 可选:设置语调(正常语调为1.0)
    utterance.pitch = 1.0;
    that.utterance = utterance;

    that._index = 0;
    const length = turf.length(that._json);
    const scale = 60;
    that._count = Math.round((length / that._speed) * 60 * 60) * scale;
    that._step = length / that._count;
    that._stepPlay = -1;
    that._flag = 0;
    that._playId = "play-" + Date.now();
    // 添加路径图层
    that._map.addSource(that._playId, {
      type: "geojson",
      data: that._json,
    });
    that._map.addLayer({
      id: that._playId,
      type: "line",
      source: that._playId,
      layout: {
        "line-cap": "round",
        "line-join": "round",
      },
      paint: {
        "line-color": "#aaaaaa",
        "line-width": 10,
      },
    });
    // 添加已播放路径
    that._map.addSource(that._playId + "-played", {
      type: "geojson",
      data: that._json,
    });
    that._map.addLayer({
      id: that._playId + "-played",
      type: "line",
      source: that._playId + "-played",
      layout: {
        "line-cap": "round",
        "line-join": "round",
      },
      paint: {
        "line-color": "#09801a",
        "line-width": 10,
      },
    });

    // 添加路径上的箭头
    that._map.loadImage(arrow, function (error, image) {
      if (error) throw error;
      that._map.addImage(that._playId + "-arrow", image);
      that._map.addLayer({
        id: that._playId + "-arrow",
        source: that._playId,
        type: "symbol",
        layout: {
          "symbol-placement": "line",
          "symbol-spacing": 50,
          "icon-image": that._playId + "-arrow",
          "icon-size": 0.6,
          "icon-allow-overlap": true,
        },
      });
    });

    // 添加动态图标
    that._map.loadImage(icon, function (error, image) {
      if (error) throw error;
      that._map.addImage(that._playId + "-icon", image);
      that._map.addSource(that._playId + "-point", {
        type: "geojson",
        data: that._getDataByCoords(),
      });
      that._map.addLayer({
        id: that._playId + "-point",
        source: that._playId + "-point",
        type: "symbol",
        layout: {
          "icon-image": that._playId + "-icon",
          "icon-size": 0.75,
          "icon-allow-overlap": true,
          "icon-rotation-alignment": "map",
          "icon-pitch-alignment": "map",
          "icon-rotate": 50,
        },
      });
      that._animatePath();
    });
  }

  pause() {
    this._play = false;
    window.cancelAnimationFrame(this._flag);
  }

  start() {
    this._index = 0;
    this.play();
  }

  play() {
    this._play = true;
    this._animatePath();
  }

  _animatePath() {
    if (this._index > this._count) {
      window.cancelAnimationFrame(this._flag);
    } else {
      const coords = turf.along(this._json, this._step * this._index).geometry
        .coordinates;
      // 已播放的线
      const start = turf.along(this._json, 0).geometry.coordinates;
      this._map
        .getSource(this._playId + "-played")
        .setData(turf.lineSlice(start, coords, this._json));

      // 车的图标位置
      this._map
        .getSource(this._playId + "-point")
        .setData(this._getDataByCoords(coords));
      // 计算旋转角度
      const nextIndex =
        this._index === this._count ? this._count - 1 : this._index + 1;
      const coordsNext = turf.along(this._json, this._step * nextIndex).geometry
        .coordinates;
      let angle = turf.bearing(turf.point(coords), turf.point(coordsNext)) - 90;
      if (this._index === this._count) angle += 180;
      this._map.setLayoutProperty(
        this._playId + "-point",
        "icon-rotate",
        angle
      );
      const camera = this._map.getFreeCameraOptions();
      camera.position = mapboxgl.MercatorCoordinate.fromLngLat(coords, 100);
      camera.lookAtPoint(coordsNext);
      this._map.setFreeCameraOptions(camera);
      this._map.setPitch(80);
      this._index++;
      if (this._play) {
        this._playInstruction(coords);
        this._flag = requestAnimationFrame(() => {
          this._animatePath();
        });
      }
    }
  }

  _playInstruction(coords) {
    const { steps } = this._path;
    const stepPlay = this._stepPlay;
    const start = this._stepPlay !== -1 ? this._stepPlay : 0
    for (let i = start; i < steps.length; i++) {
      const step = steps[i];
      const polyline = step.polyline
        .split(";")
        .map((v) => v.split(",").map(Number));
      const pt = turf.point(coords);
      const line = turf.lineString(polyline);
      const dis = turf.pointToLineDistance(pt, line) * 1000;
      if (i > this._stepPlay && dis < 10) {
        this._stepPlay = i;
        break;
      }
    }
    if (stepPlay !== this._stepPlay) {
      this.utterance.text = steps[this._stepPlay].instruction;
      window.speechSynthesis.speak(this.utterance);
    }
  }

  _getDataByCoords(coords) {
    if (!coords || coords.length !== 2) return null;
    return turf.point(coords, {
      label: this._formatDistance(this._step * this._index),
    });
  }

  _formatDistance(dis) {
    if (dis < 1) {
      dis = dis * 1000;
      return dis.toFixed(0) + "米";
    } else {
      return dis.toFixed(2) + "千米";
    }
  }

  destory() {
    window.cancelAnimationFrame(this._flag);
    if (this._map.getSource(this._playId + "-point")) {
      this._map.removeLayer(this._playId + "-point");
      this._map.removeSource(this._playId + "-point");
    }
    if (this._map.getSource(this._playId + "-played")) {
      this._map.removeLayer(this._playId + "-played");
      this._map.removeSource(this._playId + "-played");
    }
    if (this._map.getSource(this._playId)) {
      this._map.removeLayer(this._playId);
      this._map.removeLayer(this._playId + "-arrow");
      this._map.removeSource(this._playId);
    }
  }
}

export default AnimationRoute;

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

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

相关文章

软件测试的重要一环:「性能测试」怎么做?

性能测试是软件测试中的重要一环&#xff0c;今天给大家介绍性能测试及如何使用RunnerGo完成性能测试任务。 性能测试是什么&#xff1f; 一句话概括&#xff1a;不断地通过不同场景的系统表现去探究系统设计与资源消耗之间的平衡&#xff0c;为开发人员提供消除瓶颈所需的诊…

IntelliJ IDEA 设置数据库连接全局共享

前言 在日常的软件开发工作中&#xff0c;我们经常会遇到需要在多个项目之间共享同一个数据库连接的情况。默认情况下&#xff0c;IntelliJ IDEA 中的数据库连接配置是针对每个项目单独存储的。这意味着如果你在一个项目中配置了一个数据库连接&#xff0c;那么在另一个项目中…

从零搭建开源陪诊系统:关键技术栈与架构设计

构建一个开源陪诊系统是一个涉及多种技术的复杂工程。为了让这个系统具备高效、可靠和可扩展的特点&#xff0c;我们需要从架构设计、技术栈选择到代码实现等方面进行全面的考量。本文将从零开始&#xff0c;详细介绍搭建开源陪诊系统的关键技术栈和架构设计&#xff0c;并提供…

C#中的委托、匿名方法、Lambda、Action和Func

委托 委托概述 委托是存有对某个方法的引用的一种引用类型变量。定义方法的类型&#xff0c;可以把一个方法当作另一方法的参数。所有的委托&#xff08;Delegate&#xff09;都派生自 System.Delegate 类。委托声明决定了可由该委托引用的方法。 # 声明委托类型 委托类型声…

汽车免拆诊断案例 | 2019 款奥迪 A6L 车行驶中偶发熄火

故障现象  一辆2019款奥迪A6L车&#xff0c;搭载2.0T发动机&#xff0c;累计行驶里程约为9万km。车主反映&#xff0c;车辆行驶中偶发熄火&#xff0c;故障频率较高。 故障诊断  接车后试车&#xff0c;起动发动机&#xff0c;可以正常起动着机。使用故障检测仪检测&#x…

ELK之路第一步——Elasticsearch集群的搭建以及踩坑记录

elasticSearch集群 前言一、架构二、下载三、虚拟机相关设置3.1 创建es用户3.2 为建es用户赋权sudo3.3 更换es目录所属用户 四、Elasticsearch配置文件修改4.1 修改elasticsearch.yml4.2 修改jvm.options4.3 修改jdk路径 五、启动六、启动报错七、可视化界面cerebro 前言 Elk&…

springboot080房屋租赁管理系统的设计与实现(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;房屋租赁管理系统的设计与实现 摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好…

Linux 基础io_ELF_虚拟物理地址_动态库加载

1.可执行程序格式 ELF [wwshcss-ecs-178e myshell]$ ll total 56 -rw-rw-r-- 1 wws wws 92 Oct 17 19:14 file -rw-rw-r-- 1 wws wws 82 Oct 12 16:51 makefile -rw-r--r-- 1 wws wws 90 Oct 17 19:13 myfile -rwxrwxr-x 1 wws wws 20128 Oct 16 21:02 myshell -rw-r…

005:航空力学基础、无人机操纵、飞机性能

摘要&#xff1a;本文详细介绍无人机稳定性、操控性、飞机性能等概念。 一、飞机的稳定性 概念&#xff1a; 飞机的稳定性&#xff08;安定性&#xff09;&#xff0c;是指在飞机受到扰动后&#xff0c;不经飞行员操纵&#xff0c;能恢复到受扰动前的原始状态&#xff08;即原…

萤石设备视频接入平台EasyCVR私有化视频平台变电站如何实现远程集中监控?

一、方案背景 随着城市经济的发展和电力系统的改造&#xff0c;变电站的数量和规模逐渐增加&#xff0c;对变电站的安全管理和监控需求也越来越高。视频监控系统作为重要的安全管理手段&#xff0c;在变电站中起到了关键的作用。 目前青犀视频研发的萤石设备视频接入平台EasyC…

刷题 - 图论

1 | bfs/dfs | 网格染色 200. 岛屿数量 访问到马上就染色&#xff08;将visited标为 true)auto [cur_x, cur_y] que.front(); 结构化绑定&#xff08;C17&#xff09;也可以不使用 visited数组&#xff0c;直接修改原始数组时间复杂度: O(n * m)&#xff0c;最多将 visited 数…

生信软件39 - GATK最佳实践流程重构,提高17倍分析速度的LUSH流程

1. LUSH流程简介 基因组测序通常用于分子诊断、分期和预后&#xff0c;而大量测序数据在分析时间方面提出了挑战。 对于从FASTQ到VCF的整个流程&#xff0c;LUSH流程在非GVCF和GVCF模式下都大大降低了运行时间&#xff0c;30 X WGS数据耗时不到2 h&#xff0c;从BAM到VCF约需…

力扣143:重排链表

给定一个单链表 L 的头节点 head &#xff0c;单链表 L 表示为&#xff1a; L0 → L1 → … → Ln - 1 → Ln请将其重新排列后变为&#xff1a; L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … 不能只是单纯的改变节点内部的值&#xff0c;而是需要实际的进行节点交换。 示…

【已解决】C# NPOI如何在Excel文本中增加下拉框

前言 上图&#xff01; 解决方法 直接上代码&#xff01;&#xff01;&#xff01;&#xff01;综合了各个大佬的自己修改了一下&#xff01;可以直接规定在任意单元格进行设置。 核心代码方法块 #region Excel增加下拉框/// <summary>/// 增加下拉框选项/// </s…

在 Ubuntu 上安装 OpenCV 3.2.0 的详细指南

以下步骤将指导您如何在 Ubuntu 系统上从源码编译并安装 OpenCV 3.2.0。 步骤 1&#xff1a;更新系统并安装必备工具 首先&#xff0c;更新您的系统并安装编译 OpenCV 所需的基本工具和库。 sudo apt-get update sudo apt-get upgrade sudo apt-get install build-essential…

docker 可用镜像服务地址(2024.10.25亲测可用)

1.错误 Error response from daemon: Get “https://registry-1.docker.io/v2/” 原因&#xff1a;镜像服务器地址不可用。 2.可用地址 编辑daemon.json&#xff1a; vi /etc/docker/daemon.json内容修改如下&#xff1a; {"registry-mirrors": ["https://…

C++进阶之路:日期类的实现、const成员(类与对象_中篇)

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

Qt之QCamera的简单使用

文章目录 一、相机操作相关示例1.摄像头操作内容使用示例2.摄像头信息展示使用示例3.摄像头设置切换、预览操作示例 二、相机使用个人操作理解1.相机类支持信息获取2.相机类曝光、焦点、图像处理控制信息获取3.快速启动相机设置&#xff08;各个设备处于理想状态&#xff09; 三…

内网穿透:如何借助Cloudflare连接没有公网的电脑的远程桌面(RDP)

内网穿透&#xff1a;如何借助Cloudflare连接没有公网的电脑的远程桌面(RDP)-含详细原理配置说明介绍 前言 远程桌面协议(RDP, Remote Desktop Protocol)可用于远程桌面连接&#xff0c;Windows系统&#xff08;家庭版除外&#xff09;也是支持这种协议的&#xff0c;无需安装…

基础数据结构——队列(链表实现,数组实现)

1.概述 计算机科学中&#xff0c;queue 是以顺序的方式维护的一组数据集合&#xff0c;在一端添加数据&#xff0c;从另一端移除数据。习惯来说&#xff0c;添加的一端称为尾&#xff0c;移除的一端称为头&#xff0c;就如同生活中的排队买商品 接口信息如下 public interfa…