[关闭]
@universal
2023-03-16T05:52:04.000000Z
字数
8287
阅读
142
不定期补全的八股笔记
算法和数据结构相关
HashMap原理
数组+链表、原长16、扩容2N、扩容因子0.75、Hash冲突加链表(链表长度过8转红黑树)
ConcurrentHashMap如何实现线程安全
CAS + volatile关键字 + sychronized锁
CAS:比较并交换
volatile关键字:强制写入主存
sychronized锁:只允许一个线程执行
SparseArray和HashMap的区别
SparseArray:双数组,删除O(1)(删除做标记,size和put时整理),二分查找,负载100%(存完才扩容)
HashMap:数组+链表,查找O(1),负载75%(负载因子)
HashMap为什么要用红黑树,红黑树和完全二叉树有什么优点?
红黑树相当于排序数据,可以自动的使用二分法进行定位,性能较高。一般情况下,hash值做的比较好的话基本上用不到红黑树。
红黑树不追求完全平衡,旋转次数比完全二叉树少,相对排序的操作事件更少。
ArrayList如何保证线程安全
加锁(synchronized),CopyOnWriteList(每次操作都会进行数组拷贝)
ArrayList/LinkedList/HashMap/LinkedHashMap区别
(都不线程安全,数组线程安全用Vetor,Map线程安全用HashTable)
ArrayList:无序集合,多用查找
LinkedList:有序集合(双向链表),多用增删
HashMap:无序Map
LinkedHashMap区别:有序Map
二叉树遍历步骤
先序遍历、中序遍历、后序遍历
对称加密和非对称加密
对称加密用同一对密钥,非对称加密是公钥加密私钥解密,但是都可逆
对称加密:DES/AES/3DES
非对称加密:RSA/ECC
MD5加密原理
4比特位组成一个16进制字符,不可逆的加密算法,不属于对称加密也不属于非对称加密。
JAVA基础
抽象类和接口
抽象类只能继承,接口属于实现。单继承、多实现。
重载和重写的区别
重载方法名相同,参数不同。重写必须都相同
静态内部类和非静态内部类的区别
非静态内部类不能有静态成员,静态内部类都可以有
非静态内部类可以访问外部类所有成员,静态内部类只能访问外部类的静态成员
非静态内部类不能脱离外部类被创建,但是静态内部类可以。
值传递和引用传递
值传递会复制值,引用传递值传递参数地址,值传递修改值原值不受影响,引用传递修改值原值也会一并修改。
Java传参是
值传递
,传引用类型时传的是引用地址的拷贝,所以可以改变原值。
equals()和==的区别
equals()比较值,==比较在内存中的首地址。equals()作为Object里的方法可以重写,但是==是操作符,不能重写。
String s = new String("xx")创建了几个String对象
字符串不存在:2个,堆空间一个,常量池一个。
字符串存在:1个,堆空间的那个对象。
try/catch中try里有return,finally还会执行吗
会。try中的return会被存在栈帧的局部变量表里,代码先执行到finally,之后才回栈帧取局部变量。(来自JVM规范的说明)
try/catch中,Exception和Error的区别
二者都是处理异常抛出的问题,Error存在属于JVM层次的程序异常,try/catch无法捕获,Exception是可以捕获的。
Parcelable和Serializable的区别
Parcelable:Android独有,I/O在共享内存,读写更快,需要实现一些特定方法
Serializable:I/O在磁盘,实现简单,读写比Parcelable慢些
Intent传递的对象为什么需要序列化
Intent的传输数据本质由binder完成,启动Activity需要切换进程到AMS的system_server进程,需要进行进程间通信(进程隔离是无法传输对象的),序列化对象用共享内存/文件的方式来解决跨进程的数据传递问题。
同时,由于binder的虚拟映射空间的限制,Intent传递的数据大小也会有对应的限制(1MB)
泛型和泛型擦除
泛型:不确定的数据类型,静态域中无法使用,泛型不能被捕获
泛型擦除:编译期通过类型检查,类型相同,通过擦除确定具体类型,类型不相同则报错
List能否转为List
String是Object的子类型,但是List不是List。直接赋值编译器会报错,可以强转,但是转回来的时候可能会报错。
JVM泛型擦除时,List会变成List,不关心具体类型。
泛型的supers和extends
extends是上界,限制T和T派生类。supers是下界,限制T和T超类。
extends只取不存(生产场景),super只存不取(消费场景)。
注解和注解使用场景
注解:@xxxx,提供有关程序但是不影响程序的数据
使用场景:SOURCE(IDE语法检查,APT编译期处理注解),RUNTIME(Retrofit,用于反射生成request),CLASS(hotfix场景,能不讲不讲吧。。这个不熟)
JAVA并发
单核(一个cpu),是否还需要使用多线程
需要。
CPU速度比IO过程快,单核可以通过合理分配时间片实现多线程机制。理论上单核多线程提高时间片利用率依旧可以减少用户响应时间。
线程安全性
原子性、可见性、有序性。
volatile和synchronized的区别:
(所以要一起用)
synchronized:原子,可见
volatile:可见,有序
synchronized修饰范围
修饰范围:代码块、方法、静态方法、类
静态方法和普通方法的区别:静态方法锁类,普通方法锁实例。
synchronized锁优化
悲观锁
引入偏向锁>轻量级锁>重量级锁。锁在对象内存的对象头里。
偏向锁CAS一次,如果还有线程竞争升级到轻量级锁
轻量锁检测锁记录,获取锁通过CAS检测是否存在线程竞争,如果当前线程是竞争者则自旋等待,自旋一定次数后升级重量级锁
重量级锁涉及线程的阻塞的唤醒,需要消耗很多时间。(升级之前只有这个状态)
CAS原理
比较和交换。(乐观锁)
A原值,B目标值,V内存地址。
A=V时,V改成B。否则A重新获取V比较(自旋)。
AQS原理
不会,下一个。
公平锁和非公平锁
公平锁严格按照线程申请顺序,非公平锁所有的线程都会争抢占有。
ReentrantLock原理
不会,下一个。
volatile关键字
只作用在成员属性的关键字。
只保证内存可见性(强制写入主存)并禁止指令重排(插入内存屏障)。(所以单例要用volatile修饰,防止重排出现提前初始化)
无法保证线程安全,只能保证变量可见性。
死锁和解决方案
定义:多个进程互相竞争资源或通信造成阻塞现象。
解决方法:有序资源分配(资源给进程编号,依次申请),银行家算法(先试探申请的进程需要的资源,安全则分配,不安全则等待)
ThreadLocal
存储当前线程变量的类,比如Handler的Looper,就是从ThreadLocal中获取的(thread获取到ThreadLocalMap,ThreadLocal为Map的key获取到Looper)
多线程操作同一个对象方式
加锁。
线程生命周期多次调用start()
不能start()是线程从创建态转就绪态,这个过程不能由其他状态转变。
守护线程
setDaemon(true)设置。守护线程在非守护线程没结束的时候不会结束自己的生命周期。所有非守护线程结束后,会杀死所有守护线程。
经典守护线程:JAVA的垃圾回收线程。
线程退出
run()执行完毕后自动退出。stop()抛异常退出。
标志位退出。interrupt()退出(本质也是标志位处理)。
线程操作sleep/wait/yield/join区别
sleep:既释放cpu资源也释放锁(可中断,响应中断异常)
wait:释放cpu资源不释放锁(notify/notifyAll唤醒,可中断)
yield:对同优先级线程让出cpu调度
join:当前线程进入阻塞直到另一个线程运行结束
如何保证线程的按序执行
join让其中一个线程等待另一个线程
wait/notify配合使用实现
闭锁
非阻塞式生产者和消费者原理
wait()/notify()+sychronized配合使用。
生产者检测资源>0会释放资源,<0会持有锁生产资源并通知消费者
消费则检测资源>0则消费并通知生产者生产,<0则阻塞等待
线程池原理
用来管理线程,提高线程利用率。
核心线程(不会回收)>最大线程>等待队列
系统支持类型:单线程线程池(只有一个)、定长线程池(固定数量且无限存货)、定时线程池(按时间排序)、缓存线程池(普通线程池,有可用就用,没有就创建,超过空闲时间销毁)
开启线程的方式,开启大量线程的问题,如何优化
开启线程方式:继承Thread然后start(),实现Runnable然后new Thread().start(),CallBack(本质也是Runnable的实现)
开启大量线程的问题:消耗时间/内存/cpu资源
优化:控制数量/用线程池提升复用率
pthread概念、new一个线程占用的内存
pthread是POSIX的线程标准,java中创建线程就是对pthread的封装。
线程内存:每个线程1M
HandlerThread
通过Handler实现任务队列的角色,并且单独一个线程执行,不需要频繁创建Thread。
AsynTask相关
用来处理异步任务。
串行线程池sDefaultExecutor排列任务,普通线程池THREAD_POOL_EXECUTOR执行任务,Handler执行线程切换。
默认情况下串行,特定情况下也能并行执行(有一个特定方法)
多线程操作手段
Thread/Runnable/ThreadHandler/AsynTask/线程池/IntentService(封装了ThreadHandler)
如何判断当前线程是主线程
Thread.currendThread()获取当前线程,Looper.getMainLooper().getThread()获取主线程。
线程间通信
同步用锁,传递消息用Handler。
JVM虚拟机原理
JVM结构
线程共享区域:堆(实例、数组/新:老=1:2/新老年龄15分界/新复制老标记整理且只要有一个满了就会触发一起回收)、方法区(类、静态变量[GCRoot]、常量池的常量[GCRoot])
线程私有区域:程序计数器(指令)、JVM栈(方法/局部变量)、本地方法栈。JVM栈和本地方法栈中正在引用的对象也是GCRoot,存活周期跟随线程周期。
JVM类加载过程
class文件加载到内存的过程。加载->验证->准备->解析->初始化
双亲委派/双亲委托
:发生在类的加载过程中。防止重复加载,保证系统类不被篡改。
缓存查找->父加载器查找->BootStrap ClassLoader查找->逐级返回查找知道classLoader自己
过程中如果找到就返回class,找不到最后会抛出异常。
BootStrap ClassLoader(加载核心类库的最顶层加载类)、Extention ClassLoader(拓展类加载器,加载拓展目录下的jar包,parent=null)、APP ClassLoader(应用程序中的类,parent=Extention ClassLoader)
new一个对象的过程
类加载->检查加载->分配内存->内存空间初始化->设置->对象初始化
类加载
检查加载(是否加载过,加载过就替换实际内存地址)
分配内存(堆中划分内存空间、规整用指针碰撞、不规整用空闲列表、CAS保证并发安全)
内存空间初始化(给对象属性赋初始值)
设置(对象头信息设置,hash值/GC年龄等)
对象初始化(走构造方法初始化)
Java对象会不会分配到栈中
可能会。
方法逃逸(传参到其他方法)、线程逃逸(赋值给其他线程访问的变量)。
GC流程
可达性分析法分析是否需要回收。堆内存分年轻代和老年代。其中一个满了就会触发GC,GC先从年轻代开始,年轻代会有对象往老年代走,就会触发老年代一起GC。
进入老年代条件:大对象直接进老年代/年龄超过15的进老年代/年轻代空间不足/动态对象年龄判定
如果full gc后还是没有内存空间,会触发OOM。
虚拟机会不会回收Class
会。类所有实例被回收 + 类加载器被回收 + class对象没有任何地方被引用,就允许class回收。
Java的几种引用
强引用(回收不了)、弱引用(内存不够回收)、软引用(GC必回收)、虚引用(没有实际引用,任何时候都有可能回收)。
StackOverFlow和OutOfMemory区别
StackOverFlow:栈空间不足
OutOfMemory:堆空间不足
StringBuffer和StringBuilder区别
StringBuffer线程安全,StringBuilder非线程安全。
JVM、DVM、ART
JVM:实现了Java跨平台的能力,基于栈,执行.class,只能运行一个实例。
DVM:实现了JVM规范,Android独有,基于寄存器,执行.dex,每个应用都运行在独立进程。
ART:4.4后新增,在APP安装时会进行预编译,将代码转化为机器语言,效率更高。7.0后混合AOT和JIT和解释执行。
PathClassLoader和DexClassLoader
8.0以后作用一致,加载jar/apk/dex
8.0以前PathClassLoader无法进行dex2oat操作
ClassNotFound原因
apk中未包含需要的类的数据
dex分包,在Secondary Dex加载之前,使用不在主dex中的类
类数据不合法,验证失败导致加载失败
编译型语言和解释型语言
编译型语言:利用编译器将源码转换成二进制数据,生成可执行程序,一般不能跨平台。
解释型语言:边执行边转换,几乎都能跨平台。
反射的应用场景
反射:运行时判断对象所属类/构造对象/获取类变量和方法/调用对象private方法
场景:插件化/热修复/Retrofit
反射为什么慢
参数拆装箱、检查可见性、校验参数、无法内联、JIT无法优化
动态代理原理
Proxy.newProxyInstance()里。
没看懂,下一个。
Java中的IO为什么耗时
IO访问后DMA处理,DMA处理时不占用CPU事件,IO耗时是因为存在大量IO任务,所以需要通过算法合并和cache缓解压力。
网络相关
TCP三次握手和四次挥手过程
三次握手
:客户端SYN -> 服务端ACK + SYN -> 客户端ACK
为什么三次握手
:两次握手服务端无法确认客户端是否收到信号。四次握手没必要,三次握手就能确认,四次属于资源浪费。
四次挥手
:客户端FIN + ACK并开始关闭 -> 服务端ACK,并开始关闭 -> 服务端FIN + ACK,最后确认状态 -> 客户端ACK,2s后彻底关闭 ->服务端收到ACK后关闭
为什么握手三次挥手要四次
:握手时只发送连接,且ACK和SYN同时发送。挥手时被动关闭者可能需要发送一些数据,所以ACK和FIN是分开发送的,所以需要四次。
TCP和UDP的区别
tcp面向连接,udp无连接
tcp使用流量控制和拥塞控制可靠传输,udp不可靠传输
tcp只支持一对一,udp支持n对n
tcp字节流传输,udp报文传输
tcp适用于可靠传输(如文件等),udp适用于实时通讯(IM类型)
TCP拥塞控制和流量控制
拥塞控制防止过多数据注入网络,流量控制是点对点的通信量的控制。
拥塞控制:慢开始、拥塞避免、快重传、快恢复
流量控制:使用大小可变的流量窗口
Http和Https的区别
http明文传输,https = ssl(安全协议)+http 是加密传输
https需要使用CA证书
http建立通信需要3个包(三次握手),https需要12个包(tcp的3个+ssl的9个)
http默认80端口,https默认443端口
SSL握手过程
客户端向服务端索要并验证公钥,双方协商生成密钥,采用密钥加密通信。
公钥放证书里,只要证书可信,公钥就可信。
POST和GET的区别
在应用角度上看:URL的数据拼接差异、数据长度差异
在TCP角度看没区别,都是TCP/IP,GET可以带BODY,POST也可以拼接参数,应用的角度只是规范上的差异。(如果有面试官怼你说POST也可以加参数,就这样怼他!!)
URL到浏览器显示内容经历了什么
DNS解析 -> TCP连接 + SSL握手 -> 发送请求 -> 服务端处理请求并返回请求结果 -> 浏览器处理结果渲染页面 -> Http连接断开
断点续传原理
头文件带Range:bytes=x,告诉服务端从那里继续下发,客户端可以借助RandomAccessFile()的seek(),从指定位置开始写入。
如何保证下载文件的完整性
通过网络请求上传本地版本,带上url/version/对应文件算出的md5(可保证唯一性即可),服务端发现不匹配后重新下发,重新下载。
Kotlin相关
高阶函数
一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数。
let的原理
任意类型都可以使用let{}处理,因为let内置函数内部对泛型进行了let函数拓展,所有类型都等于泛型。
T.let() T是泛型,R是返回类型,let以最后一行作为返回值。
run原理
run和let原理相似,也是对泛型进行拓展,只是run以this作为上下文,let以it作为上下文。
Kotlin泛型的形变
形变的三个区域:不变、协变、逆变
不变:没有继承相关概念,可以是读取也可以修改
协变:out,只能读取不能修改
逆变:in,只能修改不能读取
Kotlin协程
基于线程的调度库。把运行在不同线程中的代码写在一个代码块里。用同步的方法写异步的代码。
借助suspend关键字实现,定义了suspend关键字的方法,在执行时会被挂起,如果有子线程就放到子线程去处理,如果没有依旧会在主线程处理,执行完毕后会通知主线程。
UI绘制
View的绘制原理
从ViewRootImpl的scheduleTraversals()中,有一个performTraversals()方法,执行了performMeasure()/performlayout()/performDraw()方法,从DecorView开始触发绘制流程,父View又向下触发子View的绘制流程,知道最后一层View,完成整个页面的绘制。
View的绘制流程
onMeasure() -> onLayout() -> onDraw()
注意点:
需要4个构造函数全实现
尽可能减少UI层级
尽量减少在View中使用Handler,可以直接用View自带的Handler
绘制过程中三个方法尽量减少使用局部变量,尤其是bitmap这种占用内存比较大的局部变量,容易造成内存抖动
View和ViewGroup的区别
从事件分发的角度来说,子View没有onInterceptTouchEvent(),但是可以调requestDisallowInterceptTouchEvent()请求父View不拦截。
从UI绘制角度来说,View不实现onLayout(),且View的绘制流程由上层ViewGroup触发。
View的绘制流程从Activity的哪个生命周期开始执行的
onResume()
Activity/Window/View的联系和区别
Activity是组件,负责界面/交互/业务逻辑
Window是窗口,是View的载体,也是Activity内部持有的对象
onResume中是否可以测量宽高
无法准确获取宽高。
因为performTransvals()中post的runnable不能够保证在我们获取宽高之前执行完成。
为什么view.post()就可以
:因为view.post()的事件一定排在上边说的那个runnable的后边。新创建Handler去post也不一定获取的到。
子线程为什么不能更新UI
ViewRootImpl里有mThread的checkThread()方法,不是说子线程不能更新UI,而是checkThread()一旦发现当前线程和mThread不一致,会直接抛出异常
DectorView/ViewRootImpl/View的关系
DectorView是ViewTree的最顶层,ViewRootImpl是WindowManager管理View的实现类.
自定义View执行invalidate()为什么有时候不会回调draw()
内容目录
android
2
启动Activity的那点事儿
View的事件分发机制
codeReview
1
Emergencies (紧急事件)
flutter
1
Flutter
iOS
2
iOS入门——Block学习
iOS入门——@property属性
java
2
String、StringBuffer、StringBuilder
强引用、弱引用、软引用、虚引用
tool
1
Gradle学习
view
3
RecycleBin/Recycler的回收机制(下)
RecycleBin/Recycler的回收机制(上)
LinearLayout和RelativeLayout对比
数据加密
1
Android中的数据加密方式
源码分析
3
Are you OK ? OK了解下
启动Activity的那点事儿
View的事件分发机制
音视频
1
H264视频编码
未分类
16
不定期补全的八股笔记
LiveData相关
flutter 前期开源项目调研
libraryCore (图书馆爬虫工具类集合)
nothing but important
EventBus实践
411命名公约
随手app
校园广播
Android 第五次培训
Android第四次培训
Android第二次培训
Android第一次培训
Context基础讲解
Android第一次培训
Service的入门讲解
以下【标签】将用于标记这篇文稿:
下载客户端
关注开发者
报告问题,建议
联系我们
添加新批注
在作者公开此批注前,只有你和作者可见。
私有
公开
删除
查看更早的 5 条回复
回复批注
×
通知