(1),HashMap的源码,实现原理,JDK8中对HashMap做了怎样的优化。
--------HashMap是由数组(Entry[])+链表(拉链法)组成的存储区间,HashMap<key,value>,的储存规则:通过key的hash值(key.hashCode())%length(数组长度) <==>(key.hashCode()) & (length-1)<源码方式:位运算时间复杂度低>,通过这个计算式算出hashmap中value的数组下标(bucketIndex:存储位置)。由于可能出现重复结果(比如:12%16=12,28%16=12,108%16=12),当出现重复结果时,这个被称为哈希码碰撞,出现这种情况是将进行equal()判断,hashCode不同,equal()=false,hashCode相同,equal()不一定ture,通过hashCode和equal()对bucketIndex进行判断,判断key是否存在,存在了,进行替换,key不存在,就进行链表存储(Entry[i] = b,b.next = a)。JDK8引入红黑树(平衡的二叉树)。而当链表长度太长(默认超过8)时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。
(2),HaspMap扩容是怎样扩容的,为什么都是2的N次幂的大小。
-------- HashMap有两个参数影响其性能:初始容量和加载因子。默认初始容量是16,加载因子是0.75。容量是哈希表中桶(Entry数组)的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,通过调用 rehash 方法将容量翻倍。
当容量一定是2^n时,h & (length - 1) == h % length,它俩是等价不等效的,位运算效率非常高,实际开发中,很多的数值运算以及逻辑判断都可以转换成位运算,但是位运算通常是难以理解的,因为其本身就是给电脑运算的,运算的是二进制,这个等式实际上可以推理出来,2^n转换成二进制就是1+n个0,减1之后就是0+n个1,如16 -> 10000,15 -> 01111,那根据&位运算的规则,都为1(真)时,才为1,那0≤运算后的结果≤15,假设h <= 15,那么运算后的结果就是h本身,h >15,运算后的结果就是最后三位二进制做&运算后的值,最终,就是%运算后的余数,我想,这就是容量必须为2的幂的原因。
(3),二进制补码
补码:二进制数的反码 + 1
负数的二进制形式(补码形式存在):负数对应正数的二进制数,对正数二进制数取反(0->1, 1->0)得到反码,再对反码加1
1、一个正整数,当用原码、反码、补码表示时,符号位都固定为0,用二进制表示的数位值都相同,即三种表示方法完全一样
2、一个负整数,当用原码、反码、补码表示时,符号位都固定为1,用二进制表示的数位值都不相同,即三种表示方法完全一样.此时由原码表示法变成补码表示法的规则如下:
①原码符号位为1不变,整数的每一位二进制数位求反得到反码
②反码符号位为1不变,反码数值为最低位加1,得到补码
<< 1 : 左移1位(*2) >> 1 : 右移1位( /2 ) >>> 1 : 正数与 >> 1 效果一样,负数时,右移一位高位补0
>>:带符号右移。正数右移高位补0,负数右移高位补1。 >>>:无符号右移。无论是正数还是负数,高位通通补0。
位运算: 按位与(&):比较的两者同为1时结果才为1,否则为0
按位或( | ):比较的两者只要有一个为1,结果就为1
按位异或(^):比较的两者相位为异(一个为0,另一个为1或反过来)则结果为1,否则(同为1或同为0)结果为0
(4),HashMap,HashTable,ConcurrentHashMap的区别
-------- HashMap和HashTable:
1,hashMap是非线程安全(多并发情况下会出错),hashTable是线程安全,内部方法都是被synchronized修饰。
2,hashmap允许键和值都为null,hashtable只要键和值任一为null就会报NullPointerException(空指针异常)
3,hashMap默认数组大小16,hashmap扩容为乘2, hashmap通过对哈希值进行位运算(h<key的hash值> & (length<数组长度> - 1)); hashtable默认数组大小11,hashtable扩容为乘2+1,hashmap通过对哈希值进行取模(h<key的hash值> % (length<数组长度>)); 位运算处理效率远高于取模运算,位运算针对二进制运算(计算机采用二进制)
-------- HashMap和ConcurrentHashMap:
1,ConcurrentHashMap在HashMap的基础上,引入"分段锁"(segmentFor)的概念,将数据分为多个segment,每个segment相当于一个 hashtable。默认大小为16(concurrency level),每次操作就对一个segment加锁(不同于hashtable每次操作全部锁死),避免多线程锁的几率,提高并发效率。
(5),HashMap在高并发下如果没有处理线程安全会有怎样的安全隐患,具体表现是什么。
-------- 1,HashMap在多线程下put可能造成get死循环; 2,多线程下put可能造成元素丢失(哈希碰撞造成元素覆盖丢失)
(6),java中四种修饰符的限制范围。
public:同一个类,同一个包,不同包的子类,不同包的非子类 ; protected:同一个类,同一个包,不同包的子类; 默认(default):同一个类,同一个包; private:同一个类;
(7),创建对象的五种方式
1,直接new( new TestDemo() ) 2,使用class类的newInstance方法( NewInstanceDemo.class.newInstance() ) 3,使用constructor类的newInstance方法( Constructor<NewInstanceDemo> constructor = NewInstanceDemo.class.getConstructor(); NewInstanceDemo newInstanceDemo_constructor = constructor.newInstance(); ) 4,使用clone方法( (CloneDemo) demo.clone(),demo需要调用Cloneable接口 ) 5,使用反序列化( 对象调用Serializable )
(8),Object类中的方法
Object() clone():另存一个当前对象; equal():判定两个对象是否相同 hashCode():获取对象的hash值用于 toString():返回一个String对象; getClass():返回一个Class对象; wait():让当前线程失去操作权限,当前线程进入等待序列 wait(long):long为最大等待时间(毫秒) wait(long,int); long为最大等待时间(毫秒),int:纳秒 notify():对某一持有对象锁的线程获取操作权限 notifyAll(): 对所有持有对象锁的线程获取操作权限 finalize():在垃圾回收时调用
(9),接口和抽象类的区别,注意JDK8的接口可以有实现。
1,接口可以多实现,抽象类只能单继承(本质:一个类可以调用多个接口,但只能继承一个类) 2,抽象类可以由abstract + public(或protect)修饰,接口只能通过abstract+public修饰 3,抽象类可以有子类实行的抽象方法也可以有具体方法,jdk8以前,接口只有子类实现的抽象方法,jdk8时,接口也可以有具体实现方法 4,变量上,抽象类可以有各种修饰符对变量进行修饰,接口中只能有public statc final(接口中变量只能读不能写,接口是对开闭原则的体现) 5,抽象类可以有静态代码块和静态方法,接口不能有静态代码块和需要子类实现的静态方法,但可以有具体实现的静态方法(jdk8) 6,抽象类可以有构造方法,接口不能有构造方法 7,接口只能继承(extends)接口,抽象类可以继承(extends)类,也能调用(implements)接口
(10),动态代理的两种方式,以及区别
jdk动态代理:jdk内置的Proxy动态代理可以在运行时动态生成字节码,而没必要针对每个类编写代理类。中间主要使用InvocationHandle和Proxy.newProxyInstance(loader,interfaces,h) ---> loader:一个ClassLoader对象,定义由哪个ClassLoader对象来生成对象进行加载 ----> interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样就能调用接口中的方法 -----> h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上,间接通过invoke来执行 缺点:被代理的类必须实现接口,未实现接口则没办法完成动态代理。
cglib动态代理:针对类来实现代理,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理。因为采用的是继承,所以不能对final修饰的类进行代理
(11),Java序列化的方式
序列化:将java对象转变成字节流形式。用于:写入硬盘或通过网络传输数据 实现方法:1,实体类调用Serializable接口; 2,实体类调用Externalizable
(12),传值和传引用的区别,Java是怎么样的,有没有传值引用
1、在java中的参数传递都是按值传递,这句话的意思:按值传递是传递的值的拷贝,按引用传递其实传递的是引用的地址值,所以统称按值传递。 2、在java中只有基本类型(int 等)和特殊定义方式的String(举例:String str = “aaa”)是按值传递,其它的都是按引用传递。
(13),一个ArrayList在循环过程中删除,会不会出问题,为什么 1、循环删除时,可能会造成需要删除的连续数据,没有删除干净(比如:正序遍历使用remove()方法删除两个连续的“b”字符时,第二个字符不能删除),解决方法:倒序遍历删除 2、foreach循环遍历使用remove()方法删除数据时会报(ConcurrentModificationException: list的remove()方法改变了modCount的值,而foreach类似于Iterator的简写,iterator中有 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
)。解决方法:改用Iterator的remove()方法
(14),@transactional注解在什么情况下会失效,为什么
属于声明式事务管理,常作用于方法,用于当方法抛出异常时,对事务进行回滚,保证数据一致性。@Transactional注解只能作用于public方法,其它protected等虽然不会报错但注解无用,默认情况下,@Transactional会对 unchecked异常 进行事务回滚,不会对 checked异常 作用。 @Transactional注解失效: 1、方法是否为public修饰; 2、异常类型是否为unchecked; 3、数据库引擎需要支持事务(比如:mysql需要使用innodb修饰表); 4、是否开启对注解的解析(<tx:annotation-driver transaction-manager="transactionManager" proxy-target-class="true">); 5、spring是否扫描到该包; 6、异常是否被catch,未抛出
(15),异常与错误
异常(Exception)与错误(Error)继承同一个接口(Throwable)。Exception,是程序可以解决的异常。Error是程序无法处理的错误(如:jvm出现错误)。 java里面将派生于Error或者RuntimeException(比如空指针,1/0)的异常称为unchecked异常,其他继承自java.lang.Exception得异常统称为Checked Exception,如IOException、TimeoutException等
(16),八大排序
直接插入排序,shell排序,直接选择排序,冒泡排序---------->时间复杂度:O(n^2) 快速排序:时间复杂度,平均和最好情况下:O(nlog2n),最坏情况:O(n^2),是不稳 定排序。 堆排序: 归并排序: 基数排序:
(17),jvm内存结构
程序计数器:jvm中的程序计数器与汇编中所属的程序计数器(PC寄存器),除了无物理概念上的实体外,功能上与之相同。用于当前需要执行的指令的地址(java多线程的实现是通过线程之间轮流获取cpu执行时间来实现的,因此任何时刻cpu只会执行一个线程的指令,所以为了线程之间互不干扰,每个线程都有自己独立的程序计数器,可以说程序计数器是每个线程所私有的)。jvm规定,在执行非native方法时,程序计数器保存的是当前需要执行的指令的地址,而在执行native(本地)方法时, 程序计数器中的值为undefined。程序计数器的存储空间不会随程序执行而发生变化,所以不会产生内存溢出的情况(OutOfMemory); java栈:java栈就是java方法执行的内存模型,java栈中存放着一个个的栈帧,每一个栈帧对应一个调用的方法,每个栈帧有局部变量表(用于存储方法中的局部变量,包括非静态变量以及函数形参,对于基本类型变量直接存储值,对于引用类型变量则存储对象的引用),操作数栈,指向当前方法所属的类的运行时常量池的引用,方法返回地址,一些额外附加信息。当前线程执行的方法必定在java栈的顶部(这就是递归方法易造成栈溢出),每个线程都有自己独立的java栈; 本地方法栈:功能类似于java栈,但本地方法栈服务于本地方法(Native method); 堆:用于存储对象本身和数组(数组引用存于java栈中),堆是被所有线程共享的,在jvm中只有一个堆; 方法区:方法区被所有线程共享,方法区用于存储每个类的信息(类名,方法信息,字段信息等),静态变量,常量,编译器编译后的代码等;
(18),JVM方法栈的工作过程,方法栈和本地方法栈有什么区别
JVM方法栈的工作过程:java栈对于每个线程是私有的,java栈中存放着一个个栈帧,每个栈帧对应一个方法,当程序执行一个方法时就会创建一个栈帧并对其进行压栈至到方法执行完成再出栈。 方法栈作用于java方法,本地方法栈功能类似于方法栈,但却作用于本地方法(Native Method)
(19),JVM的栈中引用如何和堆中的对象产生关联
在堆中产生一个数组或对象后,可以在栈中定义一个特殊变量,让栈中的变量取值等于对象或数组在堆内存中的首地址,栈中的这个变量就成了堆中对象或数组的引用变量。
(20),jvm中GC回收对象:jvm中,程序计数器,虚拟机(java)栈,本地方法栈都随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈出栈操作,实现了自动内存清理,因此,垃圾回收主要集中于java堆和方法区中。
(21),GC的常见算法,CMS以及G1的垃圾回收过程,CMS的各个阶段哪两个是Stop the world的,CMS会不会产生碎片,G1的优势
GC常见算法: 引用计数法:为对象添加一个引用计数器,每多一个引用就加一,每少一个引用就减一,当引用数为0时将不能被引用。实现简单但不能用于互相循环引用的问题; 根搜索算法:以一系列叫“GC Roots”的对象(java栈引用的对象,方法区中的类静态属性引用的对象,方法区中常量引用的对象,本地方法栈中引用的对象)作为起点向下搜索,走过的路径称为引用链,当一个对象没有和任何引用链相连时,证明此对象不可用(图论的说法为:不可达),那么判定对象为可回收对象; 标记-清除算法:分为标记和清除两个阶段,先把所有活动的对象标记出来,然后把没有被标记的对象统一清除掉。但存在两个问题:一是效率问题,两个过程效率都不高,二是空间问题,清除之后会产生大量不连续的内存; 复制算法:将原有的内存空间分成两块,每次只使用其中一块。在GC时,将正在使用的内存块中存活的对象复制到未使用的那一块中,然后清除正在使用的内存块中剩余的所有对象,并交换两块内存的角色,完成一次垃圾回收。它比标记-清楚算法高效,但不适用于存活对象较多的内存,因为这样复制时间会消耗太多,同时只有一半内存使用将造成极大内存浪费; 标记整理算法:适用于存活对象较多的场合,标记阶段与标记-清除算法的标记阶段一样,整理阶段,将所有存活的对象压缩到内存的一端,之后清理边界外所有的空间。弊端:效率不高;
CMS以及G1的垃圾回收过程,CMS的各个阶段哪两个是Stop the world的,CMS会不会产生碎片,G1的优势 CMS收集器:是一种以获取最短回收停顿时间为目的的收集器,是基于“标记-清楚”算法实现的,共四个步骤:初始标记<只标记GC Roots直接关联到的对象>;并发标记<进行GC Roots Tracing的过程>,重新标记<修正并发标记期间因程序继续运行而导致标记产生变动的一部分对象的标记记录>,并发清除<清楚未标记的对象>。其中初始标记和重新标记任然需要“Stop The World”(在执行垃圾收集算法时,java应用程序中系统只允许GC线程进行运行,其他线程全部被挂起)。优势:并发收集,低停顿。缺点:产生大量空间碎片,并发阶段会降低吞吐量。 G1收集器与CMS相比的优势:1,空间整合,G1采用标记整理算法,不会产生内存空间碎片。分配大对象是不会因为无法找到连续空间而提前触发下一次GC;2,可预测停顿,G1不止降低停顿时间还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。,,
(22),静态方法中不能调用非静态方法,非静态方法可以调用静态方法和非静态方法。原因:静态方法属于类的,当类加载时就会为其分配内存空间;而非静态方法属于对象,需要当创建一个对象后才能为其分配内存空间。所以当静态方法中直接调用非静态方法时,就相当于在调用一个不存在的方法。
(23),开放-封闭原则:针对拓展是开放的,针对修改是封闭的。
里氏替换原则:子类型必须能完全替代父类型。
依赖倒转原则:对抽象进行编程,不要对实现进行编程。
工厂模式:定义一个创建对象的工厂接口,工厂接口本身不创建对象,而是交给子类或其实现类去创建,将实际创建工作放到子类中去完成。
单例模式:创建对象模式之一,确保一个类只能创建一个实例对象。需确保两点:1、不能让外界创建对象,换而言之,构造方法需要为私有;2、提供可以得到实例对象的接口
(24),Math.random()取的是:[0,1)的随机数