-
jdk1.8中HashMap底层链表转红黑树的阈值为什么是8?红黑树转链表为什么是6?
和hashcode碰撞次数的泊松分布有关,主要是为了寻找一种时间和空间的平衡。
红黑树中的TreeNode是链表中的Node所占空间的2倍,虽然红黑树的查找效率为o(logN),要优于链表的o(N),但是当链表长度比较小的时候,即使全部遍历,时间复杂度也不会太高。固,要寻找一种时间和空间的平衡,即在链表长度达到一个阈值之后再转换为红黑树。
之所以是8,是因为Java的源码贡献者在进行大量实验发现,hash碰撞发生8次的概率已经降低到了0.00000006,几乎为不可能事件,如果真的碰撞发生了8次,那么这个时候说明由于元素本身和hash函数的原因,此次操作的hash碰撞的可能性非常大了,后序可能还会继续发生hash碰撞。所以,这个时候,就应该将链表转换为红黑树了,也就是为什么链表转红黑树的阈值是8。
最后,红黑树转链表的阈值为6,主要是因为,如果也将该阈值设置于8,那么当hash碰撞在8时,会反生链表和红黑树的不停相互激荡转换,白白浪费资源。
-
红黑树有何特性
红黑树是一棵二叉搜索树,它在每个节点增加了一个存储位记录节点的颜色,可以是RED,也可以是BLACK;通过任意一条从根到叶子简单路径上颜色的约束,红黑树保证最长路径不超过最短路径的二倍,因而近似平衡。
具体性质如下:
每个节点颜色不是黑色,就是红色
根节点是黑色的
如果一个节点是红色,那么它的两个子节点就是黑色的(没有连续的红节点)
对于每个节点,从该节点到其后代叶节点的简单路径上,均包含相同数目的黑色节点
-
Java对象实例化过程
比如Student s = new Student()创建对象,
编译期间,会生成Student.class字节码文件。
初始化时:
1、类加载器ClassLoader,加载Student.class字节码到内存;
2、在栈里面为变量s申请一个空间,用来声明s;
3、new的时候,在堆内开辟空间。然后,开始进行默认初始化,String类型默认给null,int类型默认给0等。默认初始化后,开始进行显示初始化,比如成员变量里name默认值为Alice,所以这时会初始化name为Alice。
3、执行Student()构造方法,构造方法进栈,进行构造方法初始化
4、执行构造方法初始化,构造方法执行完毕后出栈,把堆内的对象物理地址,复制给栈内的s,也就是s存放的是对象的引用(物理地址)。
5、执行Student里面的方法时,方法进栈,方法里隐式的this指向堆内存空间
-
T1、T2、T3三个线程,如何保证它们顺序执行?
可以利用Thread类的join方法。Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。
join方法传参和不传参的区别?
join方法中如果传入参数,则表示这样的意思:如果A线程中掉用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并行执行。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。
join方法实现原理
join方法是通过调用线程的wait方法来达到同步的目的的。例如A线程中调用了B线程的join方法,则相当于在A线程中调用了B线程的wait方法,当B线程执行完(或者到达等待时间),B线程会自动调用自身的notifyAll方法唤醒A线程,从而达到同步的目的。
public class JoinTestSync {
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
ThreadJoinTest1 t1 = new ThreadJoinTest1("今天");
ThreadJoinTest1 t2 = new ThreadJoinTest1("明天");
ThreadJoinTest1 t3 = new ThreadJoinTest1("后天");
/*
* 通过join方法来确保t1、t2、t3的执行顺序
* */
t1.start();
t1.join();
t2.start();
t2.join();
t3.start();
t3.join();
}
}
class ThreadJoinTest1 extends Thread{
public ThreadJoinTest1(String name){
super(name);
}
@Override
public void run(){
for(int i=0;i<5;i++){
System.out.println(this.getName() + ":" + i);
}
}
}
-
SparseArray和ArrayMap的内部实现
SparseArray由两个数组mKeys和mValues存放;其中key的类型为int型,这就显得SparseArray比HashMap更省内存一些,SparseArray在存储和读取数据时候,使用的是二分查找法。
SparseArray比HashMap更省内存,在某些条件下性能更好,主要是因为它避免了对key的自动装箱(int转为Integer类型),它内部则是通过两个数组来进行数据存储的,一个存储key,另外一个存储value,为了优化性能,它内部对数据还采取了压缩的方式来表示稀疏数组的数据,从而节约内存空间。
使用二分查找法和之前的key比较当前我们添加的元素的key的大小,然后按照从小到大的顺序排列好,所以,SparseArray存储的元素都是按元素的key值从小到大排列好的。而在获取数据的时候,也是使用二分查找法判断元素的位置,所以,在获取数据的时候非常快,比HashMap快的多。
ArrayMap是一个键值对映射的数据结构,它设计上更多的是考虑内存的优化,内部是使用两个数组进行数据存储,一个数组记录key的hash值,另外一个数组记录Value值,它和SparseArray一样,也会对key使用二分法进行从小到大排序,区别是ArrayMap的key是hash值。
虽说SparseArray性能比较好,但是由于其添加、查找、删除数据都需要先进行一次二分查找,所以在数据量大的情况下性能并不明显,将降低至少50%。
满足下面两个条件我们可以使用SparseArray代替HashMap:
数据量不大,最好在千级以内;
key必须为int类型,这中情况下的HashMap可以用SparseArray代替。
参考:
https://blog.csdn.net/u010687392/article/details/47809295
-
invalidate()和requestLayout()方法调用过程的区别
★ requestLayout()方法请求重新布局,会调用measure过程和layout过程,但不会调用draw过程,也不会重新绘制任何View包括该调用者本身。
★ invalidate()系列方法请求重绘视图(View树),如果View大小没有发生变化就不会调用measure和layout过程,相反,View的大小发生改变了就会调用measure和layout的过程,并且哪个View请求invalidate()系列方法,就绘制该View。
-
描述TCP三次握手与四次挥手的过程与意义
最简单的理解
一:建立TCP连接:三次握手协议
客户端:我要对你讲话,你能听到吗;
服务端:我能听到;而且我也要对你讲话,你能听到吗;
客户端:我也能听到。
…….
互相开始通话
……..
二:关闭TCP连接:四次握手协议
客户端:我说完了,我要闭嘴了;
服务端:我收到请求,我要闭耳朵了;
(客户端收到这个确认,于是安心地闭嘴了。)
…….
服务端还没倾诉完自己的故事,于是继续唠唠叨叨向客户端说了半天,直到说完为止
…….
服务端:我说完了,我也要闭嘴了;
客户端:我收到请求,我要闭耳朵了;
-
getFragmentManager 、getSupportFragmentManager 与getChildFragmentManager三者之间的区别
getSupportFragmentManager()主要用于支持 3.0以下android系统API版本,3.0以上系统可以直接调用getFragmentManager() ,因为fragment是3.0以后才出现的组件,为了这之前的系统版本也能使用fragment,借助V4包里面的getSupportFragmentManager()方法来间接获取FragmentManager()对象,3.0版本之后,有了Fragment的api,就可以直接使用getFragmentManager()这个方法来获取对象。
getFragmentManager()所得到的是所在fragment 的父容器的管理器(此处重点在父容器)。
getChildFragmentManager()所得到的是在fragment 里面子容器的管理器mChildFragmentManager,是FragmentManager另一个变量(此处重点在子容器)。
FragmentManager mFragmentManager;
FragmentManager mChildFragmentManager = new FragmentManagerImpl();
-
什么情况下会发生栈溢出/堆溢出
栈溢出(StackOverflowError)
栈是线程私有的,他的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口灯信息。局部变量表又包含基本数据类型,对象引用类型(局部变量表编译器完成,运行期间不会变化)。
public class JvmTest {
private int i = 0;
public void a(){
System.out.println(i++);
a();
}
public static void main(String[] args) {
JvmTest j = new JvmTest();
j.a();
}
}
堆溢出(OutOfMemoryError:java heap space)
heap space表示堆空间,堆中主要存储的是对象。如果不断的new对象则会导致堆中的空间溢出。
public class JvmTest {
public static void main(String[] args) {
List<String> aList = new ArrayList<String>();
try{
while(true){
aList.add("asdasdasdas");
}
}catch(Throwable e){
System.out.println(aList.size());
e.printStackTrace();
}
}
}
-
线程间通信有哪些实现方式?
1.使用 volatile 关键字
基于 volatile 关键字来实现线程间相互通信是使用共享内存的思想,大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。
2.使用Object类的wait() 和 notify() 方法
Object类提供了线程间通信的方法:wait()、notify()、notifyaAl(),它们是多线程通信的基础。注意: wait和 notify必须在synchronized代码块中使用。
3.使用handler线程间通信
-
断点续传如何实现,写一个示例:
private void downLoadFile() {
try {
if (file == null){
file = new File(rootFile,name);
file.delete();
raf = new RandomAccessFile(file,"rwd");
Log.i("tag","length " + raf.length());
}else{
downLoadSize = file.length();
if (raf == null){
raf = new RandomAccessFile(file,"rwd");
}
raf.seek(downLoadSize);
}
totalSize = getContentLength(path);
if (downLoadSize==totalSize){
//已经下载完成
return ;
}
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(path).
addHeader("Range","bytes="+downLoadSize+"-"+totalSize).build();
Response response = client.newCall(request).execute();
InputStream ins = response.body().byteStream();
int len = 0;
byte[]by = new byte[1024];
long endTime = System.currentTimeMillis();
while ((len =ins.read(by))!=-1 && isDown){
raf.write(by,0,len);
downLoadSize += len;
if (System.currentTimeMillis()-endTime>1000){
final double dd = downLoadSize/(totalSize*1.0);
DecimalFormat format = new DecimalFormat("#0.00");
String value = format.format((dd*100))+"%";
handler.post(new Runnable() {
@Override
public void run() {
progress.onProgress((int) (dd*100));
}
});
}
}
//raf.close();
response.close();
}catch (Exception e){
e.getMessage();
}
}
在下载文件的时候,可以进行暂停,然后继续下载。实现方式是,暂停是保存上一次下载的文件大小的位置,使用RandomAccessFile,获取上一次保存的位置,继续下载seek到上记录的位置,同时设置conn.setRequestProperty("Range", "bytes=" + downSize + "-"),将请求的文件位置定位到记录的位置。
参考:
https://www.jianshu.com/p/17afae82c008?tdsourcetag=s_pcqq_aiomsg
网友评论