《Java编程思想第四版》笔记---1~9章(1)面向对象基础

《Java编程思想第四版》笔记---1~9章(1)面向对象基础

1.java中的4种访问制权限
(1)public:最大访问控制权限,对所有的类都可见。
(2)protect:同一包可见,不在同一个包的所有子类也可见。
(3)default:包访问权限,即同一个包中的类可以可见。默认不显示指定访问控 制权限时就是default包访问控制权限。
(4)private:最严格的访问控制权限,仅该类本身可见,对外一切类都不可以访问(反射机制可以访问)。

2.面向对象编程中两种对象组合方式(is-a 和 has-a)
(1).is-a组合:一个类继承具有相似功能的另一个类,根据需要在所继承的类基础上进行扩展。
优点:具有共同属性和方法的类可以将共享信息抽象到父类中,增强代码复用性,同时也是多态的基础。
缺点:子类中扩展的部分对父类不可见,另外如果共性比较少的时候使用继承会增加冗余代码。
(2).has-a组合:has-a组合是在一个类中引用另一个类作为其成员变量。
优点:可扩展性和灵活性高。在对象组合关系中应优先考虑has-a组合关系。
缺点:具有共性的类之间看不到派生关系。
3.多态:
在面向对象编程中,子类中拥有和父类相同方法签名的方法称为子类方法覆盖父类方法,当调用子类方法的某个操作时,不必明确知道子类的具体类型,只需要将子类类型看作是父类的引用调用其操作方法,在运行时,JVM会根据引用对象的具体子类类型而调用应该的方法,这就是多态。
多态的基础是java面向对象编程的晚绑定机制。编程中有如下两种绑定机制:
(1)早绑定(前期绑定):一般在非面向对象编程语言中使用,在程序编译时即计算出具体调用方法体的内存地址。
(2)晚绑定(后期绑定):面向对象编程语言中经常使用,在程序编译时无法计算出具体调用方法体的内存地址,只进行方法参数类型和返回值类型的校验,在运行时才能确定具体要调用方法体的内存地址。
4.java单继承的优点:
相比于C++的多继承,java只支持类的单继承,java中的所有类的共同基类是 Object类,Object类是java类树的唯一根节点,这种单继承有以下好处:
(1).单继承可以确保所有的对象拥有某种共同的特性,这样JVM虚拟机对所有的类进行系统级的操作将提供方便,所有的java对象可以方便地在内存堆栈中创建,传递参数也变的更加方便简单。
(2).java的单继承使得实现垃圾回收器功能更加容易,因为可以确保JVM知道所有对象的类型信息。

5.选择容器对象的两个原则:
(1).容器所能提供不同的类型的接口和外部行为是否能够满足需求。
(2).不同容器针对不同的操作效率不同。
例如:ArrayList中,随机访问元素是一个耗时固定的操作,而LinkedList,随机访问元素需要在列表中走动,访问的元素越靠尾,花费的时间越长。

ArrayList和LinkedList的大致区别如下:
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2.对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

6.类型转换:
Java中有两种常见的类型转换:向上类型转换(upcast)和向下类型转换(downcast):
(1)向上类型转换(upcast):
向上类型转换是将子类类型对象强制转换为父类类型,经典用法是面向对象的多态特性。向上类型转换时,子类对象的特性将不可见,只有子类从父类继承的特性仍然保持可见,向上类型转换时编译器会自动检查是否类型兼容,通常是安全的。

(2)向下类型转换(downcast):
向下类型转换是将父类类型强制转换为子类类型,转换过后父类中不可见的子类特性又恢复可见性,向下类型转换时,编译器无法自动检测是否类型兼容,往往会产生类型转换错误的运行时异常,通常不安全。

7.java中5个存放数据的地方:
(1)寄存器(Registers):位于CPU内部,是速度最快的存储区,但是数量和容量有限。在java中不能直接操作寄存器。
( 2)栈(Stack):栈位于通用随机访问存储器 (General random-access memory,RAM,内存) 中,通过处理器的栈指针访问,栈指针从栈顶向栈底分配内存,从栈底向栈顶释放内存。栈是仅次于寄存器的速度第二快的存储器,在java程序中,一般的8种基本类型数据和对象的引用通常存放在栈内存中,不通过new关键字的字符串对象也是存放在栈的字符串池中。
优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。
缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
(3)堆(Heap):也是位于通用随机访问存储器 (General random-access memory,RAM,内存) 中的共享内存池。Java的堆是一个运行时数据区,类的对象从中分配空间,凡是通过new关键字创建的对象都存放在堆内存中,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的。
优势是可以动态地分配内存大小,编译器对它的生命周期一无所知,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。
缺点是,由于要在运行时动态分配内存,存取速度较慢。
(4).常量存储器(Constant storage):java中的常量是存放在系统内嵌的只读存储器中(read-only memory,ROM)的。
(5).非随机存储器(Non-RAM storage):对于流对象和持久化对象,通常存放在程序外的存储器,如硬盘。
8.异常处理
异常提供了一种从错误状况进行可靠恢复的途径。不能只单单抛出异常而不处理,可以借助异常情况进行程序的有效恢复,编出更加健壮的程序。
异常处理不是面向对象的特征,在面向对象语言出现之前已经存在。
9.并发编程
需要解决共享资源问题
10.基础数据类型
boolean 1位 - - Boolean
char 16位 Unicode 0 Unicode 2的16次方-1 Character
byte 8位 -128 +127 Byte(注释①)
short 16位 -2的15次方 +2的15次方-1 Short(注释①)
int 32位 -2的31次方 +2的31次方-1 Integer
long 64位 -2的63次方 +2的63次方-1 Long
float 32位 IEEE754 IEEE754 Float
double 64位 IEEE754 IEEE754 Double
Void - - - Void(注释①)
①:到Java 1.1才有,1.0版没有。
数值类型全都是有符号(正负号)的,所以不必费劲寻找没有符号的类型。
boolean 类型的存储空间没有明确指定。
12.对象
(1)对象并不具备基本数据类型一样的生命周期,它可以存活在作用域之外。
(2)基本数据类型如果没有初始化也会获得一个默认值,这只限于作为类的成员变量时才有,并不适用于局部变量。所以定义变量时最好初始化一个固定值。
Boolean false
Char '\u0000'(null)
byte (byte)0
short (short)0
int 0
long 0L
float 0.0f
double 0.0d
13.注释和嵌入式文档
注释三种:
(1)单行注释: //
(2)多行注释: /* /
进行编译时,/
/之间的所有东西都会被忽略
(3)文档注释:/
* */
javadoc只能为public(公共)和protected(受保护)成员处理注释文 档。“private”(私有)和“友好”(详见5章)成员的注释会被忽略,我们看不到任何输出(也可以用-private标记包括private成员)。
文档注释符号:
(1)@see:引用其他类
所有三种类型的注释文档都可包含@see标记,它允许我们引用其他类里的文档。对于这个标记,javadoc会生成相应的HTML,将其直接链接到其他文档。格式如下:
@see 类名
@see 完整类名
@see 完整类名#方法名
(2)类文档标记
@version 版本信息
其中,“版本信息”代表任何适合作为版本说明的资料。若在javadoc命令行使用了“-version”标记,就会从生成的HTML文档里提取出版本信息。
@author 作者信息
其中,“作者信息”包括您的姓名、电子函件地址或者其他任何适宜的资料。若在javadoc命令行使用了“-author”标记,就会专门从生成的HTML文档里提取出作者信息。
可为一系列作者使用多个这样的标记,但它们必须连续放置。全部作者信息会一起存入最终HTML代码的单独一个段落里。
(3)变量文档标记
变量文档只能包括嵌入的HTML以及@see引用。
(4)方法文档标记
@param 参数名 说明
其中,“参数名”是指参数列表内的标识符,而“说明”代表一些可延续到后续行内的说明文字。一旦遇到一个新文档标记,就认为前一个说明结束。可使用任意数量的说明,每个参数一个。
@return 说明
其中,“说明”是指返回值的含义。它可延续到后面的行内。
@exception 完整类名 说明
其中,“完整类名”明确指定了一个违例类的名字,它是在其他某个地方定义好的。而“说明”(同样可以延续到下面的行)告诉我们为什么这种特殊类型的违例会在方法调用中出现。
@deprecated
这是Java 1.1的新特性。该标记用于指出一些旧功能已由改进过的新功能取代。该标记的作用是建议用户不必再使用一种特定的功能,因为未来改版时可能摒弃这一功能。若将一个方法标记为@deprecated,则使用该方法时会收到编译器的警告。
14.操作符
(1)赋值=
区分基本数据类型和非基本数据类型得赋值,基本数据类型是直接内容赋值把内容复制给新的变量;而非基本数据类型是引用赋值(别名现象),指向的是同一块内存区域。
Java中赋值运算是把赋值运算符”=”右边的值简称右值拷贝到赋值运算符左边的变量,如a=b,即把b代表的变量或常量值复制给变量a,切记a只能是变量,不能说常量值。
a.原始类型赋值运算:
Java中8种原始数据类型赋值运算是将赋值运算符右边的值拷贝到赋值运算符左边的变量中。
原始类型赋值运算后,无论改变赋值运算符那一边的值,都不会影响赋值运算符另一边的值。
b.引用类型的赋值运算:
Java中除了8中原始数据类型外,所有的数据类型都是对象类型,对象类型的赋值运算是操作引用,如a=b,把b引用赋值给a引用,即原本b引用指向的对象现在由a和b引用同时指向。
引用赋值运算符又叫别名运算符,即它相当于给引用对象取了一个别名,其实引用的还是同一个对象。
引用类型的赋值运算,如果赋值运算符任意一边的引用改变了被引用对象的值,赋值运算符另一边的引用也会受影响,因为两个引用指向的是同一个被引用的对象。
(2)算术操作符
加、减、乘、除、取模
(3)一元加减
x=-a;
减号用于改变数据的符号,加号与减号相对应。
(4)自增自减
后缀:i++ i-- 先生成值再计算
前缀:++i --i 先自增或自减,再生成值
例如:
int i=1;
int a=1;
int b=a+ ++i;
输出:b=3
如果 int b=a+i++;
输出:b=2,i=2
(5)关系操作符(<,>,<=,>=,==)
==和!=适用于所有的基本数据类型,对于非基本数据类型的对象要使用等于和不等于要重写equal方法。
(6)逻辑操作符(&&,||,!)
“与”,“或”,“非”只适用于布尔值。存在“短路”现象,对于&&运算,只要前面的返回false,后面的就不会去计算;||运算,对于前面存在结果为true的运算,后面的运算就不会执行。
(7)直接常量
long L/l
float F/f
double D/d
十六进制 0X/0x 前缀
八进制 0前缀
指数计数法:
e:代表自然对数基数,约等于2.718(Math.E)
例:1.39E-43
(8)位操作符(&,|)
操作的对象是整数基本数据类型的二进制位。
(9)移位运算:
左移运算符<<:将比特位左移指定位数,右边部分补0,左移一位相当于乘2。
右移运算符>>:将比特位右移指定位数,如果是正数,左边第一位(符号位)补0,其余位补0,如果是负数,左边第一位补1,其余位补0。右移一位相当于除2。
无符号右移运算符>>>:将比特位右移指定位数,不论是正数或者负数,左边移除位统统补0。
移位可与赋值结合使用(<<=,>>=,>>>=),不过要注意数据类型转换带来的问题,例如byte和short无符号右移,会被转成int类型再右移,然后赋值回原类型(byte或short)产生数据截断,可能得到-1的错误结果。
(10)三元操作符(X?a:b)
(11)字符操作符+和+=(字符串连接符)
注意:表达式以字符串开头,后面的操作数全部会转换成字符串操作。
例:
int a=0,b=1,c=2;
String d = "a,b,c";
print(d+a+b+c);
输出:a,b,c012
(12)类型转换
窄化转换:可能面临数据丢失。需要显式转换。
int i=200;
long c=(long)i;
扩展转换:转换是安全的。
注意:java中是不用考虑移植问题的,所以没有sizeof()用于计算数据类型在内存的大小。
boolean类型是不能转换类型的。
15.java中,比int类型小的原始类型(char、byte、short)进行数学运算或者位运算时,数据类型首先转换成int类型,然后进行相应的运算。
16.swich语句
选择因子必须是int或char整数值,也可以是5新增的enum类型。
17.方法重载(overloading)
方法同名,参数列表(顺序,类型,个数)不同称为方法重载,注意方法的返回值类型不同不能作为方法重载。
注意:重载的方法参数类型涉及到转型的情况,类型提升或窄化。char类型找不到合适的方法,可以提升成int类型。
(1)void m1(int i){}
(2)void m1(long i){}
类型提升:
int k=100;
m1(k);
如果存在(1)(2),调用的是(1);
如果只存在(2),k会由int类型转成long类型;
类型窄化:
long k=100l;
m1(k);
如果存在(1)(2),调用的是(2);
如果只存在(1),k会由long类型转成int类型,数据可能发生丢失;
18.默认构造器
没有构造器情况下,编译器会自动创建一个默认的构造器(没有参数),如果定义了构造器,编译器就不会帮你创建默认构造器。
19.this关键字
this关键字只能在方法中使用,表示调用该方法的对象的引用。同一个类中的方法互相调用不用使用this关键字,使用了也没有错。
20.static方法是没有this的方法,它是属于某个类的方法,跟类的对象无关,所以就没有this,通过类名可以直接调用,它可以访问类中静态的成员和静态方法。
20.java中的析构函数
Java中没有像C/C++的析构函数,用来销毁不用的对象是否内存空间,只有以下三个方法用于通知垃圾回收器回收对象。
(1).finalize( )只是通知JVM的垃圾收集器当前的对象不再使用可以被回收了,但是垃圾回收器根据内存使用状况来决定是否回收。
finalize()最有用的地方是在JNI调用本地方法时(C/C++方法),调用本地方法的析构函数消耗对象释放函数。
(2). System.gc()是强制析构,显式通知垃圾回收器释放内存,但是垃圾回收器也不一定会立即执行,垃圾回收器根据当前内存使用状况和对象的生命周期自行决定是否回收。
(3).RunTime.getRunTime().gc()和System.gc()类似。
注意:这三个函数都不能保证垃圾回收器立即执行,推荐不要频繁使用。
21.垃圾回收器原理
(1).引用计数(ReferenceCounting)垃圾回收算法:
一种简单但是速度较慢的垃圾回收算法,每个对象拥有一个引用计数器(Reference Counter),当每次引用附加到这个对象时,对象的引用计数器加1。当每次引用超出作用范围或者被设置为null时,对象的引用计数器减1。垃圾回收器遍历整个对象列表,当发现一个对象的引用计数器为0时,将该对象移出内存释放。
引用计数算法的缺点是,当对象环状相互引用时,对象的引用计数器总不为0,要想回收这些对象需要额外的处理。
引用计数算法只是用来解释垃圾回收器的工作原理,没有JVM使用它实现垃圾回收器。
引用计数的改进算法:
任何存活的对象必须被在静态存储区或者栈(Stack)中的引用所引用,因此当遍历全部静态存储区或栈中的引用时,即可以确定所有存活的对象。每当遍历一个引用时,检查该引用所指向的对象,同时检查该对象上的所有引用,没有引用指向的对象和相互自引用的对象将被垃圾回收器回收。
(2).暂停复制(stop-and-copy)算法:
垃圾回收器的收集机制基于:任何一个存活的对象必须要被一个存储在栈或者静态存储区的引用所引用。
暂停复制的算法是:程序在运行过程中首先暂停执行,把每个存活的对象从一个堆复制到另一个堆中,已经不再被使用的对象被回收而不再复制。
暂停复制算法有两个问题:
a.必须要同时维护分离的两个堆,需要程序运行所需两倍的内存空间。JVM的解决办法是在内存块中分配堆空间,复制时简单地从一个内存块复制到另一个内存块。
b.第二个问题是复制过程的本身处理,当程序运行稳定以后,只会产生很少的垃圾对象需要回收,如果垃圾回收器还是频繁地复制存活对象是非常低性能的。JVM的解决方法是使用一种新的垃圾回收算法——标记清除(mark-and-sweep)。
一般来说标记清除算法在正常的使用场景中速度比较慢,但是当程序只产生很少的垃圾对象需要回收时,该算法就非常的高效。
(3).标记清除(mark-and-sweep)算法:
和暂停复制的逻辑类似,标记清除算法从栈和静态存储区开始追踪所有引用寻找存活的对象,当每次找到一个存活的对象时,对象被设置一个标记并且不被回收,当标记过程完成后,清除不用的死对象,释放内存空间。
标记清除算法不需要复制对象,所有的标记和清除工作在一个内存堆中完成。
注意:SUN的文档中说JVM的垃圾回收器是一个后台运行的低优先级进程,但是在早期版本的JVM中并不是这样实现的,当内存不够用时,垃圾回收器先暂停程序运行,然后进行垃圾回收。
(4).分代复制(generation-copy)算法:
一种对暂停复制算法的改进,JVM分配内存是按块分配的,当创建一个大对象时,需要占用一块内存空间,严格的暂停复制算法在释放老内存堆之前要求把每个存活的对象从源堆拷贝到新堆,这样做非常的消耗内存。
通过内存堆,垃圾回收器可以将对象拷贝到回收对象的内存堆中,每个内存块拥有一个世代计数(generation count)用于标记对象是否存活。每个内存块通过对象被引用获得世代计数,一般情况下只有当最老的内存块被回收时才会创建新的内存块,这主要用于处理大量的短存活周期临时对象回收问题。一次完整的清理过程中,内存块中的大对象不会被复制,只是根据引用重新获得世代计数。
JVM监控垃圾回收器的效率,当发现所有的对象都是长时间存活时,JVM将垃圾回收器的收集算法调整为标记清除,当内存堆变得零散碎片时,JVM又重新将垃圾回收器的算法切换会暂停复制,这就是JVM的自适应分代暂停复制标记清除垃圾回收算法的思想。
22.java即时编译技术(JIT)
Java的JIT是just-in-timecomplier技术,JIT技术是java代码部分地或全部转换成本地机器码程序,不再需要JVM解释,执行速度更快。
当一个”.class”的类文件被找到时,类文件的字节码被调入内存中,这时JIT编译器编译字节码代码。
JIT有两个不足:
(1).JIT编译转换需要花费一些时间,这些时间贯穿于程序的整个生命周期。
(2).JIT增加了可执行代码的size,相比于压缩的字节码,JIT代码扩展了代码的size,这有可能引起内存分页,进而降低程序执行速度。
对JIT不足的一种改进技术是延迟评估(lazy evaluation):其基本原理是字节码并不立即进行JIT编译除非必要,在最近的JDK中采用了一种类似延迟JIT的HotSpot方法对每次执行的代码进行优化,代码执行次数越多,速度越快。
23.非内部类的访问控制权限只能是默认的包访问权限或者是public的,不能是protected和private的。内部类的访问控制权限可以是protected和private。
24.Java中的高精度数值类型
BigInteger和BigDecimal是java中的高精度数值类型,由于它们是用于包装java的基本数据类型,因此这两个高精度数值类型没有对应的原始类型。
(1).BigInteger支持任意精度的整数,即使用BigInteger可以表示任意长度的整数值而在运算中不会因为范围溢出丢失信息。
(2).BigDecimal支持任意精度的固定位数浮点数,可以用来精确计算货币等数值。
普通的float和double型浮点数因为受到小数点位数限制,在运算时不能准确比较,只能以误差范围确定是否相等,而BigDecimal就可以支持固定位数的浮点数并进行精确计算。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容

  • 一、Java 简介 Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计...
    子非鱼_t_阅读 4,082评论 1 44
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,015评论 11 349
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,295评论 18 399
  • 打包成功 上传苹果服务器出错提示语:ERROR ITMSitunes store operation failed...
    Gabriella0629阅读 137评论 0 0
  • 什么是平庸。辞海告诉我,为庸碌、普通、寻常而不突出,碌碌无为者视为平庸。 不知道何时何地,自己已感觉如此,于是开始...
    青鸟飝漁阅读 788评论 2 2