- 博客(116)
- 资源 (26)
- 收藏
- 关注

原创 毕业一年半拿遍所有大厂offer,我是怎么做到的?
最近一段时间面试了几家互联网公司,陆续通过了阿里、头条、美团,滴滴,京东的面试,基本上面试的公司都通过了,所以在这里想分享一些自己面试的经验给大家,希望能帮助大家拿到心仪的offer我的基本情况:19届本科,现在在一家小公司,毕业一年半,后端开发文章在牛客还挺受欢迎的,希望对大家有帮助牛客原地址:https://www.nowcoder.com/discuss/594676?source_id=profile_create_nctrack&channel=-1我在csdn上看到有人直接搬运
2021-04-02 13:16:20
1308
4
原创 什么是Redis的渐进式ReHash?
Redis 的渐进式 rehash 是一种哈希表扩容方法,用于在数据增长时逐步将元素迁移到新的哈希表,而不影响系统的正常读写操作。以下是 Redis 渐进式 rehash 的详细过程和优势。
2025-01-06 23:46:27
536
原创 MySQL叶子节点为啥使用双向链表?不使用单向呢?
MySQL 的 B+ 树使用双向链表提供更高效的顺序扫描和倒序扫描;支持范围查询和倒序范围查询,提高查询灵活性和性能;避免倒序查询时的回溯操作,减少额外的计算开销;提高插入和删除操作的效率;保证索引操作的一致性和完整性。这种设计使得 B+ 树在执行复杂查询时具有显著的性能优势,尤其是在数据量大、查询频繁的场景下。
2025-01-05 13:03:15
664
原创 Spring Bean的初始化过程是怎么样的?
实例化 Bean设置属性Aware 接口回调前初始化处理(执行初始化方法后初始化处理(Bean 可用销毁过程(当需要时)这种机制使得 Spring 提供了灵活的 Bean 生命周期管理,可以通过不同的钩子和配置来满足应用的需求。
2025-01-04 13:26:10
509
原创 MySQL存储引擎如何完成一条更新语句的执行!
文章首发到公众号:月伴飞鱼那么我们先想一下这条SQL语句是如何执行的?首先肯定是我们的系统通过一个数据库连接发送到了MySQL上,然后肯定会经过SQL接口、解析器、优化器、执行器几个环节,解析SQL语句,生成执行计划,接着去由执行器负责这个计划的执行,调用InnoDB存储引擎的接口去执行。大致会走下图的这个流程我们就来探索一下这个存储引擎里的架构设计,以及如何基于存储引擎完成一条更新语句的执行。
2024-10-09 13:17:22
743
原创 架构设计之解析CQRS架构模式!
在这种架构模式下,命令侧的数据变化后及时同步(事件、消息队列)到查询侧,两侧数据并非实时。这种分离可以通过不同的数据模型、数据库甚至服务来实现,从而优化读写性能和可伸缩性。的使用者可以根据实际情况,将读写分离开单独部署,然后引入。《重构:改善既有代码的设计》的作者也提醒要小心使用。CQRS模式的核心设计理念来自于一条设计原则,即。被拆分成了两个接口,分别承担查询和写职责。上述方法既改变了数据,又返回了数据状态。后,查询和命令两侧通常会采用独立的。在本文中,我将对此做出详细解释。文章首发到公众号:月伴飞鱼。
2024-10-08 11:04:16
884
原创 MySQL新增字段/索引会不会锁表?
通过优化表结构修改的操作,开发者可以避免或最小化锁表时间,从而保证系统的正常运行。因此,在对大型表进行结构修改时,仍建议在低负载时执行,以最小化对应用程序的影响。所以实际操作的过程中,要关注表的数据多小,最终的数据大小(要关注索引数据)。相反,它会使用一种更轻量级的操作来添加新字段,从而减少锁定时间和资源消耗。之后,实际单纯的增加一个字段,表结构修改和索引添加通常不会锁定整个表。表级锁指在执行某些操作时,为了保证数据的一致性,对整个表加锁。)操作,允许一些表修改操作在不锁定表的情况下进行。
2024-10-07 14:56:52
1446
原创 写出好的Join语句,前提你得懂这些!
因为驱动结果集越大,意味着需要循环的次数越多,也就是说在被驱动结果集上面所 需要执行的查询检索次数会越多。比如,当两个表(表 A 和 表 B) Join 的时候,如果表 A 通过 WHERE 条件过滤后有 10 条记录,而表 B 有 20 条记录。如果我们选择表 A 作为驱动表,也就是被驱动表的结果集为 20,那么我们通过 Join 条件对被驱动表(表 B)的比较过滤就会有 10 次。反之,如果我们选择表 B 作为驱动表,则需要有 20 次对表 A 的比较过滤。
2024-10-01 16:43:49
840
原创 如何实现一个优秀的散列表!
假设现在有一篇很长的文档,如果希望统计文档中每个单词在文档中出现了多少次,应该怎么做呢?很简单!我们可以建一个HashMap,以String类型为Key,Int类型为Value;遍历文档中的每个单词word,找到键值对中key为word的项,并对相关的value进行自增操作。如果该key=word的项在 HashMap中不存在,我们就插入一个(word,1)的项表示新增。这样每组键值对表示的就是某个单词对应的数量,等整个文档遍历完成,我们就可以得到每个单词的数量了。if (!
2024-09-30 13:37:40
917
原创 聊聊JIT是如何影响JVM性能的!
我们知道Java虚拟机栈是线程私有的,每个线程对应一个栈,每个线程在执行一个方法时会创建一个对应的栈帧,栈帧负责存储局部变量变量表、操作数栈、动态链接和方法返回地址等信息,每个方法的调用过程,相当于栈帧在Java栈的入栈和出栈过程但是栈帧的创建是需要耗费资源的,尤其是对于 Java 中常见的 getter、setter 方法来说,这些代码通常只有一行,每次都创建栈帧的话就太浪费了。
2024-09-29 10:37:39
1190
原创 ConcurrentHashMap核心原理,这次彻底给整明白了!
减少堆内存的使用可以更高效使用内存」❝这句话怎么理解呢?❞堆内存用的越少,堆被填满的几率就越低,新生代回 收的次数更少,对象的晋升年龄也就不会很频繁地增加,这意味着对象被提升到老年代的 可能性也降低了,因此, Full GC会减少,降低了GC发生的频率「下面将介绍几种减少内存使用的方式」微信搜索:月伴飞鱼,交个朋友公众号后台回复666,获得免费电子书籍,必读书籍这里全都有书籍:Java性能权威指南。
2024-09-28 09:51:50
986
原创 高效使用内存,这些你得知道!
减少堆内存的使用可以更高效使用内存」❝这句话怎么理解呢?❞堆内存用的越少,堆被填满的几率就越低,新生代回 收的次数更少,对象的晋升年龄也就不会很频繁地增加,这意味着对象被提升到老年代的 可能性也降低了,因此, Full GC会减少,降低了GC发生的频率「下面将介绍几种减少内存使用的方式」微信搜索:月伴飞鱼,交个朋友公众号后台回复666,获得免费电子书籍,必读书籍这里全都有书籍:Java性能权威指南。
2024-09-27 15:17:20
936
原创 一个简单案例,带你看懂GC日志!
接下来详细介绍GC过程:」这行代码直接分配了一个4MB的大对象,此时这个对象会直接进入老年代,接着array1不再引用这个对象连续分配了4个数组,其中3个是2MB的数组,1个是128KB的数组,如下图所示,全部会进入Eden区域中此时还能放得下2MB的对象吗?不可能了,因为Eden区已经放不下了。因此此时会直接触发一次Young GC。我们看下面的GC日志:❝❞。
2024-09-26 12:41:32
781
原创 RocketMQ消息重试机制解析!
由于网络抖动、服务宕机等一些不确定的因素,`RocketMQ`在发送消息的时候很有可能出现消息发送或者消费失败的问题。所以`RocketMQ`消息重试分为2种:`Producer`端重试和`Consumer`端重试。
2024-09-25 13:22:35
1394
原创 领域设计之理解聚合与聚合根!
微信搜索:月伴飞鱼,交个朋友,进面试交流群公众号后台回复666,可以获得免费电子书籍参考资料《领域驱动设计:软件核心复杂性应对之道》《实现领域驱动设计》
2024-09-23 11:52:19
982
原创 Redis面试真题总结(四)
RDB持久化通过将数据以二进制的形式保存到磁盘的RDB文件中,包含了Redis数据的全量快照。因此,在过期时间到达之后,过期键可能仍然存在一段时间,直到Redis执行删除操作。Redis使用过期键的删除策略来自动清除已经过期的键,以释放内存空间。如果你知道一些键是可以安全删除的,你可以为它们设置过期时间,然后使用。需要注意的是,无论采用哪种删除策略,Redis并不是立即清除过期键。如果数据的重要性不等,你可以为重要的数据设置过期时间,然后使用。)持久化是Redis提供的另一种数据持久化方法。
2024-09-22 12:20:59
1281
原创 Redis面试真题总结(三)
缓存雪崩是指在缓存中大量的缓存数据同时过期或者缓存服务器宕机,导致大量请求直接访问后端数据库。的数据,这当然会导致缓存无法命中,然后请求就会穿透缓存层,直接访问数据库。)是一种数据结构,用于快速判断一个元素是否属于一个集合。最常见的缓存击穿场景就是有大量请求同时查询一个热点。)持久化是Redis提供的一种数据持久化方法。缓存击穿是指当缓存中没有某个。
2024-09-21 13:25:56
936
原创 Redis面试真题总结(二)
字符串类型是 Redis 最基础的数据结构,首先键是字符串类型,而且其他几种结构都是在字符串类型基础上构建的。现在,我们对链表做一些增强,我们抽取出部分数据在上一级创建一个新的链表,上级链表的元素个数是下级链表的1/2。跳跃表不仅能提供较快的查找速度,而且插入和删除元素也相对简单,主要是调整索引,改变指针的指向即可。列表类型是用来储存多个有序的字符串,列表中的每个字符串成为元素,一个列表最多可以储存。跳跃表最主要的特点是它的查找,插入,删除操作的时间复杂度都是。因此,跳跃表在很多场景下,例如Redis的。
2024-09-20 11:45:47
1116
原创 Redis面试真题总结(一)
Redis是一个高性能的开源内存数据库系统,它使用键值对存储数据,并支持多种数据结构。例如,一个电商网站,用户下订单后,可以把订单任务放入Redis的消息队列。Redis将数据存储在内存中,而内存的读写速度比磁盘快几个数量级。Redis最常见的使用场景是作为内存职业键值存储来构建缓存。例如,可以缓存从数据库查询出来的数据,后面再需要这些数据时。由于Redis的高性能特性,也经常被用于构建实时系统。例如,一个在线游戏,可以使用Redis来存储用户的分数。来记录用户发布的消息数量,或者用户被赞的次数。
2024-09-19 09:53:01
1180
原创 JVM面试真题总结(十三)
当下一次Minor GC发生时,JVM会再次检查Eden区和已经存有对象的Survivor区(例如:S0区)分代复制算法的基本思想是将新创建的对象分配到Eden区,当Eden区满时,触发一次Minor GC。这些策略的目的是尽可能将生命周期长的对象提前移入老年代,减少新生代的GC次数,提高系统的运行效率。而Survivor区的空间较小,主要用于存放从Eden区复制过来仍然存活的对象。需要注意的是,虽然两个Survivor区的总空间占新生代的2/10。,这种垃圾收集的频率较高,但每次收集的时间较短。
2024-09-18 09:58:25
783
原创 JVM面试真题总结(十二)
除了上述变化外,Java 8中的内存结构大致保持不变,包括Java堆、栈、程序计数器、本地方法栈等。Java堆主要用于存储对象实例,栈用于存储局部变量、方法调用等,程序计数器用于存储当前线程的执行位置。总的来说,永久代被移除是为了简化垃圾收集,避免内存溢出,提高性能,以及实现更好的内存控制和监控。而栈用于存储基本数据类型、对象引用变量和方法调用相关信息,大小固定,生命周期较短,访问相对较快。在实际应用中,我们需要关注元空间的内存使用情况,以便在需要时进行调整。)和JVM内存模型是两个不同的概念。
2024-09-17 09:59:40
876
原创 JVM面试真题总结(十一)
总的来说,Java内存模型主要解决了多线程环境下共享数据的一致性、可见性等问题,是Java并发编程的基础。这种模型的好处是,由于启动类加载器是最顶部的加载器,因此它加载的都是最可信任的类库(Java的核心类库)但是在运行时,JVM会优先使用由启动类加载器加载的Java核心类库中的String类。双亲委派模型是Java类加载器的一个重要特性,它可以确保Java核心库的类型安全。:设置元空间的初始大小(Java 8中替代了永久代的概念)。:设置永久代的最大大小(仅在Java 7及更早版本中使用)。
2024-09-16 09:52:58
1118
原创 JVM面试真题总结(十)
当程序需要使用某个类时,如果这个类还没有被加载到内存中,那么系统就会通过类加载器来加载这个类。直接内存并不是Java虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。然而,要注意的是,虽然对象实例本身是在堆上分配的,但是对这些对象的引用通常是在栈上分配的。通过这种技术,JVM可以判断出一个新创建的对象的引用是否会逃逸出当前方法或者当前线程。一旦类被加载到内存中,就可以创建这个类的对象,或者调用这个类的静态方法和静态字段。JVM将会根据类的字节码中的指令,对类进行初始化。
2024-09-15 10:14:32
1487
原创 JVM面试真题总结(九)
而synchronized关键字在锁定和解锁时也会插入相应的内存屏障,以确保操作的顺序性和可见性。在Java中,volatile关键字和synchronized关键字的实现就使用了内存屏障。例如,如果你写了一个递归函数,没有提供适当的递归出口,那么这个函数就会无限递归下去。以上面的代码为例,这个方法会不断地调用自己,每次调用都会创建一个新的栈帧并压入栈中。它可以确保某些内存操作的顺序性,以及它们对其他处理器可见的顺序。栈内存主要用于存储局部变量和执行动态链接,还用于方法调用和返回。垃圾收集器的工作过程。
2024-09-14 09:49:35
709
原创 JVM面试真题总结(八)
每个Region都有一个用于垃圾回收的优先级,G1收集器会优先选择回收垃圾最多的Region。G1收集器重新划分了Java堆,主要是为了解决CMS收集器的一些问题,提高垃圾收集的效率。CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用。CMS收集器以最小的停顿时间为目标的收集器,容易产生内存碎片。CMS收集器:初始标记→并发标记→重新标记→标记清楚。G1收集器:初始标记→并发标记→最终标记→筛选回收。区别三:CMS收集器和G1收集器的优劣性。区别二:使用的算法不一样。
2024-09-13 10:06:28
797
原创 JVM面试真题总结(七)
具体来说,引用计数算法为每个对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1。ZGC和G1都是高级的垃圾收集器,为了满足大内存和低延迟的需求而设计。例如,如果堆中的存活对象非常多,或者垃圾收集线程的数量设置得过少。在某些情况下,G1可能无法达到预设的停顿时间目标。引用计数算法是一种非常直观、简单的垃圾收集算法。)垃圾收集器是一种面向服务器的垃圾收集器。当引用失效时,计数器值就减1。)是自动内存管理的重要部分。
2024-09-12 11:18:03
903
原创 JVM面试真题总结(六)
不过,对于新生代这样对象存活率较低的区域,复制算法通常是一种非常高效的垃圾收集方式。当Eden区满时,就会触发一次Minor GC,GC会检查Eden区中的所有对象。因此,现代的垃圾收集器通常不单独使用标记-清除算法,而是结合其他算法一起使用。GC(垃圾收集)的标记整理算法是一种用于回收垃圾对象并释放内存空间的方法。复制算法是垃圾收集中的一种常见算法,它主要被用于新生代的垃圾回收。在实际的应用场景中,标记-整理算法通常用于堆内存的垃圾回收。不过,它的代价是需要移动对象,这会增加一定的开销。
2024-09-11 09:48:27
760
原创 JVM面试真题总结(五)
浮动垃圾并不会影响程序的正确性,但是它可能会暂时占用一些内存,直到下一次垃圾收集时才能被回收。这个算法的一个主要优点是,它可以在程序的执行过程中并发地进行垃圾回收,而不需要暂停整个程序。浮动垃圾是指在进行垃圾收集过程中新生成的,但是在当前垃圾收集结束后无法被回收的对象。三色标记法是一种用于垃圾回收的算法,主要用于处理并发垃圾回收的问题。最后,在清除阶段,所有的白色对象都被认为是垃圾对象,并被回收。然后,垃圾收集器选择一个灰色对象,扫描这个对象的所有引用。标记阶段开始时,根对象被标记为灰色。
2024-09-10 10:10:09
1068
原创 JVM面试真题总结(四)
内存泄漏是指程序在申请内存后,无法释放已申请的内存空间,即使程序可能已经不再需要这部分内存。jhat: 用于与jmap 命令搭配使用,分析jmap 生成的 dump 文件。Java提供了一些强大的工具来帮助开发者定位和解决JVM问题。,我们不断地向其中添加数据,但是却忘记从中删除不再需要的数据。jstack: 用于查看JVM 虚拟机当前时刻的线程快照。jstat:用于监视JVM 虚拟机运行时状态信息的命令。jinfo :用于实时查看和调整虚拟机运行参数。jps: 用于显示所有JVM 虚拟机进程。
2024-09-09 12:04:36
1242
1
原创 JVM面试真题总结(三)
Full GC的特点是能够清理整个堆内存中的无用对象,但是运行速度较慢,因为它需要处理整个堆内存。Full GC,也被称为全局垃圾收集,主要是对Java堆内存中的新生代和老年代进行的垃圾收集。通过合理的内存设置和垃圾收集器选择,可以降低Full GC的频率,从而提高应用的性能。GC Roots,或者说垃圾回收根,是垃圾收集器进行垃圾回收时的起始点。然后,垃圾收集器会标记出所有堆内存中无用(即不再被引用)的对象。最后,垃圾收集器会清理掉这些无用的对象,从而回收内存空间。,是Java中垃圾收集器的一种。
2024-09-08 09:51:42
1691
原创 JVM面试真题总结(二)
Minor GC的主要优点是效率高,因为新生代通常只占据堆空间的一小部分,并且新生代中的大多数对象都是。在Java内存模型中,volatile是一种特殊的变量,对它的读写操作具有特殊的内存语义。过程中,垃圾收集器会检查新生代中的对象,清理无用的对象(即没有被其他对象引用的对象)需要注意的是,Young GC只能清理年轻代中的无用对象,对于老年代中的无用对象。清理无用的对象(即没有被其他对象引用的对象),并将仍然存活的对象移动到。在Java的内存模型中,堆内存被分为新生代和老年代。
2024-09-07 11:15:35
1031
1
原创 JVM面试真题总结(一)
例如,在多线程环境中,如果两个线程都在访问和修改同一块内存,那么指令重排序可能会导致数据不一致的问题。最后,由于处理器使用了缓存和读/写缓冲区,实际的内存读/写操作的顺序可能会与原始的程序顺序不同。但是如果B指令的运算并不依赖于A指令读取的数据,那么处理器就可以先执行B指令,再执行A指令。首先,源代码在编译的过程中,编译器可能会进行优化,改变程序中语句的执行顺序。在执行程序时,处理器可能会改变指令的执行顺序,这就是所谓的指令重排序。Java既是解释执行的,也是编译执行的,它采用了一种折中的方式。
2024-09-06 09:53:46
1209
原创 操作系统面试真题总结(七)
涉及的任务无法继续执行,因为每个任务都在等待其他任务释放资源,但是没有任务会释放它的资源,因为它们都在等待。乐观锁和悲观锁是并发控制中两种不同的策略,用于处理多个线程对共享资源的并发访问问题。总的来说,避免和预防死锁是一种权衡,需要在资源的有效利用和系统稳定性之间做出平衡。在实际系统中,可能会使用多种策略与技术相结合的方式来处理死锁。而乐观锁假设不会有冲突发生,在更新操作时进行冲突检测。只要这四个条件中的任意一个得不到满足,就不会发生死锁。一旦死锁已经发生,解除死锁可能会涉及到比较复杂的操作。
2024-09-05 10:02:04
601
原创 操作系统面试真题总结(六)
涉及的任务无法继续执行,因为每个任务都在等待其他任务释放资源,但是没有任务会释放它的资源,因为它们都在等待。这是因为CPU可以通过时间片轮转或其他任务切换策略,在各个任务之间快速切换,给人以它们在同时进行的错觉。这是一种非阻塞调用,也就是说,还没得到结果,就继续做别的事情,不会因为单一操作的等待而阻塞。这是一种阻塞调用,也就是说,进行某项操作的过程中,不得不停下来等待,直到这个操作完成。如果你的电脑有多个核心或处理器,你就可以在多个核心或处理器上同时执行多个线程,这是。,主要取决于具体的需求和场景。
2024-09-04 10:50:58
1056
原创 操作系统面试真题总结(五)
阻塞适用于需要确保结果完整性和依赖顺序的情况,而非阻塞适用于需要提高并发性和响应性的情况。非阻塞是指任务在等待某个操作完成时,不会暂停自己的执行,而是立即返回,继续执行其他任务。阻塞是指任务在等待某个操作完成时,暂停自己的执行,并等待操作完成后再继续执行。当线程切换发生时,操作系统会保存当前线程的上述上下文,加载目标线程的上下文。值得注意的是,线程切换是有性能开销的,因为涉及到保存和加载上下文的操作。而线程是进程内的执行单元,共享进程的内存空间和系统资源。在实际应用中,阻塞和非阻塞可以用在不同的场景中。
2024-09-03 09:51:55
753
原创 操作系统面试真题总结(四)
实际上,当一个进程创建大量线程时,由于每个线程都都需要一定的系统资源(例如,存储线程的上下文消息、栈空间等)进程的地址空间是指操作系统为每个进程分配的虚拟内存空间,用于存储进程的代码、数据和堆栈等信息。因此,实际上一个进程可以创建的线程数量是受限制的,需要考虑到系统的资源和限制因素。需要注意的是,进程的终止并不是立即发生的,而是通过信号通知进程终止。线程共享进程的内存空间和系统资源,每个线程有独立的程序计数器(在操作系统中,线程是进程的一部分,是进程内的一个执行单元。常用的调度算法有如下几个。
2024-09-02 09:48:22
658
1
原创 操作系统面试真题总结(三)
总之,僵尸进程是指在父进程没有及时回收子进程终止状态时,子进程成为已经结束但仍占用系统资源的状态。进程的创建是通过操作系统调度和管理的,当一个程序被执行时,操作系统会为其创建一个独立的进程。僵尸进程的主要原因是进程在结束执行后,父进程并没有及时处理子进程的终止状态信息。不同的方法可以根据实际情况进行选择和组合,以有效地处理过多的僵尸进程。因此,及时处理僵尸进程是非常重要的,可以采取合适的方法来清理僵尸进程。虽然僵尸进程本身无害,但过多的僵尸进程可能是不可取的。在操作系统中,进程是指正在执行的程序实例。
2024-09-01 10:20:46
1326
原创 操作系统面试真题总结(二)
通过使用虚拟内存,操作系统能够为每个进程提供独立的地址空间,使得进程之间相互隔离,更安全稳定。物理地址,又称实际地址或绝对地址,是数据在计算机系统中物理内存(RAM)的实际位置或者地址。在操作系统中,逻辑地址(有时也被称为虚拟地址),是在运行过程中的程序或进程所看到的地址。对于程序来说,它只需要对内存进行抽象的、逻辑的操作,不用关心具体数据在物理内存中的位置。在虚拟内存中,每个进程能够访问的内存空间大于实际物理内存的容量。栈主要存储局部变量和函数调用的信息,比如函数的返回地址和参数。
2024-08-31 11:39:30
1142
原创 操作系统面试真题总结(一)
硬件中断是由硬件设备触发的,比如外部设备的请求、时钟中断等。操作系统如果同意了这个请求,会在堆上找到一块合适的空闲空间,然后将其分配给程序。当函数执行结束后,对应的栈帧就会被释放,相关的存储空间将供后续的函数调用使用。每次调用这段代码的一个新实例时,都会有一个新的栈帧被创建以存储这次调用的信息。因此,划分用户态和内核态,为操作系统的运行提供了一个有序、可控和安全的环境。中断的作用是打断当前的程序执行流程,转而执行与中断事件相关的处理程序。(LIFO)的数据结构,也就是说最后进入的元素会被首先取出。
2024-08-29 10:02:25
1418
尚硅谷大数据技术之Kafka(笔记+代码+资料).rar
2019-05-28
尚硅谷大数据之Zookeeper视频(笔记+代码+资料)
2019-05-28
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人