- 博客(65)
- 收藏
- 关注
原创 Java异常处理机制(基础知识)
Java语言提供了相对完善的异常处理机制。注意,这里的异常实际上包含了后面要说的“错误”和“异常”,即Error和Exception。一、Java异常分类错误和异常在Java中,所有的错误和异常有一个共同的祖先 Throwable类,这意味着这些错误和异常可以被捕获(catch)和抛出(throw)。Throwable的两个重要子类:Error(错误)和Exception(异常),此二者是有区别的。Error:程序无法处理的、比较严重的错误,一般会使得JVM处...
2021-09-15 21:32:24 473
转载 Java8 Lambda之Collectors.toMap
Collectors.toMap 作用是将List 转成mapCollectors.toMap(key,v->v,(v1,v2)->v1)//其中key 就是map得key值//第二个参数就是map得value//第三个参数的作用是当出现一样的key值得时候如何取舍其中V1代表旧值,v2代表新值,示例中取旧值应用场景当list中都为user对象,这个时候我需要根据年龄对所有用户进行合并分组,这个时候就可以如下写法list.stream.collect(Collec.
2021-08-17 14:50:01 1145
原创 PriorityQueue优先队列
PriorityQueue是接口Queue的实现类,是基于优先堆的无界队列。排序规则可以是元素的默认排序,也可以构造PriorityQueue为其传入Comparator以自定义排序规则。对默认情况来说,维护的是最小堆,即每次poll得到的是最小元素,但是如果用迭代器进行遍历,不保证有序。可以传入Comparator实例,也可以用匿名内部类。public class TT { static Comparator<Integer> compareInt = new Compar
2021-03-12 11:11:09 341
原创 MySql之sql语句(自用)
搭配group by使用,同组内运算avg(x),sum(x),count(*)数字处理rount(x, 3) 保留3位小数,并四舍五入字符串处理left(str, 5) 截取左端5个字符right(str, 5) 截取右端5个字符排序升序 asc (默认)降序 desc...
2021-03-10 18:57:34 170
原创 Linux命令 查看端口占用情况
netstat-a (all)显示所有选项,netstat默认不显示LISTEN相关-t (tcp)仅显示tcp相关选项-u (udp)仅显示udp相关选项-n 拒绝显示别名,能显示数字的全部转化成数字。(重要)-l 仅列出有在 Listen (监听) 的服务状态-p 显示建立相关链接的程序名(macOS中表示协议 -p protocol)-r 显示路由信息,路由表-e 显示扩展信息,例如uid等-s 按各个协议进行统计 (重要)-c 每隔一个固定时间,执行该netstat命令。
2021-02-16 16:05:11 424
原创 Linux进程管理
进程终止进程终止分为两步进程自己执行退出操作exit,释放占用的部分系统资源,之后向父进程发送退出信号,报告自己已经退出; 父进程相应子进程的进程退出信号,执行回收操作wait,找到已经退出的子进程,回收其中的统计信息,释放其进程控制块和系统堆栈,从而将进程彻底销毁。系统调用wait()作用:回收已经退出的进程详细功能:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经退出的子进程,wait就会收集这个子进程的信息,并把
2021-02-13 23:38:50 110
转载 getParameter和getAttribute区别(超详细分析)
转载链接:https://www.cnblogs.com/misscai/p/9693705.html(一)对getParameter过程,如下图:(二)对getAttribute过程,如下图两者区别① getParameter()获取的是客户端设置的数据,getAttribute()获取的是服务器设置的数据。② getParameter()永远返回字符串,getAttribute()返回值是任意类型既然parameter和attribute都是传递参数,为什么不直接..
2021-01-20 22:13:27 211 1
原创 Servlet(2) 三个域对象
1. Servlet的作用域的作用是什么——共享数据2. 三者的作用域request(HttpServletRequest)request是表示一个请求,只要发出一个请求就会创建一个request,它的作用域:仅在当前请求中有效。常用于服务器中同一请求不同页面之间的参数传递,常应用于表单的控件值传递。方法:request.setAttribute();request.getAttribute();request.removeAttribute();request.getParameter
2021-01-20 21:41:20 185
原创 Servlet(1) Servlet容器和Servlet
Servlet是一个Java程序,一个Servlet应用有一个或多个Servlet程序。JSP页面会被转换和编译成Servlet程序。Servlet应用无法独立运行,必须运行在Servlet容器中。Servlet容器将用户的请求传递给Servlet应用,并将结果返回给用户。...
2021-01-19 21:05:15 505
原创 Java并发工具之CyclicBarrier
1、CyclicBarrier的简单概述现实生活中我们经常会遇到这样的情景,在进行某个活动前需要等待人全部都齐了才开始。例如吃饭时要等全家人都上座了才动筷子,旅游时要等全部人都到齐了才出发,比赛时要等运动员都上场后才开始。在JUC包中为我们提供了一个同步工具类能够很好的模拟这类场景,它就是CyclicBarrier类。利用CyclicBarrier类可以实现一组线程相互等待,当所有线程都到达某个屏障点后再进行后续的操作。下图演示了这一过程。从功能上来说,CyclicBarrier 和 Cou
2021-01-09 14:57:28 148 1
原创 Java并发工具类之CountDownLatch
概述CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信的作用。CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现,计数器初始值为线程的数量。当每一个线程完成自己任务后,调用countDown()方法使计数器的值减1。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。方法说明public void await(
2021-01-09 14:24:38 183
原创 浅谈LockSupport工具类
引Java的并发包是基于AQS (AbstractQueuedSynchronizer)框架的,AQS框架需要借助于两个工具类:Unsafe(提供CAS操作) LockSupport(提供park/unpark操作)LockSupport的使用LockSupport定义了一组公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能。LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread thread)方法来唤醒一个被阻塞的线程。在Java6
2021-01-09 11:54:46 159
原创 Java如何实现原子操作
在Java中可以通过锁和循环CAS的方式来实现原子操作CAS实现原子操作的三大问题(1)ABA问题。因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A。从Java1.5开始,JDK的Atomic包里提供了一个类AtomicSta
2021-01-08 21:38:39 383
原创 面试:Synchronized知识点
1.Synchronized如何保证原子性、可见性和有序性原子性:确保线程互斥的访问同步代码;可见性:保证共享变量的修改对其他线程能够及时可见。线程解锁前,必须把共享变量的最新值刷新到主内存中 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新获取最新的值有序性:对一个监视器锁的释放操作先行发生(happens-before)与后面对该监视器锁的获取操作2.Synchronized使用形式对于普通同步方法,锁对象是当前实例对象。 对于静态同步方法,锁..
2021-01-08 21:09:09 144
原创 面试:Synchronized锁升级(理解)
偏向锁偏向锁是JDK6中的重要引进,因为HotSpot作者经过研究实践发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低,引进了偏向锁。偏向锁是在单线程执行代码块时使用的机制,如果在多线程并发的环境下(即线程A尚未执行完同步代码块,线程B发起了申请锁的申请),则一定会转化为轻量级锁或者重量级锁。引入偏向锁主要目的是:为了在没有多线程竞争的情况下尽量减少不必要的轻量级锁执行路径。因为轻量级锁的加锁解锁操作是需要依赖多次CAS原子指令的,而偏向锁只需
2021-01-08 20:46:35 220
原创 Java中的等待/通知机制
1. 引言有这样一个场景:线程A要在对象O符合某个状态时才会执行,如果不符合,线程A就会暂停执行并等待,而对象O的状态要由另一个线程B去改变。现在就会有一个关键问题:处于等待中的线程A,如何得知对象O变为了符合自己运行条件的状态?我们容易想到的一个方法是,让线程A不断地去检查对象O的状态,如果不符合就一直检查,直到符合为止。这个方法是有效的,但是存在如下问题线程A不停地检查对象O的状态,这会白白浪费CPU资源; 为了降低对CPU资源的耗费,可以让线程A每次检查之后睡眠一段时间,但是这又会导
2021-01-07 21:18:04 1313
原创 Java join()原理分析
1. join()的用法有两个线程A和B,如果A执行了B.join()(即A调用了B的join()方法),其含义是:当前线程A等待线程B执行结束,才会从此方法返回并继续执行。可以通俗理解为线程A等待线程B执行完后再继续执行。也可以设置超时时间,如果线程B在设置的时间内还没结束,那A就不再接着等了,而是从join方法中返回并继续执行,这种情况调用join(long millis)或join(long millis,int nanos)。具体在代码中的用法就不说了,因为网上有太多了。2. 源码和原理
2021-01-07 18:32:24 347
原创 条件变量和互斥锁
1. 原文章:条件变量中互斥锁的作用一直都有一个问题,就是条件变量为什么要和互斥锁一起使用,今天看了一篇文章,并结合APUE这本书,知道了其中的原因。函数pthread_cond_wait()有几步操作:1。判断条件2.如果条件满足,继续执行;如果条件不满足,就将线程挂到条件变量的等待线程队列中。如果不加锁的话,这两步之间就可能存在时间窗口,也就是(1)当线程1判断条件不满足,(2)然后准备把线程挂起的时候,线程2改变了条件,(3)接着线程1挂在了条件变量的等待队列上,这样就可能死锁。如果加上锁
2021-01-06 17:20:00 1389
原创 操作系统之虚拟内存
虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。 ——维基百科为什么要使用虚拟内存安全:如何用户程序可以直接访问物理内存,会造成安全问题。虚拟内存技术使得同时驻留在内存的多个用户进程相互之间不会发生干扰,也不会访问操作系统所占有的空间。 扩大程序的地址空间:利用虚拟存储技术,从逻辑上对内存空间进行扩充,从而可以使用户在较小的内存里运行较..
2021-01-05 22:18:40 934
原创 操作系统之进程调度
衡量调度算法的指标主要有两个周转时间= 完成时间−到达时间 响应时间= 首次运行时间−到达时间1. 先到先服务(First Come First Served,FCFS)按照到达的顺序执行,也被称为先进先出(First In First Out,FIFO)调度优点:简单,而且易于实现缺点:会出现护航效应,一些耗时较少的资源消费者被排在重量级的资源消费者之后。这个调度方案可能让你想起在杂货店只有一个排队队伍的时候,如果看到前面的人装满3辆购物车食品,你感觉如何?这会等很长时间。2.
2021-01-04 17:48:14 418
原创 操作系统之上下文切换
系统调用的过程发生CPU 上下文的切换CPU 寄存器里原来用户态的指令位置,需要先保存起来。接着,为了执行内核态代码,CPU 寄存器需要更新为内核态指令的新位置。最后才是跳转到内核态运行内核任务。而系统调用结束后,CPU 寄存器需要恢复原来用户保存的状态,然后再切换到用户空间,继续运行进程。所以,一次系统调用的过程,其实是发生了两次 CPU 上下文切换。不过,需要注意的是,系统调用过程中,并不会涉及到虚拟内存等进程用户态的资源,也不会切换进程。这跟我们通常所说的进程上下文切换是不一样的:进程上下文切
2021-01-04 16:18:56 2348
原创 Redis6.0为什么引入多线程
1. Reactor设计模式Reactor 模式的基本设计思想是基于I/O复用模型来实现的。这里说下I/O复用模型。和传统IO多线程阻塞不同,I/O复用模型中多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象等待。当某个连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理。什么意思呢?餐厅老板也发现了顾客点餐慢的问题,于是他采用了一种大胆的方式,只留了一个服务员。当客人点餐的时候,这个服务员就去招待别的客人,客人点好餐后直接喊服务员来进行服务。这里的顾客和服务
2021-01-03 21:49:48 691
原创 关于操作系统I/O的一些基础知识
内容来自书籍《操作系统导论》1. 操作系统是如何与设备交互的首先看一下典型系统的架构,如图。其中,CPU通过某种内存总线(memory bus)连接到系统内存。图像或者其他高性能I/O设备通过常规的I/O总线(I/O bus)连接到系统。最后,更下面是外围总线,外围总线将最慢的设备连接到系统,包括磁盘、鼠标及其他类似设备。对于操作系统来说,它要连接设备,并控制设备。对于设备来说,一方面它要有具体的机制实现自己的功能(比如打印机要实现本身打印的功能),另一方面它要提供接口供操作系统调用(不然无
2021-01-03 20:09:32 1057
原创 进程、线程、协程
1. 进程进程,直观点说,保存在硬盘上的程序运行以后,会在内存空间里形成一个独立的内存体,这个内存体有自己独立的地址空间,有自己的堆。操作系统会以进程为单位,分配系统资源(CPU时间片、内存等),进程是资源分配的最小单位。2. 线程线程,有时被称为轻量级进程(Lightweight Process,LWP),是操作系统调度(CPU调度)执行的最小单位。3. 进程和线程的区别和联系拥有资源:进程是资源分配的基本单位,进程中至少一个线程,它们共享该进程的资源。进程的资源包括地址空间,打开
2021-01-03 11:24:24 129
转载 基础知识之什么是I/O
本文内容来之书籍《Netty 4核心原理与手写RPC框架实战》1. 什么是I/O我们都知道在UNIX世界里一切皆文件,而文件是什么呢?文件就是一串二进制流而已,其实不管是Socket,还是FIFO(First Input First Output,先进先出队列))、管道、终端。对计算机来说,一切都是文件,一切都是流。在信息交换的过程中,计算机都是对这些流进行数据的收发操作,简称为I/O操作(Input andOutput),包括往流中读出数据、系统调用Read、写入数据、系统调用Write。不过计算
2021-01-02 22:05:19 5751
原创 网络编程之IO模型
小小开场白Java后端面试几乎必问IO模型,比如BIO、NIO和AIO;比如select、poll和epoll。我将对这块知识进行梳理和总结,一备自己使用,二为提供参考。打开浏览器,输入BIO、NIO和AIO,都是直接讲三者的区别,输入select、poll和epoll也是这样。输入IO模型,也都是直接分析对比Linux的五种IO模型。奈何我这样的小白,连些概念属于什么范畴,之间是什么关系,都不知道。作为小白,情不自禁要写小白友好型文章,所以我会对这些概念的关系进行介绍,因为这很重要。学一个东西之前
2021-01-02 20:43:31 130
原创 面试常问:BIO,NIO,AIO
概念解释Java 中的 BIO、NIO 和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装。程序员在使用这些 API 的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。BIO属于同步阻塞IO,(Blocking I/O) NIO属于同步非阻塞IO,(Non-blocking IO) AIO属于异步非阻塞IO,(Asynchronous I/O)1. BIOBIO是典型的一请求一应答通信模型,也叫做每连接每线
2021-01-02 20:41:25 435 2
原创 I/O多路复用技术(select/poll/epoll)
I/O多路复用技术在I/O编程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者I/O多路复用技术进行处理。I/O多路复用技术通过把多个I/O阻塞复用到同一个select阻塞上,从而实现系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程模型相比,I/O多路复用的最大优势就是系统开销小,系统不需要创建新的线程,也不需要维护这些线程的运行,降低了系统的维护工作量,节省了系统资源。目前支持I/O多路复用的系统调用有select,pselect,poll,epoll,在Linux网络编程
2021-01-02 17:16:47 830
原创 Linux的五种IO模型
在《UNIX网络编程 第一卷:套接口API》中,作者提出了五种IO模型,分别是阻塞IO、非阻塞IO、多路复用IO、信号驱动IO、异步IO。0. 基础当在应用程序中,当我们想接收数据,我们会调用套接字库的函数read或recv,它们会调用系统调用来读取数据。为了叙述方便,我们把通过套接字读取数据的操作看作调用了recvfrom系统调用。当一个recvfrom操作发生时,它会经历两个不同的阶段等待数据准备好(等数据从网路中到达,然后将数据拷贝到内核缓冲区) 把数据从内核缓冲区拷贝到用户进程缓冲
2020-12-30 20:38:14 375
原创 网络通信的基础知识
1. 两台主机是如何通信的这里,我们不探讨两台主机是如何进行网络连接的,而是探讨两台主机在建立了网络连接后,是如何通过网络进行通信的。如果学习过《计算机网络》的话,你一定知道类似下面的这张图“数据在各层之间传递的过程”。这张图是站在计算机网络协议栈的角度,来说明了两台计算机上的两个进程是如何通信的。既然是协议,协议就是规则,是抽象的。所以通过这个图,我们只知道数据是如何抽象地在网络协议层之间流动的;并且知道两台主机之间的数据传输,实质上是通过它们之间的“物理传输媒体”实现的,简单理解为网线就行,因为这不
2020-12-30 15:16:22 1392 1
原创 Redis的数据类型(对象)
1. 概述在这篇Redis的底层数据结构中,我们介绍了Redis的底层数据结构。但是Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,包括字符串对象、列表对象、哈希对象、集合对象和有序集合对象。我们可以把它们看作Redis的五个数据类型。即使是同一种数据类型,在不同的场景下,其底层数据结构可能是不一样的,这样做的目的是优化在不同场景下的使用效率。首先明确,Redis中使用对象来表示数据库中的键和值,每当创建一个键值对时,我们会至少创建两个对象,一个键对象
2020-12-26 18:57:43 305
原创 Redis的底层数据结构
一、SDS(简单动态字符串)1. SDS的定义struct sdshdr{ //记录buf数组中已使用字节的数量(不包括空字符'\0') //等于 SDS 保存字符串的长度 int len; //记录 buf 数组中未使用字节的数量 int free; //字节数组,用于保存字符串 char buf[];}如果SDS保存字符串"Redis",其结构如图所示:2. SDS和C语言字符串的区别2.1 数组大小当
2020-12-25 21:27:30 125
原创 Java并发编程中锁的正确使用方法
有需要的可以先去看看这篇操作系统教材上关于死锁的知识:操作系统 关于死锁的面试题死锁的必要条件有四个,只要破坏其中一个,就可避免死锁。互斥条件:在一段时间内,某资源只能被一个进程占用 请求和保持条件:进程已保持了资源,又提出了新的资源请求,而该资源已被其他进程占有,此时该进程阻塞,但不释放已持有的资源 不可抢占条件:进程已经获得的资源,在未使用完之前不能被抢占,只能等该进程使用完后自己释放 循环等待条件:发生死锁时,存在一个进程-资源循环链,1等2,2等3,......那么在Java中,具体
2020-12-23 16:47:24 821
原创 操作系统 关于死锁的面试题
进程/线程死锁的原因、条件、如何防止死锁。1. 进程/线程死锁的原因:竞争资源(不可抢占资源,可消耗性资源) 进程/线程推进顺序非法2、进程/线程死锁的四个必要条件(只要有一个不成立,就不会发生死锁)互斥条件:在一段时间内,某资源只能被一个进程占用 请求和保持条件:进程已保持了资源,又提出了新的资源请求,而该资源已被其他进程占有,此时该进程阻塞,但不释放已持有的资源 不可抢占条件:进程已经获得的资源,在未使用完之前不能被抢占,只能等该进程使用完后自己释放 循环等待条件:发生死锁时,存在
2020-12-23 09:58:32 414
原创 用户态和内核态切换
1. 切换方式从用户态到内核态切换可以通过三种方式,或者说会导致从用户态切换到内核态的操作:系统调用,这个上面已经讲解过了,在我公众号之前的文章也有讲解过。其实系统调用本身就是中断,但是软件中断,跟硬中断不同。系统调用机制是使用了操作系统为用户特别开放的一个中断来实现,如 Linux 的 int 80h 中断。 异常:如果当前进程运行在用户态,如果这个时候发生了异常事件,会触发由当前运行进程切换到处理此异常的内核相关进程中 外围设备中断:外围设备完成用户请求的操作之后,会向CPU发出中断信号,这
2020-12-22 21:30:27 12386
原创 为什么线程切换开销大
1. Unix/Linux的体系架构从宏观上来看,Linux操作系统的体系架构分为用户态和内核态(或者用户空间和内核)。内核从本质上看是一种软件,它控制计算机的硬件资源,并提供上层应用程序运行的环境。用户态即上层应用程序的活动空间,应用程序的执行必须依托于内核提供的资源,包括CPU资源、存储资源、I/O资源等。为了使上层应用能够访问到这些资源,内核必须为上层应用提供访问的接口:即系统调用。系统调用是操作系统的最小功能单位,每个系统调用都会实现一个简单的操作。在应用程序中可以调用所需的系统调用来完成任
2020-12-22 17:01:49 4848
原创 多线程(what,why,when)
1. 什么是多线程多线程是指在一个进程中包含多个并发执行的线程,这些执行不同的任务,互相协作共同完成进程的任务目标。2. 为什么要使用多线程在一个程序的执行过程中有许多耗时的操作,如磁盘IO、数据库的读写等。如果使用单线程就必须等待这些操作执行完才能去执行其他的操作,在等待的过程中CPU是空闲的。使用多线程可以将耗时操作放在后台继续执行的同时,执行其他操作,这样就可以提高CPU等资源的利用率,提高系统的吞吐量。3. 使用多线程带来了什么坏处对多线程的管理需要额外的CPU开销,线程的切换也
2020-12-22 10:36:47 138
原创 面试题:进程间通信方式,线程间通信方式
一、进程间通信(IPC,Inter-Process Communication)是指在不同进程间传播或交换信息1. 无名管道特点半双工(数据流向仅有一个方向),具有固定的读端和写端 只能用于父进程或兄弟线程之间通信(具有血缘关系的线程之间) 一种特殊文件,可以用普通的read、write函数进行读写,但又不是普通文件,不属于任何其它文件系统,仅存在于内存之中 通信的数据是无格式的流并且大小受限2. 命名管道(先进先出队列)特点不同于无名管道之处在于它提供一个与之关联的路径名,以F
2020-12-21 22:40:53 5081 1
原创 操作系统之线程同步
1. 概述一个进程中的多个线程运行时要解决两个重要问题线程之间如何通信 线程之间如何同步线程不安全的原因有两个多个线程共享资源 多个线程以不恰当的相对顺序执行线程同步的目的就是让多个线程以合理的相对顺序推进,或者互斥,或者协作,以保证结果的正确性。2. 同步的方法2.1 概念解释竞争:多个线程同时进入同一段代码或访问同一个资源的现象。 临界资源:各线程只能以互斥的方式访问的共享资源,称作临界资源 临界区:访问临界资源的那段代码称为临界区。为了保证各线程互斥地访问临界资源,
2020-12-21 18:42:21 394
原创 浅谈volatile的原理
1. 概述Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新, 线程可以通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁要更加方便。volatile具有以下特性可见性:对于volatile变量,一个线程对它的修改,对其他线程是可见的。也就是说,任何线程读这个变量时,读到的都是最新值。 原子性:volatile只能保证对单个变量读写的原子性,对复合操作(比如i++)是不具有原子性的。2.volatile的可见性那么volatile的.
2020-12-20 12:42:52 164 2
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人