分类 技术 下的文章 - 第 8 页 - 酷游博客
首页
关于
友链
Search
1
阿里的简历多久可以投递一次?次数多了有没有影响?可以同时进行吗?
45 阅读
2
Java中泛型的理解
40 阅读
3
Java 14 发布了,再也不怕 NullPointerException 了!
38 阅读
4
Java中的可变参数
37 阅读
5
该如何创建字符串,使用" "还是构造函数?
30 阅读
技术
登录
/
注册
找到
556
篇与
技术
相关的结果
- 第 8 页
2025-01-22
晋升,出书,结婚,买房,总结下我的2019年
我从不喜欢写命题作文,这要是在从前,我也是决不愿意写什么年终总结的。 我讨厌别人讲奋斗史,更加痛恨别人秀优越。因为我觉得凡是过往,都是自己一脚一脚走出来人,无论在文字上怎么描述,别人都是绝对无法真正感同身受的。 但是自从2017年开始,每年的结束的时候,会在公众号写一下自己过去这一年的所思所想。是因为我突然发现,时间过得太快了,要不是记录点什么,总感觉过去就永远都是过去。 也不知道从什么时候开始,我越发得觉得事情变化的太快了,以至于我曾不只一次的和很多朋友说过这句话: 今年所发生的事,打死去年的你都不会相信 回头想想,大部分时间,确实是这样的。站在2020年的开端,回顾下刚刚过去的2019年,那些发生的种种,打死那个远去的2018年的我,也是断然不会信的。 晋升 “升职加薪、当上总经理、出任CEO、迎娶白富美、走上人生巅峰”,大概是所有俗人的梦想。 没错,我就是个俗人。 每年的4-7月,是阿里的晋升季,2019年的这三个月,我和2017年一样,很忙。但是区别的是,这一年压力更大。 压力来源于未知,好在结果是好的。 我不知道自己是不是这个层级里面年龄最小的,但是敢肯定的是一定远远低于平均年龄,但是再过几年就不好说了,毕竟长江后浪推前浪,我们终究会被排在沙滩上。 其实这不见得是好事儿。我记得我当初刚入职的时候,填写的新人介绍,其中有缺点和优点两项,我填写的内容都是”年轻”。 当我们说一个人有潜力的时候,只有他自己知道,有些时候也有可能力不从心。于是也不知道是哪天,我把自己的钉钉签名改成了:干就完了!  这一年,还带了两个徒弟,但是我感觉自己还没学会怎么当一个好导师,总觉得自己有时候过于严苛。 好在两位转正答辩都比较顺利。虽然最终其中一个由于种种原因没能留下来,但是我相信他能有一个好的未来。 更高的层级,意味着更大的责任,也以为着更多的话语权。但是有时候,其实这并不是好事。 有几次,事后我都觉得自己可能太坚持自己的观点了,或者会反思自己刚刚哪句话好像说的有点重了。这也是我慢慢发现的我的一个毛病,发现了这一点,我好像在慢慢成长了。 成长,或许就是妥协!?不,是大局观! 出书 2018年,我受朋友邀请参与《程序员的三门课》的编写,耗时一年之久,在编辑姐姐的多次催稿下,我们终于在2019年的国庆之前把他出版了。 在预售期,这本书一度占据了京东、当当等平台的预售榜单中前几名的位置,在上市两个月时间内,首印的4000册已经售罄了,之后又加印了第二版。 前几天问编辑,听说第二版的书也已经全都发到各个平台去了。 关于这本书的写作过程和所思所想,我分别写在了《我也终于出了一本书》和本书的序言中。 首先我非常感谢本书的联合作者以及编辑对我的支持,还有很多读者们的支持。 甚至我看到当当上面有人点名提到”为了Hollis来的”之类的话,真的是十分感动的。  这本书算是凝聚了我工作几年来想和程序员朋友们想说的所有话,真心的希望可以对朋友们有一点点帮助。 年终的时候,受邀参加了出版社的年会,还得到了一个『年度优秀作者』的奖杯。但是总感觉受之有愧。 很多人问第二本书什么时候出,我想这个可能要很久了,至少目前我还完全没有计划,因为不知道自己还可以写什么。估计好要再沉淀很长一段时间了。 结婚 说到结婚,这个事在2018年是并没有计划的,虽然那一年很多人问过我什么时候结婚,但是我的回答都是还没计划呢。 这个事情是真的,2018年是真的完全没计划这件事,那时候我还和女朋友开玩笑说我们要等到恋爱10周年的时候再领证。 我其实私下里问过女朋友,你着不着急结婚,你家人着不着急结婚。得到的答案都是不急。然后我们又都觉得婚礼是个及其麻烦的事情,所以就一直拖着。 但是没想到,恋爱的第9年,在双方父母的催促下,我们两人也觉得,这事情没必要做什么计划,既然父母觉得是时候了,那就把这事儿办了。 05月19日 结婚登记照拍摄 10月01日 双方家长正式见面 10月17日 结婚登记 11月03日 婚纱照拍摄 12月31日 婚礼西服定制 目前该办的都办完了,就剩过年回老家要举行的婚礼了。 2010年10月17日,那时候我和女朋友还在上高中,压力最大的高三,我们在一起了。2019年10月17日,我们正式扯了证。恋爱第10年开始的第一天,也是结婚开始的第一天。 结婚那天我发了个朋友圈,只给部分亲戚和朋友看了。 今天想想,公众号的粉丝也是我的朋友,所以也发给大家看看,希望得到大家的祝福:  买房 我的公众号的老读者可能知道,我2017年4月份在杭州买了房,那时候有一部分钱是自己攒的,还有一部分钱是家里给拿的。 曾经年少的时候说”我要靠自己买房,不依赖父母”,但是2017年眼看着房价在一直涨,于是决定,不管怎样,先上车再说。 就这样,自己凑了一部分,然后父母又给拿了一些,在杭州买了一套房。 2019年,一方面是因为自己有了一点闲钱,另外一方面也是因为学习了”香帅的北大金融学课”之后对投资与买房有了新的认识。还有就是听说哈尔滨成立了国家级新区,并且很多企业都纷纷在新区投资。 当然还有一个重要的原因,就是想孝敬一下父母,直接给钱他们是肯定不要的,于是我说为了投资,我们买房。因为我没办法回去办手续,所以写爸爸的名字。 就这样,凑了一些首付,在哈尔滨给父母买了一套房。 2019年的年末刚刚交完首付,贷款还在办理中。  这件事儿,在2018年,我是完完全全没有考虑过的,主要可能是因为那时候手里没钱吧。 其他 再说说自己其他方面的事情。 公众号这一年发布了703篇文章,平均每天两篇。共53篇原创,平均每周一篇。  每周一篇原创,对于工作十分繁忙的我来说,坚持下来还真的是不容易。每个你看到的我原创文章的那个周一前的周末,我至少有一个整天是在写文章的。 这一年在公众号中发布的文章共有251万阅读数,1万个点赞。 不知不觉一年内影响了200+万个程序员,想想还真的有点小激动呢。 因为公众号发文次数有限制,所以我今年还创建了另外一个公众号,今天统计了一下,也有15篇原创了。希望自己在2020年可以多多输出更多干货。 今年业余时间还尝试了一个新的领域,那就是股票。 原因是阿里在港股上市,为了支持公司的股票,我打新的时候买了一些BABA的港股,然后涨了点,在员工股票交易窗口期关闭之前就卖掉了。赚了5000块钱。然后就卷铺盖全都撤出来了。 还有一些美股由于最近不需要用钱,就一直也没舍得买,没事看看公司股票上涨,心里还是会暗暗窃喜一下。 差不多,这就是我的2019年,对于来说意义比较重大的一年,这一年干了好几件重要的事儿。而且很多件事儿都是在年初并没有计划过的。 不管我们舍不舍得,2019年都过去了,2020年已经来了。不管我们计不计划,2020年还是会有很多事会发生。 而且。相信2020年还是会有很多很多事,是打死现在的你都不会相信的。 所以,对于2020,满怀期待吧! 最后,感谢大家。2019,感谢有你;2020,我们继续。
技术
# 随笔
酷游
1月22日
0
10
0
2025-01-22
求你了,不要再在对外接口中使用枚举类型了!
最近,我们的线上环境出现了一个问题,线上代码在执行过程中抛出了一个IllegalArgumentException,分析堆栈后,发现最根本的的异常是以下内容: java.lang.IllegalArgumentException: No enum constant com.a.b.f.m.a.c.AType.P_M 大概就是以上的内容,看起来还是很简单的,提示的错误信息就是在AType这个枚举类中没有找到P_M这个枚举项。 于是经过排查,我们发现,在线上开始有这个异常之前,该应用依赖的一个下游系统有发布,而发布过程中是一个API包发生了变化,主要变化内容是在一个RPC接口的Response返回值类中的一个枚举参数AType中增加了P_M这个枚举项。 但是下游系统发布时,并未通知到我们负责的这个系统进行升级,所以就报错了。 我们来分析下为什么会发生这样的情况。 问题重现 首先,下游系统A提供了一个二方库的某一个接口的返回值中有一个参数类型是枚举类型。 一方库指的是本项目中的依赖 二方库指的是公司内部其他项目提供的依赖 三方库指的是其他组织、公司等来自第三方的依赖 public interface AFacadeService { public AResponse doSth(ARequest aRequest); } public Class AResponse{ private Boolean success; private AType aType; } public enum AType{ P_T, A_B } 然后B系统依赖了这个二方库,并且会通过RPC远程调用的方式调用AFacadeService的doSth方法。 public class BService { @Autowired AFacadeService aFacadeService; public void doSth(){ ARequest aRequest = new ARequest(); AResponse aResponse = aFacadeService.doSth(aRequest); AType aType = aResponse.getAType(); } } 这时候,如果A和B系统依赖的都是同一个二方库的话,两者使用到的枚举AType会是同一个类,里面的枚举项也都是一致的,这种情况不会有什么问题。 但是,如果有一天,这个二方库做了升级,在AType这个枚举类中增加了一个新的枚举项P_M,这时候只有系统A做了升级,但是系统B并没有做升级。 那么A系统依赖的的AType就是这样的: public enum AType{ P_T, A_B, P_M } 而B系统依赖的AType则是这样的: public enum AType{ P_T, A_B } 这种情况下,在B系统通过RPC调用A系统的时候,如果A系统返回的AResponse中的aType的类型位新增的P_M时候,B系统就会无法解析。一般在这种时候,RPC框架就会发生反序列化异常。导致程序被中断。 原理分析 这个问题的现象我们分析清楚了,那么再来看下原理是怎样的,为什么出现这样的异常呢。 其实这个原理也不难,这类RPC框架大多数会采用JSON的格式进行数据传输,也就是客户端会将返回值序列化成JSON字符串,而服务端会再将JSON字符串反序列化成一个Java对象。 而JSON在反序列化的过程中,对于一个枚举类型,会尝试调用对应的枚举类的valueOf方法来获取到对应的枚举。 而我们查看枚举类的valueOf方法的实现时,就可以发现,如果从枚举类中找不到对应的枚举项的时候,就会抛出IllegalArgumentException: public static T valueOf(Class enumType, String name) { T result = enumType.enumConstantDirectory().get(name); if (result != null) return result; if (name == null) throw new NullPointerException("Name is null"); throw new IllegalArgumentException( "No enum constant " + enumType.getCanonicalName() + "." + name); } 关于这个问题,其实在《阿里巴巴Java开发手册》中也有类似的约定:  这里面规定”对于二方库的参数可以使用枚举,但是返回值不允许使用枚举“。这背后的思考就是本文上面提到的内容。 扩展思考 为什么参数中可以有枚举? 不知道大家有没有想过这个问题,其实这个就和二方库的职责有点关系了。 一般情况下,A系统想要提供一个远程接口给别人调用的时候,就会定义一个二方库,告诉其调用方如何构造参数,调用哪个接口。 而这个二方库的调用方会根据其中定义的内容来进行调用。而参数的构造过程是由B系统完成的,如果B系统使用到的是一个旧的二方库,使用到的枚举自然是已有的一些,新增的就不会被用到,所以这样也不会出现问题。 比如前面的例子,B系统在调用A系统的时候,构造参数的时候使用到AType的时候就只有P_T和A_B两个选项,虽然A系统已经支持P_M了,但是B系统并没有使用到。 如果B系统想要使用P_M,那么就需要对该二方库进行升级。 但是,返回值就不一样了,返回值并不受客户端控制,服务端返回什么内容是根据他自己依赖的二方库决定的。 但是,其实相比较于手册中的规定,我更加倾向于,在RPC的接口中入参和出参都不要使用枚举。 一般,我们要使用枚举都是有几个考虑: 1、枚举严格控制下游系统的传入内容,避免非法字符。 2、方便下游系统知道都可以传哪些值,不容易出错。 不可否认,使用枚举确实有一些好处,但是我不建议使用主要有以下原因: 1、如果二方库升级,并且删除了一个枚举中的部分枚举项,那么入参中使用枚举也会出现问题,调用方将无法识别该枚举项。 2、有的时候,上下游系统有多个,如C系统通过B系统间接调用A系统,A系统的参数是由C系统传过来的,B系统只是做了一个参数的转换与组装。这种情况下,一旦A系统的二方库升级,那么B和C都要同时升级,任何一个不升级都将无法兼容。 我其实建议大家在接口中使用字符串代替枚举,相比较于枚举这种强类型,字符串算是一种弱类型。 如果使用字符串代替RPC接口中的枚举,那么就可以避免上面我们提到的两个问题,上游系统只需要传递字符串就行了,而具体的值的合法性,只需要在A系统内自己进行校验就可以了。 为了方便调用者使用,可以使用javadoc的@see注解表明这个字符串字段的取值从那个枚举中获取。 public Class AResponse{ private Boolean success; /** * @see AType */ private String aType; } 对于像阿里这种比较庞大的互联网公司,随便提供出去的一个接口,可能有上百个调用方,而接口升级也是常态,我们根本做不到每次二方库升级之后要求所有调用者跟着一起升级,这是完全不现实的,并且对于有些调用者来说,他用不到新特性,完全没必要做升级。 还有一种看起来比较特殊,但是实际上比较常见的情况,就是有的时候一个接口的声明在A包中,而一些枚举常量定义在B包中,比较常见的就是阿里的交易相关的信息,订单分很多层次,每次引入一个包的同时都需要引入几十个包。 对于调用者来说,我肯定是不希望我的系统引入太多的依赖的,一方面依赖多了会导致应用的编译过程很慢,并且很容易出现依赖冲突问题。 所以,在调用下游接口的时候,如果参数中字段的类型是枚举的话,那我没办法,必须得依赖他的二方库。但是如果不是枚举,只是一个字符串,那我就可以选择不依赖。 所以,我们在定义接口的时候,会尽量避免使用枚举这种强类型。规范中规定在返回值中不允许使用,而我自己要求更高,就是即使在接口的入参中我也很少使用。 最后,我只是不建议在对外提供的接口的出入参中使用枚举,并不是说彻底不要用枚举,我之前很多文章也提到过,枚举有很多好处,我在代码中也经常使用。所以,切不可因噎废食。 当然,文中的观点仅代表我个人,具体是是不是适用其他人,其他场景或者其他公司的实践,需要读者们自行分辨下,建议大家在使用的时候可以多思考一下。
技术
# 干货
酷游
1月22日
0
8
0
2025-01-22
Java unsupported major minor version 52.0
Here are the major version of every JRE released so far : Java SE 8 = 52, Java SE 7 = 51, Java SE 6.0 = 50, Java SE 5.0 = 49, JDK 1.4 = 48, JDK 1.3 = 47, JDK 1.2 = 46, JDK 1.1 = 45 You can see that Java 8 has major version 52。 which means if you run javac command from Java 8 installation, it will by default generate a class with major version 52. If you run this class file in JRE 7, you will get “Unsupported major.minor version 52.0”. Same is the case with an applet compiled in JDK 1.8, running in a browser with JRE 1.7 Read more: http://javarevisited.blogspot.com/2015/05/fixing-unsupported-majorminor-version.html#ixzz3uUfe4BbX 翻译一下就是:使用jdk8编译的class文件,使用jre1.7运行的时候就会报该异常。
技术
# Debug
酷游
1月22日
0
19
0
2025-01-22
使用Optional避免NullPointerException
对于一个Java开发人员来说,最常见的异常估计就是NullPointerException了。当需要一个对象的而程序试图使用null的时候,就会抛出NPE。 粗心的使用null会造成各种惊人的错误。通过研究Google代码库,我们发现95%的集合不应该包含任何null值,让这些情况快速失败,而不是默默地接受null对开发人员来说更有用。 此外,null是不讨人喜欢的,因为他代表着模棱两可。很难去判断返回一个null代表着什么意思。例如,如果Map.get(key)返回一个null。你很难判断是返回的值本身是null,还是值在map中根本不存在。null可以表示成功、失败甚至任何情况。在写代码时我们应该尽量使用其他方式来代替null来明确表达你的意愿。 不得不说,在某些特定场景中使用null也是很正确的。无论在内存方面还是在性能方面,null都有其自身的优势。所以,在对象数组中使用null是不可避免的。但相对于底层库来说,在应用级别的代码中,他确实导致了很多混乱,也带来了很多不确定的语义和不容易解决的bug。就像使用Map.get一样,null代表着多种语义。关键的原因就是,null并无法明确的表示他自身的意义。 当null可以被用来一个不存在的东西时,我们不得不花更多的努力来确保程序不会抛出NullPointerExcepiton,其实常用的方法之一是: if(user != null){ user.login(); } 但是,相信很多人都厌倦了这种写法。 Optional Guava库(Java8中也提供了)中提供了Optional接口来使null快速失败,即在可能为null的对象上做了一层封装。Optional 的最常用价值在于,例如,假设一个方法返回某一个数据类型,调用这个方法的代码来根据这个方法的返回值来做下一步的动作,若该方法可以返回一个null值表示成功,或者表示失败,在这里看来都是意义含糊的,所以使用Optional 作为返回值,则后续代码可以通过isPresent()来判断是否返回了期望的值(原本期望返回null或者返回不为null,其意义不清晰),并且可以使用get()来获得实际的返回值。 接下来,通过一个例子来看Optional到底有什么用,我们写一个方法,判断当前用户的用户名是为null,如果用户名为null,我们就叫他游客。 code1 public void sayHello(String name){ if(name==null){ name = "游客"; } System.out.println("Hello, "+name); } code2 import com.google.common.base.Optional; public void sayHello(String name){ name = Optional.fromNullable(name).or("游客"); System.out.println("Hello, "+name); } 单从代码风格上来看,我们可以看出code2显得更加优雅。 使用Optional除了赋予null语义,增加了可读性,最大的优点在于它是一种傻瓜式的防护。Guava中Optional类就是用来强制提醒开发者,注意对Null的判断。迫使你积极思考引用缺失的情况 常用方法: Optional .of(T)为Optional赋值,当T为Null直接抛NullPointException,建议这个方法在调用的时候直接传常量,不要传变量 Optional .fromNullable(T) 为Optional赋值,当T为Null则使用默认值。建议与or方法一起用,风骚无比 T or(T) 当Optional的值为null时,使用or赋予的值返回。与fromNullable是一对好基友 Optional .absent() 为Optional赋值,采用默认值 T get() 当Optional的值为null时,抛出IllegalStateException,返回Optional的值 boolean isPresent() 如果Optional存在值,则返回True T orNull() 当Optional的值为null时,则返回Null。否则返回Optional的值 Set asSet() 将Optional中的值转为一个Set返回,当然只有一个值啦,或者为空,当值为null时。 参考资料: Guava Optional 的应用 Using and avoiding null 拓展阅读 What’s the point of Guava’s Optional class
技术
# guava
酷游
1月22日
0
22
0
2025-01-22
JVM内存结构 VS Java内存模型 VS Java对象模型
Java作为一种面向对象的,跨平台语言,其对象、内存等一直是比较难的知识点。而且很多概念的名称看起来又那么相似,很多人会傻傻分不清楚。比如本文我们要讨论的JVM内存结构、Java内存模型和Java对象模型,这就是三个截然不同的概念,但是很多人容易弄混。 可以这样说,很多高级开发甚至都搞不不清楚JVM内存结构、Java内存模型和Java对象模型这三者的概念及其间的区别。甚至我见过有些面试官自己也搞的不是太清楚。不信的话,你去网上搜索Java内存模型,还会有很多文章的内容其实介绍的是JVM内存结构。 首先,这三个概念是完全不同的三个概念。本文主要对这三个概念加以区分以及简单介绍。其中每一个知识点都可以单独写一篇文章,本文并不会深入介绍,感兴趣的朋友可以加入我的知识星球和球友们共同学习。 JVM内存结构 我们都知道,Java代码是要运行在虚拟机上的,而虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途。其中有些区域随着虚拟机进程的启动而存在,而有些区域则依赖用户线程的启动和结束而建立和销毁。在《Java虚拟机规范(Java SE 8)》中描述了JVM运行时内存区域结构如下: 各个区域的功能不是本文重点,就不在这里详细介绍了。这里简单提几个需要特别注意的点: 1、以上是Java虚拟机规范,不同的虚拟机实现会各有不同,但是一般会遵守规范。 2、规范中定义的方法区,只是一种概念上的区域,并说明了其应该具有什么功能。但是并没有规定这个区域到底应该处于何处。所以,对于不同的虚拟机实现来说,是由一定的自由度的。 3、不同版本的方法区所处位置不同,上图中划分的是逻辑区域,并不是绝对意义上的物理区域。因为某些版本的JDK中方法区其实是在堆中实现的。 4、运行时常量池用于存放编译期生成的各种字面量和符号应用。但是,Java语言并不要求常量只有在编译期才能产生。比如在运行期,String.intern也会把新的常量放入池中。 5、除了以上介绍的JVM运行时内存外,还有一块内存区域可供使用,那就是直接内存。Java虚拟机规范并没有定义这块内存区域,所以他并不由JVM管理,是利用本地方法库直接在堆外申请的内存区域。 6、堆和栈的数据划分也不是绝对的,如HotSpot的JIT会针对对象分配做相应的优化。 如上,做个总结,JVM内存结构,由Java虚拟机规范定义。描述的是Java程序执行过程中,由JVM管理的不同数据区域。各个区域有其特定的功能。 Java内存模型 Java内存模型看上去和Java内存结构(JVM内存结构)差不多,很多人会误以为两者是一回事儿,这也就导致面试过程中经常答非所为。 在前面的关于JVM的内存结构的图中,我们可以看到,其中Java堆和方法区的区域是多个线程共享的数据区域。也就是说,多个线程可能可以操作保存在堆或者方法区中的同一个数据。这也就是我们常说的“Java的线程间通过共享内存进行通信”。 Java内存模型是根据英文Java Memory Model(JMM)翻译过来的。其实JMM并不像JVM内存结构一样是真实存在的。他只是一个抽象的概念。JSR-133: Java Memory Model and Thread Specification中描述了,JMM是和多线程相关的,他描述了一组规则或规范,这个规范定义了一个线程对共享变量的写入时对另一个线程是可见的。 那么,简单总结下,Java的多线程之间是通过共享内存进行通信的,而由于采用共享内存进行通信,在通信过程中会存在一系列如可见性、原子性、顺序性等问题,而JMM就是围绕着多线程通信以及与其相关的一系列特性而建立的模型。JMM定义了一些语法集,这些语法集映射到Java语言中就是volatile、synchronized等关键字。 在Java中,JMM是一个非常重要的概念,正是由于有了JMM,Java的并发编程才能避免很多问题。这里就不对Java内存模型做更加详细的介绍了,想了解更多的朋友可以参考《Java并发编程的艺术》。 Java对象模型 Java是一种面向对象的语言,而Java对象在JVM中的存储也是有一定的结构的。而这个关于Java对象自身的存储模型称之为Java对象模型。 HotSpot虚拟机中,设计了一个OOP-Klass Model。OOP(Ordinary Object Pointer)指的是普通对象指针,而Klass用来描述对象实例的具体类型。 每一个Java类,在被JVM加载的时候,JVM会给这个类创建一个instanceKlass,保存在方法区,用来在JVM层表示该Java类。当我们在Java代码中,使用new创建一个对象的时候,JVM会创建一个instanceOopDesc对象,这个对象中包含了对象头以及实例数据。 这就是一个简单的Java对象的OOP-Klass模型,即Java对象模型。 总结 我们再来区分下JVM内存结构、 Java内存模型 以及 Java对象模型 三个概念。 JVM内存结构,和Java虚拟机的运行时区域有关。 Java内存模型,和Java的并发编程有关。 Java对象模型,和Java对象在虚拟机中的表现形式有关。 关于这三部分内容,本文并未分别展开,因为涉及到的知识点实在太多,如果读者感兴趣,可以自行学习。
技术
# Java
酷游
1月22日
0
5
0
上一页
1
...
7
8
9
...
112
下一页
易航博客