标签: java

Android面试题整理

JAVA 相关

 

1.静态内部类、内部类、匿名内部类,为什么内部类会持有外部类的引用?持有的引用是this?还是其它?

 

静态内部类:使用static修饰的内部类

 

内部类:就是在某个类的内部又定义了一个类,内部类所嵌入的类称为外部类

 

匿名内部类:使用new生成的内部类

 

因为内部类的产生依赖于外部类,持有的引用是类名.this

 

2.Java中try catch finally的执行顺序

 

先执行try中代码,如果发生异常执行catch中代码,*后一定会执行finally中代码

 

3.equals与==的区别:

 

==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相

 

4.Object有哪些公用方法?

 

方法equals测试的是两个对象是否相等

 

方法clone进行对象拷贝

 

方法getClass返回和当前对象相关的Class对象

 

方法notify,notifyall,wait都是用来对给定对象进行线程同步的

 

5.String、StringBuffer与StringBuilder的区别

 

String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象 StringBuffer和StringBuilder底层是 char[]数组实现的 StringBuffer是线程安全的,而StringBuilder是线程不安全的

 

6.Java的四种引用的区别

 

强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM 也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象

 

软引用:在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。

 

弱引用:具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象

 

虚引用:顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。

 

7.介绍垃圾回收机制

 

标记回收法:遍历对象图并且记录可到达的对象,以便删除不可到达的对象,一般使用单线程工作并且可能产生内存碎片

 

标记-压缩回收法:前期与*种方法相同,只是多了一步,将所有的存活对象压缩到内存的一端,这样内存碎片就可以合成一大块可再利用的内存区域,提高了内存利用率

 

复制回收法:把现有内存空间分成两部分,gc运行时,它把可到达对象复制到另一半空间,再清空正在使用的空间的全部对象。这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。

 

分代回收发:把内存空间分为两个或者多个域,如年轻代和老年代,年轻代的特点是对象会很快被回收,因此在年轻代使用效率比较高的算法。当一个对象经过几次回收后依然存活,对象就会被放入称为老年的内存空间,老年代则采取标记-压缩算法

集合、数据结构相关

1.你用过哪些集合类

数据结构中用于存储数据的有哪些

数组

数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;

链表

链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。

2.说说hashMap是怎样实现的

哈希表:由数组+链表组成的

当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,*先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

3.ArrayList,LinkedList的区别

ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。

对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。

对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

4.ArrayList和Vector的主要区别是什么?

ArrayList 和Vector底层是采用数组方式存储数据

Vector:

线程同步

当Vector中的元素超过它的初始大小时,Vector会将它的容量翻倍,

ArrayList:

线程不同步,但性能很好

当ArrayList中的元素超过它的初始大小时,ArrayList只增加50%的大小

5.HashMap和 HashTable 的区别:

HashTable比较老,是基于Dictionary 类实现的,HashTable 则是基于 Map接口实现的

HashTable 是线程安全的, HashMap 则是线程不安全的

HashMap可以让你将空值作为一个表的条目的key或value

 

算法相关

1.排序算法和稳定性,快排什么时候情况*坏?

2.给*外层的rootview,把这个根视图下的全部button背景设置成红色,手写代码,不许用递归

算法原理:

Android的view视图是按树形结构分布,所以按树形结构遍历

循环判断每一层的ViewGroup元素,将其入栈;否则判断当前view是否是Button类实例,是则改写背景色

当前ViewGroup检查childView完成后,判断栈是否非空,取出栈顶元素ViewGroup重复步骤2直至栈为空。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void changeAllBtnBGColor(View view, int color) {
if (view == null || !(view instanceof ViewGroup))
return;
Stack m = new Stack<>();
while (view != null) {
ViewGroup tmpGroup = (ViewGroup) view;
int count = tmpGroup.getChildCount();
for (int i = 0; i < count; i++) { View child = tmpGroup.getChildAt(i);
if (child instanceof ViewGroup) m.add(child);
else if (child instanceof Button) { child.setBackgroundColor(color);
} }
if (m.isEmpty()) break;
else view = m.pop();
}
}

 

Thread、AsynTask相关

1.wait()和sleep()的区别

sleep来自Thread类,和wait来自Object类

调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁

sleep睡眠后不出让系统资源,wait让出系统资源其他线程可以占用CPU

sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒

2.若Activity已经销毁,此时AsynTask执行完并且返回结果,会报异常吗?

当一个App旋转时,整个Activity会被销毁和重建。当Activity重启时,AsyncTask中对该Activity的引用是无效的,因此onPostExecute()就不会起作用,若AsynTask正在执行,折会报 view not attached to window manager 异常

同样也是生命周期的问题,在 Activity 的onDestory()方法中调用Asyntask.cancal方法,让二者的生命周期同步

3.Activity销毁但Task如果没有销毁掉,当Activity重启时这个AsyncTask该如何解决?

还是屏幕旋转这个例子,在重建Activity的时候,会回掉Activity.onRetainNonConfigurationInstance()重新传递一个新的对象给AsyncTask,完成引用的更新

4.Android 线程间通信有哪几种方式(重要)

共享内存(变量);

文件,数据库;

Handler;

Java 里的 wait(),notify(),notifyAll()

5.请介绍下 AsyncTask的内部实现,适用的场景是

AsyncTask 内部也是 Handler 机制来完成的,只不过 Android 提供了执行框架来提供线程池来

执行相应地任务,因为线程池的大小问题,所以 AsyncTask 只应该用来执行耗时时间较短的任务,

比如 HTTP 请求,大规模的下载和数据库的更改不适用于 AsyncTask,因为会导致线程池堵塞,没有

线程来执行其他的任务,导致的情形是会发生 AsyncTask 根本执行不了的问题。

网络相关

1.TCP三次握手

2.为什么TCP是可靠的,UDP早不可靠的?为什么UDP比TCP快?

TCP/IP协议高,因为其拥有三次握手双向机制,这一机制保证校验了数据,保证了他的可靠性。

UDP就没有了,udp信息发出后,不验证是否到达对方,所以不可靠。

但是就速度来说,还是UDP协议更高,毕竟其无需重复返回验证,只是一次性的

3.http协议了解多少,说说里面的协议头部有哪些字段?

http(超文本传输协议)是一个基于请求与响应模式的、无状态的、应用层的协议;http请求由三部分组成,分别是:请求行、消息报头、请求正文。

HTTP消息报头包括普通报头、请求报头、响应报头、实体报头

4.https了解多少

HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。

5.谈谈 HTTP 中Get 和 Post 方法的区别

GET – 从指定的服务器中获取数据,明文发送内容

POST – 提交数据给指定的服务器处理

1. POST请求不能被缓存下来

2. POST请求不会保存在浏览器浏览记录中

3. 以POST请求的URL无法保存为浏览器书签

4. POST请求没有长度限制

6.推送心跳包是TCP包还是UDP包或者HTTP包

心跳包的实现是调用了socket.sendUrgentData(0xFF)这句代码实现的,所以,当然是TCP包。

7.如何实现文件断点上传

在 Android 中上传文件可以采用 HTTP 方式,也可以采用 Socket 方式,但是 HTTP 方式不能上传

大文件,这里介绍一种通过 Socket 方式来进行断点续传的方式,服务端会记录下文件的上传进度,

当某一次上传过程意外终止后,下一次可以继续上传,这里用到的其实还是 J2SE 里的知识。

这个上传程序的原理是:客户端*次上传时向服务端发送

“Content-Length=35;filename=WinRAR_3.90_SC.exe;sourceid=“这种格式的字符串,服务端

收到后会查找该文件是否有上传记录,如果有就返回已经上传的位置,否则返回新生成的 sourceid

以及 position 为 0,类似 sourceid=2324838389;position=0“这样的字符串,客户端收到返回后

的字符串后再从指定的位置开始上传文件。

Fragment相关

1.Fragment 如何实现类似 Activity 栈的压栈和出栈效果的?

Fragment 的事物管理器内部维持了一个双向链表结构,该结构可以记录我们每次 add 的

Fragment 和 replace 的 Fragment,然后当我们点击 back 按钮的时候会自动帮我们实现退栈操作。

2.Fragment 在你们项目中的使用

Fragment 是 android3.0 以后引入的的概念,做局部内容更新更方便,原来为了到达这一点要

把多个布局放到一个 activity 里面,现在可以用多 Fragment 来代替,只有在需要的时候才加载

Fragment,提高性能。

Fragment 的好处:

1. Fragment 可以使你能够将 activity 分离成多个可重用的组件,每个都有它自己的生命周期和

UI。

2. Fragment 可以轻松得创建动态灵活的 UI 设计,可以适应于不同的屏幕尺寸。从手机到平板电

脑。

3. Fragment 是一个独立的模块,紧紧地与 activity 绑定在一起。可以运行中动态地移除、加入、

交换等。

4. Fragment 提供一个新的方式让你在不同的安卓设备上统一你的 UI。

5. Fragment 解决 Activity 间的切换不流畅,轻量切换。

6. Fragment 替代 TabActivity 做导航,性能更好。

7. Fragment 在 4.2.版本中新增嵌套 fragment 使用方法,能够生成更好的界面效果

3.如何切换 fragement,不重新实例化

正确的切换方式是 add(),切换时 hide(),add()另一个 Fragment;再次切换时,只需 hide()当前,

show()另一个

四大组件相关

1.Activity和Fragment生命周期有哪些?

Activity——onCreate->onStart->onResume->onPause->onStop->onDestroy

Fragment——onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume->onPause->onStop->onDestroyView->onDestroy->onDetach

2.广播的两种注册方式及有什么区别

3.内存不足时,怎么保持Activity的一些状态,在哪个方法里面做具体操作?

Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,它们不同于 onCreate()、onPause()等生命周期方法,它们并不一定会被触发。当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity,onSaveInstanceState() 会被调用。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。除非该activity是被用户主动销毁的,通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。

4.启动service的两种方法?有什么区别?

一种是startService(),另一种是bindService()。这两者的区别是*种方式调用者开启了服务,即会与服务失去联系,两者没有关联。即使访问者退出了,服务仍在运行。如需解除服务必须显式的调用stopService方法。主要用于调用者与服务没有交互的情况下,也就是调用者不需要获取服务里的业务方法。比如电话录音。而后者调用者与服务绑定在一起的。当调用者退出的时候,服务也随之退出。用于需要与服务交互。

5.Android中的Context, Activity,Appliction有什么区别?

相同:Activity和Application都是Context的子类。

Context从字面上理解就是上下文的意思,在实际应用中它也确实是起到了管理上下文环境中各个参数和变量的总用,方便我们可以简单的访问到各种资源。

不同:维护的生命周期不同。 Context维护的是当前的Activity的生命周期,Application维护的是整个项目的生命周期。

使用context的时候,小心内存泄露,防止内存泄露,注意一下几个方面:

1. 不要让生命周期长的对象引用activity context,即保证引用activity的对象要与activity本身生命周期是一样的。

2. 对于生命周期长的对象,可以使用application,context。

3. 避免非静态的内部类,尽量使用静态类,避免生命周期问题,注意内部类对外部对象引用导致的生命周期变化。

6.Context是什么?

它描述的是一个应用程序环境的信息,即上下文。

该类是一个抽象(abstract class)类,Android提供了该抽象类的具体实现类(ContextIml)。

通过它我们可以获取应用程序的资源和类,也包括一些应用级别操作,例如:启动一个Activity,发送广播,接受Intent,信息,等。

7.Service 是否在 main thread 中执行, service 里面是否能执行耗时的操

作?

默认情况,如果没有显示的指 servic 所运行的进程, Service 和 activity 是运行在当前 app 所在进

程的 main thread(UI 主线程)里面。

service 里面不能执行耗时的操作(网络请求,拷贝数据库,大文件 )

 

特殊情况 ,可以在清单文件配置 service 执行所在的进程 ,让 service 在另外的进程中执行

1
2
3
4
5
<service
android:name="com.baidu.location.f"
android:enabled="true"
android:process=":remote" >
</service>

 

8.Activity 怎么和 Service 绑定,怎么在 Activity 中启动自己对应的

Service?

Activity 通过 bindService(Intent service, ServiceConnection conn, int flags)跟 Service 进行

绑定,当绑定成功的时候 Service 会将代理对象通过回调的形式传给 conn,这样我们就拿到了

Service 提供的服务代理对象。

在 Activity 中可以通过 startService 和 bindService 方法启动 Service。一般情况下如果想获取

Service 的服务对象那么肯定需要通过 bindService()方法,比如音乐播放器,第三方支付等。如

果仅仅只是为了开启一个后台任务那么可以使用 startService()方法。

9.说说 Activity、Intent、Service 是什么关系

他们都是 Android 开发中使用频率*高的类。其中 Activity 和 Service 都是 Android 四大组件

之一。他俩都是 Context 类的子类 ContextWrapper 的子类,因此他俩可以算是兄弟关系吧。不过

兄弟俩各有各自的本领,Activity 负责用户界面的显示和交互,Service 负责后台任务的处理。Activity

和 Service 之间可以通过 Intent 传递数据,因此可以把 Intent 看作是通信使者。

10.请描述一下 BroadcastReceiver

BroadCastReceiver 是 Android 四大组件之一,主要用于接收系统或者 app 发送的广播事件。

广播分两种:有序广播和无序广播。

内部通信实现机制:通过 Android 系统的 Binder 机制实现通信。

1. 无序广播:完全异步,逻辑上可以被任何广播接收者接收到。优点是效率较高。缺点是一个接收者不

能将处理结果传递给下一个接收者,并无法终止广播 intent 的传播。

2. 有序广播:按照被接收者的优先级顺序,在被接收者中依次传播。比如有三个广播接收者 A,B,C,

优先级是 A > B > C。那这个消息先传给 A,再传给 B,*后传给 C。每个接收者有权终止广播,比

如 B 终止广播,C 就无法接收到。此外 A 接收到广播后可以对结果对象进行操作,当广播传给 B 时,

B 可以从结果对象中取得 A 存入的数据。

在通过 Context.sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler,

initialCode, initialData, initialExtras)时我们可以指定 resultReceiver 广播接收者,这个接收者我们

可以认为是*终接收者,通常情况下如果比他优先级更高的接收者如果没有终止广播,那么他的

onReceive 会被执行两次,*次是正常的按照优先级顺序执行,第二次是作为*终接收者接收。

如果比他优先级高的接收者终止了广播,那么他依然能接收到广播

11.为什么要用 ContentProvider?它和 sql 的实现上有什么差别?

ContentProvider 屏蔽了数据存储的细节,内部实现对用户完全透明,用户只需要关心操作数据的

uri 就可以了,ContentProvider 可以实现不同 app 之间共享。

Sql 也有增删改查的方法,但是 sql 只能查询本应用下的数据库。而 ContentProvider 还可

以去增删改查本地文件. xml 文件的读取等。

12.说说 ContentProvider、ContentResolver、ContentObserver 之间的关系

a. ContentProvider 内容提供者,用于对外提供数据

b. ContentResolver.notifyChange(uri)发出消息

c. ContentResolver 内容解析者,用于获取内容提供者提供的数据

d. ContentObserver 内容监听器,可以监听数据的改变状态

e. ContentResolver.registerContentObserver()监听消息。

View 相关

1.onInterceptTouchEvent()和onTouchEvent()的区别

onInterceptTouchEvent()用于拦截触摸事件

onTouchEvent()用于处理触摸事件

2.RemoteView在哪些功能中使用

APPwidget和Notification中

3. SurfaceView和View的区别是什么?

SurfaceView中采用了双缓存技术,在单独的线程中更新界面

View在UI线程中更新界面

4.View的绘制过程

一个View要显示在界面上,需要经历一个View树的遍历过程,这个过程又可以分为三个过程,也就是自定义View中的三要素:大小,位置,画什么,即onMesure(),onLayout(),onDraw()。

1.onMesure()确定一个View的大小;

2.onLayout()确定View在父节点上的位置;

3.onDraw()绘制View 的内容;

5.如何自定义ViewGroup

1.指定的LayoutParams

2.onMeasure中计算所有childView的宽和高,然后根据childView的宽和高,计算自己的宽和高。(当然,如果不是wrap_content,直接使用父ViewGroup传入的计算值即可)

3.onLayout中对所有的childView进行布局。

6.View中onTouch,onTouchEvent,onClick的执行顺序

dispatchTouchEvent—->onTouch—->onTouchEvent—–>onClick。在所有ACTION_UP事件之后才触发onClick点击事件。

性能优化相关

1.ListView卡顿的原因与性能优化,越多越好

重用converView: 通过复用converview来减少不必要的view的创建,另外Infalte操作会把xml文件实例化成相应的View实例,属于IO操作,是耗时操作。

减少findViewById()操作: 将xml文件中的元素封装成viewholder静态类,通过converview的setTag和getTag方法将view与相应的holder对象绑定在一起,避免不必要的findviewbyid操作

避免在 getView 方法中做耗时的操作: 例如加载本地 Image 需要载入内存以及解析 Bitmap ,都是比较耗时的操作,如果用户快速滑动listview,会因为getview逻辑过于复杂耗时而造成滑动卡顿现象。用户滑动时候不要加载图片,待滑动完成再加载,可以使用这个第三方库glide

Item的布局层次结构尽量简单,避免布局太深或者不必要的重绘

尽量能保证 Adapter 的 hasStableIds() 返回 true 这样在 notifyDataSetChanged() 的时候,如果item内容并没有变化,ListView 将不会重新绘制这个 View,达到优化的目的

在一些场景中,ScollView内会包含多个ListView,可以把listview的高度写死固定下来。 由于ScollView在快速滑动过程中需要大量计算每一个listview的高度,阻塞了UI线程导致卡顿现象出现,如果我们每一个item的高度都是均匀的,可以通过计算把listview的高度确定下来,避免卡顿现象出现

使用 RecycleView 代替listview: 每个item内容的变动,listview都需要去调用notifyDataSetChanged来更新全部的item,太浪费性能了。RecycleView可以实现当个item的局部刷新,并且引入了增加和删除的动态效果,在性能上和定制上都有很大的改善

ListView 中元素避免半透明: 半透明绘制需要大量乘法计算,在滑动时不停重绘会造成大量的计算,在比较差的机子上会比较卡。 在设计上能不半透明就不不半透明。实在要弄就把在滑动的时候把半透明设置成不透明,滑动完再重新设置成半透明。

尽量开启硬件加速: 硬件加速提升巨大,避免使用一些不支持的函数导致含泪关闭某个地方的硬件加速。当然这一条不只是对 ListView。

2.如何避免 OOM 问题的出现

使用更加轻量的数据结构 例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构。通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效,在于他们避免了对key与value的自动装箱(autoboxing),并且避免了装箱后的解箱。

避免在Android里面使用Enum Android官方培训课程提到过“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”,具体原理请参考《Android性能优化典范(三)》,所以请避免在Android里面使用到枚举。

减小Bitmap对象的内存占用 Bitmap是一个*容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用可谓是重中之重,,通常来说有以下2个措施: ++inSampleSize++:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。 ++decode format++:解码格式,选择ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差异

Bitmap对象的复用 缩小Bitmap的同时,也需要提高BitMap对象的复用率,避免频繁创建BitMap对象,复用的方法有以下2个措施 LRUCache : “*近*少使用算法”在Android中有*其普遍的应用。ListView与GridView等显示大量图片的控件里,就是使用LRU的机制来缓存处理好的Bitmap,把近期*少使用的数据从缓存中移除,保留使用*频繁的数据, inBitMap高级特性:利用inBitmap的高级特性提高Android系统在Bitmap分配与释放执行效率。使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用之前那张Bitmap在Heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放Bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小

使用更小的图片 在涉及给到资源图片时,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用更小的图片。尽量使用更小的图片不仅可以减少内存的使用,还能避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图时会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。

StringBuilder 在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。

避免在onDraw方法里面执行对象的创建 类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。

避免对象的内存泄露

3.三级缓存的原理

从缓存中加载。

从本地文件中加载(数据库,SD)

从网络加载。

a.加载 bitmap 的时候无需考虑 bitmap 加载过程中出现的 oom(内存溢出)和 android 容器快速

滑动的时候出现的图片错位等现象。(16M)

b. 支持加载网络图片和本地图片。

c. 内存管理使用的 lru 算法(移除里面是有频率*少的对象),更好的管理 bitmap 的内存

Android其他

1.讲一下android中进程的优先级?

前台进程

可见进程

服务进程

后台进程

空进程

2.介绍Handle的机制

Handler通过调用sendmessage方法把消息放在消息队列MessageQueue中,Looper负责把消息从消息队列中取出来,重新再交给Handler进行处理,三者形成一个循环

通过构建一个消息队列,把所有的Message进行统一的管理,当Message不用了,并不作为垃圾回收,而是放入消息队列中,供下次handler创建消息时候使用,提高了消息对象的复用,减少系统垃圾回收的次数

每一个线程,都会单独对应的一个looper,这个looper通过ThreadLocal来创建,保证每个线程只创建一个looper,looper初始化后就会调用looper.loop创建一个MessageQueue,这个方法在UI线程初始化的时候就会完成,我们不需要手动创建

3.Dalvik虚拟机与JVM有什么区别

Dalvik 基于寄存器,而 JVM 基于栈。基于寄存器的虚拟机对于更大的程序来说,在它们编译的时候,花费的时间更短。

Dalvik执行.dex格式的字节码,而JVM执行.class格式的字节码。

4.每个应用程序对应多少个Dalvik虚拟机

每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执行 ,而所有的Android应用的线程都对应一个Linux线程

5.应用常驻后台,避免被第三方杀掉的方法

Service设置成START_STICKY kill 后会被重启(等待5秒左右),重传Intent,保持与重启前一样

通过 startForeground将进程设置为前台进程, 做前台服务,优先级和前台应用一个级别,除非在系统内存非常缺,否则此进程不会被 kill

双进程Service: 让2个进程互相保护对方,其中一个Service被清理后,另外没被清理的进程可以立即重启进程

用C编写守护进程(即子进程) : Android系统中当前进程(Process)fork出来的子进程,被系统认为是两个不同的进程。当父进程被杀死的时候,子进程仍然可以存活,并不受影响(Android5.0以上的版本不可行

联系厂商,加入白名单

6.根据自己的理解描述下Android数字签名。

所有的应用程序都必须有数字证书,Android系统不会安装一个没有数字证书的应用程序

Android程序包使用的数字证书可以是自签名的,不需要一个权威的数字证书机构签名认证

如果要正式发布一个Android程序,必须使用一个合适的私钥生成的数字证书来给程序签名,而不能使用adt插件或者ant工具生成的调试证书来发布。

数字证书都是有有效期的,Android只是在应用程序安装的时候才会检查证书的有效期。如果程序已经安装在系统中,即使证书过期也不会影响程序的正常功能。

7.Dalvik基于JVM的改进

几个class变为一个dex,constant pool,省内存

Zygote,copy-on-write shared,省内存,省cpu,省电

基于寄存器的bytecode,省指令,省cpu,省电

Trace-based JIT,省cpu,省电,省内存

8.ARGB_8888占用内存大小

本题的答案,是4byte,即ARGB各占用8个比特来描述。

9.apk安装卸载的原理

安装过程:复制apk安装包到data/app目录下,解压并扫描安装包,把dex文件(dalvik字节码)保存到dalvik-cache目录,并data/data目录下创建对应的应用数据目录。

卸载过程:删除安装过程中在上述三个目录下创建的文件及目录。

10.通过Intent传递一些二进制数据的方法有哪些?

使用Serializable接口实现序列化,这是Java常用的方法。

实现Parcelable接口,这里Android的部分类比如Bitmap类就已经实现了,同时Parcelable在Android AIDL中交换数据也很常见的。

11.横竖屏切换时Activity的生命周期

此时的生命周期跟清单文件里的配置有关系。

不设置Activity的android:configChanges时,切屏会重新调用各个生命周期默认首先销毁当前activity,然后重新加载。

设置Activity android:configChanges=”orientation|keyboardHidden|screenSize”时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法

12.Serializable 和 Parcelable 的区别

在使用内存的时候,Parcelable 类比 Serializable 性能高,所以推荐使用 Parcelable 类。

1. Serializable 在序列化的时候会产生大量的临时变量,从而引起频繁的 GC。

2. Parcelable 不能使用在要将数据存储在磁盘上的情况。尽管 Serializable 效率低点,但在这

种情况下,还是建议你用 Serializable 。

13.Android 中如何捕获未捕获的异常

自 定 义 一 个 Application , 比 如 叫 MyApplication 继 承 Application 实 现

UncaughtExceptionHandler。

覆写 UncaughtExceptionHandler 的 onCreate 和 uncaughtException 方法。

14.Android 的权限规则

Android 中的 apk 必须签名

基于 UserID 的进程级别的安全机制

默认 apk 生成的数据对外是不可见的

AndroidManifest.xml 中的显式权限声明

15.多线程间通信和多进程之间通信有什么不同,分别怎么实现?

一、进程间的通信方式

1. 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的

进程间使用。进程的亲缘关系通常是指父子进程关系。

2. 有名管道 (namedpipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的

通信。

3. 信号量(semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它

常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进

程间以及同一进程内不同线程之间的同步手段。

4. 消息队列( messagequeue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符

标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

5. 信号 (sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

6. 共享内存(shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内

存由一个进程创建,但多个进程都可以访问。共享内存是*快的 IPC 方式,它是针对其他进程间

通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间

的同步和通信。

7. 套接字(socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同

及其间的进程通信。

二、线程间的通信方式

1. 锁机制:包括互斥锁、条件变量、读写锁

*互斥锁提供了以排他方式防止数据结构被并发修改的方法。

*读写锁允许多个线程同时读共享数据,而对写操作是互斥的。

*条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁

的保护下进行的。条件变量始终与互斥锁一起使用。

2. 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量

3. 信号机制(Signal):类似进程间的信号处理

线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机

制。

16.说说 LruCache 底层原理

LruCache 使用一个 LinkedHashMap 简单的实现内存的缓存,没有软引用,都是强引用。如果添

加的数据大于设置的*大值,就删除*先缓存的数据来调整内存。

maxSize 是通过构造方法初始化的值,他表示这个缓存能缓存的*大值是多少。

size 在添加和移除缓存都被更新值,他通过 safeSizeOf 这个方法更新值。safeSizeOf 默认返回 1,

但一般我们会根据 maxSize 重写这个方法,比如认为 maxSize 代表是 KB 的话,那么就以 KB 为单

位返回该项所占的内存大小。

除异常外首先会判断 size 是否超过 maxSize,如果超过了就取出*先插入的缓存,如果不为空就

删掉,并把 size 减去该项所占的大小。这个操作将一直循环下去,直到 size 比 maxSize 小或者缓存

为空。

Android的4种文件类型Java,class,dex,apk

Android的4种文件类型Java,class,dex,apk

Java文件—–应用程序源文件

Android本身相当一部分都是用java编写而成(基本上架构图里头蓝色的部份都是用Java开发的),android的
应用必须使用java来开发

Class文件——Java编译后的目标文件
不像J2se,java编译成class就可以直接运行,android平台上class文件不能直接在android上运行。 由于Google
使用了自己的Dalvik来运行应用, 所以这里的class也肯定不能在AndroidDalvik的java环境中运行, android
的class文件实际上只是编译过程中的中间目标文件,需要链接成dex文件后才能在dalvik上运行

 

Dex文件—–Android平台上的可执行文件
Android虚拟机Dalvik支持的字节码文件格式Google在新发布的Android平台上使用了自己的Dalvik虚拟机
来定义, 这种虚拟机执行的并非Java字节码, 而是另一种字节码: dex格式的字节码。在编译Java代码之后,
通过Android平台上的工具可以将Java字节码转换成Dex字节码。虽然Google称Dalvik是为了移动设备定
做的,但是业界很多人认为这是为了规避向sun申请Javalicense。这个DalvikVM针对手机程式/CPU做过*
佳化,可以同时执行许多VM而不会占用太多Resource。
Apk文件——-Android上的安装文件
Apk是Android安装包的扩展名,一个Android安装包包含了与某个Android应用程序相关的所有文件。apk
文件将AndroidManifest.xml文件、应用程序代码(.dex文件)、资源文件和其他文件打成一个压缩包。一个工
程只能打进一个.apk文件

Java虚拟机工作原理详解 (一)

Java虚拟机工作原理详解 (一)

一、类加载器

首先来看一下java程序的执行过程。

%title插图%num

从这个框图很容易大体上了解java程序工作原理。首先,你写好java代码,保存到硬盘当中。然后你在命令行中输入

  1. javac YourClassName.java

此时,你的java代码就被编译成字节码(.class).如果你是在Eclipse IDE或者其他开发工具中,你保存代码的时候,开发工具已经帮你完成了上述的编译工作,因此你可以在对应的目录下看到class文件。此时的class文 件依然是保存在硬盘中,因此,当你在命令行中运行

  1. java YourClassName

就完成了上面红色方框中的工作。JRE的来加载器从硬盘中读取class文件,载入到系统分配给JVM的内存区域–运行数据区(Runtime Data Areas). 然后执行引擎解释或者编译类文件,转化成特定CPU的机器码,CPU执行机器码,至此完成整个过程。

接下来就重点研究一下类加载器究竟为何物?又是如何工作的?

首先看一下来加载器的一些特点,有点抽象,不过总有帮助的。

》》层级结构

类加载器被组织成一种层级结构关系,也就是父子关系。其中,Bootstrap是所有类加载器的父亲。如下图所示:

%title插图%num

–Bootstrap class loader:

当运行java虚拟机时,这个类加载器被创建,它加载一些基本的java API,包括Object这个类。需要注意的是,这个类加载器不是用java语言写的,而是用C/C++写的。

–Extension class loader:

这个加载器加载出了基本API之外的一些拓展类,包括一些与安全性能相关的类。(目前了解得不是很深,只能笼统说,待日后再详细说明)

–System Class Loader:

它加载应用程序中的类,也就是在你的classpath中配置的类。

–User-Defined Class Loader:

这是开发人员通过拓展ClassLoader类定义的自定义加载器,加载程序员定义的一些类。

》》委派模式(Delegation Mode)

仔细看上面的层次结构,当JVM加载一个类的时候,下层的加载器会将将任务委托给上一层类加载器,上一层加载检查它的命名空间中是否已经加载这个 类,如果已经加载,直接使用这个类。如果没有加载,继续往上委托直到顶部。检查完了之后,按照相反的顺序进行加载,如果Bootstrap加载器找不到这 个类,则往下委托,直到找到类文件。对于某个特定的类加载器来说,一个Java类只能被载入一次,也就是说在Java虚拟机中,类的完整标识是 (classLoader,package,className)。一个雷可以被不同的类加载器加载。

%title插图%num

举个具体的例子来说明,现在加入我有一个自己定义的类MyClass需要加载,如果不指定的话,一般交App(System)加载。接到任务 后,System检查自己的库里是否已经有这个类,发现没有之后委托给Extension,Extension进行同样的检查,发现还是没有继续往上委 托,*顶层的Boots发现自己库里也没有,于是根据它的路径(Java 核心类库,如java.lang)尝试去加载,没找到这个MaClass类,于是只好(人家看好你,交给你完成,你无能为力,只好交给别人啦)往下委托给 Extension,Extension到自己的路径(JAVA_HOME/jre/lib/ext)是找,还是没找到,继续往下,此时System加载 器到classpath路径寻找,找到了,于是加载到Java虚拟机。

现在假设我们将这个类放到JAVA_HOME/jre/lib/ext这个路径中去(相当于交给Extension加载器加载),按照同样的规则, *后由Extension加载器加载MyClass类,看到了吧,统一各类被两次加载到JVM,但是每次都是由不同的ClassLoader完成。

》》可见性限制

下层的加载器能够看到上层加载器中的类,反之则不行,也就是是说委托只能从下到上。

》》不允许卸载类

类加载器可以加载一个类,但是它不能卸载一个类。但是类加载器可以被删除或者被创建。

当类加载完毕之后,JVM继续按照下图完成其他工作:

%title插图%num

框图中各个步骤简单介绍如下:

Loading:文章前面介绍的类加载,将文件系统中的Class文件载入到JVM内存(运行数据区域)

Verifying:检查载入的类文件是否符合Java规范和虚拟机规范。

Preparing:为这个类分配所需要的内存,确定这个类的属性、方法等所需的数据结构。(Prepare a data structure that assigns the memory required by classes and indicates the fields, methods, and interfaces defined in the class.)

Resolving:将该类常量池中的符号引用都改变为直接引用。(不是很理解)

Initialing:初始化类的局部变量,为静态域赋值,同时执行静态初始化块。

那么,Class Loader在加载类的时候,究竟做了些什么工作呢?

要了解这其中的细节,必须得先详细介绍一下运行数据区域。

二、运行数据区域

Runtime Data Areas:当运行一个JVM示例时,系统将分配给它一块内存区域(这块内存区域的大小可以设置的),这一内存区域由JVM自己来管理。从这一块内存中分 出一块用来存储一些运行数据,例如创建的对象,传递给方法的参数,局部变量,返回值等等。分出来的这一块就称为运行数据区域。运行数据区域可以划分为6大 块:Java栈、程序计数寄存器(PC寄存器)、本地方法栈(Native Method Stack)、Java堆、方法区域、运行常量池(Runtime Constant Pool)。运行常量池本应该属于方法区,但是由于其重要性,JVM规范将其独立出来说明。其中,前面3各区域(PC寄存器、Java栈、本地方法栈)是 每个线程独自拥有的,后三者则是整个JVM实例中的所有线程共有的。这六大块如下图所示:

%title插图%num

》PC计数器:

每一个线程都拥有一个PC计数器,当线程启动(start)时,PC计数器被创建,这个计数器存放当前正在被执行的字节码指令(JVM指令)的地址。

》Java栈:

同样的,Java栈也是每个线程单独拥有,线程启动时创建。这个栈中存放着一系列的栈帧(Stack Frame),JVM只能进行压入(Push)和弹出(Pop)栈帧这两种操作。每当调用一个方法时,JVM就往栈里压入一个栈帧,方法结束返回时弹出栈 帧。如果方法执行时出现异常,可以调用printStackTrace等方法来查看栈的情况。栈的示意图如下:

%title插图%num

OK。现在我们再来详细看看每一个栈帧中都放着什么东西。从示意图很容易看出,每个栈帧包含三个部分:本地变量数组,操作数栈,方法所属类的常量池引用。

》局部(本地)变量数组:

局部(本地)变量数组中,从0开始按顺序存放方法所属对象的引用、传递给方法的参数、局部变量。举个例子:

 

  1. public void doSomething(int a, double b, Object o) {  
  2. }

这个方法的栈帧中的局部变量存储的内容分别是:

  1. 0: this  
  2. 1: a  
  3. 2,3:b  
  4. 4:0  

看仔细了,其中double类型的b需要两个连续的索引。取值的时候,取出的是2这个索引中的值。如果是静态方法,则数组第0个不存放this引用,而是直接存储传递的参数。》操作数栈:

操作数栈中存放方法执行时的一些中间变量,JVM在执行方法时压入或者弹出这些变量。其实,操作数栈是方法真正工作的地方,执行方法时,局部变量数组与操作数栈根据方法定义进行数据交换。例如,执行以下代码时,操作数栈的情况如下:

 

  1. int a = 90;  
  2. int b = 10;  
  3. int c = a + b;  

%title插图%num

注意在这个图中,操作数栈的地步是在上边,所以先压入的100位于上方。可以看出,操作数栈其实是一个数据临时存储区,存放一些中间变量,方法结束了,操作数栈也就没有啦。

》栈帧中数据引用:

除了局部变量数组和操作数栈之外,栈帧还需要一个常量池的引用。当JVM执行到需要常量池的数据时,就是通过这个引用来访问常量池的。栈帧中的数据 还要负责处理方法的返回和异常。如果通过return返回,则将该方法的栈帧从Java栈中弹出。如果方法有返回值,则将返回值压入到调用该方法的方法的 操作数栈中。另外,数据区中还保存中该方法可能的异常表的引用。下面的例子用来说明:

  1. class Example3C{  
  2.     public static void addAndPrint(){  
  3.         double result = addTwoTypes(1,88.88);  
  4.         System.out.println(result);
  5.     }
  6.     public static double addTwoTypes(int i, double d){  
  7.     return i+d;  
  8.     }
  9. }

执行上述代码时,Java栈如下图所示:%title插图%num

花些时间好好研究上图。一样需要注意的是,栈的底部在上方,先押人员addAndPrint方法的栈帧,再压入addTwoTypes方法的栈帧。上图*右边的文字说明有错误,应该是addTwoTypes的执行结果存放在addAndPrint的操作数栈中。

》》本地方法栈

当程序通过JNI(Java Native Interface)调用本地方法(如C或者C++代码)时,就根据本地方法的语言类型建立相应的栈。

》》方法区域

方法区域是一个JVM实例中的所有线程共享的,当启动一个JVM实例时,方法区域被创建。它用于存运行放常量池、有关域和方法的信息、静态变量、类 和方法的字节码。不同的JVM实现方式在实现方法区域的时候会有所区别。Oracle的HotSpot称之为永久区域(Permanent Area)或者永久代(Permanent Generation)。

》》运行常量池

这个区域存放类和接口的常量,除此之外,它还存放方法和域的所有引用。当一个方法或者域被引用的时候,JVM就通过运行常量池中的这些引用来查找方法和域在内存中的的实际地址。

》》堆(Heap)

堆中存放的是程序创建的对象或者实例。这个区域对JVM的性能影响很大。垃圾回收机制处理的正是这一块内存区域。

所以,类加载器加载其实就是根据编译后的Class文件,将java字节码载入JVM内存,并完成对运行数据处于的初始化工作,供执行引擎执行。

三、 执行引擎(Execution  Engine)

类加载器将字节码载入内存之后,执行引擎以Java 字节码指令为但愿,读取Java字节码。问题是,现在的java字节码机器是读不懂的,因此还必须想办法将字节码转化成平台相关的机器码。这个过程可以由 解释器来执行,也可以有即时编译器(JIT Compiler)来完成。

安卓遇到未捕获的 Java 异常会直接崩溃,为什么要这么设计

安卓遇到未捕获的 Java 异常会直接崩溃,为什么要这么设计

 

t4we · 20 小时 48 分钟前 · 1218 次点击

既然代码都跑在消息循环里,为什么非 debug 版本的 app,系统不主动捕获异常,然后处理下一个消息?这样用户体验不是更好吗?

12 条回复    2021-08-21 09:10:22 +08:00

WebKit
    13

WebKit   5 天前 via Android

这。无论起 java 还是 C 层的崩溃。。你可以自己拦截处理异常。
xingda920813
    14

xingda920813   4 天前

我觉得其实没有必要崩溃. 普通的 Java SE 和服务端的 Tomcat 都是这样, 一个线程抛未捕获的异常, 不会导致整个 JVM 结束. 一个请求异常, 那个请求会返回 500 错误, 但整个 Web 容器不会挂掉.
ikas
    15

ikas   4 天前

问题是..一旦出了异常,ui 线程中各种状态就会变得复杂..就跟为啥要限制 ui 使用单线程一样
关于消息循环,你也可以通过一个技巧来捕捉异常..但是即使你捕捉了异常..你会发现你能做得也很少..出错的 ui 部分就”卡住”了..这时候就需要复杂的来处理,,比如直接重建相关 ui..然后事情就各种复杂了…

//
new Handler(mainLooper).post(() -> {
while (true) {
try {
Looper.loop();
} catch (MainLoopExitException e) {
running = false;
return;
} catch (Throwable e) {
//处理逻辑
}
}
});
//github 其实是有这样的写好的异常处理的.已经带了 ui 部分的处理.不过我忘记了是啥了

haaro
    1

haaro   20 小时 2 分钟前   ❤️ 1

“系统主动捕获异常,然后处理下一个消息”会导致后续的逻辑出现更大更不可控的问题,有点类似连锁反应
misaka19000
    2

misaka19000   20 小时 0 分钟前

防止异常被吃掉吧
chendy
    3

chendy   19 小时 56 分钟前

开发者没处理的异常,系统也不知道该怎么处理
与其让 app 以未知的状态强行继续运行不如直接崩掉
gamexg
    4

gamexg   19 小时 56 分钟前

fail-fast

能编译时就编译时出错,
运行时发现错误就立刻出错,而不是把错误数据带到后面
方便排查问题

unco020511
    5

unco020511   19 小时 55 分钟前

关键系统不知道怎么运行了啊
AoEiuV020
    6

AoEiuV020   19 小时 53 分钟前

方便排查 bug 吧,等继续执行直到下不去了,这个源头就很难追溯了,
xylxAdai
    7

xylxAdai   19 小时 51 分钟前   ❤️ 1

不快点崩掉,等这个异常没处理在几分钟之后因为这个异常崩了,你堆栈咋找到嘛。
dqzcwxb
    8

dqzcwxb   19 小时 48 分钟前   ❤️ 1

因为你不关心会产生什么问题,你只是嫌麻烦
chengyiqun
    9

chengyiqun   19 小时 14 分钟前

那样我敢肯定会有一堆带着大量 bug 上线的 app.
zongren
    10

zongren   17 小时 29 分钟前

其实应该优化一下,个别异常可以不崩
silymore
    11

silymore   15 小时 2 分钟前 via iPhone

普通的 java 异步线程挂了还是正常跑啊
Keyi
    12

Keyi   50 分钟前

App 用户体验不是 Android 来保证而是 App 来保证的吧

Java 和 Python 并列第二、Julia 下滑,揭晓 RedMonk *新编程语言榜单!

近日,全球知名行业分析公司 RedMonk 发布了 Q3 的编程语言榜单,本排行榜基于 GitHub 和 Stack Overflow 两大社区中使用及讨论的编程语言热度进行排行,真实地反应了开发者对各大编程语言应用的现状,也希望透过此榜单能够为更多的从业者在工具的抉择层面带来一些借鉴。

%title插图%num

RedMonk 编程语言排行榜 TOP 20

通过调查与分析,RedMonk 编程语言排行榜 TOP 20 如下:

%title插图%num

  1. JavaScript
  2. Python
  3. Java
  4. PHP
  5. CSS
  6. C++
  7. C#
  8. TypeScript
  9. Ruby
  10. C
  11. Swift
  12. R
  13. Objective-c
  14. Shell
  15. Scala
  16. Go
  17. PowerShell
  18. Kotlin
  19. Rust
  20. Dart

%title插图%num

值得关注的变化

和上个季度的榜单相比,在本季度中,RedMonk 官方也总结出一些值得关注的重要变化:

  • Java

过去一段时间中,在 Python 迅猛的增速之下,Java 的脚步有所放缓,从原来的第二名下降到了第三位,就在很多从业者担心 Java 的使用率是否会进一步下降时,在本季度中,Java 重新回归,与 Python 并列榜单第二位。

不过,在编程语言层出不穷的发展趋势下,不少从业者似乎并不太看好 Java,甚至定期为企业基础设施的坚定者(Java)撰写墓志铭。但现实来看,Java 屹立不倒自然有其自身的发展优势,它曾作为用于数字电视机顶盒的一种主力编程语言,在不断变化的技术潮流中,仍然可以找到其能够胜任的地方。

这些年来,Java 在这些排名上的表现给人留下深刻印象,而且由于它表现出非凡的适应快速变化环境的能力,因此它是一种难以与之抗衡的语言。

  • TypeScript

TypeScript 在本季度的排名位居第八位,现在该语言面临*大的问题是其是否还能够持续增长甚至超越 C#、C++、PHP 等老牌编程语言?亦或是现在的排名已经到了它的*限?

对此,RedMonk 官方表示,现在一切说不准,但一年前这个时候,TypeScript 在排名所依据的综合得分中落后第 5 名语言 6 分,但这次差距只有两个点。当然,过去的表现也不总是能够代表其未来的发展。

  • Go/Kotlin/Rust

与 TypeScript 一样,Kotlin 和 Rust 在本次排名中都没有变化。一方面,这一现状可能会令该些语言的拥护者失望,但另一方面,它也反映了系统语言新出现的一些现实问题,即 Kotlin 和 Rust 的相对表现,以及 Go 的长期停滞不前会给我们带来一些思考。

多年来,Java 一直面临着强大挑战者的挑战,以争夺企业应用程序首选语言的称号。但是,正如上文所述,Java 并没有在被挑战的过程中受到一定的影响。事实上,与 Go、Kotlin 和 Rust 不同,Java 在排在第三位的两个季度之后,它的份额在此次成功增长。通过自身适应性和企业应用的习惯相结合,Java 仍在企业级应用程序市场中占有很大的份额,这也意味着 Go、Rust、Kotlin 这些挑战者能够带来的影响微乎其微,同时因为这些语言共享 JVM 平台,所以其与 Java 之间的竞争要远小于这些语言内部之间的竞争。

  • Dart

上个季度,RedMonk 在发布编程语言榜单时,曾分析了 Dart 正在逐渐上涨的原因:虽然其语法不如 Kotlin 简洁,但是随着 Flutter 框架的成熟发展,Dart 的未来可期。

如今经过了一个季度的时间,Dart 正如期待的那番,取代了 Perl 的位置成功进入榜单的 TOP 20。在实现这一小成就之后,现在的问题就是 Dart 能否持续保持着 TOP 20 的排名了。当前,排在其前面的 Kotlin 和 Rust 两门语言的受欢迎度要略胜 Dart 一筹,因此,Dart 能否在挑战中站稳自己的脚步,还得看接下来的表现。

  • Julia

不久前,Julia 所在的公司 Julia Computing 刚刚完成了 2400 万美元的 A 轮融资,这笔资金将被用于发展 Julia 生态系统,而 Julia 语言自设计之初也被寄予厚望,正如其开发团队所设想的那番,Julia 将集 Python、C、Ruby、R、Perl 等众多语言之所长:

我们想要一种拥有自由许可的开源语言,同时拥有 C 的速度和 Ruby 的灵活。我们想要一种同像性语言,有像 Lisp 这样真正的宏,也有像 Matlab 这样的浅显熟悉的数学符号。我们想要一门像 Python 一样可用于通用编程,像 R 一样易于统计,像 Perl 一样自然地用于字符串处理,像 Matlab 一样强大的线性代数,像 shell 一样擅长将程序粘合在一起的语言。它简单易学,却能让严苛的黑客为之倾心。我们希望它是交互式的,具备可编译性。

不过,通过调查数据显示,Julia 在竞争愈发激烈的技术圈中,排名有所下滑,一年前 Julia 徘徊于 TOP 20 的边缘位置,位于第 24 位,但在本季度中它跌至第 28 位。值得注意的是,排名越低,语言之间的*对差异变得越小,但是对于一种语言而言,这样的负面表现并不令人鼓舞。

对此,RedMonk 深入分析 Julia 语言下滑的原因时发现,在 Julia 发展过程中,由于其非常注重分析等功能的构建,Julia 经常被开发者列入与 Python、R 等常用统计分析工具的竞争中,其中 Python、R 语言早已在各种实践中证明了自己的受欢迎度与可持续性,此时,新兴 Julia 语言的加入就有些黯然失色了。在此,也希望获得新一轮融资的 Julia 在未来会有亮眼的特性出现。

完整的榜单:

%title插图%num

图 RedMonk Q3 编程语言排行榜

参考来源:

The RedMonk Programming Language Rankings: June 2021

Java知识全面总结:并发编程+JVM+设计模式+常用框架+….

本文整理的Java知识体系主要包括基础知识,工具,并发编程,数据结构与算法,数据库,JVM,架构设计,应用框架,中间件,微服务架构,分布式架构等内容。同时也有作为程序员的一些思考,包含了作为一个Java工程师在开发工作学习中需要用到或者可能用到的*大部分知识。

学习需要的也不是一腔热血,需要的是长期的规划,这里分享一些思维方式,大家可以自行进行查漏补缺和规划:千里之行始于足下,希望大家根据自己的薄弱点,查缺补漏,学的精通一点,从现在开始行动起来。

以下是我们的成长路线总结内容,希望能够帮助你们!(免费分享给大家)

%title插图%num

%title插图%num

%title插图%num

%title插图%num

%title插图%num

%title插图%num

%title插图%num

 

Java中的增强for循环的实现原理与坑

在JAVA中,遍历集合和数组一般有以下三种形式:

for (int i = 0; i < list.size(); i++) {
   System.out.print(list.get(i) + ",");
}

Iterator iterator = list.iterator();
while (iterator.hasNext()) {
   System.out.print(iterator.next() + ",");
}

for (Integer i : list) {
   System.out.print(i + ",");
}

*种是普通的for循环遍历、第二种是使用迭代器进行遍历,第三种我们一般称之为增强for循环(for each)。

实现原理

可以看到,第三种形式是JAVA提供的语法糖,这里我们剖析一下,这种增强for循环底层是如何实现的。

我们对以下代码进行反编译

for (Integer : list) {
   System.out.println(i);
}

反编译后:

Integer i;
for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(i)){
   i = (Integer)iterator.next();        
}

反编译后的代码其实比较复杂,我们按照执行顺序拆解一下:

Integer i; 定义一个临时变量i

Iterator iterator = list.iterator(); 获取List的迭代器

iterator.hasNext(); 判断迭代器中是否有未遍历过的元素

i = (Integer)iterator.next(); 获取*个未遍历的元素,赋值给临时变量i

System.out.println(i) 输出临时变量i的值

如此循环往复,直到遍历完List中的所有元素。

通过反编译,我们看到,其实JAVA中的增强for循环底层是通过迭代器模式来实现的。

增强for循环的坑

这里说是增强for循环的坑,其实主要是因为有些人不了解增强for循环的实现原理而可能踩入的坑。

既然增强for循环通过迭代器实现,那么必然有迭代器的特性。

Java中有fail-fast机制。在使用迭代器遍历元素的时候,在对集合进行删除的时候一定要注意,使用不当有可能发生ConcurrentModificationException,这是一种运行时异常,编译期并不会发生。只有在程序真正运行时才会爆发。

如以下代码:

for (Student stu : students) {    
   if (stu.getId() == 2)     
       students.remove(stu);    
}

会抛出ConcurrentModificationException异常。

Iterator是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出

java.util.ConcurrentModificationException异常。

所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。

但你可以使用 Iterator 本身的方法 remove() 来删除对象,Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

正确的在遍历的同时删除元素的姿势:

Iterator<Student> stuIter = students.iterator();    
while (stuIter.hasNext()) {    
   Student student = stuIter.next();    
   if (student.getId() == 2)    
       stuIter.remove();//这里要使用Iterator的remove方法移除当前对象,如果使用List的remove方法,则同样会出现ConcurrentModificationException    
}

好啦,这里给你介绍了增强for循环的实现原理,以及使用不当可能踩入的坑。所以,虽然是一个简单的for-each语法,但是也要了解其原理,不然可能导致一些莫名其妙的问题。

转载自:http://mp.weixin.qq.com/s?__biz=MzI3NzE0NjcwMg==&mid=2650121134&idx=1&sn=a34a1bd547f00e479e9f6dbde8848fe4&chksm=f36bbe8fc41c3799d1bb2c781f81f51e28651d2fb8eb5670a31caac5ba782b66416e5fdf1b1c&mpshare=1&scene=1&srcid=0413o4rBaecPGUVy6Bi9lzt7#rd

Java 字符串转成运算公式

转载自:https://www.cnblogs.com/cocoat/p/6956610.html
GroovyShell 实现
public static void main(String args[]) {

        Binding binding = new Binding();

        binding.setVariable("F",2.5);
        binding.setVariable("T",30);
        binding.setVariable("A",100);
        binding.setVariable("P0",100);

        binding.setVariable("language", "Groovy");

        GroovyShell shell = new GroovyShell(binding);

        Object F1 =shell.evaluate("P1=(1+0.1 * (F/100) * T)*P0; return P1 ");

        Object F2 =shell.evaluate("P1=P0*(0.055*0.20+1.0011)+A; return P1 ");

        System.out.println(F1);
        System.out.println(F2);

    }
<dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>2.4.7</version>
</dependency>
ScriptEngine实现
public static void main(String args[]) {

        ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
        Compilable compilable = (Compilable) engine;
        Bindings bindings = engine.createBindings(); //Local级别的Binding
        String script = "(1+0.1 * (F/100) * T)*P0"; //定义函数并调用
        CompiledScript JSFunction = null; //解析编译脚本函数
        try {
            JSFunction = compilable.compile(script);
            bindings.put("F", 2.5);
            bindings.put("T", 30);
            bindings.put("A", 100);
            bindings.put("P0", 100);
            Object result = JSFunction.eval(bindings);
            System.out.println(result); //调用缓存着的脚本函数对象,Bindings作为参数容器传入
        } catch (ScriptException e) {
            e.printStackTrace();
        }


    }

为什么说Java中只有值传递

对于初学者来说,要想把这个问题回答正确,是比较难的。在第二天整理答案的时候,我发现我竟然无法通过简单的语言把这个事情描述的很容易理解,遗憾的是,我也没有在网上找到哪篇文章可以把这个事情讲解的通俗易懂。所以,就有了我写这篇文章的初衷。这篇文章中,我从什么是方法的实际参数和形式参数开始,给你讲解为什么说Java中只有值传递。

辟谣时间

关于这个问题,在StackOverflow上也引发过广泛的讨论,看来很多程序员对于这个问题的理解都不尽相同,甚至很多人理解的是错误的。还有的人可能知道Java中的参数传递是值传递,但是说不出来为什么。

在开始深入讲解之前,有必要纠正一下大家以前的那些错误看法了。如果你有以下想法,那么你有必要好好阅读本文。

错误理解一:值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。

错误理解二:Java是引用传递。

错误理解三:传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。

实参与形参

我们都知道,在Java中定义方法的时候是可以定义参数的。比如Java中的main方法,public static void main(String[] args),这里面的args就是参数。参数在程序语言中分为形式参数和实际参数。

形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。

实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。

简单举个例子:

public static void main(String[] args) { ParamTest pt = new ParamTest(); pt.sout(“Hollis”);//实际参数为 Hollis }

public void sout(String name) { //形式参数为 name System.out.println(name); }

实际参数是调用有参方法的时候真正传递的内容,而形式参数是用于接收实参内容的参数。

值传递与引用传递

上面提到了,当我们调用一个有参函数的时候,会把实际参数传递给形式参数。但是,在程序语言中,这个传递过程中传递的两种情况,即值传递和引用传递。我们来看下程序语言中是如何定义和区分值传递和引用传递的。

值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

有了上面的概念,然后大家就可以写代码实践了,来看看Java中到底是值传递还是引用传递 ,于是,*简单的一段代码出来了:

public static void main(String[] args) { ParamTest pt = new ParamTest();

int i = 10; pt.pass(10); System.out.println(“print in main , i is ” + i); }

public void pass(int j) { j = 20; System.out.println(“print in pass , j is ” + j); }

上面的代码中,我们在pass方法中修改了参数j的值,然后分别在pass方法和main方法中打印参数的值。输出结果如下:

print in pass , j is 20 print in main , i is 10

可见,pass方法内部对name的值的修改并没有改变实际参数i的值。那么,按照上面的定义,有人得到结论:Java的方法传递是值传递。

但是,很快就有人提出质疑了(哈哈,所以,不要轻易下结论咯。)。然后,他们会搬出以下代码:

public static void main(String[] args) { ParamTest pt = new ParamTest();

User hollis = new User(); hollis.setName(“Hollis”); hollis.setGender(“Male”); pt.pass(hollis); System.out.println(“print in main , user is ” + hollis); }

public void pass(User user) { user.setName(“hollischuang”); System.out.println(“print in pass , user is ” + user); }

同样是一个pass方法,同样是在pass方法内修改参数的值。输出结果如下:

print in pass , user is User{name=’hollischuang’, gender=’Male’} print in main , user is User{name=’hollischuang’, gender=’Male’}

经过pass方法执行后,实参的值竟然被改变了,那按照上面的引用传递的定义,实际参数的值被改变了,这不就是引用传递了么。于是,根据上面的两段代码,有人得出一个新的结论:Java的方法中,在传递普通类型的时候是值传递,在传递对象类型的时候是引用传递。

但是,这种表述仍然是错误的。不信你看下面这个参数类型为对象的参数传递:

public static void main(String[] args) { ParamTest pt = new ParamTest();

String name = “Hollis”; pt.pass(name); System.out.println(“print in main , name is ” + name); }

public void pass(String name) { name = “hollischuang”; System.out.println(“print in pass , name is ” + name); }

上面的代码输出结果为

print in pass , name is hollischuang print in main , name is Hollis

这又作何解释呢?同样传递了一个对象,但是原始参数的值并没有被修改,难道传递对象又变成值传递了?

Java中的值传递

上面,我们举了三个例子,表现的结果却不一样,这也是导致很多初学者,甚至很多高级程序员对于Java的传递类型有困惑的原因。

其实,我想告诉大家的是,上面的概念没有错,只是代码的例子有问题。来,我再来给大家画一下概念中的重点,然后再举几个真正恰当的例子。

值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

那么,我来给大家总结一下,值传递和引用传递之前的区别的重点是什么。

pass

我们上面看过的几个pass的例子中,都只关注了实际参数内容是否有改变。如传递的是User对象,我们试着改变他的name属性的值,然后检查是否有改变。其实,在实验方法上就错了,当然得到的结论也就有问题了。

为什么说实验方法错了呢?这里我们来举一个形象的例子。再来深入理解一下值传递和引用传递,然后你就知道为啥错了。

你有一把钥匙,当你的朋友想要去你家的时候,如果你直接把你的钥匙给他了,这就是引用传递。这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。

你有一把钥匙,当你的朋友想要去你家的时候,你复刻了一把新钥匙给他,自己的还在自己手里,这就是值传递。这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。

但是,不管上面那种情况,你的朋友拿着你给他的钥匙,进到你的家里,把你家的电视砸了。那你说你会不会受到影响?而我们在pass方法中,改变user对象的name属性的值的时候,不就是在“砸电视”么。

还拿上面的一个例子来举例,我们真正的改变参数,看看会发生什么?

public static void main(String[] args) { ParamTest pt = new ParamTest();

User hollis = new User(); hollis.setName(“Hollis”); hollis.setGender(“Male”); pt.pass(hollis); System.out.println(“print in main , user is ” + hollis); }

public void pass(User user) { user = new User(); user.setName(“hollischuang”); user.setGender(“Male”); System.out.println(“print in pass , user is ” + user); }

上面的代码中,我们在pass方法中,改变了user对象,输出结果如下:

print in pass , user is User{name=’hollischuang’, gender=’Male’} print in main , user is User{name=’Hollis’, gender=’Male’}

我们来画一张图,看一下整个过程中发生了什么,然后我再告诉你,为啥Java中只有值传递。

pass1

稍微解释下这张图,当我们在main中创建一个User对象的时候,在堆中开辟一块内存,其中保存了name和gender等数据。然后hollis持有该内存的地址0x123456(图1)。当尝试调用pass方法,并且hollis作为实际参数传递给形式参数user的时候,会把这个地址0x123456交给user,这时,user也指向了这个地址(图2)。然后在pass方法内对参数进行修改的时候,即user = new User();,会重新开辟一块0X456789的内存,赋值给user。后面对user的任何修改都不会改变内存0X123456的内容(图3)。

上面这种传递是什么传递?肯定不是引用传递,如果是引用传递的话,在user=new User()的时候,实际参数的引用也应该改为指向0X456789,但是实际上并没有。

通过概念我们也能知道,这里是把实际参数的引用的地址复制了一份,传递给了形式参数。所以,上面的参数其实是值传递,把实参对象引用的地址当做值传递给了形式参数。

我们再来回顾下之前的那个“砸电视”的例子,看那个例子中的传递过程发生了什么。

pass2

同样的,在参数传递的过程中,实际参数的地址0X1213456被拷贝给了形参,只是,在这个方法中,并没有对形参本身进行修改,而是修改的形参持有的地址中存储的内容。

所以,值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。在判断实参内容有没有受影响的时候,要看传的的是什么,如果你传递的是个地址,那么就看这个地址的变化会不会有影响,而不是看地址指向的对象的变化。就像钥匙和房子的关系。

那么,既然这样,为啥上面同样是传递对象,传递的String对象和User对象的表现结果不一样呢?我们在pass方法中使用name = "hollischuang";试着去更改name的值,阴差阳错的直接改变了name的引用的地址。因为这段代码,会new一个String,在把引用交给name,即等价于name = new String("hollischuang");。而原来的那个”Hollis”字符串还是由实参持有着的,所以,并没有修改到实际参数的值。

pass3

所以说,Java中其实还是值传递的,只不过对于对象参数,值的内容是对象的引用。

总结

无论是值传递还是引用传递,其实都是一种求值策略(Evaluation strategy)。在求值策略中,还有一种叫做按共享传递(call by sharing)。其实Java中的参数传递严格意义上说应该是按共享传递。

按共享传递,是指在调用函数时,传递给函数的是实参的地址的拷贝(如果实参在栈中,则直接拷贝该值)。在函数内部对参数进行操作时,需要先拷贝的地址寻找到具体的值,再进行操作。如果该值在栈中,那么因为是直接拷贝的值,所以函数内部对参数进行操作不会对外部变量产生影响。如果原来拷贝的是原值在堆中的地址,那么需要先根据该地址找到堆中对应的位置,再进行操作。因为传递的是地址的拷贝所以函数内对值的操作对外部变量是可见的。

简单点说,Java中的传递,是值传递,而这个值,实际上是对象的引用。

而按共享传递其实只是按值传递的一个特例罢了。所以我们可以说Java的传递是按共享传递,或者说Java中的传递是值传递。

内存屏障

为什么会有内存屏障

  • 每个CPU都会有自己的缓存(有的甚至L1,L2,L3),缓存的目的就是为了提高性能,避免每次都要向内存取。但是这样的弊端也很明显:不能实时的和内存发生信息交换,分在不同CPU执行的不同线程对同一个变量的缓存值不同。
  • 用volatile关键字修饰变量可以解决上述问题,那么volatile是如何做到这一点的呢?那就是内存屏障,内存屏障是硬件层的概念,不同的硬件平台实现内存屏障的手段并不是一样,java通过屏蔽这些差异,统一由jvm来生成内存屏障的指令。

内存屏障是什么

  • 硬件层的内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
  • 内存屏障有两个作用:
  1. 阻止屏障两侧的指令重排序;
  2. 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
  • 对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;
  • 对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的*新数据更新写入主内存,让其他线程可见。

java内存屏障

  • java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。
  • LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  • StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
  • LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
  • StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中*大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

volatile语义中的内存屏障

  • volatile的内存屏障策略非常严格保守,非常悲观且毫无安全感的心态:

在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;
在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;

  • 由于内存屏障的作用,避免了volatile变量和其它指令重排序、线程之间实现了通信,使得volatile表现出了锁的特性。

final语义中的内存屏障

  • 对于final域,编译器和CPU会遵循两个排序规则:
  1. 新建对象过程中,构造体中对final域的初始化写入和这个对象赋值给其他引用变量,这两个操作不能重排序;(废话嘛)
  2. 初次读包含final域的对象引用和读取这个final域,这两个操作不能重排序;(晦涩,意思就是先赋值引用,再调用final值)
  • 总之上面规则的意思可以这样理解,必需保证一个对象的所有final域被写入完毕后才能引用和读取。这也是内存屏障的起的作用:
  • 写final域:在编译器写final域完毕,构造体结束之前,会插入一个StoreStore屏障,保证前面的对final写入对其他线程/CPU可见,并阻止重排序。
  • 读final域:在上述规则2中,两步操作不能重排序的机理就是在读final域前插入了LoadLoad屏障。
  • X86处理器中,由于CPU不会对写-写操作进行重排序,所以StoreStore屏障会被省略;而X86也不会对逻辑上有先后依赖关系的操作进行重排序,所以LoadLoad也会变省略。
友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速