自定义博客皮肤VIP专享

*博客头图:

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

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

博客底图:

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

栏目图:

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

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(53)
  • 问答 (1)
  • 收藏
  • 关注

原创 Reactor反应器模式

Reactor反应器模式是高性能网络编程在设计和架构层面的基础模式。为什么呢?只有彻底了解反应器的原理,才能真正构建好高性能的网络应用,才能轻松地学习和掌握Netty框架。同时,反应器模式也是BAT级别大公司必不可少的面试题。Reactor反应器模式为何如此重要在详细介绍什么是Reactor反应器模式之前,首先说明一下它的重要性。到目前为止,高性能网络编程都绕不开反应器模式。很多著名的服务器软件或者中间件都是基于反应器模式实现的。比如说,“全宇宙最有名的、最高性能”的Web服务器Nginx,就是基于

2021-02-22 17:50:47 425

原创 Java NIO通信基础详解

高性能的Java通信,绝对离不开Java NIO技术,现在主流的技术框架或中间件服务器,都使用了Java NIO技术,譬如Tomcat、Jetty、Netty。学习和掌握NIO技术,已经不是一项加分技能,而是一项必备技能。不管是面试,还是实际开发,作为Java的“攻城狮”(工程师的谐音),都必须掌握NIO的原理和开发实践技能。Java NIO简介在1.4版本之前,Java IO类库是阻塞IO;从1.4版本开始,引进了新的异步IO库,被称为Java New IO类库,简称为JAVA NIO。New IO

2021-02-21 18:18:41 326

原创 高并发IO的底层原理

IO的原理和模型是隐藏在编程知识底下的,是开发人员必须掌握的基础原理,是基础的基础,更是通关大公司面试的必备知识。本章从操作系统的底层原理入手,通过图文并茂的方式,为大家深入剖析高并发IO的底层原理,并介绍如何通过设置来让操作系统支持高并发。IO读写的基础原理大家知道,用户程序进行IO的读写,依赖于底层的IO读写,基本上会用到底层的read&write两大系统调用。在不同的操作系统中,IO读写的系统调用的名称可能不完全一样,但是基本功能是一样的。这里涉及一个基础的知识:read系统调用,并不是

2021-02-20 22:51:31 245

原创 Unix IO 模型

Unix IO 模型简介一个输入操作通常包括两个阶段:等待数据准备好从内核向进程复制数据对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所等待分组到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。Unix 下有五种 I/O 模型:阻塞式 I/O非阻塞式 I/OI/O 复用(select 和 poll)信号驱动式 I/O(SIGIO)异步 I/O(AIO)阻塞式 I/O应用进程被阻塞,直到数据复制到应用进程缓冲区中才返回。

2021-01-10 21:02:08 92

原创 IO常见类的使用

Java 的 I/O 大概可以分成以下几类:磁盘操作: File字节操作: InputStream 和 OutputStream字符操作: Reader 和 Writer对象操作: Serializable网络操作: SocketFile相关File 类可以用于表示文件和目录的信息,但是它不表示文件的内容。递归地列出一个目录下所有文件:public static void listAllFiles(File dir) { if (dir == null || !dir.exis

2021-01-10 20:45:07 142

原创 OutputStream

主要内容包括OutputStream及其部分子类,以分析源代码的方式学习。关心的问题包括:每个字节输出流的作用,各个流之间的主要区别,何时使用某个流,区分节点流和处理流,流的输出目标等问题。OutputStream的类树如下所示,其中,ObjectOutputStream和PipedOutputStream本文将不做讨论。java.io.OutputStream (implements java.io.Closeable, java.io.Flushable) java.io.ByteArra

2021-01-10 20:08:11 372

原创 InputStream

抽象类InputStreampublic abstract int read() // 读取数据public int read(byte b[]) // 将读取到的数据放在 byte 数组中,该方法实际上是根据下面的方法实现的,off 为 0,len 为数组的长度public int read(byte b[], int off, int len) // 从第 off 位置读取 len 长度字节的数据放到 byte 数组中,流是以 -1 来判断是否读取结束的public long ski

2021-01-10 19:40:08 548

原创 装饰模式

概述装饰模式可以在不改变一个对象本身功能的基础上给对象增加额外的新行为,在现实生活中,这种情况也到处存在,例如一张照片,我们可以不改变照片本身,给它增加一个相框,使得它具有防潮的功能,而且用户可以根据需要给它增加不同类型的相框,甚至可以在一个小相框的外面再套一个大相框。装饰模式是一种用于替代继承的技术,它通过一种无须定义子类的方式来给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系。在装饰模式中引入了装饰类,在装饰类中既可以调用待装饰的原有类的方法,还可以增加新的方法,以扩充原有类的功能。

2021-01-10 19:15:56 146

原创 IO分类

IO分类 - 从传输方式上从数据传输方式或者说是运输方式角度看,可以将 IO 类分为:字节流字符流字节是个计算机看的,字符才是给人看的字节流字符流字节流和字符流的区别字节流读取单个字节,字符流读取单个字符(一个字符根据编码的不同,对应的字节也不同,如 UTF-8 编码是 3 个字节,中文编码是 2 个字节。)字节流用来处理二进制文件(图片、MP3、视频文件),字符流用来处理文本文件(可以看做是特殊的二进制文件,使用了某种编码,人可以阅读)。简而言之,字节是个计算机看的,字

2021-01-10 18:52:49 561

原创 Java8其它更新

处理数值Java8添加了对无符号数的额外支持。Java中的数值总是有符号的,例如,让我们来观察Integer: int可表示最多2^32个数。Java中的数值默认为有符号的,最后一个二进制数字表示符号(0为正数,1为负数)。所以从十进制的0开始,最大的有符号正整数为2^31 - 1。你可以通过Integer.MAX_VALUE来访问它:System.out.println(Integer.MAX_VALUE); // 2147483647System.out.println(Integ

2021-01-08 20:52:01 99

原创 日期API

理解时间和日期库需要理解如下问题:Java8之前的Date有哪些槽点?Java8之前使用哪些常用的第三方时间库?Java8关于时间和日期有哪些类和方法,变比Java8之前它的特点是什么?其它语言时间库?Java8之前的Date有哪些槽点Tiago Fernandez做过一次投票,选举最烂的JAVA API,排第一的EJB2.X,第二的就是日期API。槽点一最开始的时候,Date既要承载日期信息,又要做日期之间的转换,还要做不同日期格式的显示,职责较繁杂(不懂单一职责,你妈妈知道吗? 纯属

2021-01-07 18:47:06 90

原创 移除Permgen

很多开发者都在其系统中见过“java.lang.OutOfMemoryError: PermGen space”这一问题。这往往是由类加载器相关的内存泄漏以及新类加载器的创建导致的,通常出现于代码热部署时。相对于正式产品,该问题在开发机上出现的频率更高,在产品中最常见的“问题”是默认值太低了。常用的解决方法是将其设置为256MB或更高。PermGen space简单介绍PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,说说为什么会内存益出.

2021-01-07 18:35:42 108

原创 JRE精简

理解Java8 JRE精简需理解几个问题:为什么精简Java8 JRE,及好处是啥?在不同平台上如何编译等?Oracle公司如期发布了Java 8正式版!没有让广大javaer失望。对于一个人来说,18岁是人生的转折点,从稚嫩走向成熟,法律意味着你是完全民事行为能力人,不再收益于未成年人保护法,到今年为止,java也走过了18年,java8是一个新的里程碑,带来了前所未有的诸多特性,lambda表达式,Stream API,新的Date time api,多核并发支持,重大安全问题改进等,相信ja

2021-01-07 18:23:26 294

原创 类型推断优化

理解Java 8 类型推断需理解几个问题:什么是泛型Java7对泛型推断做了哪些优化Java8对此有做了哪些优化简单理解泛型泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。通俗点讲就是“类型的变量”。这种类型变量可以用在类、接口和方法的创建中。理解Java泛型最简单的方法是把它看成一种便捷语法,能节省你某些Java类型转换(casting)上的操作:List<Apple> box = new ArrayList<A

2021-01-07 18:18:16 84

原创 重复注解

理解Java 8 重复注解需理解几个问题:Jdk8之前对重复注解是怎么做的?Jdk8对重复注解添加了什么支持?什么是重复注解允许在同一申明类型(类,属性,或方法)的多次使用同一个注解JDK8之前java 8之前也有重复使用注解的解决方案,但可读性不是很好,比如下面的代码:public @interface Authority { String role();}public @interface Authorities { Authority[] value();}

2021-01-07 18:10:53 189

原创 类型注解

理解Java 8 类型注解需理解几个问题:什么是类型注解?注解在JDK哪个版本中出现的,可以在哪些地方用注解?类型注解的作用是什么?为什么会出现类型注解(JSR308)?什么是类型注解注解大家都知道,从java5开始加入这一特性,发展到现在已然是遍地开花,在很多框架中得到了广泛的使用,用来简化程序中的配置。那充满争议的类型注解究竟是什么? 复杂还是便捷?在java 8之前,注解只能是在声明的地方所使用,比如类,方法,属性;java 8里面,注解可以应用在任何地方,比如://创建类实例

2021-01-07 18:08:04 165

原创 默认方法

理解Java 8 默认方法需理解几个问题:为什么会出现默认方法?接口中出现默认方法,且类可以实现多接口的,那和抽象类有啥区别?多重实现的默认方法冲突怎么办?Java 8 - 默认方法什么是默认方法,为什么要有默认方法 先上例子 什么是默认方法 为什么出现默认方法 java 8抽象类与接口对比 多重继承的冲突 举例子 总结 ¶ 什么是默认方法,为什么要有默认方法 ¶ 先上例子 一个接口A,Clazz类实现了接口A。 public interface A {default void foo(){

2021-01-07 17:57:46 216

原创 Optional类深度解析

身为一名Java程序员,大家可能都有这样的经历: 调用一个方法得到了返回值却不能直接将返回值作为参数去调用别的方法。我们首先要判断这个返回值是否为null,只有在非空的前提下才能将其作为其他方法的参数。这正是一些类似Guava的外部API试图解决的问题。一些JVM编程语言比如Scala、Ceylon等已经将对在核心API中解决了这个问题。新版本的Java,比如Java 8引入了一个新的Optional类。Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则i.

2021-01-05 19:54:46 2517

原创 函数编程(lambda表达式)

我们关心的是如何写出好代码,而不是符合函数编程风格的代码。简介在Java世界里面,面向对象还是主流思想,对于习惯了面向对象编程的开发者来说,抽象的概念并不陌生。面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象。 现实世界中,数据和行为并存,程序也是如此,因此这两种编程方式我们都得学。这种新的抽象方式还有其他好处。很多人不总是在编写性能优先的代码,对于这些人来说,函数式编程带来的好处尤为明显。程序员能编写出更容易阅读的代码——这种代码更多地表达了业务逻辑,而不是从机制上如何实现。易读的代码.

2021-01-05 19:34:49 686

原创 计划任务

自JDK 1.5 开始,JDK提供了ScheduledThreadPoolExecutor类用于计划任务(又称定时任务),这个类有两个用途:在给定的延迟之后运行任务周期性重复执行任务在这之前,是使用Timer类来完成定时任务的,但是Timer有缺陷:Timer是单线程模式;如果在执行任务期间某个TimerTask耗时较久,那么就会影响其它任务的调度;Timer的任务调度是基于绝对时间的,对系统时间敏感;Timer不会捕获执行TimerTask时所抛出的异常,由于Timer是单线程,所以一

2021-01-03 19:58:09 189

原创 Java 8 Stream并行计算原理

Java 8 Stream简介从Java 8 开始,我们可以使用Stream接口以及lambda表达式进行“流式计算”。它可以让我们对集合的操作更加简洁、更加可读、更加高效。Stream接口有非常多用于集合计算的方法,比如判空操作empty、过滤操作filter、求最max值、查找操作findFirst和findAny等等。Stream单线程串行计算Stream接口默认是使用串行的方式,也就是说在一个线程里执行。下面举一个例子:public class StreamDemo { publi

2021-01-03 19:57:33 638

原创 Fork/Join框架

什么是Fork/JoinFork/Join框架是一个实现了ExecutorService接口的多线程处理器,它专为那些可以通过递归分解成更细小的任务而设计,最大化的利用多核处理器来提高应用程序的性能。与其他ExecutorService相关的实现相同的是,Fork/Join框架会将任务分配给线程池中的线程。而与之不同的是,Fork/Join框架在执行任务时使用了工作窃取算法。fork在英文里有分叉的意思,join在英文里连接、结合的意思。顾名思义,fork就是要使一个大任务分解成若干个小任务,而joi

2021-01-03 19:56:19 186

原创 通信工具类

JDK中提供了一些工具类以供开发者使用。这样的话我们在遇到一些常见的应用场景时就可以使用这些工具类,而不用自己再重复造轮子了。它们都在java.util.concurrent包下。先总体概括一下都有哪些工具类,它们有什么作用,然后再分别介绍它们的主要使用方法和原理。类作用Semaphore限制线程的数量Exchanger两个线程交换数据CountDownLatch线程等待直到计数器减为0时开始工作CyclicBarrier作用跟CountDownLatch类

2021-01-03 19:55:19 99

原创 CopyOnWrite容器

什么是CopyOnWrite容器在说到CopyOnWrite容器之前我们先来谈谈什么是CopyOnWrite机制,CopyOnWrite是计算机设计领域中的一种优化策略,也是一种在并发场景下常用的设计思想——写入时复制思想。那什么是写入时复制思想呢?就是当有多个调用者同时去请求一个资源数据的时候,有一个调用者出于某些原因需要对当前的数据源进行修改,这个时候系统将会复制一个当前数据源的副本给调用者修改。CopyOnWrite容器即写时复制的容器,当我们往一个容器中添加元素的时候,不直接往容器中添加,而是

2021-01-03 19:53:56 120

原创 并发容器集合

同步容器与并发容器我们知道在java.util包下提供了一些容器类,而Vector和HashTable是线程安全的容器类,但是这些容器实现同步的方式是通过对方法加锁(sychronized)方式实现的,这样读写均需要锁操作,导致性能低下。而即使是Vector这样线程安全的类,在面对多线程下的复合操作的时候也是需要通过客户端加锁的方式保证原子性。如下面例子说明:public class TestVector { private Vector<String> vector; //方法一

2021-01-03 19:53:12 189 1

原创 锁接口和类

前面我们介绍了Java原生的锁——基于对象的锁,它一般是配合synchronized关键字来使用的。实际上,Java在java.util.concurrent.locks包下,还为我们提供了几个关于锁的类和接口。它们有更强大的功能或更高的性能。synchronized的不足之处我们先来看看synchronized有什么不足之处。如果临界区是只读操作,其实可以多线程一起执行,但使用synchronized的话,同一时间只能有一个线程执行。synchronized无法知道线程有没有成功获取到锁使用s

2021-01-03 19:51:51 120

原创 阻塞队列

阻塞队列的由来我们假设一种场景,生产者一直生产资源,消费者一直消费资源,资源存储在一个缓冲池中,生产者将生产的资源存进缓冲池中,消费者从缓冲池中拿到资源进行消费,这就是大名鼎鼎的生产者-消费者模式。该模式能够简化开发过程,一方面消除了生产者类与消费者类之间的代码依赖性,另一方面将生产数据的过程与使用数据的过程解耦简化负载。我们自己coding实现这个模式的时候,因为需要让多个线程操作共享变量(即资源),所以很容易引发线程安全问题,造成重复消费和死锁,尤其是生产者和消费者存在多个的情况。另外,当缓冲池空

2021-01-03 19:50:38 146

原创 线程池原理

为什么要使用线程池使用线程池主要有以下三个原因:创建/销毁线程需要消耗系统资源,线程池可以复用已创建的线程。控制并发的数量。并发数量过多,可能会导致资源消耗过多,从而造成服务器崩溃。(主要原因)可以对线程做统一管理。线程池的原理Java中的线程池顶层接口是Executor接口,ThreadPoolExecutor是这个接口的实现类。我们先看看ThreadPoolExecutor类。ThreadPoolExecutor提供的构造方法一共有四个构造方法:// 五个参数的构造函数publ

2021-01-03 19:47:48 295 1

原创 AQS

AQS简介AQS是AbstractQueuedSynchronizer的简称,即抽象队列同步器,从字面意思上理解:抽象:抽象类,只实现一些主要逻辑,有些方法由子类实现;队列:使用先进先出(FIFO)队列存储数据;同步:实现了同步的功能。那AQS有什么用呢?AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的同步器,比如我们提到的ReentrantLock,Semaphore,ReentrantReadWriteLock,SynchronousQueue,FutureT

2021-01-03 19:46:09 68

原创 CAS与原子操作

乐观锁与悲观锁的概念锁可以从不同的角度分类。其中,乐观锁和悲观锁是一种分类方式。悲观锁悲观锁就是我们常说的锁。对于悲观锁来说,它总是认为每次访问共享资源时会发生冲突,所以必须对每次数据操作加上锁,以保证临界区的程序同一时间只能有一个线程在执行。乐观锁乐观锁又称为“无锁”,顾名思义,它是乐观派。乐观锁总是假设对共享资源的访问没有冲突,线程可以不停地执行,无需加锁也无需等待。而一旦多个线程发生冲突,乐观锁通常是使用一种称为CAS的技术来保证线程执行的安全性。由于无锁操作中没有锁的存在,因此不可能出现

2021-01-02 22:42:02 146

原创 synchronized与锁

这篇文章我们来聊一聊Java多线程里面的“锁”。首先需要明确的一点是:Java多线程的锁都是基于对象的,Java中的每一个对象都可以作为一个锁。还有一点需要注意的是,我们常听到的类锁其实也是对象锁。每个Java类在JVM中只有一个Class对象(可以有多个实例对象,多个实例共享这个Class对象),而Class对象也是特殊的Java对象。所以我们常说的类锁,其实就是Class对象的锁。Synchronized关键字说到锁,我们通常会谈到synchronized这个关键字。它翻译成中文就是“同步”

2021-01-02 22:32:06 110

原创 volatile

几个基本概念在介绍volatile之前,我们先回顾及介绍几个基本的概念。内存可见性在Java内存模型那一章我们介绍了JMM有一个主内存,每个线程有自己私有的工作内存,工作内存中保存了一些变量在主内存的拷贝。内存可见性,指的是线程之间的可见性,当一个线程修改了共享变量时,另一个线程可以读取到这个修改后的值。重排序为优化程序性能,对原有的指令执行顺序进行优化重新排序。重排序可能发生在多个阶段,比如编译重排序、CPU重排序等。happens-before规则是一个给程序员使用的规则,只要程序员在写

2021-01-02 22:20:21 135

原创 重排序与happens-before

什么是重排序?计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排。为什么指令重排序可以提高性能?简单地说,每一个指令都会包含多个步骤,每个步骤可能使用不同的硬件。因此,流水线技术产生了,它的原理是指令1还没有执行完,就可以开始执行指令2,而不用等到指令1执行结束之后再执行指令2,这样就大大提高了效率。但是,流水线技术最害怕中断,恢复中断的代价是比较大的,所以我们要想尽办法不让流水线中断。指令重排就是减少中断的一种技术。我们分析一下下面这个代码的执行情况:a = b + c;d

2021-01-02 22:08:06 125

原创 Java内存模型并发相关知识

并发编程模型的两个关键问题线程间如何通信?即:线程之间以何种机制来交换信息线程间如何同步?即:线程以何种机制来控制不同线程间操作发生的相对顺序有两种并发模型可以解决这两个问题:消息传递并发模型共享内存并发模型这两种模型之间的区别如下表所示:在Java中,使用的是共享内存并发模型。Java内存模型的抽象结构运行时内存的划分先谈一下运行时数据区,下面这张图相信大家一点都不陌生:对于每一个线程来说,栈都是私有的,而堆是共有的。也就是说在栈中的变量(局部变量、方法定义参数、异常处

2021-01-02 21:53:50 68

转载 并发编程模型

并发系统可以采用多种并发编程模型来实现。并发模型指定了系统中的线程如何通过协作来完成分配给它们的作业。不同的并发模型采用不同的方式拆分作业,同时线程间的协作和交互方式也不相同。这篇并发模型教程将会较深入地介绍目前(2015年,本文撰写时间)比较流行的几种并发模型。并发模型与分布式系统之间的相似性本文所描述的并发模型类似于分布式系统中使用的很多体系结构。在并发系统中线程之间可以相互通信。在分布式系统中进程之间也可以相互通信(进程有可能在不同的机器中)。线程和进程之间具有很多相似的特性。这也就是为什么很

2021-01-02 16:20:30 117

原创 Java线程间的通信

合理的使用Java多线程可以更好地利用服务器资源。一般来讲,线程内部有自己私有的线程上下文,互不干扰。但是当我们需要多个线程之间相互协作的时候,就需要我们掌握Java线程的通信方式。本文将介绍Java线程之间的几种通信原理。锁与同步在Java中,锁的概念都是基于对象的,所以我们又经常称它为对象锁。线程和锁的关系,我们可以用婚姻关系来理解。一个锁同一时间只能被一个线程持有。也就是说,一个锁如果和一个线程“持有”(结婚),那其他线程如果需要得到这个锁,就得等这个线程和这个锁“释放”(离婚)。在我们的线

2021-01-02 14:09:24 125

原创 线程组和线程优先级

线程组(ThreadGroup)Java中用ThreadGroup来表示线程组,我们可以使用线程组对线程进行批量控制。ThreadGroup和Thread的关系就如同他们的字面意思一样简单粗暴,每个Thread必然存在于一个ThreadGroup中,Thread不能独立于ThreadGroup存在。执行main()方法线程的名字是main,如果在new Thread时没有显式指定,那么默认将父线程(当前执行new Thread的线程)线程组设置为自己的线程组。示例代码:public class D

2021-01-02 13:56:26 116

原创 Java线程的状态及主要转化方法

操作系统中的线程状态转换首先我们来看看操作系统中的线程状态转换。在现在的操作系统中,线程是被视为轻量级进程的,所以操作系统线程的状态其实和操作系统进程的状态是一致的。操作系统线程主要有以下三个状态:就绪状态(ready):线程正在等待使用CPU,经调度程序调用之后可进入running状态。执行状态(running):线程正在使用CPU。等待状态(waiting): 线程经过等待事件的调用或者正在等待其他资源(如I/O)。Java线程的6个状态// Thread.State 源码p

2021-01-02 13:39:23 90

原创 Java多线程入门-类和接口

Thread类和Runnable接口上一章我们了解了操作系统中多线程的基本概念。那么在Java中,我们是如何使用多线程的呢?首先我们需要有一个线程类。JDK提供了Thread类和Runnable接口来让我们实现自己的“线程”类。因此我们有两种方式来使用多线程:继承Thread类,并重写run方法;实现Runnable接口的run方法;继承Thread类先学会怎么用,再学原理。首先我们来看看怎么用Thread和Runnable来写一个Java多线程程序。首先是继承Thread类:publ

2021-01-02 00:22:56 101

原创 进程与线程的基本概念

进程产生的背景最初的计算机只能接受一些特定的指令,用户每输入一个指令,计算机就做出一个操作。当用户在思考或者输入时,计算机就在等待。这样效率非常低下,在很多时候,计算机都处在等待状态。批处理操作系统后来有了批处理操作系统,把一系列需要操作的指令写下来,形成一个清单,一次性交给计算机。用户将多个需要执行的程序写在磁带上,然后交由计算机去读取并逐个执行这些程序,并将输出结果写在另一个磁带上。批处理操作系统在一定程度上提高了计算机的效率,但是由于批处理操作系统的指令运行方式仍然是串行的,内存中始终只有一个

2021-01-01 23:58:41 103

空空如也

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

TA关注的人

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