java深入源码级的面试题(有难度)
哪些情况下的对象会被垃圾回收机制处理掉?
1.引用计数器标记为0的对象
2.对象到gc roots没有任何引用链
3.那些属于软引用、弱引用、虚引用的对象。
运行时数据区域包括哪些?
运行时数据区会把它所管理的程序,为它划分不同的数据区域,每个区域都有相应的作用。
程序计数器
虚拟机栈
本地方法栈
Java 堆
方法区
运行时常量池
直接内存
1.程序计数器
程序计数器就是当前线程执行字节码的行号指示器,每个线程都有不同的计数器,所以计算器是私有的。
2.虚拟机栈
JAVA虚拟机栈是线程私有的,生命周期和线程相同,java虚拟机栈描述的就是JAVA方法的内存模型。每个方法执行时都会创建一个栈帧(Stack Frame),用于存储。
- 局部变量
- 操作数栈
- 动态链接
- 方法出口信息
每一个方法被调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
这个区域有两种异常情况:
StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度
OutOfMemoryError:虚拟机栈扩展到无法申请足够的内存时
3.本地方法栈
- 虚拟机栈为虚拟机执行 Java 方法(字节码)服务。
- 本地方法栈(Native Method Stacks)为虚拟机使用到的 Native 方法服务。
4.Java 堆(线程共享)
java堆是虚拟机管理内存中最大的一块,在虚拟机创建时启动。堆的唯一作用就是存放对象的实例,所有对象实例以及数组都要在堆上分配,他是被所有线程共享的块区域,JAVA堆可以处于物理上连续的内存空间中。
堆还可以细分
- 新生代
- 老年代
5.方法区(线程共享)
方法区与JAVA堆一样,是各个线程共享的内存区域,它用于存储已经被JVM加载的类信息、常量、静态变量、即实编译器编译后的代码。
6.运行时常量池
运行时常量池是方法区的一部分,用于存储编译生成的各种字面量符号引用,在类加载后进入方法区的运行时常量池中存放。受方法区内存限制,当常量池无法申请内存时会抛出OOM。
7.直接内存
本机直接内存的分配不会受到JAVA堆大小的限制,但是会受到本机总内存大小的限制,可能会导致OOM的出现。
讲一下常见编码方式?
ASCII
ASCII 码,总共有 128 个,用一个字节的低 7 位表示,0~31 是控制字符如换行回车删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来。GBK
它的出现是为了扩展 GB2312,加入更多的汉字,它的编码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表示 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说用 GB2312 编码的汉字可以用 GBK 来解码,并且不会有乱码。UTF-8
如果一个字节,最高位(第 8 位)为 0,表示这是一个 ASCII 字符(00 - 7F)。可见,所有 ASCII 编码已经是 UTF-8 了。
如果一个字节,以 11 开头,连续的 1 的个数暗示这个字符的字节数,例如:110xxxxx 代表它是双字节 UTF-8 字符的首字节。
如果一个字节,以 10 开始,表示它不是首字节,需要向前查找才能得到当前字符的首字节UTF-16
统一采用两个字节表示一个字符,虽然在表示上非常简单方便,但是也有其缺点,有很大一部分字符用一个字节就可以表示的现在要两个字节表示,存储空间放大了一倍,在现在的网络带宽还非常有限的今天,这样会增大网络传输的流量,而且也没必要。而 UTF-8 采用了一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以是由 1~6 个字节组成。
utf-8编码中的中文占几个字节;int型几个字节?
少数汉字占用三个字节,大部分都是四个字节。数字和字母都是一个字节。
Java的异常体系
Throwable
是整个Java异常体系的顶层父类,它有两个子类,分别是:Error和Exception。Error
表示系统致命错误,程序无法处理的错误,比如OutOfMemoryError、ThreadDeath等。这些错误发生时,Java虚拟机只能终止线程。Exception
是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。
String为什么要设计成不可变的?
安全性
不可变的天生线程安全
被当做map key,不可变也很安全。
string经常作为参数,不可变减少安全问题效率
字符串池节省空间
一个string对应一个hashCode,减少计算。扩展
可以用反射来改变string中value的值,严格意义上说并非不可变。
Object类的equal和hashCode方法重写,为什么?
Android基础知识点
Activity之间的通信方式
- getIntent获取一个Bundle来进行数据传输,bundle支持序列化和一些基本数据类型的传递。
- 广播
- SharedPreference/SQLite/File/Android剪切板等
- 静态变量也可以
- application存储也可以
外部方式
- SharedPreference
- Sqlite
- 利用IO流
- Service
- 剪切板
Activity上有Dialog的时候按Home键时的生命周期
两个Activity 之间跳转时必然会执行的是哪几个方法?
- A
onStop
onPause - B
onCreate
onStart
onResume
前台切换到后台,然后再回到前台,Activity生命周期回调方法。弹出Dialog,生命值周期回调方法。
前台切后台
onPause
onSaveInstanceState
onStop
后台切前台
onReStart
onStart
onResume
弹出Dialog
不会执行任何生命周期方法,但是会调用activity的onWindowsFocusChange方法返回false,因为焦点到了Dialog上。
Activity状态保存于恢复
- 通过onSaveInstaenceState保存数据
- 恢复可以在onCreate中的savedInstanceState参数获取之前保存的值
如何实现Fragment的滑动?
ViewPager套二个fragment就可以滑动了,如果是想滑动fragment里面的内容,就实现对应view的onTouchEvent方法处理move方法。
fragment之间传递数据的方式?
- setArguments getArguments 传递Bundle
- 接口回调
- 静态变量
- getApplication
- io
- sqlite
Activity 怎么和Service 绑定?
通过bindService方式启动一个服务,创建ServiceConnection对象,它有一个onServiceConnected方法会返回一个IBinder对象,我们可以通过这个对象来操作service中的方法。
public class TestSerice extends Service {
private MyBind mMyBind = new MyBind();
@Override
public void onCreate() {
super.onCreate();
Log.i("jinwei", "onCreate运行服务");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i("jinwei", "销毁服务");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("jinwei", "onStartCommand服务");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
Log.i("jinwei", "onStart服务");
}
@Override
public boolean onUnbind(Intent intent) {
Log.i("jinwei", "onUnbind服务");
return super.onUnbind(intent);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i("jinwei", "onBind服务");
return mMyBind;
}
class MyBind extends Binder {
public void startDownload() {
Log.i("jinwei", "startDownload");
}
}
ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i("jinweilog", "onServiceConnected" + name);
mMyBind = (TestSerice.MyBind) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i("jinweilog", "onServiceDisconnected");
}
};
怎么在Activity 中启动自己对应的Service?
startService
startService(intent)启动的服务意味着和调用者的生命周期就无关系了,调用者如果被销毁了但是它调起得服务不会销毁,它还会继续在后台运行,就是启动服务器的进程被杀死了但是服务还会继续运行。bindService
这种方式启动的服务生命周期就是和调用者绑定在一起了,调用者退出了服务也就退出了。
service和activity怎么进行数据交互?
Activity调用bindService (Intent service, ServiceConnectionconn, int flags)方法,得到Service对象的一个引用,这样Activity可以直接调用到Service中的方法.
Service向Activity发送消息,可以使用广播.
Service的开启方式
startService
startService(intent)启动的服务意味着和调用者的生命周期就无关系了,调用者如果被销毁了但是它调起得服务不会销毁,它还会继续在后台运行,就是启动服务器的进程被杀死了但是服务还会继续运行。
bindService
这种方式启动的服务生命周期就是和调用者绑定在一起了,调用者退出了服务也就退出了。
请描述一下Service 的生命周期
startService情况
onCreate
onStartCommand
onDestroy-
bindService情况
onCreate
onBind
onUnbind
onDertory
谈谈你对ContentProvider的理解
1.需要暴露数据给其他应用,对自己的应用的数据库进行增删改查操作。
2.ContentProvider提供了对底层数据库操作的抽象,使用不需要关心底层数据的一些操作,即使是底层数据库变化了没关系。
3.exdents ContentProvider 实现增删改查的方法,这个类是其他应用操作数据库的一个纽带,任何操作都要经过它。
说说ContentProvider、ContentResolver、ContentObserver 之间的关系
ContentProvider
内容提供者,用于对外提供数据,一个抽象类,里面包含了增删改查的抽象方法,数据的具体实现就是在这个方法里面实现。ContentResolver
内容解析者, 用于获取内容提供者提供的数据,它是Context的一个抽象类,可以在activity中获取到。ContentObserver
内容监听器, 可以监听数据的改变状态
广播的分类
- 普通广播(Normal Broadcast)
- 系统广播(System Broadcast)
- 有序广播(Ordered Broadcast)
- App应用内广播(Local Broadcast)
通过 LocalBroadcastManager
- 粘性广播
由于在Android5.0 & API 21中已经失效
广播使用的方式和场景
- 动态注册
public void registerBroadCast() {
IntentFilter filter = new IntentFilter();
filter.addAction("com.guangbo");//设置过滤,不设置会收不到。
registerReceiver(new MyBroadCast(), filter);
}
- 静态注册
//静态注册常驻广播
- 使用场景
- 系统级别
广播是基于发布/订阅事件模型设计的,我们可以通过它来监听系统级别的一些事件,比如耳机的拔插、网络的变动、蓝牙变动等等 - 自定义级别
我们可以通过继承BroadcastReceiver类来实现自己的广播接收者,比如一个app皮肤变更的逻辑,需要变更的地方就实现这个广播,当发出一个广播的时候全部的注册广播的地方都能收到。
在manifest 和代码中如何注册和使用BroadcastReceiver?
本地广播和全局广播有什么差别?
manifest
通过代码注册
通过registerReceiver来注册,传入一个IntentFilter
IntentFilter filter = new IntentFilter();
filter.addAction("com.guangbo");//设置过滤,不设置会收不到。
registerReceiver(new MyBroadCast(), filter);
BroadcastReceiver,LocalBroadcastReceiver 区别
全局广播
发送的广播支持其他应用接收
支持静态注册,程序没有运行也能接收到广播
可以拦截广播,支持优先级
安全性低本地广播
只能在本应用发布和接收广播
只有在程序运行的时候发送和接收广播
只能在本应用使用所以安全性高
AlertDialog,popupWindow,Activity区别
AlertDialog
AlertDialog是Dialog的子类,主要用于在activity之上弹出一些对话框提示,可以设置title,message,关闭和确认的but监听,可以自定义布局,通过show方法来弹出。Popuwindow
popuwindow是悬浮在actiivty之上的窗口,可以接收任意view作为布局,需要指定宽高。activity四大组件
区别
alertDialog是非堵塞的,popuwindow是堵塞的。dialog弹出来后我们可以做其他操作,比如物理back会先关闭dialog。但是windows需要先手动调动dmiss方法才能进行其他操作。
Application 和 Activity 的 Context 对象的区别
application和activity都是Context的子类,Context是android中的上下问,我们任何组件都需要依赖Context这个环境。application和activity维护的生命周期不一样,app是维护一个全局的context,后者是维护一个activity的生命周期。
Android属性动画特性
- 动画过程中也可以响应View得点击事件
- 可以对任何对象做动画,如果对象有设置属性,则传入对象需要有get set方法
- 可以控制动画集合的执行顺序。
- 能够监听到动画的变化数值,可以根据数值自定义动画效果
LinearLayout、RelativeLayout、FrameLayout的特性及对比,并介绍使用场景。
LinearLayout
LinearLayout适合那些布局固定的情况,比如view顶级视图DecorView就是一个LinearLayout,包含一个顶部标题栏,顶部的导航栏这些都是可以确定的。RelativeLayout
RelativeLayout是相对布局,主要是相对于某一个view的上下左右、相对于布局的上下左右、居中的显示FrameLayout
FrameLayout中放进去的布局默认都是在左上角,新进去的会覆盖前面的view对比
- RelativeLayout会调用二次Meause,LinearLayout如果有widght属性也会调用二次
- 如果是布局确认的情况下最好使用LinearLayout,RelativeLayout可以减少一些布局层级,从跟布局来说LinearLayout的效率要高。
谈谈对接口与回调的理解
- 接口
接口和回调是相辅相成的,实现一个接口就必然会有调用。回调的意思就是回头在调用,想什么时候调就什么时候调用。
接口也是多态的一种,由于在Java中实现的是单继承规则,所以导致,如何我们需要扩展类更多的属性时,单继承有了一定限制。接口可以解决这个问题,Java中一个类可以实现多个接口。接口的出现可以让我们不需要关系接口内部的具体实现,调用这只要持有这个接口就能调用。如果需要扩展更多的功能我们只需要扩展实现这个接口的类,不会影响到之前的实现类。
介绍下SurfView
SurfaceView是一个专门用于做频繁绘制的View子类,它的绘制操作是在子线程中执行,所以频繁绘制不会阻塞线程,使用它去做一些需要频繁绘制和长时间绘制效果会高很多。而如果这种操作放入到View中去做的话就不合适了,首先View的刷新UI操作都是需要在UI线程中,也就是我们的主线程中。如果去执行一些需要长时间绘制,就会造成UI阻塞造成无法响应其他时间
RecycleView的使用
- 导入支持包
- 继承一个RecycleView.Adapter
- 实现方法
- 创建一个继承RecycleView.ViewHolder得子类,用于空间的绑定
- 在bindViewHolder中设置view数据。
序列化的作用,以及Android两种序列化的区别
Serializable是JAVA中提供的系列化接口,在需要实现的类中实现接口,实现它后自动会做序列化操作。这种方式的缺点是
使用的反射序列化过程比较慢,在序列化过程会创建很多临时变量,容易触发GC。
Parcelable 速度快是android自己提供的序列化接口。据 google 工程师的说法,这些代码将会运行地特别快。原因之一就是我们已经清楚地知道了序列化的过程,而不需要使用反射来推断。同时为了更快地进行序列化,对象的代码也需要高度优化
差值器
插值器(Interpolator)决定 值 的变化规律(匀速、加速blabla),即决定的是变化趋势,变化是非线性的。
public interface TimeInterpolator {
/**
* Maps a value representing the elapsed fraction of an animation to a value that represents
* the interpolated fraction. This interpolated value is then multiplied by the change in
* value of an animation to derive the animated value at the current elapsed animation time.
*
* @param input A value between 0 and 1.0 indicating our current point
* in the animation where 0 represents the start and 1.0 represents
* the end
* @return The interpolation value. This value can be more than 1.0 for
* interpolators which overshoot their targets, or less than 0 for
* interpolators that undershoot their targets.
*/
float getInterpolation(float input);
}
估值器
设置 属性值 从初始值过渡到结束值 的变化具体数值
fraction:动画完成度,插值器getInterpolation()的返回值(input的值决定了fraction的值),代表时间流逝的百分比
startValue:动画的初始值
endValue:动画的结束值
public interface TypeEvaluator {
/**
* This function returns the result of linearly interpolating the start and end values, with
* fraction
representing the proportion between the start and end values. The
* calculation is a simple parametric calculation: result = x0 + t * (x1 - x0)
,
* where x0
is startValue
, x1
is endValue
,
* and t
is fraction
.
*
* @param fraction The fraction from the starting to the ending values
* @param startValue The start value.
* @param endValue The end value.
* @return A linear interpolation between the start and end values, given the
* fraction
parameter.
*/
public T evaluate(float fraction, T startValue, T endValue);
}
Android中数据存储方式
- 文件存储
- SharedPreferences
- SQLite数据库存储
- ContentProvider
- 网络存储
Java中实现多态的机制是什么?
重载多态
不同的子类可以重载父类方法,实现不同的逻辑。参数重载
不同参数类型,不同参数数量接口多态
不同实现类实现接口,内部实现逻辑不一样。但是调用方法可以是一样的。类的多态
不同子类继承同一个父类,重载父类方法,子类内部实现了不同的逻辑。
如何将一个Java对象序列化到文件里?
public static void main(String[] args) {
String path = "/Users/jw/莱熙/bean";
//序列化
ClassA bean = new ClassA();
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
oos.writeObject(bean);
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
//反序列化
try {
ObjectInputStream ojs = new ObjectInputStream(new FileInputStream(path));
ClassA o = (ClassA) ojs.readObject();
System.out.println(o.name2);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
public class ClassA implements Serializable {
public static String name1 = "A类静态属性";
public String name2 = "A类非静态属性";
public static void getName1() {
System.out.println("A类静态方法");
}
public void getName2() {
System.out.println("A类非静态方法");
}
}
说说你对Java反射的理解
-
- 反射可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。
-
- 反射机制可以动态地创建对象并调用其属性
-
- 反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
-
- Java反射框架主要提供以下功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
- 在运行时调用任意一个对象的方法
说说你对Java注解的理解
- 注解是Java5开始添加到 Java 的
- 注解就是一个标签
- 一个注解会有一些元注解,元注解也是一张标签,但是它是一张特殊的标签,它的作用和目的就是给其他普通的标签进行解释说明的。
五种元注解
- @Retention
- @Documented
- @Target
- @Inherited
- @Repeatable 5 种
Java 预置的注解
学习了上面相关的知识,我们已经可以自己定义一个注解了。其实 Java 语言本身已经提供了几个现成的注解。
@Deprecated
这个元素是用来标记过时的元素,想必大家在日常开发中经常碰到。编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。@Override
这个大家应该很熟悉了,提示子类要复写父类中被 @Override 修饰的方法@SuppressWarnings
阻止警告的意思。之前说过调用被 @Deprecated 注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过 @SuppressWarnings 达到目的。@SafeVarargs
参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。@FunctionalInterface
函数式接口注解,这个是 Java 1.8 版本引入的新特性。函数式编程很火,所以 Java 8 也及时添加了这个特性。
函数式接口 (Functional Interface) 就是一个具有一个方法的普通接口。