自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(44)
  • 收藏
  • 关注

原创 详谈 Java 中的字符编码

详谈 Java 中的字符编码概述UnicodeCodePointLATIN 1UTF-16Code UnitUTF-8UTF-8 和 UTF-16 比较概述Java 语言内部使用的是 Unicode 字符集,存储一个字符时,支持 UTF-16 和 LATIN1 两种编码方式。但其实,Java 内部还实现了 ASCII、UTF-8 编码,可以很容易实现这些编码之间的相互转换。对于基本类型 cha...

2020-07-23 15:53:02 2112

原创 进程和线程:进程的实现

进程表为了实现进程模型,操作系统维护了一张进程表。每一个进程占用一个进程表项(又称进程控制块)。进程表项中保存了进程状态切换时所必要的信息,如程序计数器、堆栈指针、内存分配状况、打开的文件、账号和调度信息等。典型进程表表项字段:怎么实现进程切换进程切换一般利用中断。每一个 I/O 类关联一个中断向量(interrupt vector)的位置(靠近内存底部的固定区域),它包含中断服务程序的入口地址。中断发生后操作系统的底层工作步骤:硬件压入堆栈程序计数器等硬件从中断向量装入新的程序计数器汇

2021-01-29 15:43:32 172

原创 进程和线程:进程的状态和层次结构

三种状态运行态:该时刻实际占用 CPU 的进程。就绪态:可运行但还没有被分配时间片。一般在就绪队列中。阻塞态:除非某种外部事件发生,否则进程无法运行。一般在阻塞队列中。四种转换关系运行 -> 阻塞:一般操作系统发现进程因缺少某种条件无法运行下去的时候,发生这种转换。在一些系统中,进程可以执行诸如 pause 的系统调用进入阻塞状态。在 Unix 等系统中,当进程从管道或者设备文件读取数据时,如果没有有效输入,也会自动阻塞。运行 -> 就绪:一般由于正常进程调度发生这种转换。可能

2021-01-29 15:07:18 283

原创 进程和线程:进程的创建和终止

进程创建的时机系统初始化一个进程执行了创建进程的系统调用用户请求创建一个新进程一个批处理作业初始化进程分类前台进程:和用户交互的进程。守护进程:停留在后台处理的进程。(线程分类也类似,如 Java 中就将线程分为用户线程和守护线程,守护线程为用户线程提供一些基本服务以保证 Java 程序的正常执行,只有当非守护线程全部退出后,JVM 才能正常关闭)进程与窗口的关系每个窗口运行一个进程,通过鼠标用户可以选择一个进程并与该进程交互。Unix:新进程接管创建它的进程的窗口。Win

2021-01-29 14:54:50 379 1

原创 内存管理:虚拟内存

虚拟内存基本思想每个进程拥有自己的 地址空间,该空间被分割成多个块,每一块称作 一页(page),每一页有连续的地址范围。这些页被映射到物理内存,但并不是进程所有的页都必须在内存中才能运行。当进程引用到一部分在物理内存中的页时,就由硬件立刻执行映射。当引用到不在物理内存中的页时,则由操作系统负责将缺失的页装入物理内存并重新执行失败的指令。虚拟地址(Virtual Address)由程序产生的这些地址称为虚拟地址(逻辑地址),分成 页号(高位部分) 和 偏移量(地位部分) 两部分。它们构成了一个 虚拟地

2021-01-28 19:40:01 185

原创 32. 从上到下打印二叉树 Ⅰ

描述从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。示例例如:给定二叉树: [3,9,20,null,null,15,7],返回:[3,9,20,15,7]思路此题考察的是二叉树的层序遍历,本质便是对一颗二叉树的广度优先搜索。不论是针对二叉树而是有向图,广度优先搜索一般都是借助队列结构实现的,利用优先进入队列的元素会优先离开队列的特点,只需从树根开始,保证每个节点入队时,其左儿子和右儿子也会依序入队,则出队时的顺序即时二叉树的层序遍历。代码 public i

2021-01-11 21:15:38 46

原创 基于生产者-消费者模式的桌面搜索器

基于生产者-消费者模式的桌面搜索器FileCrawler 为生产者,负责在某个文件层次结构中搜索符合要求的文件,并将它们放入工作队列。Indexer 为消费者,每次从工作队列中取出一个文件并为它建立索引。结构优势生产者-消费者模式提供了一种适合线程的方法将桌面搜索问题分解为更简单的组件。将文件遍历和建立索引分解为两个独立的操作,比之放一个操作中实现有着更高的可读性和可重用性。此方式每个操作仅需完成一个独立的任务,且任务的控制流由阻塞队列全权负责。性能优势利用生产者-消费者模式,使得遍历文件

2021-01-10 14:40:16 91

原创 并发容器

并发容器并发容器是专门针对多线程环境下对容器并发访问设计的,并发容器还增加了对一些常见复合操作的支持,这些操作都是线程安全。使用并发容器代替同步容器,可以极大地提高程序的伸缩性、并发性以及降低风险。并发容器 <> 同步容器ConcurrentHashMap <> 同步的 HashMap、CopyOnWriteArrayListConcurrentSkipListMap <> SortedMap、TreeMapConcurrentSkipListSet

2021-01-10 14:34:31 76

原创 挖掘并行性

挖掘并行性大多数服务器应用程序都存在一个明显的任务边界:单个客户请求。但有时候即便是单个请求也存在可挖掘的并行性。异构任务的并行化所带来的提升往往十分有限,只有当大量相互独立且同构的任务可以并发进行处理时,才能体现出将程序的工作负载分配到多个任务中带来的真正性能提升。...

2021-01-10 14:17:47 89

原创 页面渲染器

页面渲染器本文将使用不同方式实现 Web 页面渲染器,并分析不同实现方式的优缺点。SingleThreadRendererSingleThreadRenderer 是一个串行页面渲染器,它先渲染绘制文本元素,同时为图像预留出矩形空间,待文本渲染完毕后开始下载图像,图像下载完后将它们渲染并绘制在相应的预留空间中。由于图像下载的过程中大部分时间在等待 I/O,而 CPU 是空闲的,浪费了计算资源。故为了获得更高的 CPU 利用率和响应性,可以将问题拆分成多个独立的任务并发执行。public class

2021-01-10 14:16:58 238

原创 获取任务的结果

获取任务的结果Executor 框架使用 Runnable 作为其基本的任务表现形式。如果提交的任务是一次延迟计算且我们需要获取计算的结果,只通过 Runnable 是无法实现的。原因在于其 run 方法不能返回一个值或抛出一个受查异常。可以使用 Callable 或 Future 来实现。如果向 Executor 提交一组计算任务,且希望在计算完成后获取结果,有两种方式:保留与每个任务关联的 Future,然后反复使用 get 方法,同时将 timeout 参数指定为 0,从而实现轮询判断任务是否

2021-01-08 20:07:17 352

原创 停止基于线程的服务(一)

停止基于线程的服务应用程序通常会创建拥有多个线程的服务,如 线程池 即可以表示一种拥有多个线程的服务。应用程序退出时,这些服务所拥有的线程也应该结束,JVM 才能被正常关闭。在 Java 中,线程由 Thread 对象表示,且和其它对象一样可以被自由共享。此外,线程有其所有者,即创建该线程的类,如线程池即是其工作者线程的所有者,应该通过线程的所有者来操控它们。和其它封装对象一样,线程的所有权是不可传递的:应用程序可以拥有服务,服务也可以拥有工作者线程,但应用程序不能拥有工作者线程,因此应用程序不能直接

2021-01-07 17:21:00 75

原创 12. 机器人的运动范围

描述地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?示例示例 1:输入:m = 2, n = 3, k = 1输出:3示例 2:输入:

2021-01-07 12:34:56 313

原创 12. 矩阵中的路径

描述请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。[[“a”,“b”,“c”,“e”],[“s”,“f”,“c”,“s”],[“a”,“d”,“e”,“e”]]但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第

2021-01-07 12:09:46 135

原创 11. 旋转数组的最小数字

描述把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。示例输入:[3,4,5,1,2]输出:1输入:[2,2,2,0,1]输出:0思路此题很容易想到顺序查找,时间复杂度为 O(n),但显然顺序查找没有利用到旋数组的特点,必然还有更优解。有序序列查找问题很容易想到二分法,此题也可以用二分法。但此题并不是严格意义上的有序序

2021-01-06 15:28:32 51

原创 9. 用两个栈实现队列

描述用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )示例输入:[“CQueue”,“appendTail”,“deleteHead”,“deleteHead”][[],[3],[],[]]输出:[null,null,3,-1]输入:[“CQueue”,“deleteHead”,“appendTail”,“appendT

2021-01-06 15:10:37 52

原创 6. 从尾到头打印链表

描述输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。示例输入:head = [1,3,2]输出:[2,3,1]思路这道题就是简单的逆序访问单链表节点,可以用递归在回溯的时候访问它,也可以自己定义一个辅助栈去存储访问过的节点数据。两种方法所用的辅助空间几乎相同,递归所需在栈中存储的信息更多,可能空间代价略高一些。代码/** * Definition for singly-linked list. * public class ListNode { * int

2020-12-31 16:33:26 47

原创 5. 替换空格

描述请实现一个函数,把字符串 s 中的每个空格替换成"%20"。示例输入:s = “We are happy.”输出:“We%20are%20happy.”思路从头开始的惯性思维很容易让我们想到从字符串的第一个字符开始遍历,碰到空格则替换成"%20"。但是这里存在一个问题,那就是"%20"占了三个字符,而空格仅占一个,因此,为了不覆盖后面的有效字符,必须将后面的字符序列整体向后移动两个字符单位,每次移动的代价是O(n),整体算法的复杂度来到了O(n^2)。这显然不是一个高效的方法,我们需要转变一

2020-12-31 16:31:55 53

原创 4. 二维数组中的查找

描述在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。示例现有矩阵 matrix 如下:[[1, 4, 7, 11, 15],[2, 5, 8, 12, 19],[3, 6, 9, 16, 22],[10, 13, 14, 17, 24],[18, 21, 23, 26, 30]]思路矩阵的特点是行递增、列递增,因此若以右上角为起

2020-12-31 16:28:06 40

原创 3. 数组中重复的数字

描述在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。示例例如:输入:[2, 3, 1, 0, 2, 5, 3]输出:2 或 3思路要想找到数组中重复的数字,惯性思维便是遍历这个数组,并且记住每个已经遇到的数字,当遇到重复数字时直接返回,该策略找到的是数组中第一个重复的数字。使用 Java 实现这一种策略十分简单,可以借助容器 HashSet 不允许存储重复元

2020-12-31 16:24:05 69

原创 基于不同任务执行策略的 Web 服务器

基于不同任务执行方式的 Web 服务器在任务执行策略一文中,简单介绍了 Java 常见的三种任务执行策略及其优缺点。本文将介绍分别基于这三种任务执行策略的服务器的特点,以说明在特定的场景中,合适的任务执行策略的重要性。单线程 Web 服务器该例程是一个串行的 Web 服务器,该服务器每次只能接受一个请求,主线程在接受连接和处理相关请求等操作间不断地交替运行,该策略使得服务器资源利用率低,无法提供高吞吐率或快速响应性。public class SingleThreadWebServer { p

2020-12-14 21:29:00 84

原创 管理延迟任务和周期任务以及获取任务的结果

延迟任务和周期任务在 Java 中,管理延迟和周期任务主要有两种方式:Timer 和 ScheduledThreadPoolExecutorTimerTimer 可以管理延迟任务和周z期任务,但存在一些缺陷:Timer 是单线程的,如果某个任务执行时间过长可能会破坏其它 TimerTask 的定时准确性。Timer 线程不会捕捉异常,若 TimerTask 抛出未受查异常则将终止整个 Timer 线程,该状况称为线程泄露(Thread Leakage)。Timer 只支持绝对时间而非相对时间

2020-12-14 21:06:11 153

原创 Spring AOP 使用 (二)

委托类和接口委托类是一个简单的算术计算器,对于 Spring AOP 技术而言,接口不是必须的。public interface MathCalculator { public int add(int num1, int num2); public int minus(int num1, int num2); public int mul(int num1, int num2); public int div(int num1, int num2);}@Servic

2020-12-14 14:40:55 86

原创 Executor

Executor在 Java 类库中,任务执行的主要抽象不是 Thread,而是 Executor。虽然 Executor 只是一个简单的接口,但却为 JDK 中灵活且强大的异步任务执行框架提供了基础。异步任务执行框架使用 Runnable 表示任务,能支持多种任务执行策略,且提供了一种标准方法将任务的提交和执行解耦。此外,Executor 的实现还提供了对生命周期的支持,以及统计信息收集、应用程序管理机制和性能监视等机制。Executor 基于生产者-消费者模式,提交任务的操作相当于生产者,执行任

2020-12-13 16:30:46 878

原创 任务执行策略

执行执行策略执行任务的方式可以分为两类:串行执行和并行执行。其中,串行执行即执行完一个任务后再执行下一个,是单线程的;而并行执行的方式则有许多种,取决于你的并行策略。下面介绍 Java 常见的三种任务执行方式。串行执行每次只能执行一个任务,若服务器程序使用串行处理机制通常无法提供高吞吐量和快速响应性。为每个任务分配一个线程增加线程可以提高系统吞吐率,但问题是无限制的创建线程非但降低性能,而且可能会引发危险。使用ExecutorExecutor 是 Java 类库中的一个接口,提供了标准方法将任

2020-12-13 15:56:32 856

原创 阻塞方法

阻塞方法线程阻塞和阻塞方法被阻塞的线程往往需要等待一个不受它控制的事件发生后才能继续执行,如等待I/O操作完成,等待某个锁可用或等待一个外部计算结束。而可以导致线程阻塞的方法,我们称为 阻塞方法。在 Java 中,能抛出 InterruptedException 的方法一定是阻塞方法,这个异常会在处于阻塞状态的线程被中断时抛出。Java 中,每个线程有一个中断状态,Thread 提供了 interrupt 方法用以中断线程(设置中断状态)。需要注意的是,中断是一种协作机制。一个线程不能强制另一个线程

2020-12-13 15:00:04 1313

原创 同步容器存在的并发问题

同步容器java.util 中,存在一些古老的同步容器类,如 Vector、Hashtable。这些同步容器类,主要依靠持有内部锁(synchronized 修饰方法)来保证对容器状态访问的原子性。因此,几乎所有需要访问容器状态的方法,都是 synchronized 修饰的同步方法。虽然保证了线程安全,但也极大降低了并发性,使得同步容器在并发场景下堪忧的性能令人诟病。并发问题如果单独使用同步容器所提供的操作,可以放心使用,不会带来任何的并发问题。因为同步逻辑已经被封装在该操作对应的方法中。但如果使用复

2020-10-01 17:35:29 160

原创 Servlet实操系列(一):部署Servlet到Tomcat

Servlet是什么?Servlet其实没有什么高深的概念,它只不过是JavaEE规范中定义的一组接口,该组接口用以规定服务端程序如何去处理一个请求。以Java Web开发为例,通常我们会选择使用某个实现了完整或部分JavaEE组件的Web服务器,而Servlet正是这些组件中的一个。我们无需关心Web服务器是如何接收和响应一个HTTP请求的,作为Java Web程序员,我们只需关注如何处理请求即可,这使得我们能够聚焦于业务。由此可见,如何去处理请求,是Servlet的核心职责。部署Servlet到To

2020-07-26 16:28:30 967

原创 详解HTTP协议(一):概念篇

作为Web程序员,HTTP恐怕是与之关系最为紧密的应用层协议了。 HTTP全称HyperText Transfer Protocol(超文本传输协议 ),基于B/S架构,是Web技术的核心。 B/S架构下,客户端程序和服务器程序运行在不同的端系统中,客户端程序可以成功向服务器程序请求到一个Web页面,正式通过交换HTTP报文的方式实现的,HTTP协议定义了这些报文的结构和服务器进行报文交换的方式。浏览器请求Web页面的流程当用户请求一个Web页面时,浏览器向服务器发出一个HTTP请求报文,该报文中包含所

2020-07-26 14:23:08 197

原创 笔试中快速实现并查集——Java语言实现

并查集——Java语言实现并查集(Union Set)介绍过程初始化查询元素所属集合两个集合合并存在的问题查询优化并查集(Union Set)介绍并查集是一种树型数据结构,常用于处理一些不相交集合的合并及查询问题。每个并查集内都有若干个集合,每个集合都有一个代表节点。若将并查集看作森林,则其内部每个集合都是一颗树,代表节点即为树根。但和普通树不同的是,这里的树不再是父节点拥有子节点的引用,而是子节点拥有父节点的引用。也就是说,可以通过任何一个子节点找到最终的树根,也是该集合的代表节点。此外,根节点中的

2020-05-28 14:50:45 260

原创 链表回文问题——额外空间复杂度O(1)解法

@TOC描述设计一个算法,判断一个单链表是否是回文结构。分析对于双向链表而言,回文结构判断可以很轻松地达到额外空间复杂度 O(1),时间复杂度 O(N)。然而,对于单链表而言,就并不是那么容易了。常规思路,很容易想到再准备一个新的单链表,用头插法将原先的单链表中的节点依次插入这个链表。这样,新的单链表就是原先单链表的逆序版本。然后再同时遍历这两个链表,判断两个链表的当前节点是否相等(equ...

2020-05-12 17:33:09 902

原创 zigzag遍历矩阵

zigzag遍历矩阵描述分析描述按之字型遍历一个矩阵。分析之字形遍历顺序如下图所示:处理一个二维矩阵一定不能陷入繁琐的细节处理中,如果此题思考方向为到了 2 后如何转向 5,到了 5 后如何转向 9,到了 9 后又如何转向 6,这样编程会变得异常复杂。应该抽象出子过程,本题的子过程就是遍历矩阵斜着的一行,只不过每隔一行遍历方向改变。如下图所示:O1 和 O2 代表斜着一行的两个端点...

2020-05-08 22:06:27 709

原创 小和问题

小和问题描述分析代码描述在一个数组中,每一个数左边比当前数小的累加起来,叫做这个数组的小和。求一个数组的小和。例子:[1,3,4,2,5]1 左边比 1 小的数:没有;3 左边比 3 小的数:1;4 左边比 4 小的数:1,3;2 左边比 2 小的数:1;5 左边比 5 小的数:1,3,4,2;所以小和为:1+1+3+1+1+3+4+2=16分析小和问题可以利用归并排序高效...

2020-05-08 21:40:17 331

原创 打印两个有序链表的公共部分

打印两个有序链表的公共部分描述分析代码描述给定两个有序链表的头指针head1和head2,打印两个链表的公共部分。分析使用一趟归并即可。代码class PrintCommon { public static void print(Node1 head1, Node1 head2) { Node1 cur1 = head1.next; ...

2020-05-07 13:02:06 154 1

原创 矩阵旋转

矩阵旋转描述分析代码描述将一个正方形矩阵顺时针旋转 90 度。分析本题和转圈打印矩阵类似, 将矩阵分解成由外至内的方形圈,先处理最外层的圈,当最外层的圈中的元素都旋转到了正确位置后,再往里一层进行处理,直到最内层的圈处理完毕。处理每一层圈的过程是一致的,可以抽象出来。处理每一圈的时候,可以先从四个角点出发。矩阵旋转 90 度后,四个角点会移动位置:O1 -> O2, O2 -&gt...

2020-05-06 15:37:50 637

原创 转圈打印矩阵

转圈打印矩阵描述分析步骤代码描述给定一个矩阵 matrix,要求以转圈的方式打印。额外空间复杂度为 O(1)。分析如果一个 4x4 的矩阵 matrix 按行展开的元素顺序为:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16。那么该举证转圈打印的顺序便是:1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10。如下图所示。处理一个二维矩...

2020-05-06 14:21:06 180

原创 鉴别完全二叉树

鉴别完全二叉树描述分析步骤代码描述设计一个算法,判断一颗二叉树是否是完全二叉树。分析一颗 k 层 n 个节点的完全二叉树,和 k 层的满二叉树按层序遍历的前 n 个节点形状一致。所以,一颗完全二叉树中的任何节点如果有右儿子,那么一定有左儿子。且层序遍历时,一旦出现空节点,后续节点必定全为空。可以用一个标记标量 mode 记住是否出现过空节点。步骤① 初始 mode = 0② 层序遍历...

2020-05-05 10:44:46 99

原创 二叉树序列化与反序列化

二叉树序列化与反序列化描述分析先序遍历序列化代码实现反序列化代码实现描述实现二叉树的序列化与反序列化。分析序列化二叉树的方式有很多,最经典的就是将一颗二叉树转化为字符串,这样方便存储到文件。反序列化时只需解析这个字符串,提取出每个节点的内容信息,将二叉树还原。二叉树序列化有许多应用,必须 OJ 平台在判断你提交的二叉树是否正确时,往往是将你的二叉树序列化成字符串,然后与正确答案的序列化字符...

2020-05-04 11:00:23 195

原创 鉴别搜索二叉树

鉴别搜索二叉树描述分析代码描述设计一个算法,判断一颗二叉树是否是搜索二叉树。分析一颗搜索二叉树,任何子树的左儿子一定小于根,右儿子一定大于根。由于这个特性,可见搜索二叉树的中序遍历一定是升序的。反之,如果一颗二叉树的中序遍历是升序的,那么它一定是搜索二叉树。故而,判断一颗二叉树的中序遍历是否升序,就可以判断这棵二叉树是否是搜索二叉树。代码...

2020-05-04 10:41:29 212

原创 快速找到二叉树的后继节点

快速找到二叉树的后继节点描述分析步骤代码描述设计一个算法,给定一个二叉树中的节点,返回它的后继节点。该二叉树的节点有父节点引用。分析二叉树结点的后继一般指的是中序遍历中,该结点的后一个结点。如果二叉树的节点是没有父节点的引用的。那么对二叉树进行中序遍历可以很容易拿到指定节点的后继节点。现在二叉树的节点拥有父节点的引用,也就是说访问二叉树时不需要使用栈就可以轻松回溯,解决此问题的效率会高...

2020-05-02 21:07:11 506

空空如也

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除