八股整理-JavaSE部分
本文最后更新于274 天前,其中的信息可能已经过时,如有错误请发送邮件到15578243672@163.com

.

1.Java语言有什么特点

  • 面向对象与过程
  • 平台无关性,其关键在于JVM在不同平台上将字节码翻译成不同的机械码
  • 支持多线程
  • 支持 JIT 编译,也就是即时编译器,它可以在程序运行时将字节码转换为热点机器码来提高程序的运行速度

2.JVM,JRE,JDK之间有什么区别

  • JVM是Java虚拟机,负责将编译好的字节码文件转换成对应平台的机械码,也就实现了JAVA的跨平台特点
  • JRE是 Java 运行时环境,包含了运行 Java 程序所必需的库,以及 JVM。
  • JDK包括 JRE,编译器 JAVAC、JAVA文档生成工具 JAVADOC、JAVA 字节码工具 JAVAP 等。为开发者提供了开发、编译、调试 Java 程序的一整套环境。

3.说说什么是跨平台?原理是什么

所谓的跨平台,是指 Java 语言编写的程序,一次编译后,可以在多个操作系统上运行。

原理是增加了一个中间件 JVM,JVM 负责将 Java 字节码转换为特定平台的机器码,并执行

4.什么是字节码?采用字节码的好处是什么?

所谓的字节码,就是 Java 程序经过编译后产生的 .class 文件。

Java 程序从源代码到运行需要经过三步:

  • 编译:将源代码文件 .java 编译成 JVM 可以识别的字节码文件 .class
  • 解释:JVM 执行字节码文件,将字节码翻译成操作系统能识别的机器码
  • 执行:操作系统执行二进制的机器码

采用字节码文件使得JAVA程序能够Write once,Run anyway

5.为什么有人说 Java 是“编译与解释并存”的语言?

编译型语言是指编译器针对特定的操作系统,将源代码一次性翻译成可被该平台执行的机器码。

解释型语言是指解释器对源代码进行逐行解释,解释成特定平台的机器码并执行

 Java 是“编译与解释并存”的语言,是因为 Java 程序需要先将 Java 源代码文件编译字节码文件,再解释执行。

6.Java 有哪些数据类型

大体上分为两种类型,基本数据类型和引用数据类型

基本数据类型有byte,char,short,int,long,float,double,boolean

引用数据类型有数组,我们定义的类,枚举等

7.自动类型转换、强制类型转换了解吗?

在计算表达式的时候,两个不同的数据类型进行运算,其结果的数据类型取决于运算中数据范围表示最大的那一个。

当把一个范围较小的数值或变量赋给另外一个范围较大的变量时,会进行自动类型转换;反之,需要强制转换。

8.什么是自动拆箱/装箱?

涉及到包装类和其对应的基本数据类型,我们引入包装类一方面是为了简化我们的操作,包装类中有很多已经包装好的方法,另一方面是包装类的默认值有其意义

自动装箱就是JVM自动帮我们把int类型转换成Interger,使用的是Integer.valueOf(),并且如果范围是在-128到127之间的话,就不需要new一个新的对象

自动拆箱时,编译器通过调用类似intValue(),doubleValue()这类的方法将对象转换成原始类型值。

9.&和&&有什么区别?

& 是 逻辑与

&&是短路与运算。逻辑与跟短路与的差别是非常大的,虽然二者都要求运算符左右两端的布尔值都是 true,整个表达式的值才是 true。

&&之所以称为短路运算是因为,如果&&左边的表达式的值是 false,右边的表达式会直接短路掉,不会进行运算。

10.break,continue,return 的区别及作用?

  • break 跳出整个循环,不再执行循环(结束当前的循环体)
  • continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)
  • return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)

11.用效率最高的方法计算 2 乘以 8?

位运算即可,左移是乘以2的相对应次方倍,右移就是除以

12.float 是怎么表示小数的?

详情见计算机组成原理中的32位浮点数表示法

13.讲一下数据准确性高是怎么保证的?

在金融计算中,保证数据准确性有两种方案,一种使用 BigDecimal,一种将浮点数转换为整数 int 进行计算。

肯定不能使用 float 和 double 类型,它们无法避免浮点数运算中常见的精度问题,因为这些数据类型采用二进制浮点数来表示,无法准确地表示,例如 0.1

14.⾯向对象和⾯向过程的区别?

面向过程是以过程为核心,通过函数完成任务,程序结构是函数+步骤组成的顺序流程。

面向对象是以对象为核心,通过对象交互完成任务,程序结构是类和对象组成的模块化结构,代码可以通过继承、组合、多态等方式复用。

15.面向对象编程有哪些特性?

面向对象编程有三大特性:封装、继承、多态。

封装是指将数据(属性,或者叫字段)和操作数据的方法(行为)捆绑在一起,形成一个独立的对象,只对外开发一个访问用的接口

继承允许一个类(子类)继承现有类(父类或者基类)的属性和方法。以提高代码的复用性,建立类之间的层次关系,子类还可以重写或者扩展从父类继承来的属性和方法,从而实现多态

多态指同一个接口或方法在不同的类中有不同的实现,比如说动态绑定,父类引用指向子类对象,方法的具体调用会延迟到运行时决定,其实现原理是动态绑定

16.重载和重写的区别?

如果一个类有多个名字相同但参数个数不同的方法,我们通常称这些方法为方法重载(overload)。如果方法的功能是一样的,但参数不同,使用相同的名字可以提高程序的可读性。

如果子类具有和父类一样的方法(参数相同、返回类型相同、方法名相同,但方法体可能不同),我们称之为方法重写(override)。方法重写用于提供父类已经声明的方法的特殊实现,是实现多态的基础条件。

17.访问修饰符 public、private、protected、以及默认时的区别?

  • default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。可以修饰在类、接口、变量、方法。
  • private : 在同一类内可见。可以修饰变量、方法。注意:不能修饰类(外部类)
  • public : 对所有类可见。可以修饰类、接口、变量、方法
  • protected : 对同一包内的类和所有子类可见。可以修饰变量、方法。注意:不能修饰类(外部类)

18.抽象类和接口有什么区别?

从继承上看,抽象类不能多继承,接口可以多继承

从构造方法上来看,抽象类可以有构造方法,但其是不能实例化的,只能由继承的子类实例化,接口是没有构造方法的

抽象类使用 abstract 关键字定义,不能被实例化,只能作为其他类的父类。普通类没有 abstract 关键字,可以直接实例化。

此外抽象类可以包含抽象方法和非抽象方法。抽象方法没有方法体,必须由子类实现。普通类只能包含非抽象方法。

19.成员变量与局部变量的区别有哪些?

  1. 从语法形式上看:成员变量是属于类的,⽽局部变量是在⽅法中定义的变量或是⽅法的参数;成员变量可以被 public , private , static 等修饰符所修饰,⽽局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
  2. 从变量在内存中的存储⽅式来看:如果成员变量是使⽤ static 修饰的,那么这个成员变量是属于类的,如果没有使⽤ static 修饰,这个成员变量是属于实例的。对象存于堆内存,如果局部变量类型为基本数据类型,那么存储在栈内存,如果为引⽤数据类型,那存放的是指向堆内存对象的引⽤或者是指向常量池中的地址。
  3. 从变量在内存中的⽣存时间上看:成员变量是对象的⼀部分,它随着对象的创建⽽存在,⽽局部变量随着⽅法的调⽤⽽⾃动消失。
  4. 成员变量如果没有被赋初值:则会⾃动以类型的默认值⽽赋值(⼀种情况例外:被 final 修饰的成员变量也必须显式地赋值),⽽局部变量则不会⾃动赋值

20.static 关键字了解吗?

static 关键字可以用来修饰变量、方法、代码块和内部类,以及导入包

静态变量: 是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个副本。

实例变量: 必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存

静态方法:static 修饰的方法,也被称为类方法。在外部调⽤静态⽅法时,可以使⽤”类名.⽅法名“的⽅式,也可以使⽤”对象名.⽅法名“的⽅式。静态方法里不能访问类的非静态成员变量和方法。

实例⽅法:依存于类的实例,需要使用”对象名.⽅法名“的⽅式调用;可以访问类的所有成员变量和方法。

21.final 关键字有什么作用?

当 final 修饰一个类时,表明这个类不能被继承。比如,String 类、Integer 类和其他包装类都是用 final 修饰的。

当 final 修饰一个方法时,表明这个方法不能被重写(Override)。也就是说,如果一个类继承了某个类,并且想要改变父类中被 final 修饰的方法的行为,是不被允许的

当 final 修饰一个变量时,表明这个变量的值一旦被初始化就不能被修改。

如果是基本数据类型的变量,其数值一旦在初始化之后就不能更改;如果是引用类型的变量,在对其初始化之后就不能再让其指向另一个对象

22.final、finally、finalize 的区别?

final 是一个修饰符,可以修饰类、方法和变量。当 final 修饰一个类时,表明这个类不能被继承;当 final 修饰一个方法时,表明这个方法不能被重写;当 final 修饰一个变量时,表明这个变量是个常量,一旦赋值后,就不能再被修改了。

finally 是 Java 中异常处理的一部分,用来创建 try 块后面的 finally 块。无论 try 块中的代码是否抛出异常,finally 块中的代码总是会被执行。通常,finally 块被用来释放资源,如关闭文件、数据库连接等。

finalize 是Object 类的一个方法,用于在垃圾回收器将对象从内存中清除出去之前做一些必要的清理工作。

23..==和 equals 的区别?

在 Java 中,== 操作符和 equals() 方法用于比较两个对象:

①、==:用于比较两个对象的引用,即它们是否指向同一个对象实例。

如果两个变量引用同一个对象实例,== 返回 true,否则返回 false

对于基本数据类型(如 intdoublechar 等),== 比较的是值是否相等。

②、equals() 方法:用于比较两个对象的内容是否相等。默认情况下,equals() 方法的行为与 == 相同,即比较对象引用

24.为什么重写 equals 时必须重写 hashCode ⽅法?

因为基于哈希的集合类(如 HashMap)需要基于这一点来正确存储和查找对象。

具体地说,HashMap 通过对象的哈希码将其存储在不同的“桶”中,当查找对象时,它需要使用 key 的哈希码来确定对象在哪个桶中,然后再通过 equals() 方法找到对应的对象。

如果重写了 equals()方法而没有重写 hashCode()方法,那么被认为相等的对象可能会有不同的哈希码,从而导致无法在 HashMap 中正确处理这些对象

这主要是由于哈希码(hashCode)的本质和目的所决定的。

哈希码是通过哈希函数将对象中映射成一个整数值,其主要目的是在哈希表中快速定位对象的存储位置。

由于哈希函数将一个较大的输入域映射到一个较小的输出域,不同的输入值(即不同的对象)可能会产生相同的输出值(即相同的哈希码)。

这种情况被称为哈希冲突。当两个不相等的对象发生哈希冲突时,它们会有相同的 hashCode。

为了解决哈希冲突的问题,哈希表在处理键时,不仅会比较键对象的哈希码,还会使用 equals 方法来检查键对象是否真正相等。如果两个对象的哈希码相同,但通过 equals 方法比较结果为 false,那么这两个对象就不被视为相等。

25.Java 是值传递,还是引用传递?

是值传递,传递的指向的地址值,对于引用数据类型来说

26.说说深拷贝和浅拷贝的区别?

浅拷贝会创建一个新对象,但这个新对象的属性(字段)和原对象的属性完全相同。如果属性是基本数据类型,拷贝的是基本数据类型的值;如果属性是引用类型,拷贝的是引用地址,因此新旧对象共享同一个引用对象。

深拷贝也会创建一个新对象,但会递归地复制所有的引用对象,确保新对象和原对象完全独立。新对象与原对象的任何更改都不会相互影响。

27.Java 创建对象有哪几种方式?

Java 有四种创建对象的方式:

①、new 关键字创建,这是最常见和直接的方式,通过调用类的构造方法来创建对象。

②、反射机制创建,反射机制允许在运行时创建对象,并且可以访问类的私有成员,在框架和工具类中比较常见。

③、clone 拷贝创建,通过 clone 方法创建对象,需要实现 Cloneable 接口并重写 clone 方法。

④、序列化机制创建,通过序列化将对象转换为字节流,再通过反序列化从字节流中恢复对象。需要实现 Serializable 接口。

28.String 是 Java 基本数据类型吗?可以被继承吗?

String不是基本数据类型,其不可以被继承

29.String 和 StringBuilder、StringBuffer 的区别?

String 是不可变的,平常开发用得最多,当遇到大量字符串连接时,就用 StringBuilder,它不会生成很多新的对象,StringBuffer 和 StringBuilder 类似,但每个方法上都加了 synchronized 关键字,所以是线程安全的。

请说说 String 的特点

  • String类的对象是不可变的。也就是说,一旦一个String对象被创建,它所包含的字符串内容是不可改变的。
  • 每次对String对象进行修改操作(如拼接、替换等)实际上都会生成一个新的String对象,而不是修改原有对象。这可能会导致内存和性能开销,尤其是在大量字符串操作的情况下。

请说说 StringBuilder 的特点

  • StringBuilder提供了一系列的方法来进行字符串的增删改查操作,这些操作都是直接在原有字符串对象的底层数组上进行的,而不是生成新的 String 对象。
  • StringBuilder不是线程安全的。这意味着在没有外部同步的情况下,它不适用于多线程环境。
  • 相比于String,在进行频繁的字符串修改操作时,StringBuilder能提供更好的性能。 Java 中的字符串连+操作其实就是通过StringBuilder实现的。

请说说 StringBuffer 的特点

StringBufferStringBuilder类似,但StringBuffer是线程安全的,方法前面都加了synchronized关键字。

总结

  • String:适用于字符串内容不会改变的场景,比如说作为 HashMap 的 key。
  • StringBuilder:适用于单线程环境下需要频繁修改字符串内容的场景,比如在循环中拼接或修改字符串,是 String 的完美替代品。
  • StringBuffer:现在已经不怎么用了,因为一般不会在多线程场景下去频繁的修改字符串内容。

30.String str1 = new String(“abc”) 和 String str2 = “abc” 的区别?

此处内容涉及字符串常量池的内容,因为常量池的存在会使得对象创建个数的不同,new会实实在在的创建一个对象,接着就是字符串常量是否在常量池中存在的问题,这会影响对象创建的个数

31.String 是不可变类吗?字符串拼接是如何实现的?

String 是不可变的,这意味着一旦一个 String 对象被创建,其存储的文本内容就不能被改变。这是因为:

①、不可变性使得 String 对象在使用中更加安全。因为字符串经常用作参数传递给其他 Java 方法,例如网络连接、打开文件等。

如果 String 是可变的,这些方法调用的参数值就可能在不知不觉中被改变,从而导致网络连接被篡改、文件被莫名其妙地修改等问题。

②、不可变的对象因为状态不会改变,所以更容易进行缓存和重用。字符串常量池的出现正是基于这个原因。

当代码中出现相同的字符串字面量时,JVM 会确保所有的引用都指向常量池中的同一个对象,从而节约内存。

③、因为 String 的内容不会改变,所以它的哈希值也就固定不变。这使得 String 对象特别适合作为 HashMap 或 HashSet 等集合的键,因为计算哈希值只需要进行一次,提高了哈希表操作的效率。

对于字符串的拼接建议使用StringBuilder,这样不会频繁的产生新的对象占用内存空间造成OOM问题,频繁使用+拼接字符串会产生新的对象

32.intern 方法有什么作用?

  • 如果当前字符串内容存在于字符串常量池(即 equals()方法为 true,也就是内容一样),直接返回字符串常量池中的字符串
  • 否则,将此 String 对象添加到池中,并返回 String 对象的引用

33.String 怎么转成 Integer 的?原理?

String 转成 Integer,主要有两个方法:

  • Integer.parseInt(String s)
  • Integer.valueOf(String s)

不管哪一种,最终还是会调用 Integer 类内中的parseInt(String s, int radix)方法。

34.Java 中异常处理体系?

Throwable 是 Java 语言中所有错误和异常的基类。它有两个主要的子类:Error 和 Exception,这两个类分别代表了 Java 异常处理体系中的两个分支。

Error 类代表那些严重的错误,这类错误通常是程序无法处理的。比如,OutOfMemoryError 表示内存不足,StackOverflowError 表示栈溢出。这些错误通常与 JVM 的运行状态有关,一旦发生,应用程序通常无法恢复。

Exception 类代表程序可以处理的异常。它分为两大类:编译时异常(Checked Exception)和运行时异常(Runtime Exception)。

①、编译时异常(Checked Exception):这类异常在编译时必须被显式处理(捕获或声明抛出)。

如果方法可能抛出某种编译时异常,但没有捕获它(try-catch)或没有在方法声明中用 throws 子句声明它,那么编译将不会通过。例如:IOException、SQLException 等。

②、运行时异常(Runtime Exception):这类异常在运行时抛出,它们都是 RuntimeException 的子类。对于运行时异常,Java 编译器不要求必须处理它们(即不需要捕获也不需要声明抛出)。

运行时异常通常是由程序逻辑错误导致的,如 NullPointerException、IndexOutOfBoundsException 等。

35.异常的处理方式?

遇到异常时可以不处理,直接通过throw 和 throws 抛出异常,交给上层调用者处理。

throws 关键字用于声明可能会抛出的异常,而 throw 关键字用于抛出异常

使用 try-catch 捕获异常,处理异常。

36.Java 中 IO 流分为几种?

这部分就多了,大体上分是文件流FIle,字节流InPutStream,OutPutStream,字符流Reader,Writer,缓冲流BufferInputStream,BufferOutPutStream,转换流ObjectInPutStream,ObjectOutPutStream,序列化流

Java 缓冲区溢出,如何预防

Java 缓冲区溢出主要是由于向缓冲区写入的数据超过其能够存储的数据量。可以采用这些措施来避免:

①、合理设置缓冲区大小:在创建缓冲区时,应根据实际需求合理设置缓冲区的大小,避免创建过大或过小的缓冲区。

②、控制写入数据量:在向缓冲区写入数据时,应该控制写入的数据量,确保不会超过缓冲区的容量。Java 的 ByteBuffer 类提供了remaining()方法,可以获取缓冲区中剩余的可写入数据量。

37.既然有了字节流,为什么还要有字符流?

其实字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还比较耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。

所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。

文本存储是字节流还是字符流,视频文件呢?

在计算机中,文本和视频都是按照字节存储的,只是如果是文本文件的话,我们可以通过字符流的形式去读取,这样更方面的我们进行直接处理。

比如说我们需要在一个大文本文件中查找某个字符串,可以直接通过字符流来读取判断。

处理视频文件时,通常使用字节流(如 Java 中的FileInputStreamFileOutputStream)来读取或写入数据,并且会尽量使用缓冲流(如BufferedInputStreamBufferedOutputStream)来提高读写效率。

在技术派实战项目项目中,对于文本,比如说文章和教程内容,是直接存储在数据库中的,而对于视频和图片等大文件,是存储在 OSS 中的。

因此,无论是文本文件还是视频文件,它们在物理存储层面都是以字节流的形式存在。区别在于,我们如何通过 Java 代码来解释和处理这些字节流:作为编码后的字符还是作为二进制数据。

38.BIO、NIO、AIO 之间的区别?

BIO:采用阻塞式 I/O 模型,线程在执行 I/O 操作时被阻塞,无法处理其他任务,适用于连接数较少的场景。

NIO:采用非阻塞 I/O 模型,线程在等待 I/O 时可执行其他任务,通过 Selector 监控多个 Channel 上的事件,适用于连接数多但连接时间短的场景。

AIO:使用异步 I/O 模型,线程发起 I/O 请求后立即返回,当 I/O 操作完成时通过回调函数通知线程,适用于连接数多且连接时间长的场景。

简单说一下 BIO?

BIO,也就是传统的 IO,基于字节流或字符流(如 FileInputStream、BufferedReader 等)进行文件读写,基于 Socket 和 ServerSocket 进行网络通信。

对于每个连接,都需要创建一个独立的线程来处理读写操作。

简单说下 NIO?

NIO 的魅力主要体现在网络编程中,服务器可以用一个线程处理多个客户端连接,通过 Selector 监听多个 Channel 来实现多路复用,极大地提高了网络编程的性能。

简单说下 AIO?

它引入了异步通道的概念,使得 I/O 操作可以异步进行。这意味着线程发起一个读写操作后不必等待其完成,可以立即进行其他任务,并且当读写操作真正完成时,线程会被异步地通知。

39.什么是序列化?什么是反序列化?

序列化(Serialization)是指将对象转换为字节流的过程,以便能够将该对象保存到文件、数据库,或者进行网络传输。

反序列化(Deserialization)就是将字节流转换回对象的过程,以便构建原始对象。

Serializable 接口有什么用?

Serializable接口用于标记一个类可以被序列化

serialVersionUID 有什么用?

serialVersionUID 是 Java 序列化机制中用于标识类版本的唯一标识符。它的作用是确保在序列化和反序列化过程中,类的版本是兼容的

Java 序列化不包含静态变量吗?

是的,序列化机制只会保存对象的状态,而静态变量属于类的状态,不属于对象的状态。

如果有些变量不想序列化,怎么办?

可以使用transient关键字修饰不想序列化的变量

40.Java 泛型了解么?

泛型主要用于提高代码的类型安全,它允许在定义类、接口和方法时使用类型参数,这样可以在编译时检查类型一致性,避免不必要的类型转换和类型错误。

没有泛型的时候,像 List 这样的集合类存储的是 Object 类型,导致从集合中读取数据时,必须进行强制类型转换,否则会引发 ClassCastException。

泛型常用的通配符有哪些?

常用的通配符为: T,E,K,V,?

  • ? 表示不确定的 java 类型
  • T (type) 表示具体的一个 java 类型
  • K V (key value) 分别代表 java 键值中的 Key Value
  • E (element) 代表 Element

41.什么是反射?应用?原理?

反射允许 Java 在运行时检查和操作类的方法和字段。通过反射,可以动态地获取类的字段、方法、构造方法等信息,并在运行时调用方法或访问字段。

反射有哪些应用场景?

①、Spring 框架就大量使用了反射来动态加载和管理 Bean。

②、Java 的动态代理(Dynamic Proxy)机制就使用了反射来创建代理类。代理类可以在运行时动态处理方法调用,这在实现 AOP 和拦截器时非常有用。

③、JUnit 和 TestNG 等测试框架使用反射机制来发现和执行测试方法。反射允许框架扫描类,查找带有特定注解(如 @Test)的方法,并在运行时调用它们。

反射的原理是什么?

Java 程序的执行分为编译和运行两步,编译之后会生成字节码(.class)文件,JVM 进行类加载的时候,会加载字节码文件,将类型相关的所有信息加载进方法区,反射就是去获取这些信息,然后进行各种操作。

八股整理-JavaSE部分 success
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇