- 博客(38)
- 收藏
- 关注
原创 栈虚拟机和寄存器虚拟机,有什么不同?
外加基于栈的实现更简单(无论是在源码编译器的一侧还是在虚拟机的一侧),而且主要设计者 James Gosling 的个人经验上也对这种做法非常熟悉(例如他之前实现过 PostScript 的虚拟机,也是基于栈的指令集),所以就选择了基于栈。本身也是一种栈结构,用于支持虚拟机进行方法调用和方法执行,遵循 LIFO 的原则,每个栈帧都包含了一个方法的运行信息,每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈和出栈的过程。当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的。
2024-11-01 16:32:39
1341
原创 从javap的角度轻松看懂字节码
那为什么 stack 的值为 2 呢?当我们初学编程的时候,特别想多学一点,属于横向扩展,当有了一定的编程经验后,想更上一层楼,就需要纵向扩展,不断深入地学,连根拔起,从而形成自己的知识体系。初学者一开始接触字节码的时候会感觉比较头大,没关系,我当初也是这样,随着时间的推移,经验的积累,慢慢就好了,越往深处钻,就越能体会到那种“技术我有,雄霸天下”的感觉~当前字节码文件中一共有 21 个常量,它们之间是有链接的,逐个分析会比较乱,我们采用顺藤摸瓜的方式,从上依次往下看,那些被链接的常量我们就点到为止。
2024-11-01 16:28:33
988
原创 详解Java的类文件结构(.class文件的结构)
通过 jclasslib 看一下它当中一个很重要的属性——Code, 方法的关键信息都存储在里面。1)对于基本数据类型来说,使用一个字符来表示,比如说 I 对应的是 int,B 对应的是 byte。在Java类文件中,常量池是一个索引表,它从索引值1开始计数,每个条目都有一个唯一的索引。评论区有读者问到:“怎么通过索引值,定位到在class 文件中的位置,这个是咋算的?方法表和字段表类似,区别是用来存储方法的信息,包括方法名,方法的参数,方法的签名。我画了一副图,可以完整的表示字段的结构,包含属性表在内。
2024-10-23 15:28:26
1158
原创 一文彻底搞懂 Java 类加载机制
在这个模型中,类加载器在尝试加载一个类时,首先会委派给其父加载器去尝试加载这个类,只有在父加载器无法加载该类时,子加载器才会尝试自己去加载。:如果一个类加载器收到了加载类的请求,它会先把请求委托给上层加载器去完成,上层加载器又会委托上上层加载器,一直到最顶层的类加载器;如果上层加载器无法完成类的加载工作时,当前类加载器才会尝试自己去加载这个类。上文中曾提到,如果两个类的加载器不同,即使两个类来源于同一个字节码文件,那这两个类就必定不相等——双亲委派模型能够保证同一个类最终会被特定的类加载器加载。
2024-10-23 15:25:45
1050
原创 VM到底是如何运行Java代码的?3000 字 10 张手绘图带你彻底掌握。
7、当 calculate 方法执行完成后,对应的栈帧将从虚拟机栈中弹出,方法执行的结果会被压入 main 栈帧中的操作数栈中,而方法返回地址被重置到 main 线程的 PC 寄存器中,以便于后续字节码执行引擎从 PC 寄存器中获取下一条命令的地址。8、执行引擎中的解释器会从程序计数器中获取下一个字节码指令的地址,也就是元空间中对应的字节码指令,在获取到指令之后,通过解释器解释为对应的机器指令,最终由 CPU 进行执行。并不是这样的,它包含了 JVM 执行的指令,还有类的元数据信息,如类名、方法和属性等。
2024-10-23 15:24:51
783
原创 大白话+手绘图带你认识 JVM,JVM到底是什么?
执行引擎(Execution Engine)就好像明朝时期的六部,主要用来干具体的事,“虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的,而。虚拟机,顾名思义,就是虚拟的机器(多苍白的解释),反正就是看不见摸不着的机器,一个相对物理机的叫法,你把它想象成一个会执行字节码的怪兽吧。类加载器是 JVM 最有权威的一个部门,相当于明朝张居正时期的内阁,大全独揽,朝廷想干什么,都得经过我这关。,也就是 .class 文件。
2024-10-23 15:23:08
623
原创 Java网络编程的基础:计算机网络
TCP 协议是建立在 IP 协议之上的,简单地说,IP 协议只负责发数据包,不保证顺序和正确性,而 TCP 协议负责控制数据包传输,它在传输数据之前需要先建立连接,建立连接后才能传输数据,传输完后还需要断开连接。每台计算机都需要正确配置 IP 地址和子网掩码,根据这两个就可以计算网络号,如果两台计算机计算出的网络号相同,说明两台计算机在同一个网络,可以直接通信。如果两台计算机位于同一个网络,那么他们之间可以直接通信,因为他们的 IP 地址前段是相同的,也就是网络号是相同的。
2024-10-17 10:24:54
1334
原创 Java 8 Stream流:掌握流式编程的精髓
方法接收的是一个 Predicate(Java 8 新增的一个函数式接口,接受一个输入参数返回一个布尔值结果)类型的参数,因此,我们可以直接将一个 Lambda 表达式传递给该方法,比如说。方法接收的是一个 Function(Java 8 新增的一个函数式接口,接受一个输入参数 T,返回一个结果 R)类型的参数,此时参数 为 String 类的 length 方法,也就是把。方法接收的是一个 Consumer(Java 8 新增的一个函数式接口,接受一个输入参数并且无返回的操作)类型的参数,
2024-10-17 10:21:57
619
原创 深入浅出Java 8 Lambda表达式
既能让编译器不发出警告,又能修改变量的值。大致的意思就是说,Lambda 表达式中要用到的,但又未在 Lambda 表达式中声明的变量,必须声明为 final 或者是 effectively final,否则就会出现编译错误。和匿名内部类一样,不要在 Lambda 表达式主体内对方法内的局部变量进行修改,否则编译也不会通过:Lambda 表达式中使用的变量必须是 final 的。使用数组的方式略带一些欺骗的性质,在声明数组的时候设置为 final,但更改 int 的值时却修改的是数组的一个元素。
2024-10-17 10:20:26
650
原创 Apache StringUtils:专为Java字符串而生的工具类
使用 StringUtils 的 split 方法会返回 null,而使用 String 的 split 方法会报指针异常。StringUtils 提供了非常多实用的方法,大概有下图的四页到五页,我只截了两页,实在是太多了。如果只用 String 类提供的那些方法,我们需要手写大量的额外代码,不然容易出现各种异常。其实空字符串,不只是 null 一种,还有""," ","null"等等,多种情况。有时候,我们需要将某个集合的内容,拼接成一个字符串,然后输出,这时可以使用。、使用正则表达式等等。
2024-10-15 10:16:06
984
原创 Hutool:国产良心工具包,让你的Java变得更甜
同时呢,成熟的开源库也可以最大限度的避免封装不完善带来的 bug。在 Java 中,对文件、文件夹打包压缩是一件很繁琐的事情,Hutool 封装的 ZipUtil 针对 java.util.zip 包做了优化,可以使用一个方法搞定压缩和解压,并且自动处理文件和目录的问题,不再需要用户判断,大大简化的压缩解压的复杂度。中提供了一种特殊的 Map 结构,叫做 BiMap,实现了一种双向查找的功能,可以根据 key 查找 value,也可以根据 value 查找 key,Hutool 也提供这种 Map 结构。
2024-10-15 10:11:56
2448
1
原创 一文彻底搞懂Java异常处理,YYDS
Error 的出现,意味着程序出现了严重的问题,而这些问题不应该再交给 Java 的异常处理机制来处理,程序应该直接崩溃掉,比如说 OutOfMemoryError,内存溢出了,这就意味着程序在运行时申请的内存大于系统能够提供的内存,导致出现的错误,这种错误的出现,对于程序来说是致命的。方法,该方法会将异常的堆栈信息打印到标准的控制台下,如果是测试环境,这样的写法还 OK,如果是生产环境,这样的写法是不可取的,必须使用日志框架把异常的堆栈信息输出到日志系统中,否则可能没办法跟踪。
2024-09-26 11:00:26
763
原创 招银面试官:说说 Java transient 关键字吧
在 Java 中,对象的序列化可以通过实现两种接口来实现,如果实现的是 Serializable 接口,则所有的序列化将会自动进行,如果实现的是 Externalizable 接口,则需要在 writeExternal 方法中指定要序列化的字段,与 transient 关键字修饰无关。在实际开发过程中,我们常常会遇到这样的问题,一个类的有些字段需要序列化,有些字段不需要,比如说用户的一些敏感信息(如密码、银行卡号等),为了安全起见,不希望在网络操作中传输或者持久化到磁盘文件中,那这些字段就可以加上。
2024-09-26 09:50:59
1215
原创 Java Serializable 接口:明明就一个空的接口嘛
异常堆栈信息里面告诉我们,本地的序列化 ID 为 -3818877437117647968,和持久化文件里面读取到的序列化 ID 仍然不一致,无法反序列化。接口之所以定义为空,是因为它只起到了一个标识的作用,告诉程序实现了它的对象是可以被序列化的,但真正序列化和反序列化的操作并不需要它来完成。进行反序列化的时候,会调用被序列化类的无参构造方法去创建一个新的对象,然后再将被保存对象的字段值复制过去。的中文字义为“临时的”(论英语的重要性),它可以阻止字段被序列化到文件中,在被反序列化后,
2024-09-25 17:18:35
1369
原创 Java 序列流:Java 对象的序列化和反序列化
方法,该方法是 ObjectOutputStream 类中用于将对象序列化成字节序列并输出到输出流中的方法,可以处理对象之间的引用关系、继承关系、静态字段和 transient 字段。Kryo 是一个优秀的 Java 序列化和反序列化库,具有高性能、高效率和易于使用和扩展等特点,有效地解决了 JDK 自带的序列化机制的痛点。该构造方法接收一个 OutputStream 对象作为参数,用于将序列化后的字节序列输出到指定的输出流中。反序列化是指将一个字节序列转换为一个对象,以便在程序中使用。
2024-09-25 17:13:08
761
原创 Java 转换流:Java 字节流和字符流的桥梁
通常用于解决字节流和字符流之间的转换问题,可以将字节流以指定的字符集编码方式转换为字符流,或者将字符流以指定的字符集编码方式转换为字节流。GBK 编码是一种变长的编码方式,对于 ASCII 字符(码位范围为 0x00 到 0x7F),使用一个字节表示,对于其他字符,使用两个字节表示。为了避免与 ASCII 码冲突,GBK 编码的第一个字节采用了 0x81 到 0xFE 之间除了 0x7F 的所有值,第二个字节采用了 0x40 到 0x7E 和 0x80 到 0xFE 之间的所有值,共 94 个值。
2024-09-25 17:10:55
1310
原创 Java 缓冲流:Java IO 的读写效率有了质的飞升
0xff 是一个十六进制的数,相当于二进制的 11111111,& 运算符的意思是:如果两个操作数的对应位为 1,则输出 1,否则为 0;级联问题(Cascade Problem)是指在一组缓冲流(Buffered Stream)中,由于缓冲区的大小不足以容纳要写入的数据,导致数据被分割成多个部分,并分别写入到不同的缓冲区中,最终需要逐个刷新缓冲区,从而导致性能下降的问题。其次,如果写入的字节数小于缓冲区长度,则检查缓冲区中剩余的空间是否足够容纳要写入的字节数,如果不够,则先将缓冲区中的数据刷新到磁盘中。
2024-09-25 17:05:38
888
原创 Java 字符流:Reader和Writer的故事
其中,ASCII 和 ISO-8859-1 只能表示部分字符,而 UTF-8 和 UTF-16 可以表示所有的 Unicode 字符,包括中文字符。如果我们使用了错误的字符编码,或者在读取和写入数据时没有正确处理字符编码的转换,就会导致读取出来的中文字符出现乱码。这个方法是清空缓存的意思,用于清空缓冲区的数据流,进行流的操作时,数据先被读到内存中,然后再把数据写到文件中。方法,将指定字符数组的一部分写入输出流。方法,每次可以读取一个字符,返回读取的字符(转为 int 类型),当读取到文件末尾时,返回。
2024-09-23 10:52:52
1177
原创 Java 字节流:Java IO 的基石
0111 1000 是一个8位的二进制数,它对应的十进制数是 120,对应的 ASCII 码字符是小写字母 "x"。然后,使用 while 循环逐个读取原始图片文件中的字节,并将其写入复制后的图片文件中。例如,如果参数 b 的值为 -1,那么它会被截断为 255,如果参数 b 的值为 256,那么它会被截断为 0。我们必须得明确一点,一切文件(文本、视频、图片)的数据都是以二进制的形式存储的,传输时也是。在上面的代码示例中,每次运行程序都会创建新的输出流对象,于是文件中的数据也会被清空。
2024-09-19 15:40:51
1058
原创 Java File:IO 流的起点与终点
在 IO 操作中,文件的操作相对来说是比较复杂的,但也是使用频率最高的部分,我们几乎所有的项目中几乎都躺着一个叫做 FileUtil 或者 FileUtils 的工具类。类是专门对文件进行操作的类,注意只能对文件本身进行操作,不能对文件内容进行操作,想要操作内容,必须借助输入输出流。该方法可以删除指定的文件或目录,如果指定的文件或目录不存在,则会抛出异常。更多方法,可以去看一下 hutool 的源码,里面有非常多实用的方法,多看看,绝对能提升不少编程水平。该方法可以将指定的文件或目录重命名为指定的新名称。
2024-09-19 15:39:46
896
原创 6000 字掌握 Java IO 知识体系
想想也是,他当初学习 Java IO 的时候头也大,乌央乌央的一片,全是类,估计是所有 Java 包里面类最多的,一会是 Input 一会是 Output,一会是 Reader 一会是 Writer,真不知道 Java 的设计者是怎么想的。Java 中的管道和 Unix/Linux 中的管道不同,在 Unix/Linux 中,不同的进程之间可以通过管道来通信,但 Java 中,通信的双方必须在同一个进程中,也就是在同一个 JVM 中,管道为线程之间的通信提供了通信能力。
2024-09-18 10:15:46
975
原创 Java Comparable和Comparator的区别
方法,该方法的返回值可能为负数,零或者正数,代表的意思是该对象按照排序的规则小于、等于或者大于要比较的对象。孙悟空自己实现了 Comparable 接口(他那年代也没有飞机和高铁,没得选),而我可以借助 Comparator 接口(现代化的交通工具)。Comparator 接口的定义相比较于 Comparable 就复杂的多了,不过,核心的方法只有两个,来看一下源码。有时候,我们想让类保持它的原貌,不想主动实现 Comparable 接口,但我们又需要它们之间进行比较,该怎么办呢?和我们的预期完全符合。
2024-09-18 09:54:00
510
原创 Java WeakHashMap详解(附源码分析)
为什么没有使用看似更好的通知呢,我想是因为在Java中没有一个可靠的通知回调,比如大家常说的finalize方法,其实也不是标准的,不同的JVM可以实现不同,甚至是不调用这个方法。而WeakHashMap采用的是轮询的形式,在其put/get/size等方法调用的时候都会预先调用一个poll的方法,来检查并删除失效的Entry。在Javadoc中关于WeakHashMap有这样的描述,当key不再引用时,其对应的key/value也会被移除。在开始WeakHashMap之前,我们先要对弱引用有一定的了解。
2024-09-18 09:52:13
1208
原创 Java迭代器Iterator和Iterable有什么区别?
该方法的实现方式是使用 for-each 循环遍历集合中的元素,对于每个元素,调用 Consumer 对象的 accept 方法执行指定的操作。原则上,只要一个 List 实现了 Iterable 接口,那么它就可以使用 for-each 这种方式来遍历,那具体该怎么遍历,还是要看它自己是怎么实现 Iterator 接口的。第一种我们略过,第二种用的是 Iterator,第三种看起来是 for-each,其实背后也是 Iterator,看一下反编译后的代码(如下所示)就明白了。
2024-09-18 09:49:01
1118
原创 ArrayList和LinkedList的区别:如何选择?
方法,先将 succ 的前一个节点(prev)存放到临时变量 pred 中,然后生成新的 Node 节点(newNode),并将 succ 的前一个节点变更为 newNode,如果 pred 为 null,说明插入的是队头,所以 first 为新节点;大家都知道,数组是定长的,就是说,数组一旦声明了,长度(容量)就是固定的,不能像某些东西一样伸缩自如。比如说,默认的数组大小是 10,当添加第 11 个元素的时候,数组的长度扩容了 1.5 倍,也就是 15,意味着还有 4 个内存空间是闲置的,对吧?
2024-09-18 09:46:51
1430
原创 10 张手绘图详解Java 优先级队列PriorityQueue
PriorityQueue 是 Java 中的一个基于优先级堆的优先队列实现,它能够在 O(log n) 的时间复杂度内实现元素的插入和删除操作,并且能够自动维护队列中元素的优先级顺序。通俗来说,PriorityQueue 就是一个队列,但是它不是先进先出的,而是按照元素优先级进行排序的。当你往 PriorityQueue 中插入一个元素时,它会自动根据元素的优先级将其插入到合适的位置。当你从 PriorityQueue 中删除一个元素时,它会自动将优先级最高的元素出队。
2024-09-14 11:23:43
1014
原创 详解 Java 中的双端队列(ArrayDeque附源码分析)
因此,在使用 LinkedList 时,需要频繁进行内存分配和释放,而 ArrayDeque 在创建时就一次性分配了连续的内存空间,不需要频繁进行内存分配和释放,这样可以更好地利用 CPU 缓存,提高访问效率。不过,ArrayDeque 的扩容策略(当 ArrayDeque 中的元素数量达到数组容量时,就需要进行扩容操作,扩容时会将数组容量扩大为原来的两倍)可以在一定程度上减少数组复制的次数和时间消耗,同时保证 ArrayDeque 的性能和空间利用率。扩容操作的时间复杂度为 O(n)。
2024-09-14 11:04:18
1026
2
原创 时间复杂度,评估ArrayList和LinkedList的执行效率
对,一段代码的执行时间 T(n) 和总的执行次数成正比,也就是说,代码执行的次数越多,花费的时间就越多。来说,影响时间复杂度的主要是第 2 行代码,其余的,像系数 2、常数 2 都是可以忽略不计的,我们只关心影响最大的那个,所以时间复杂度就表示为。括号中的 1 可以是 3,可以是 5,可以 100,我们习惯用 1 来表示,表示这段代码的执行时间是一个常数级别。这也就是大 O 表示法,它不关心代码具体的执行时间是多少,它关心的是代码执行时间的变化趋势,这也就是时间复杂度这个概念的由来。再举一个简单的例子。
2024-09-12 10:36:07
622
原创 Java HashMap详解:源码分析、hash 原理、扩容机制、加载因子、线程不安全
这篇文章将会详细透彻地讲清楚 Java 的 HashMap,包括 hash 方法的原理、HashMap 的扩容机制、HashMap 的加载因子为什么是 0.75 而不是 0.6、0.8,以及 HashMap 为什么是线程不安全的,基本上 HashMap 的。HashMap 的实现原理是基于哈希表的,它的底层是一个数组,数组的每个位置可能是一个链表或红黑树,也可能只是一个键值对(后面会讲)。这个我们后面会讲,先简单说一下,是因为 HashMap 的键是唯一的,所以再次 put 的时候会覆盖掉之前的键值对。
2024-09-12 10:30:05
1324
原创 栈栈栈栈栈栈栈栈栈栈栈栈栈栈栈栈栈栈,Stack 没人要了!
如果我们想要拿到最下面的盘子,就必须把它上面的所有盘子都拿走,像这样的一个操作,我们称之为后进先出,也就是“Last In First Out”(简称 LIFO)——最后的一个进的,最先出去。:浏览器的后退按钮会把我们访问的 URL 压入一个栈中,每次我们访问一个新的页面,新的 URL 就压入了栈的顶部,当我们点了后退按钮,最新的那个 URL 就从栈中移除,之前的那个 URL 就被访问到了。的,所以反转一串字符很容易,按照正常的顺序把字符压入栈中,然后再弹出来就行了。把元素 3 压入栈中的时候,
2024-09-11 11:18:18
555
原创 LinkedList控诉:我爹都嫌弃我!
LRU 缓存淘汰算法是一种常用的缓存淘汰策略,它的基本思想是,当缓存空间不够时,优先淘汰最近最少使用的缓存数据。在实现 LRU 缓存淘汰算法时,你可以使用我 LinkedList 来存储缓存数据,每次访问缓存数据时,将该数据从链表中删除并移动到链表的头部,这样链表的尾部就是最近最少使用的缓存数据,当缓存空间不够时,只需要将链表尾部的缓存数据淘汰即可。师父不忍心看到师兄这样痛苦,于是打我进入师门那一天,就强迫我练链表这门内功,一开始我很不理解,害怕师父偏心,不把师门最厉害的内功教我。
2024-09-11 10:04:09
586
原创 如何阅读《深入理解计算机系统》这本书?
只要我一直在进步,一直在学习,就能追赶上这个世界的浪潮,作出应有的贡献,其一就是帮助大家一起成长。所以,给大家一个建议,学习,不要怕自己看不懂学不会,因为对于大部分普通人来说,包括二哥在内,接收知识的过程都是一个由易到难、循序渐进的过程,没办法一蹴而就。首先,我必须得承认一点,这个视频的唯一缺点就是麦克风的噪音比较大,原因我想有两个,第一可能是大佬没有买声卡这种设备,第二就是没有对声音做降噪处理。就像 CSAPP 这门课,确实经典,确实牛逼,但当你只是一个编程初学者的时候,尽量先不去碰它,免得被劝退。
2024-09-10 17:46:40
1016
原创 Java集合框架全面解析
HashMap 实现了 Map 接口,可以根据键快速地查找对应的值——通过哈希函数将键映射到哈希表中的一个索引位置,从而实现快速访问。来简单看一下它的源码。HashSet 主要用于去重,比如,我们需要统计一篇文章中有多少个不重复的单词,就可以使用 HashSet 来实现。Set 的特点是存取无序,不可以存放重复的元素,不可以用下标对元素进行操作,和 List 有很多不同。List 的特点是存取有序,可以存放重复的元素,可以用下标对元素进行操作。Map 保存的是键值对,键要求保持唯一性,值可以重复。
2024-09-10 17:36:08
1153
原创 聊聊Java ArrayList,扩容机制了解吗?
数组的大小是固定的,一旦创建的时候指定了大小,就不能再调整了。Java 这门编程语言和别的编程语言,比如说 C语言的不同之处就在这里,如果是 C语言的话,你就必须得动手实现自己的 ArrayList,原生的库函数里面是没有的。DEFAULT_CAPACITY 为 10(见下面的代码),所以执行完这行代码后,minCapacity 为 10,参数 e 为要添加的元素,此时的值为“韩子谦”,size 为 ArrayList 的长度,此时为 0。这样做的好处是,可以有效地避免在添加新的元素时进行不必要的扩容。
2024-09-10 17:27:49
1721
原创 JAVA 线程详细讲解
System.out.println(hread.currentThread().getName() + "线程的默认优先级是:" + currentThread.getPriority());对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给别人的感觉是:多个事情同时在做!(重点:★★★★★)以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。//t.run();
2024-08-22 14:20:44
650
原创 Java后端返回树型数据
1. 树结构的节点,设置children属性,可嵌套package com.jiangxb.test.util.tree;import java.util.ArrayList;import java.util.List;/** * @author jiangxiangbo * @date 2020/8/19 * @Description: 树结构实体 */public class TreeItem { private String id; private String parentI
2024-08-22 14:14:55
538
原创 MybatisPlus如何进行批量查询,以及分页查询,条件查询
System.out.println("是否有上一页hasPrevious=" + hasPrevious);System.out.println("当前页数据集合records=" + records);System.out.println("是否有下一页hasNext=" + hasNext);System.out.println("当前页current=" + current);System.out.println("总记录数total=" + total);// 获取当前页数据集合。
2024-06-07 09:20:47
2332
原创 Injection of resource dependencies faild
额外知识:@Service注解是标注在实现类上的,因为@Service是把spring容器中的bean进行实例化,相当于new操作,只有实现类是可以进行new实例化的,接口不能。XXXServiceImpl.java层加上注解@Service。
2024-01-10 15:02:36
780
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人