建设电商网站流程/阿里巴巴国际站
前言:
在计算机科学中,数据结构是构建高效算法和程序的基础,而队列(Queue)作为一种经典的线性数据结构,具有重要的地位。与栈(Stack)不同,队列遵循“先进先出”( FIFO(先进先出)原则,即最先加入队列的元素会最先被移除。这个特性使得队列在许多应用场景中都扮演着至关重要的角色。
队列在操作系统、计算机网络、消息发送、任务调度、串行编程等多个领域都有广泛的应用。例如,操作系统中的任务调度和进程管理通常使用队列来维护待处理的任务;在网络通信中中,数据包的传输顺序也依赖于队列的排列处理;在生产者-消费者模型中,队列串联了生产者和消费者之间的数据图,确保数据按顺序流动。
随着软件开发的深入,队列的应用逐渐从基础的任务调度划分出更加复杂的场景。特别是在多线程和并发编程中,线程之间的任务分配和通信经常借助队列来实现。支持基本的入队、出队操作,还可以通过一些高级功能,如优先队列、阻塞队列等,满足复杂的需求。
在Java中,队列得到了标准库的广泛支持,Queue
接口和各种实现类(如LinkedList
、、等)为开发者提供了丰富的选择。通过这些现成的实现,我们可以轻松地将队列引入到我们的中程序中,极大地提高了开发效率。PriorityQueue
ArrayDeque
在本文中,我们将深入探讨队列的基本概念、常见操作以及Java中的队列实现。我们将了解队列的工作原理,分析其在实际Spark中的应用场景,并通过代码示例演示如何使用队列解决实际问题。
无论你是初学者,还是有一定经验的开发者,掌握队列这一基础数据结构,能够为你在编写高效、可靠的软件系统时提供强大的支持。让我们一起走进队列的世界,探索它的奥秘与无限的可能。
1.队列讲解
1.什么是队列?
队列(Queue)是一种线性数据结构,它遵循先进先出(FIFO,First In, First Out)原则。 简单来说,队列就是一排排站着的人,最先排队的人最先得到服务每当有新的元素进入队列时,它总是从队列的尾部进入(入队),而从队列的头部(移除出队)。
形象的比喻:
- 假设你正在排队买票。你站在队列的尾部,等待自己的顺序。第一个进入队列的人会最先被服务,这就是队列的工作方式。
2. 查询的基本操作
2.1队列的使用:
在Java中,Queue是个接口,底层是通过链表实现的。
队列是通过Queue
接口实现的接口。Queue
继承自Collection
接口,提供了队列的基本操作。Java的队列有几种常用的实现方式:
LinkedList
:LinkedList
类是一个端点链表实现,它实现了Queue
接口,可以作为一个队列使用。PriorityQueue
:PriorityQueue
类实现了一个优先级排序,其中每个元素都有一个优先级,队列中的元素会根据优先级来排序。ArrayDeque
:ArrayDeque
类实现了一个基于队列的双端队列,适用于需要在队列两端插入和删除元素的场景。
2.2 队列通常有四个最常用的基本操作:
- 入队(enqueue):将一个元素添加到队列的尾部。
- 出队(dequeue):移除并返回队列头部的元素。
- 查看队列首元素(peek/front):返回队列首元素,但不删除它。
- 判断队列是否为空(isEmpty):判断队列是否包含任何元素。
2.2.1 入队(Enqueue)
入队元素添加到队列的尾部。
例如,你有一个队列,初始状态为空。你加入队几个元素:
队列的尾部是3
,新元素3
被添加到队尾。
2.2.2 出队(Dequeue)
出队是移除并返回队列最前面的元素。每次出队的元素是队列中最先进入的元素(最前面的元素)。
例如:
出队后,队头的元素1
被移除,队列中的元素顺序改变,新的队头元素是2
。
2.2.3 查看队列首元素(Peek)
查看队列首元素是检查队列头部的元素,不会将其从队列中删除。这对于您查看下一个要处理的元素时很有用。
例如:
此操作仅返回2
,并不会改变队列的内容。
2.2.4 判断队列是否为空(isEmpty)
这是一个辅助操作,用于检查队列是否包含元素。如果队列为空,返回true
,否则返回false
。
3. 队列应用程序场景
队列是一种非常有用的数据结构,广泛涵盖很多实际问题中。下面列举一些常见的应用场景:
3.1 任务调度
在操作系统中,队列用于任务调度。比如,多个进程(或线程)需要访问计算机的CPU,系统将它们按顺序队列排列,最先进入的进程先获得CPU资源。
3.2 打印任务
如果您有多个打印任务,打印机将按顺序处理它们。第一个加入队列的打印任务会首先被打印,第二个任务在第一个任务打印完成后开始打印,依此类推。
3.3 消息队列
消息队列(例如:RabbitMQ,Kafka)用于传递信息或任务。生产者将消息发送到队列中,消费者从队列中接收并处理消息。消息的传递也遵循先进先出的顺序。
3.4 广度优先搜索(BFS)
队列在图论的广度优先搜索(BFS)算法中广泛应用。在BFS中,我们使用队列来存储待访问的节点,确保节点按照层次顺序被访问。每访问一个节点,队列中的元素就会被处理,相邻节点加入队列。
3.5 实时数据流
队列常用于处理实时数据流。例如,在网络流量监控系统中,可以使用队列缓冲流入的数据包,按照顺序进行处理和转发。
5. 队列的优缺点
优点:
- 先进先出(先进先出):顺序保证了基本的顺序,适合需要按顺序处理任务的场景。
- 容易实现:队列的实现相对简单,且与其他数据结构(如栈、链表)有相似之处,容易理解。
缺点:
- 操作架构:队列只能从头部删除元素,从尾部插入元素,无法像队列一样随机访问元素。
- 存储限制:某些队列(如有限队列)可能有容量,需要考虑队列满时的处理方式。
2 队列模拟实现
队列中既然可以存储元素,那底层肯定要有能够保存元素的空间,通过前面线性表的学习了解到常见的空间类型有 两种:顺序结构 和 链式结构。同学们思考下:队列的实现使用顺序结构还是链式结构好?
2.1使用双向链表来实现队列:
(一)节点类的定义:
成员变量:
public int val:用于存储节点所代表的值,这个值可以是任何你想要在链表中存放的整型数据,比如整数类型的元素值等,它是节点的关键数据部分。
public ListNode prev:是一个指向当前节点前一个节点的指针(引用),用于构建双向链表中向后的关联,使得可以从当前节点回溯到前面的节点,这也是双向链表区别于单向链表的重要特性体现。
ListNode next:同样是一个指针(引用),不过它指向当前节点的下一个节点,用于构建链表向前的连接顺序,通过这个指针可以依次访问后续节点。
链表头和尾指针声明:
head和last这两个变量都是ListNode类型,它们分别用于指向整个双向链表的头节点和尾节点。在双向链表的操作过程中,通过head可以从链表开头进行正向遍历(利用每个节点的next指针),而通过last可以从链表末尾进行反向遍历(利用每个节点的prev指针)。这两个指针方便了对整个链表的访问和操作,比如插入新节点到头部或者尾部、删除头部或者尾部节点等常见操作都依赖于它们准确地指向相应位置。
1.入队列offer
first
和last
的更新
- 空链表的处理:当链表为空时,
first
并且last
都应指向新节点,这部分代码是正确的。- 非空链表的处理:如果链表不为空,当前的
last
节点的next
指针指向新节点,并且新节点的prev
指向当前last
节点。最后更新last
为新节点,这样就保证了新节点被正确地添加到了链表的尾部。
size++
size
字段用于跟踪链表中节点的数量,每次插入节点时需要更新链表的大小,这里也正确地更新了size
。
2.出队列 poll (删除第一个节点)
队列为空(
first == null
):
- 直接返回
-1
,表示没有节点供应删除。队列有一个节点(
first == last
):
- 这种情况下,删除节点后,链表就空了,所以
first
和last
都需要设置为null
。排队有多个节点:
- 保存当前头节点的值(即要返回的值)。
- 将
first
更新为下一个节点first.next
。- 更新
first.prev
和first.next
引用,保证链表结构正确。
3.出队列peek(不删除第一个节点)
队列为空
- 当链表为空(
first == null
)时,返回-1
作为标志值。这是合理的设计,表明没有元素可以查看。返回队头内容列表:
- 如果链表不为空,直接返回
first.val
,即链表的第一个节点的值。
4.返回队列的大小Size:
5.返回队列是否为空isEmpty:
如果
first == null
,说明链表为空,返回true
;否则返回false
。
6 代码展示
public static class ListNode {ListNode next;ListNode prev;int val;ListNode(int val) {this.val = val;}ListNode first;ListNode last;int size = 0;// 入队列---向双向链表位置插入新节点public void offer(int val) {ListNode newNode = new ListNode(val); // 创建新节点if (first == null) { // 如果链表为空first = newNode; // 将新节点作为链表的头节点} else {last.next = newNode; // 将新节点添加到当前尾节点的后面newNode.prev = last; // 设置新节点的前驱节点为当前尾节点}last = newNode; // 更新尾节点为新节点size++; // 更新链表的大小} // 出队列---将双向链表第一个节点删除掉public int poll () {// 1. 队列为空// 2. 队列中只有一个元素----链表中只有一个节点---直接删除// 3. 队列中有多个元素---链表中有多个节点----将第一个节点删除int val = -1; // 默认返回值// 1. 队列为空if (first == null) {return val; // 返回 -1}// 2. 队列中只有一个元素else if (first == last) {val = first.val; // 保存节点的值first = last = null; // 清空链表}// 3. 队列中有多个元素else {val = first.val; // 保存第一个节点的值first.prev.next = null; // 将新的头节点的前驱节点的 `next` 设置为 `null`first = first.next; // 更新头节点first.prev = null; // 新头节点的前驱指针设置为 null}size--; // 更新链表的大小return val; // 返回被删除节点的值}// 获取队头元素---获取链表中第一个节点的值域public int peek () {if (first == null) {return -1;}return first.val;}public int size () {return size;}public Boolean isEmpty () {return first == null;}}
2.2循环队列:
实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。 环形队列通常使用数组实现。
数组下标循环的小技巧
1. 下标最后再往后(offset 小于 array.length): index = (index + offset) % array.length
2. 下标最前再往前(offset 小于 array.length): index = (index + array.length - offset) % array.length
3.如何区分空与满?
1. 通过添加 size 属性记录
2. 保留一个位置
3. 使用标记
4.代码实现:
public class ArrayQueue {
private int[] array; // 存储队列元素的数组
private int front; // 队列的头部索引
private int rear; // 队列的尾部索引
private int size; // 当前队列中的元素个数
private int capacity; // 队列的最大容量// 构造函数,初始化队列
public ArrayQueue(int capacity) {
this.capacity = capacity;
this.array = new int[capacity]; // 创建固定大小的数组
this.front = 0; // 队头初始位置
this.rear = 0; // 队尾初始位置
this.size = 0; // 队列初始化时没有元素
}// 判断队列是否为空
public boolean isEmpty() {
return size == 0;
}// 判断队列是否已满
public boolean isFull() {
return size == capacity;
}// 获取队列的大小
public int size() {
return size;
}// 入队操作(添加元素到队列尾部)
public void enqueue(int value) {
if (isFull()) {
System.out.println("队列已满,无法入队");
return;
}
array[rear] = value; // 将元素添加到队尾
rear = (rear + 1) % capacity; // 循环队列,重新指向数组头部(当rear等于capacity时重新指向0)
size++; // 更新队列的大小
}// 出队操作(移除队列头部的元素)
public int dequeue() {
if (isEmpty()) {
System.out.println("队列为空,无法出队");
return -1; // 如果队列为空,返回-1
}
int value = array[front]; // 获取队头的元素
front = (front + 1) % capacity; // 更新队头索引
size--; // 更新队列的大小
return value;
}// 查看队列头部的元素(不移除)
public int peek() {
if (isEmpty()) {
System.out.println("队列为空");
return -1;
}
return array[front];
}// 打印队列中的元素
public void display() {
if (isEmpty()) {
System.out.println("队列为空");
return;
}
// 从队头到队尾打印所有元素
for (int i = 0; i < size; i++) {
System.out.print(array[(front + i) % capacity] + " ");
}
System.out.println();
}
}
3.其他方法:
add(E e)
和offer(E e)
:用于向队列添加元素,add
方法会在队列已满时抛出异常,而offer
方法则返回false
。remove()
和poll()
:从队列中移除并返回队列头部的元素。remove
在队列为空时抛出异常,而poll
在队列为空时返回null
。element()
和peek()
:返回队列头部的元素但不删除它,element()
在队列中为空时抛出异常,而peek()
返回null
。size()
和isEmpty()
:size()
返回队列的元素个数,isEmpty()
检查队列是否为空。clear()
:清空队列中的所有元素。toArray()
和toArray(T[] a)
:将队列转换为数据库,toArray
返回Object[]
类型的数据库,toArray(T[] a)
返回指定类型的数据库。contains(Object o)
:检查队列中是否包含某个元素。clone()
:克隆队列并返回一个副本。
4.结语:
队列(Queue)作为一种经典的数据结构,凭借其先进先出的特性,在计算机科学中还是扮演着至关重要的角色。无论是在操作系统中的任务调度、网络中的数据包传输,在实际应用中的打印任务管理、线程池的任务处理等场景中,队列头在,执行了它替代的功能。
在本文中,我们深入探讨了Java中队列的定义、实现以及常用方法,理解了它的基本操作,如入队(enqueue)、出队(dequeue)、队列大小(size)、队头元素(peek) )等,同时还介绍了Java标准库中的Queue
接口和常见的实现类,如LinkedList
、PriorityQueue
和ArrayDeque
。每一种实现都有其独特的优势和适用场景,开发者可以根据具体的需求选择最适合的队列类型。
队列不仅是学习数据结构的一个重要环节,也是现代编程中经常需要掌握的核心概念之一。通过对队列的深入理解和运用,程序员可以更好地设计高效的程序和算法,提高系统的性能和稳定性。
无论是基本的队列操作,还是通过队列实现复杂的业务逻辑,掌握队列的使用和优化技巧,都将为我们解决实际问题提供强大的工具。希望本文能够帮助你更好地理解和使用队列,并在实际开发中灵活应用这种数据结构。
随着技术的不断发展,我们也可以探索队列在矩阵编程、多元系统中的应用,深入研究线程安全队列、阻塞队列等更复杂的队列类型。在未来的工作和学习中,队列依然坚定将是我们编程之旅中夹具的一部分。
队列看似简单,却在复杂系统的设计中发挥着巨大的作用。它还是数据流转的关键,合理使用队列,破坏我们带来高效、可靠的程序结构。无论是单机程序队列系统,掌握这一基础数据结构,将使您在编程的道路上更进一步,成为更强大的开发者。
相关文章:

数据结构(Queue队列)
前言: 在计算机科学中,数据结构是构建高效算法和程序的基础,而队列(Queue)作为一种经典的线性数据结构,具有重要的地位。与栈(Stack)不同,队列遵循“先进先出”…...

Qt 图形框架下图形拖动后位置跳动问题
在使用Qt 的图形框架QGraphicsScene,QGraphicsView实现图形显示时。遇到一个很棘手的BUG。 使用的图形是自定义的QGraphicsObject的子类。 现象是将图形添加到画布上之后,用鼠标拖动图形,图形能正常改变位置,当再次用鼠标点击图…...

【Linux篇】走进Linux — 开启开源操作系统之旅
文章目录 初识Linux一.Linux的起源与发展二.Linux的特点三.Linux的应用四.Linux的发行版本 Linux环境搭建一.Linux环境的搭建方式二.购买云服务器三.使用XShell远程登陆到Linux 初识Linux 一.Linux的起源与发展 1.初始动机: Linux是一个功能强大的开源操作系统&am…...

如何利用DBeaver配置连接MongoDB和人大金仓数据库
最近根据国产化要求,需要使用国产数据库,但习惯使用DBeaver连接各种成熟的商业或开源数据库。因此,就想着如何继续基于该工具,连接MongoDB和人大金仓数据库,查了半天很多地方说法不统一,所以自己就简单整理…...

Android 车载虚拟化底层技术-Kernel 5.10 -Android12(multi-cards)技术实现
详细代码实现见 Android Display Graphics系列文章-汇总Android Display Graphics系列文章-汇总 Android Display Graphics系列文章-汇总 Android Display Graphics系列文章-汇总 本文主要包括部分: 一、Android12的Kernel 5.10版本 1.1 Kernel 5…...

Qt之点击鼠标右键创建菜单栏使用(六)
Qt开发 系列文章 - menu(六) 目录 前言 一、示例演示 二、菜单栏 1.MenuBar 2.Menu 总结 前言 QMainWindow是一个为用户提供主窗口程序的类,包含一个菜单栏(menubar)、多个工具栏(toolbars)、一个状态栏(status…...

开发一套SDK 第一弹
自动安装依赖包 添加条件使能 #ex: filetypesh bash_ls 识别 达到预期,多个硬件环境 等待文件文件系统挂在完成 或者创建 /sys/class/ 属性文件灌入配置操作 AI 提供的 netlink 调试方法,也是目前主流调用方法,socket yyds #include <linux/module.h> #include <linux…...

sftp+sshpass
实现场景,要求客户端定时将本地的日志文件传输到服务器。 工作环境ubuntu,注意不通操作系统的版本不通,依赖的工具的版本也有所不同 实现目标需要客户端满足安装工具: 1、下载安装sshpass ---安装命令:sudo apt-ge…...

【机器学习与数据挖掘实战】案例01:基于支持向量回归的市财政收入分析
【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈机器学习与数据挖掘实战 ⌋ ⌋ ⌋ 机器学习是人工智能的一个分支,专注于让计算机系统通过数据学习和改进。它利用统计和计算方法,使模型能够从数据中自动提取特征并做出预测或决策。数据挖掘则是从大型数…...

Idea实现定时任务
定时任务 什么是定时任务? 可以自动在项目中根据设定的时长定期执行对应的操作 实现方式 Spring 3.0 版本之后自带定时任务,提供了EnableScheduling注解和Scheduled注解来实现定时任务功能。 使用SpringBoot创建定时任务非常简单,目前主要…...

Linux 安装NFS共享文件夹
程序默认使用2049端口,如果被占用需要修改端口104设置为服务端 122设置为客户端 一、在线安装(服务端和客户端执行) yum install nfs-utils rpcbind -y二、配置启动参数(服务端执行) 104服务器/mnt路径下创建shareda…...

bash 判断内存利用率是否高于60%
在 Bash 脚本中,可以通过 free 命令获取内存利用率,然后结合 awk 和条件判断语句实现监控内存利用率是否高于 60%。以下是一个示例脚本: 1. 示例脚本 #!/bin/bash# 获取总内存和已使用内存 total_mem$(free | awk /Mem:/ {print $2}) used_…...

推送(push)项目到gitlab
文章目录 1、git init1.1、在当前目录中显示隐藏文件:1.2、查看已有的远程仓库1.3、确保你的本地机器已经生成了 SSH 密钥:1.4、将生成的公钥文件(通常位于 ~/.ssh/id_rsa.pub)复制到 GitLab 的 SSH 设置中:1.5、测试 …...

centos9升级OpenSSH
需求 Centos9系统升级OpenSSH和OpenSSL OpenSSH升级为openssh-9.8p1 OpenSSL默认为OpenSSL-3.2.2(根据需求进行升级) 将源码包编译为rpm包 查看OpenSSH和OpenSSL版本 ssh -V下载源码包并上传到服务器 openssh最新版本下载地址 wget https://cdn.openb…...

硬件成本5元-USB串口采集电表数据完整方案-ThingsPanel快速入门
ThingsPanel开源物联网平台支持广泛的协议,灵活自由,本文介绍ThingsPanel通过串口来采集电表数据,简单易行,成本低廉,适合入门者学习试验,也适合一些特定的应用场景做数据采集。 适用场景: 降低…...

在AWS EMR上用Hive、Spark、Airflow构建一个高效的ETL程序
在AWS EMR(Elastic MapReduce)上构建一个高效的ETL程序,使用Hive作为数据仓库,Spark作为计算引擎,Airflow作为调度工具时,有几个关键的设计与实施方面需要注意。 在AWS EMR上构建高效的ETL程序,…...

前端(四)css选择器、css的三大特性
css选择器、css的三大特性 文章目录 css选择器、css的三大特性一、css介绍二、css选择器2.1 基本选择器2.2 组合选择器2.3 交集并集选择器2.4序列选择器2.5属性选择器2.6伪类选择器2.7伪元素选择器 三、css三大特性3.1 继承性3.2 层叠性3.3 优先级 一、css介绍 CSS全称为Casca…...

vscode 打开 setting.json
按下Ctrl Shift P(Windows/Linux)或Cmd Shift P(Mac)来打开命令面板。输入open settings,然后选择 Open User Settings(JSON)。打开settings.json文件 ------修改设置-----: 1、 html代码的行长度&am…...

关于网络安全攻防演化博弈的研究小议
1. 拉高视角,从宏观看网络安全攻防 伴随着信息化的发展,网络安全的问题就一直日益突出,与此同时,网络安全技术也成为研究热点,直到今日也没有停止。 从微观来看,网络安全技术研究指的是针对某项或某几项…...

【FAQ】HarmonyOS SDK 闭源开放能力 —Push Kit(7)
1.问题描述: 推送通知到手机,怎么配置拉起应用指定的页面? 解决方案: 1、如果点击通知栏打开默认Ability的话, actionType可以设置为0, 同时可以在.clickAction.data中,指定待跳转的page页面…...

远程桌面防护的几种方式及优缺点分析
远程桌面登录是管理服务器最主要的方式,于是很多不法分子打起了远程桌面的歪心思。他们采用暴力破解或撞库的方式破解系统密码,悄悄潜入服务器而管理员不自知。 同时远程桌面服务中的远程代码执行漏洞也严重威胁着服务器的安全,攻击者可以利…...

ASP.NET|日常开发中连接Sqlite数据库详解
ASP.NET|日常开发中连接Sqlite数据库详解 前言一、安装和引用相关库1.1 安装 SQLite 驱动1.2 引用命名空间 二、配置连接字符串2.1 连接字符串的基本格式 三、建立数据库连接3.1 创建连接对象并打开连接 四、执行数据库操作4.1 创建表(以简单的用户表为例…...

python的自动化seleium安装配置(包含谷歌的chromedriver)
目录 前言介绍 一、下载谷歌浏览器chromedriver (一)查看谷歌浏览器版本 (二)去官网下载谷歌驱动(chromdriver) (三)谷歌浏览器安装位置解压 (四)配置环境变量 二、pychram里下载安装selenium 三、测试selenium是否成功 前言介绍 Selenium是一个开源的自动化测试工具&…...

QT requested database does not belong to the calling thread.线程中查询数据报错
QT requested database does not belong to the calling thread.线程中查询数据报错 QString name "ttx"; QSqlQueryModel* sql_model; QString sql_comm QString("select * from dssb_moddve_loddt_tab where name%1").arg(name); sql_model->set…...

服务器一般装什么系统?
在服务器管理中,操作系统的选择是一个关键因素,它直接影响到服务器的稳定性、性能和可维护性。那么为什么有些服务器选择Linux,而不是Windows?选择合适的操作系统对服务器的性能和安全性有多么重要? 在众多操作系统中…...

Linux vi/vim 编辑器使用教程
Linux vi/vim 编辑器使用教程 引言 Linux 系统中的 vi 和 vim 是非常强大的文本编辑器,它们以其高效性和灵活性而闻名。vim 是 vi 的增强版,提供了更多的功能和改进的用户界面。本文将详细介绍 vi/vim 的基本用法,包括打开文件、编辑文本、…...

JavaEE多线程案例之阻塞队列
上文我们了解了多线程案例中的单例模式,此文我们来探讨多线程案例之阻塞队列吧 1. 阻塞队列是什么? 阻塞队列是⼀种特殊的队列.也遵守"先进先出"的原则. 阻塞队列是⼀种线程安全的数据结构,并且具有以下特性: 当队列满的时候,继续⼊队列就会…...

梳理你的思路(从OOP到架构设计)_基本OOP知识04
目录 1、 主动型 vs.基於被动型 API 1)卡榫函数实现API 2)API的分类 3)回顾历史 4)API >控制力 2、 结语&复习: 接口与类 1)接口的表示 2)Java的接口表示 1、 主动型 vs.基於被动…...

nginx反向代理(负载均衡)
nginx的代理 代理 四层代理 七层代理 正向代理和缓存的配置方式 🐭🐮🐯🐰🐉🐍🐴🐑🐒🐔🐶🐷 反向代理》负载均衡 负载均衡ÿ…...

Android系统应用主要模块
设置 Android Settings开发总结 Launcher Android Launcher开发学习总结 System UI Android SystemUI 学习总结...