2023校招面经

RecycleView 实现列表

​ 首先是在build.gradle里添加RecyclerView的依赖库,然后我们通过继承RecyclerView.Adapter,并在Adapter里定义一个内部类ViewHolder并在其中通过findViewById来获取列表每控件的实例,重写onCreateViewHolder,onBindViewHOlder以及getItemCount方法。onCreateViewHOlder用于创建ViewHolder实例,以及将列表所在布加载进来,监听事件的绑定;onBindViewHolder可以在子项滚动到屏幕内根据postion更新数据;getItemCount获取子项数目。

​ 最后通过在Activity内的onCreate中初始化数据,获取RecyclerView实例并设置布局。最后设置适配器。

RecycleView 实现多条目

​ 在ViewHolder定义多个控件。

HashMap,HashSet 和 HashTable

实现上:

​ 都实现了Map接口,并且都是Key-Value的形式。HashSet是集合形式,且不能重复。

源码上:

img

​ HashMap是一种散列表,采用(数组 + 链表 + 红黑树)的存储结构;

​ HashTable是数组+链表;链地址法处理冲突。

​ HashSet底层是采用HashMap实现的,每次add添加的元素作为map的key,而Value固定为PRESENT,从而实现不能重复。

在Jdk8引入树化,当元素个数达到64且链表的长度达到8时进行树化,当链表的长度小于6时反树化。这样可以利用链表对内存的使用率以及红黑树的高效检索,是一种很有效率的数据结构。

安全上:

HashMap不是线程安全的,HashTable通过synchronized实现线程安全,是同步的。

负载因子(loadFactor):
当我们第一次创建 HashMap 的时候,就会指定其容量(如果未明确指定,默认是 16),随着我们不断的向 HashMap 中 put 元素的时候,就有可能会超过其容量,那么就需要有一个扩容机制。

所谓扩容,就是扩大 HashMap 的容量,在向 HashMap 中添加元素过程中,如果 元素个数(size)超过临界值(threshold) 的时候,就会进行自动扩容(resize),并且,在扩容之后,还需要对 HashMap 中原有元素进行 rehash,即将原来桶中的元素重新分配到新的桶中。

在 HashMap 中,临界值(threshold) = 负载因子(loadFactor) * 容量(capacity)。

loadFactor 是装载因子(负载因子),表示 HashMap 满的程度,默认值为 0.75f,也就是说默认情况下,当 HashMap 中元素个数达到了容量的 3/4 的时候就会进行自动扩容。

Hash的初始Size

HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。(为什么HashMap要是2的倍数扩容,原因是减小冲突)

哈希冲突

开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式

Synchronized实现原理,泄漏

​ synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized 翻译为中文的意思是同步,也称之为同步锁
synchronized的作用是保证在同一时刻, 被修饰的代码块或方法只会有一个线程执行,以达到保证并发安全的效果。

​ Synchronized的底层实现是完全依赖JVM虚拟机的,所以谈synchronized的底层实现,就不得不谈数据在JVM内存的存储:Java对象头,以及Monitor对象监视器。

1.Synchronized是由虚拟机实现的一种互斥同步方式,当你查看被Synchronized修饰的程序块编译后的字节码时,会发现程序块被编译前后生成了monitorenter 和 monitorexit 两 个 字 节 码 指 令 ;

2.在虚拟机运行到monitorenter 时,先获取对象的锁,如果对象没有锁定,或者当前线程已经拥有这个对象的锁,则把锁+1;运行monitorexit时则将计时器-1,当计时器为0时,锁就会被释放;

3.如果对象获取失败了,那么当前的线程就要阻塞等待,直到对象锁被另一个线程释放为止;

锁泄漏(Lock Leak)

锁泄漏是指一个线程获得某个锁以后,由于程序的错误、缺陷致使该锁一直没法被释放而导致其他线程一直无法获得该锁的现象。

内部锁synchronized不会造成锁泄漏(Lock Leak),当临界区发生异常,JVM查找异常表,来保证monitorexit一定能够执行成功,锁一定会被释放。。

JVM

​ JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

构成

  • 类加载系统:负责完成类的加载

    运⾏时数据区:在运⾏Java程序的时候会产⽣的各种数据会保存在运⾏时数据区

    执⾏引擎:执⾏具体的指令(代码)

对象成为垃圾的判断依据

引⽤计数法,可达性分析算法

当Java程序创建对象时,JVM会在堆内存中为对象分配内存。 当对象不再被使用时,它们就会成为垃圾。 如果不进行垃圾回收,这些垃圾对象将永远占用内存,并最终导致内存溢出。

垃圾回收算法

标记清除算法、复制算法、标记整理算法、分代回收法

Android计算图片大小

图片高度 * 图片宽度 * 一个像素占用的字节数

Java的反射

​ 反射的概述JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。要想解剖一个类,必须先要获取到该类的

获取类对应的字节码的对象

调用某个类的对象的getClass()方法,即:对象.getClass();

调用类的class属性类获取该类对应的Class对象,即:类名.class

使用Class类中的forName()静态方法(最安全,性能最好)即:Class.forName(“类的全路径”)

应用场景

android的sdk中,有一些用hide标记的方法,或者是private方法 属性,这些方法不能直接通过sdk的api调用,如果我们需要用到此功能,只能通过反射的机制来调用它。

发一些工具类的时候,例如网络数据,数据库数据和类之间的相互转化。使用反射机制可以直接创建对象,方便代码管理。

手写单例

​ 类加载时就初始化实例,避免了多线程同步问题,天然线程安全。实例对象在第一次被调用的时候才真正构建的,而不是程序一启动就会自动构建。

//懒汉式单例类.在第一次调用的时候实例化自己 
public class Singleton {
private Singleton() {}
private static Singleton single=null;
//静态工厂方法
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}

//饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton1 {
private Singleton1() {}
private static final Singleton1 single = new Singleton1();
//静态工厂方法
public static Singleton1 getInstance() {
return single;
}
}

四大组件是运行在主线程还是子线程

安卓四大组件:Activity、Service、BroadcastReceiver和ContentProvider

Activity组件的主要作用是展示一个界面并和用户交互,它扮演的是一种前台界面的角色、

Service类似于其他应用程序的对象,运行在主线程中。这就意味着你如果在服务中进行耗时的操作,你需要开启一个子线程去处理这个操作,不然在服务中超过20秒未响应会发生ANR导致程序崩溃。IntentService的出现就是为了解决在服务中操作耗时任务的。

音乐软件被切换后,仍然能够播放音乐,浏览器软件被切换后,下载依然进行。

BroadcastReceiver是允许应用接收来自各处的广播信息,而ContentProvider则是主要用于跨应用程序的数据共享,因此数据对象不同。具体应用到场景上,广播接收器主要是应用接收感知系统的信息与变化,如终端的息屏亮屏,有网没网、短信等信息,而内容提供器则是主要和不同应用之间交互共享数据,如调用本地的图片(图库),或者访问其联系人等数据。
四个组件正常情况都是在主线程运行的,主线程又叫UI线程,顾名思义,用户触摸产生的反馈,绘制的执行都发生在这个线程。

Service执行耗时操作

Service也是运行在主线程,Service的onStartCommand() 和 onBind() 方法中不能执行耗时操作,IntentService是继承Service的抽象类,在IntentService中有一个工作线程来处理耗时操作。

Activity启动模式

standard、singleTop、singTask、singleInstance

(1)standard模式
特点:1.Activity的默认启动模式
2.每启动一个Activity就会在栈顶创建一个新的实例。例如:闹钟程序
缺点:当Activity已经位于栈顶时,而再次启动Activity时还需要在创建一个新的实例,不能直接复用。

(2)singleTop模式
特点:该模式会判断要启动的Activity实例是否位于栈顶,如果位于栈顶直接复用,否则创建新的实例。 例如:浏览器的书签。
缺点:如果Activity并未处于栈顶位置,则可能还会创建多个实例。

(3)singleTask模式
特点:使Activity在整个应用程序中只有一个实例。每次启动Activity时系统首先检查栈中是否存在当前Activity实例,如果存在
则直接复用,并把当前Activity之上所有实例全部出栈。例如:浏览器主界面。

(4)singleInstance模式
特点:该模式的Activity会启动一个新的任务栈来管理Activity实例,并且该实例在整个系统中只有一个。无论从那个任务栈中 启动该Activity,都会是该Activity所在的任务栈转移到前台,从而使Activity显示。主要作用是为了在不同程序中共享一个Activity实例。

总结:Activity 的四种启动模式各有特色,在实际开发中,根据实际情况来选择合适的启动方式即可。

程序中只能唯一一个页面,用哪种启动模式

Android 的 ANR

​ ANR,是“Application Not Responding”的缩写,即“应用程序无响应”。如果你应用程序在UI线程被阻塞太长时间,就会出现ANR,通常出现ANR,系统会弹出一个提示提示框,让用户知道,该程序正在被阻塞,是否继续等待还是关闭。

Android Handle机制

子线程里面使用Handler,给主线程发送消息

一个线程中只能存在一个 Looper,Looper 是保存在 ThreadLocal 中的。主线程(UI 线程)已经创建了一 个 Looper,所以在主线程中不需要再创建 Looper,但是在其他线程中需要创建Looper。每个线程中可以有多个 Handler,即一个 Looper 可以处理来自多个 Handler 的消息。 Looper 中维护一个 MessageQueue,来维护消息队列,消息队列中的 Message 可以来自不同的 Handler。

//主线程 在UI中的oncreate中
mhandler=new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
if(msg.what==0){
Log.e(TAG, "主线程收到消息:"+(String) msg.obj );
}
}
};


//子线程
class Ctrl extends Thread {

Message msg;
public void run(){
while (true){
try {
Thread.sleep(1000);
msg=new Message();
msg.what=0;
msg.obj="hello";
mhandler.sendMessage(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

}
}

mhandler.sendMessage(msg);

子线程更新UI方式

方法一:

主线程中定义Handler

子线程发消息,通知Handler更新UI

方法二:

在子线程中通过**runOnUiThread()**方法更新UI,如果在非上下文类中,通过传递上下文实现调用。

方法三:

AsyncTask

Asynctask是一个抽象类,它是Android封装的一个轻量级异步类(轻量级体现在使用方便,代码简洁),它可以在线程池中执行后台任务,然后把执行的进度和最终的结果呈现给主线程,并且更新UI。
Asynctask内部封装了两个线程池(SerialExecutorTHREAD_POOL_EXECUTOR),和一个Handler(IntentHandler)

两个Activity之间跳转执行生命周期

比如说有两个 Activity ,A 和 B,当在 A 里面激活 B 组件的时候, A 会调用 onPause()方法,然后 B 调用 onCreate() 、onStart()、onResume(),此时 B 覆盖了窗体, A 会调用 onStop() 方法,当然如果 B 是个透明的,或者是对话框的样式,就不会调用 A 的 onStop() 方法。
此外,倘若 B 已经存在 Activity 栈中,则无需调用 onCreate() 方法。

为什么用Fragment而不是View

1、Fragment的复用粒度更大。Fragment有完整的生命周期,从代码设计角度讲可以提高内聚性,不同情况下还可以设计不同的Fragment,比如横屏和竖屏情况下View的显示不一样,那么可以建立2个不同的Fragment去处理,代码上面可以有效的扩展。

从形态上讲和Activity更为接近,当然从编程角度上看也比View更为复杂。但是Fragment可以组装更多的View同一展示,而且生命周期有助于资源的管理。

2、简单的直接view,复杂的才用fragment,fragment资源消耗比较大。

3、一个fragment必须总是绑定到一个activity中,虽然fragment有自己的生命周期,但同时也被它的宿主activity的生命周期直接影响。
大部分情况下,Fragment用来封转UI的模块化组件;但是也可以创建没有UI的Fragment来提供后台行为,该行为会一直持续到Activity重新启动。这特别适合于定期和UI交互的后台任务或者当因配置改变而导致Activity重新启动是,保存状态变得特别重要的场合。

动态AddView 和使用RecyclerView的区别

ArrayList:

ArrayList是基于动态数组的数据结构。

因为是数组,所以ArrayList在初始化的时候,有初始大小10,插入新元素的时候,会判断是否需要扩容,扩容的步长是0.5倍原容量,扩容方式是利用数组的复制,因此有一定的开销;

LinkedList:

内部使用基于链表的数据结构实现存储,LinkedList有一个内部类作为存放元素的单元,里面有三个属性,用来存放元素本身以及前后2个单元的引用,另外LinkedList内部还有一个header属性,用来标识起始位置,LinkedList的第一个单元和最后一个单元都会指向header,因此形成了一个双向的链表结构。

UDP和TPC的区别,在网络协议哪一层

TCP 和 UDP 是负责提供端到端通信的传输层协议。TCP 是面向连接的协议,而 UDP 是无连接协议

img

进程通信方式

每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的, 所以进程之间要通信必须通过内核。

进程间通信目的一般有共享数据,数据传输,消息通知,进程控制等。以 Unix/Linux为例,介绍几种重要的进程间通信方式:管道、消息队列、****共享内存、信号量、信号、Socket

共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中

跨网络与不同主机上的进程之间通信,就需要Socket通信了

Android进程通信

使用Bundle

  我们都知道Android中三大组件Activity,Service,Receiver都支持在Intent中传递Bundle数据,而Bundle实现了Parcelable接口,所以它可以方便的在不同的进程间进行传输。当我我们在一个进程中启动另外一个进程的Activity、Service、Receiver时,我们就可以在Bundle中附加我们所需要传输给远程进程的信息并通过intent发送出去。这里注意,我们传输的数据必须能够被序列化。

快排

快排通过中间取哨兵优化,在i==j时停止

三次握手与四次挥手

1、为什么需要三次握手

目的:为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。主要防止资源的浪费。

具体过程:

  当客户端发出第一个连接请求报文段时并没有丢失,而是在某个网络节点出现了长时间的滞留,以至于延误了连接请求在某个时间之后才到达服务器。这应该是一个早已失效的报文段。但是服务器在收到此失效的连接请求报文段后,以为是客户端的一个新请求,于是就想客户端发出了确认报文段,同意建立连接。假设不采用三次握手,那么只要服务器发出确认后,新的连接就可以建立了。但是由于客户端没有发出建立连接的请求,因此不会管服务器的确认,也不会向服务器发送数据,但服务器却以为新的运输连接已经建立,一直在等待,所以,服务器的资源就白白浪费掉了。

1.1、如果在TCP第三次握手中的报文段丢失了会出现什么情况?

  客户端会认为此连接已建立,如果客户端向服务器发送数据,服务器将以RST包响应,这样就能感知到服务器的错误了。

2、为什么要四次挥手

  为了保证在最后断开的时候,客户端能够发送最后一个ACK报文段能够被服务器接收到。如果客户端在收到服务器给它的断开连接的请求之后,回应完服务器就直接断开连接的话,若服务器没有收到回应就无法进入CLOSE状态,所以客户端要等待两个最长报文段寿命的时间,以便于服务器没有收到请求之后重新发送请求。

  防止“已失效的连接请求报文”出现在连接中,在释放连接的过程中会有一些无效的滞留报文,这些报文在经过2MSL的时间内就可以发送到目的地,不会滞留在网络中。这样就可以避免在下一个连接中出现上一个连接的滞留报文了

HTTP

http1.0

HTTP1.0最早在网页中使用是在1996年,那个时候只是使用一些较为简单的网页上和网络请求上,是一种无状态、无连接的应用层协议,几年后被HTTP1.1代替并广泛使用

http1.1

  1. http1.1基于文本解析,把所有请求和响应作为纯文本
  2. http1.1加入了缓存处理(强缓存和协商缓存)
  3. http1.1拥有长连接,并支持请求管道化pipelining),
  4. http1.1流控制基于tcp连接。当连接建立时,两端通过系统默认机制建立缓冲区。并通过ack报文来通知对方接收窗口大小,因为http1.1 依靠传输层来避免流溢出,每个tcp连接需要一个独立的流控制机制

缓存处理(强缓存和协商缓存)

浏览器缓存能优化性能,而浏览器缓存分为强缓存协商缓存,都是从客户端读取缓存 强缓存

  1. 强缓存不发送请求,直接读取资源,可以获得返回200的状态码
  2. 利用http头中的ExpiresCache-Control两个字段来控制,都用来表示资源的缓存时间,Expires能设置失效时间,而Cache-Control能做到更多选项更细致,如果同时设置的话,其优先级高于Expires

协商缓存

  1. 通过服务器来确定缓存资源是否可用,通过request header判断是否命中请求,命中后返回304状态码,并返回新的request header通知客户端从缓存里取
  2. 普通刷新会启用弱缓存,忽略强缓存。只有在地址栏或收藏夹输入网址、通过链接引用资源等情况下,浏览器才会启用强缓存
  3. 如果时间过期,则向服务器发送header带有If-None-Match和If-Modified-Since的请求,回到1

http2

  1. http2相比于http1.1,性能大幅度提升
  2. http2通过一个连接来多路复用
  3. http2拥有头部压缩
  4. http2拥有新的二进制格式,使用二进制框架层把所有消息封装成二进制,且仍然保持http语法
  5. http2允许客户端和服务器端实现他们自己的流控制机制,而不是依赖传输层,两端在传输层交换可用的缓冲区大小,来让他们在多路复用流上设置自己的接收窗口
  6. http2让服务器可以将响应主动“推送”到客户端缓存中

htpp2头部压缩

  1. http2头部压缩又称为HAPCK设计简单而灵活,是因为HPACK格式有意地简单不灵活能降低由于实现错误而导致的互操作性或安全问题的风险
  2. http1.1没有头部压缩,随着请求增加,冗余头部字段会不必要地占用带宽,从而显着增加延迟,而头部压缩可消除冗余报头字段,限制已知安全攻击的漏洞,并且在受限环境中使用有限的内存要求

http2多路复用

  1. http 性能优化的关键并不在于高带宽,而是低延迟
  2. tcp 连接会随着时间进行自我「调谐」,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移提高传输的速度,这种调谐则被称为 tcp 慢启动,由于这种原因,让原本就具有突发性和短时性的 http 连接变的十分低效
  3. http/2 通过让所有数据流共用同一个连接,可以更有效地使用 tcp 连接,让高带宽也能真正的服务于 http 的性能提升。而http1.1存在低性能的线头阻塞,一旦有一个请求超时,便会出现阻塞等待的情况

http3

之前说了http2,那么http3就是为了解决http2相关问题而诞生,它基于一个新的传输层协议QUIC,而http3就是建立一个在QUIC上运行的HTTP新规范,而http3之前的版本都是基于TCP,QUIC就是为了替代TCP,解决TCP的一些缺陷

tcp

  1. 不支持流级复用,TCP会将所有对象序列化在同一个流中,因此,它不知道TCP段的对象级分区,无法在同一个流中复用数据包
  2. 会产生冗余通信,tco三次连接握手会有冗余的消息交换序列
  3. 可能会间歇性地挂起数据传输,tcp中有个因为序列顺序处理丢失的问题的缺陷称为行头阻塞

QUIC

  1. 同样拥有头部压缩,并优化了对乱序发送的支持,也优化了压缩率
  2. 放弃tcp,通过udp建立,提高了连接建立的速度,降低了延迟
  3. tcp本身是无法解决队头拥塞,quic则解决了这个问题
  4. Connection ID使得http3支持连接迁移以及NAT的重绑定

非对称加密的过程,公钥私钥

1、区别:加密一般分为两种,对称加密和非对称加密。对称加密就是加密解密都用同一个秘钥,比如DES、3DES(TripleDES)和AES等。
非对称加密就是加密和解密不是用的同一种秘钥,比如RSA算法、DSA算法、ECC算法、DH算法等。
在非对称加密中,用来加密的秘钥叫公钥,用来解密的秘钥叫私钥。公钥和私钥都是成对生成的,公钥分发给其他人用来加密,私钥用来解密。
2、优缺点:
对称加密:解密速度快,但保密性差。
非对称加密:加密算法保密性好,它消除了最终用户交换密钥的需要。但是加解密速度要远远低于对称加密。

进程与线程

线程如何切换的,谁负责切换

cpu

CPU 调度算法

先来先服务调度(First Come First Served,FCFS)

最短作业优先调度(Shortest Job First,SJF)

优先级调度(Highest Privilege First,HPF)

高响应比优先调度(Highest Response Ratio NextHRRN)

轮转法调度(Round Robin,RR)

多级队列调度

多级反馈队列调度

img

锁的概念,乐观锁,悲观锁,关键字

程序中的锁,则是用来保证我们数据安全的机制和手段

悲观锁
悲观锁(Pessimistic Lock): 就是很悲观,每次去拿数据的时候都认为别人会修改。所以每次在拿数据的时候都会上锁。这样别人想拿数据就被挡住,直到悲观锁被释放,悲观锁中的共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程

但是在效率方面,处理加锁的机制会产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,如果已经锁定了一个线程A,其他线程就必须等待该线程A处理完才可以处理

数据库中的行锁,表锁,读锁(共享锁),写锁(排他锁),以及syncronized实现的锁均为悲观锁
乐观锁
乐观锁(Optimistic Lock): 就是很乐观,每次去拿数据的时候都认为别人不会修改。所以不会上锁,但是如果想要更新数据,则会在更新前检查在读取至更新这段时间别人有没有修改过这个数据。如果修改过,则重新读取,再次尝试更新,循环上述步骤直到更新成功(当然也允许更新失败的线程放弃操作),乐观锁适用于多读的应用类型,这样可以提高吞吐量

Lock 锁

​ 在已经有了同步关键字synchronize的的情况下,Java依然在5.0版本中新增了一个同步锁对象lock.又称显示锁,之锁以新增它,是因为synchronize有一些不足,究竟synchronize有哪些不足?在后续课程synchronize与lock的区别,一节中详细介绍,本节主要是来介绍显示锁lock及使用,为什么叫显示锁?是因为我们可以手动的去获取锁与释放锁。之前使用synchronize的的时候,则是自动获取锁与释放锁,锁以synchronize的被称之为隐式锁,lock锁被称之为显示锁。

GC算法,什么时候回收,怎么回收,如何查看对象有没有被引用

​ 判断对象可被GC回收有两种办法分别是:引用计数算法根可达性算法

​ 引用计数算法是一个已经被淘汰的算法,它是给每个对象加一个计数器,当有其他对象引用该对象时,该对象的计数器加一,当这个引用失效时,计数器就会减一,当该对象的计数器为零时,就会认为该对象可以被所回收。

​ 引用计数算法是一个简单并且高效的算法,但这种算法却有一个非常大的弊端。就是这种算法会造成对象的循环引用,导致即使这个对象不再被需要,仍然存在一个一直指向它的引用,使得计数器不为零,导致该对象无法被回收,造成内存空间的浪费。

​ 根可达性算法是JVM默认的算法,他的原理就是定义一系列的根,我们把这些根称为:GC Roots。从GC Roots开始向下搜索,中间查找的路径被称为:引用链。

​ 当一个对象到GC Roots之间没有任何引用链相连接时,我们就认为这个对象可以被GC回收。

根可达性很好的解决了对象循环引用问题。

泛型

泛型:就是指在类定义时不会设置类中的属性或方法参数的具体类型,而是在类使用时(创建对象)再进行类型的定义。会在编译期检查类型是否错误。

堆栈,new是堆还是栈

栈由系统自动分配内存,用于存放函数参数值和局部变量等。
堆由开发人员进行分配和释放,若不释放,程序结束时则/由于操作系统自动回收。

基本数据类型共8类,byte、short、int、long、float、double、char、boolean。

基本类型:变量名和值都放在栈中; —引用类型:变量名(存放内存地址值,指向所引用的对象)放在栈中,该变量所指向的对象放在堆中。

注解

注解的英文名叫“Annotation”,是 Java 中给类、方法以及成员变量等元素增加元数据的方式。换言之注解就是用于描述这些元素的。

Java 的注解可以应用在类、接口、方法、方法的参数、成员变量和方法内的局部变量之上

@Override 注解用于标注方法,它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们在一个没有覆盖父类方法的方法上应用 @Override 注解时,Java编译器会告警。

JVM内存模型,寄存器

Glide

Glide.with(this).load(url).into(imageView);

Glide的缓存分为两个模块,一个是内存缓存,一个是硬盘缓存。

内存缓存的作用是防止应用重复将图片数据读取到内存当中;

硬盘缓存的作用是防止应用重复从网络或其他地方下载和读取数据。

OKhttp

1、拿到OkHttpClient对象

okhttpclient client=new OKhttpclient();

2 . 构造Request对象

Request request = new Request.Builder()
.get()
.url("https:www.baidu.com")
.build();

3、将Request封装为Call

Call call = client.newCall(request);

4 . 根据需要调用同步或者异步请求方法

//同步调用,返回Response,会抛出IO异常
Response response = call.execute();

//异步调用,并设置回调函数
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Toast.makeText(OkHttpActivity.this, "get failed", Toast.LENGTH_SHORT).show();
}

@Override
public void onResponse(Call call, final Response response) throws IOException {
final String res = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
contentTv.setText(res);
}
});
}
});

Protobuf

因为TCP协议只能发送字节流,因此需要将数据序列化。protobuf序列化和反序列化的时间开销都很少。因为序列化后的数据都是以二进制数据存储,因此空间开销也少很多。

ProtoBuf是跨语言的,使用ProtoBuf的第一步是先定一个proto 文件,使用生成器产生不同语音的代码。

Git

git status  查看当前状态
git log 查看提交日志
git merge dev 合并dev分支至当前分支
git add . 添加当前目录全部文件至暂存区
git commit -m '测试' 提交,提交信息为测试
git push origin master 推送至远端分支(master为需要推送分支,按实际需要选择)
git pull origin master 合并远端分支至本地 (git pull 等于 git fetch + git merge)
git pull --rebase origin master rebase方式合并远端分支至本地
git branch 查看当前分支
git branch dev 创建dev分支 (dev可选)
git branch -d dev 删除dev分支
git branch -r 查看远程分支
git branch -a 查看所有分支 (包括远程分支)
git checkout master 切换至master分支
git checkout -b dev 创建dev分支并切换至dev分支
git checkout -b dev origin/dev 创建远程分支到本地
git restore file 丢弃工作区修改(file为具体文件名称)
git restore * 丢弃所有工作区修改
git restore --staged file 回退暂存区文件 不会更改文件内容
git rebase --continue rebase后继续操作
git rebase --abort 退出rebase 操作

图片加载过程

img

第一步,资源匹配:

计算设备dpi,然后去dpi匹配的drawable文件夹查找图片,如果合适不缩放图片直接显示,如果不合适对图片进行缩放。

第二部,解码资源:

对图片资源进行解码,并获得图片资源Bitmap。

BitmapFactory.doDecode()进行解码;

先采样再缩放,输出Bitmap。

setImageResource和setImageBitmap

第一种setImageResource 是从资源drawable中通过资源id找到文件转成可绘制对象drawable 然后绘制。这个方法会自动适配分辨率。适用于不频繁设置图片图片资源不会太大的情况。 但是

对于大图片时或者你需要不断的重复的设置图片 调用这个方法生成的drawable里一样会生成一个bitmap对象 因为bitmap是通过bitmapfactory生成的 有一部分要调用C库所以需要开辟一部分

native本地内存空间以及一部分jvm的内存空间。而native本地内存空间是C开辟的 jvm的gc垃圾回收器是回收不了这部分空间的,这个时候如果你频繁的调用setImageResource且没有手动调

recycle native的内存空间很难被释放掉。jvm的内存也不会及时得到回收这样就相当容易导致内存溢出。


而setImageBitmap 当你需要频繁设置大图片时 通过bitmapfactory生成bitmap然后设置 然后每次设置前将之前的bitmap手动掉recycle 置为可回收状态 这样很大程度能防止内存泄露溢出

所以看你的需求 你的图片是不频繁设置且不会太大就用第一种 如果需求不断的重复更新设置那最好用第二个并且记住手动及时回收后再设置 如果有用到图片缓存的话则不要将大图片列入缓存

中 图片的缓存模块最好只存储小且利用频繁的图片以节省内存和时间开销 大图则需做手动回收 以保证低端点的机子不会oom

但通过查阅资料学习,我发现像setImageResource这些函数在完成decode后最终都是通过Java层的Createbitmap来完成,需要消耗更多内存。最优的降低内存的方式,是通过使用BitmapFactory.decodeStream()方法来创建一个bitmap,再将其设置为Imageview 的source,优化的原理在于BitmapFactory是通过JNI调用底层C/C++实现的驱动完成了decode,从而节省了java空间。

但是随着Android的变迁Bitmap的回收机制也是在变化。