equals与==的区别?
1)== 如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;
如果作用于引用类型的变量,则比较的是所指向的对象的地址
2)equals是一个方法,他比较的是是否同一个对象,同时如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址
hashMap的实现原理?
hashMap的是底层是由数组和链表组成,存储的是键值对映射,也是非线程安全的,所以效率比hashTable快,他的key可以为null不能重复,值可以。
他的实现原理是HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,
1、它调用键对象的key求hash值再计算下标
2、如果没有碰撞,直接放入桶中(碰撞的意思是计算得到的Hash值相同,需要放到同一个bucket中)
3、如果碰撞了,以链表的方式链接到后面
4、如果链表长度超过阀值( TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表
5、如果节点已经存在就替换旧值
6、如果桶满了(容量16*加载因子0.75),就需要 resize(扩容2倍后重排)hashCode()方法来计算hashcode,然后找到bucket位置来储存Entry对象。
当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。
怎么减少碰撞?
- 扰动函数可以减少碰撞,原理是如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这就意味着存链表结构减小,这样取值的话就不会频繁调用equal方法,这样就能提高HashMap的性能。(扰动即Hash方法内部的算法实现,目的是让不同对象返回不同hashcode。)
- 使用不可变的、声明作final的对象,并且采用合适的equals()和hashCode()方法的话,将会减少碰撞的发生。不可变性使得能够缓存不同键的hashcode,这将提高整个获取对象的速度,使用String,Interger这样的wrapper类作为键是非常好的选择。为什么String, Interger这样的wrapper类适合作为键?因为String是final的,而且已经重写了equals()和hashCode()方法了。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。
解决hash冲突的办法
开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
再哈希法
链地址法(hashMap的方法)
建立一个公共溢出区
参考博文:https://blog.csdn.net/weixin_41064826/article/details/79705324
hashset是怎么实现的? hashmap是怎么实现hashset的?
hashset的底层是由hashMap的实现的
java虚拟机的内存区域
- 程序计数器:记录线程的行号
- 虚拟机栈:一个线程的每个方法在执行时会创建一个栈帧(局部变量表、操作站、动态链接、方法出口灯)当方法被调用时,栈帧在JVM栈中入栈,当方法执行完成时,栈帧出栈。
- 本地方法栈:用来执行native方法的
- 堆区:存储对象实例
- 方法区:方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、final常量、静态变量、编译器即时编译的代码等。
java的垃圾回收机制
判断对象是否存活?
1、引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的
2、可达性分析
从 GC root的对象开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
在Java语言中,可作为GC Roots的对象包括下面几种:
a) 虚拟机栈中引用的对象(栈帧中的本地变量表);
b) 方法区中类静态属性引用的对象;
c) 方法区中常量引用的对象;
方法区如何判断是否需要回收
方法区主要回收的内容有:废弃常量和无用的类。对于废弃常量也可通过引用的可达性来判断,但是对于无用的类则需要同时满足下面3个条件:
① 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;
② 加载该类的ClassLoader已经被回收;
③ 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
垃圾回收算法
1、标记清除
标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,此算法一般没有虚拟机采用。
优点1:解决了循环引用的问题
优点2:与复制算法相比,不需要对象移动,效率较高,而且还不需要额外的空间
不足1:每个活跃的对象都要进行扫描,而且要扫描两次,效率较低,收集暂停的时间比较长。
不足2:产生不连续的内存碎片
2、复制算法
将内存分成两块容量大小相等的区域,每次只使用其中一块,当这一块内存用完了,就将所有存活对象复制到另一块内存空间,然后清除前一块内存空间。这样一来就不容易出现内存碎片的问题。
1、复制的代价较高,所以适合新生代,因为新生代的对象存活率较低,需要复制的对象较少;
2、需要双倍的内存空间,而且总是有一块内存空闲,浪费空间
3、标记-整理算法
思想:在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。
不会产生内存碎片,但是依旧移动对象的成本。
4、分代收集算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
内存被分为下面三个区域:
① 新生代:Enden、form survicor space、to survivor space。
② 老年代
③ 永久代:方法区
参考博文:https://blog.csdn.net/weixin_41835916/article/details/81530733
什么时候出发GC
- Minor GC
一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
- Full GC
对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC:
a) 年老代(Tenured)被写满;
b) 持久代(Perm)被写满;
c) System.gc()被显示调用;
d) 上一次GC之后Heap的各域分配策略动态变化;
Java 虚拟机的类加载机制
TCP的三次握手四次挥手
1)客户端发送一个SYN报文段,以及自己的初始化序列号seq=x;
2)服务器收到SYN报文后发送SYN+ACK,其中seq=y(为自己的序列号),确认序号为ack=x+1;
3)客户端发送ack报文,并置发送序列号为Z,确认序号为Y+1;
为什么是三次握手?A为什么最后还要发送一次确认?
为了防止已经失效的连接请求报文段又突然传到服务端,因而产生错误。A发送的一个连接请求在网络结点的时间滞留,以至于延误到连接释放的某个时间才到达B。B收到这个请求的报文段后,误以为A又发出了一次新的请求连接,于是向A发送确认报文段,同一建立连接。但是由于A并没有发出新的请求连接,所以A不会理睬B的确认也不会向B发送数据。但B却认为连接已经建立,一直等待A发来数据。B的资源就白白浪费了。
参考博文:https://blog.csdn.net/qq_27046951/article/details/82430182
1,A发送一个FIN,代表数据发送完毕,其序列号为u,它等于前面已传送数据最后一个字节的序列号+1
2,B收到A的释放报文段后发出确认,确认号ACK=u+1,确认报文段的序列号为v。A到B这个方向上的连接就释放了。
3,B发送释放报文段FIN,B还需要重复上次发送的ACK,B放的报文段序列号为w,等待A的确认。
4,A在收到B的释放报文段后,需要确认。确认号为w+1
为什么TCP协议终止链接要四次?
1、当主机A确认发送完数据且知道B已经接受完了,想要关闭发送数据口(当然确认信号还是可以发),就会发FIN给主机B。
2、主机B收到A发送的FIN,表示收到了,就会发送ACK回复。
3、但这是B可能还在发送数据,没有想要关闭数据口的意思,所以FIN与ACK不是同时发送的,而是等到B数据发送完了,才会发送FIN给主机A。
4、A收到B发来的FIN,知道B的数据也发送完了,回复ACK, A等待2MSL以后,没有收到B传来的任何消息,知道B已经收到自己的ACK了,A就关闭链接,B也关闭链接了。
A为什么等待2MSL,从TIME_WAIT到CLOSE?
- 为了保证A发送了最后一个ACK报文段能够到达B。
*防止已经失效的连接请求报文段出现在本连接中。
在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
参考博文:https://blog.csdn.net/u013271921/article/details/45218703