Java基础–给简历上的扎实打个勾
1.HashMap的源码,实现原理。jdk8中对hashmap做了怎样的优化
hashmap实际上是一个“链表的数组”的数据结构,每个元素存放链表头结点的数组,当新建一个hashmap的时候,就会初始化一个数组。静态内部类Entry就是数组中的链表元素 ,有三部分组成,键值对和他指向下一个元素的引用。存放数据的时候,先根据key的hashcode重现计算hash值,再根据hash值得到元素的的下标,即数组中的位置上,如果该位置已经存放有其他元素,那么这个位置上的元素就以链表的形式存放,新加入的放在链头,最先加入的放在链尾,如果该位置上没有元素,直接放在该位置上。取得话就比较简单了,先遍历数组,再遍历链表元素。
java8优化:①(扩容机制)初始化一个初始容量为16的数组加链表结构,负载因子为0.75,当容量大于0.75f时,resize自动扩容2的n次方②当链表长度大于8时,转化为红黑树结构。
为什么每次都是2的n次幂:hashmap是通过按位与来获取数组下标的,计算机里位运算是基本运算,位运算的效率远高于取余运算的,当容量一定是2的n次幂是,h&(length-1)与h%length相等。
2.HashMap,HashTable,ConcurrentHashMap的区别。
1.hashmap是非线程安全的,hashtable是线程安全的,hashtable中的很多方法都加了sysnchronized关键字,确保了方法的同步。
2.hashmap可以接受null键和null值,hashtable不能
3.单线程环境下hashmap的书读高于hashtable
4.hashmap的迭代器采用的是iterrator,是快速失败,操作的是集合本身,遍历过程中若有其他线程的速度高于hashmap进行增加或者删除元素,则会抛出ConcurrentModificationException(并发修改异常)。hashtable的迭代器是Enumeration,是安全失败,操作的是原集合的一个拷贝。其他线程对该集合进行操作时,不会跑出并发修改异常。
5.在进行迭代时,hashtable会锁住整个map,而ConcurrentHashMap只锁住map的一部分(性能区别),所以ConcurrentHashMap在多线程环境下的性能更好
6.让hashMap实现异步功能:Map m = Collections.syschronizeMap(hashMap);
3.HashMap在高并发下如果没有处理线程安全会有怎样的安全隐患,具体表现是什么。
1.多线程put时可能会导致get无线循环,具体表现为cpu使用率100%:在向hashmap中put元素时,会检查hashmap的容量是否足够,如果不足,扩建hash表,然后把数组从老的hash表中迁移到新的hash表中,迁移的过程就是一个rehash过程,多个线程同时操作就有可能会形成循环链表,get时就会出现infinite loop情况
2.多线程put时可能导致元素丢失:当多个线程同时执行addEntry时,如果产生哈希碰撞,导致两个线程得到同样的bucketIndex去存储,就可能发生元素覆盖丢失的情况;
解决方法:1.hashtable(不建议);2.ConcurrentHashMap;3.使用synchronizedMap()同步方法包装map,得到线程安全map,在此map上进行操作;
4.java中四种修饰符的限制范围。
public 本类,本包,不同包子类,不同包非子类
protected 本类 本包 不同包子类
default 本类 本包
private 本类
5.Object类中的方法。(五种)
clone():克隆对象:1.先覆盖克隆方法;2.实现Cloneable接口;3.方法调用,抛出异常
finalize():对象的遗言方法,当gc回收一个对象的时候,主动调用这个对象的finalize()方法;
final修饰符 ,finalize是object类里面的一个方法,处理异常时(无论是否报异常)最后执行的方法
toString():一般用来打印对象
equlas():本身是比较两个对象的地址,但是可以重写equals方法比较两个对象的值
hashcode():制定一个对象的散列特征码(hashset,hashmap底层实现),当equals方法被重写时,该方法 有必要重写以维护常规协定(对象相等,hash码相等)
6.接口和抽象类的区别,注意JDK8的接口可以有实现。
同:都能定义方法和属性,定义的方法大多时候需要子类来实现,都不能含有抽象方法,接口没有方法可以作为一个标志,比如说可序列化的接口Serializable,抽象类和接口都不能创建对象, 都能利用多态性原理使其应用指向子类对象;继承和实现接口或抽象类的子类必须实现接口或抽象类的所有方法,抽象类若没有实现方法就继续作为抽象类,要加abstract修饰,若是接口的子类没有实现的方法,也要变为抽象类。
不同:1.接口能够多实现,抽象类只能单独被继承,其本质就是,一个类能实现多个接口,而只能继承一个抽 象类。
2.在方法上 :抽象类的方法可以用abstract、public和protect修饰,而接口默认为public abstract修 饰
3.抽象类的方法可以有需要子类实现的抽象方法,也可以有具体的方法;而接口在老版jdk中,只能有· 抽象方法,jdk8的接口中可以带默认方法;
4.属性上:抽象类可以用各种各样的修饰符修饰,接口的属性默认是public static final
5.抽象类可以含有静态代码块和静态方法,而接口不能
6.抽象类可以含有构造方法,接口不能
7.设计层面上:抽象类表示的是子类“是不是”属于某一类的子类,接口则表示“有没有”特性,“能不能”做这种事。比如飞机和鸟都能飞,他们在设计上都实现了一个fly接口。实现fly()方法。远比两个类继承飞行物抽象类好得多。应为飞机和鸟有太多属性不一样;
8.设计层面2(同3):抽象类可以是一个模板,因为可以自己带集体方法,所以要加一个实现类都能有的方法,直接在抽象类中写出并实现就好,接口在旧版本不行,jdk8才有默认方法
9.jdk8中的接口的默认方法是可以被多重继承的。而抽象类不行
10.接口只能实现接口,而抽象类可以继承普通的类,也能继承接口和抽象类
7.动态代理的两种方式,以及区别。
jdk动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用invokeHandler来处理。
CGlib动态代理:利用ASM(开源的java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
区别:①.jdk代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final实现类。
②.jdk代理创建代理对象效率高,执行效率低,cglib创建效率较低,执行效率较高
③.jdk代理使用的是委托机制,动态实现接口类,在实现类里委托handler调用原始实现类方法,CGlib使用继承机制,具体说被代理类和代理类是继承关系,所有代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口;
动态代理的应用:AOP(面向切面编程),实现方式就是通过目标对象的代理在连接点前后加入通知,完成统一的 切面操作。
实现aop的技术:①.采用动态代理技术,利用截取消息的方式,对该消息进行装饰,取代原有对象行为的执行;
②.采用静态织入的方式,引入特定的语法创建“方面”,从而使编译器可以在编译期间织入有关“方面”的代码。
spring采用这两种方式来生成代理对象,具体用哪种方式由AopProxyFactory根据AdviseedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用jdk动态代理技术(解耦,但是要为每一个目标类创建接口),如果目标对象没有实现接口,则默认会采用CGlib代理(不需要接口存在,松耦合没有jdk好)。如果目标对象实现了接口,可以通过配置强制使用CGlib实现代理。
8.Java序列化的方式(两种方式)。
为何序列化:将对象以字节流的形势传入与传出,要实现序列化先要统一格式
a.相应的对象实现了序列化接口Serializeble(标识该对象可序列化,传输时会进行相关包装,java原生支持,无需依赖,使用简单,无法跨语言,字节数占用较大,对属性占用敏感,不强制声明serialVersionUID对于对象向上向下的兼容性影响很大)
b.实现接口Externalizable,继承了序列化接口,并增加了两个方法readExternal与writeExternal:一个是writeExternal(),定义了哪些属性可以序列化,哪些不可以序列化,对象经过这里把规定能序列化的序列化,不能序列化的不做处理,在凡系列化的时候自动调用readExternal方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回,再接收,就完成了反序列化
注意:transient关键字修饰的变量和静态变量不可以被序列化;
序列化和反序列化过程是类似于队列的,先进先出,顺序不能颠倒;
c.将对象包装成json字符串传输;(明文结构一目了然,可以跨语言,属性改变对解析端影响较小,字数过多,依赖于不同的第三方类库)
d.采用谷歌的ProtoBuf(谷歌内部的一种传输协议,目前已经开源,占用字节数很小,适合网络传输节省io,跨语言,但是需要依赖于工具生成代码)
9.传值和传引用的区别,Java是怎么样的,有没有传值引用。
1.java方法基本数据类型是传值,对象类型传引用
2.当参数是对象时,无论方法体内进行了何种操作,都不会改变实参对象的引用,但有可能改变实参对象的属性值
3.基本数据类型传值不会改变实参的值;
4.immutable类型因为有final属性变量无法被修改,只能重新赋值或生成对象。当Interger作为方法参数传递时,对其赋值会导致原有的引用被指向了方法内的栈地址,失去了原有的地址指向,所以赋值后的Interger做任何操作都不会影响原有的值。
10.一个ArrayList在循环过程中删除,会不会出问题,为什么。
ArrayList底层是数组,而且是Object[]类型,集合内部使用数组来保存元素,在删除元素时,会将被删除元素之后的元素往前一个位置以填补空缺,如果说当前循环删除了索引为i的元素,索引为i+1的元素会填补到i的位置,下一个循环处理的就是删除前索引为i+2的元素,也就是说,在当前循环执行删除元素之后,后面的处理都是错误的。
11.@transactional注解在什么情况下会失效,为什么。
service标签上添加@transactional可以将整个类纳入spring事务管理,每个方法执行时都会开启一个事务,这些事务采用相同的管理方式。
1.@Transactional注解只能应用到可见度为public的方法上,其他可见度不会报错,但是无设置不会起作用。
2.spring默认会对unchecked异常进行事务回滚,如果是checked异常则不会滚();可在注解上写明类型强制回滚和不会滚。checked异常:Error与RunTimeException unchecked异常:继承自exception
3.只读事务,
解决不会滚:
检查是不是public ,检查异常类型是不是unchecked异常,数据库引擎是否支持事务,是否开启了注解支持,spring是否扫描到该包,检查是不是同一个类中的方法调用,异常是不是被catch了
***如果是同一个中的方法被调用:***使用分布式事务 seata