Java常见小知识点(持续更新...)
1.在什么场景下需要重写hashcode?HashMap的实现原理?
- 如果我们对equals()方法进行了重写,一般对hashcode进行重写,保证相同的对象返回相同的hash值
- HashMap实际上是一个"链表散列"的数据结构,即数组和链表的组合
- 当我们向HashMap中put值的时候,先计算key的hash值,再根据hash值来计算出这个key在数组中的下标(通过hash与数组长度-1的位与运算,也正是因此,map长度都是2的次方,因为要保证length-1 都是 )如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上
- 默认长度是16,负载因子0.75
2.ConcurrentHashMap
ConcurrentHashMap把Map分成了几个segment,put和get的时候都根据key的hash,来计算在哪个segment中,每个segment类似于一个HashTable,都是线程安全的
3.容器
(1) java中,如果有一个类用来存放其他类的对象,这个类就叫容器,或者集合。集合就是讲若干性质相同或相近的类集合在一起而形成的整体。
之所以需要容器,是因为数组的长度难以扩充,数组中数据类型必须相同
(2) 容器和数组的区别
- 容器没有下标
- 数组的功能都可以用ArrayList来实现
- 可用toArrayList返回数组
4.ArrayList和LinkedList的区别
ArrayList基于数组,查找速度比较快,LinkedList基于链表,增删操作比较快
5.set防重
- HashSet不存入重复元素的规则.使用hashcode和equals,若计算出来一样,则加入失败
- reeSet红-黑树的数据结构,默认对元素进行自然排序。如果在比较的时候两个对象返回值为0,那么元素重复
6.Collection包装线程安全
Collections.synchronizedList(new ArrayList());
Collections.synchronizedSet(new HashSet());
7.JVM内存模型
JVM定义了若干个程序执行期间使用的数据区域,主要分为 方法区、堆、虚拟机栈、本地方法栈、程序计数器
7.1 程序计数器
程序计数器是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。分支、循环、异常跳转等基础功能依靠它。
如果执行的是一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是一个本地方法,这个计数器为空。
7.2 虚拟机栈
线程私有,生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的同时都会创建一个栈帧,
用于存放方法的局部变量表,操作栈,动态链接,方法出口等信息,Java方法从调用直至执行完成,就是一个帧栈在虚拟机栈中的入栈出栈过程
- 局部变量表: 用于存放方法参数和方法内部定义的局部变量
-
操作数栈:和局部变量表一样,操作数栈也是被组织成一个以字长为单位的数组,但是不是通过索引来获取值,是通过入栈、出栈来进行数据的
访问。 -
动态链接:虚拟机在运行时,常量池会保存大量的符号引用。如果是在类加载阶段或者第一次使用时转化为直接应用,则成为静态解析,
否则 成为动态链接。 -
返回地址:一种是正常退出,退出后会根据方法的定义来决定是否要传返回值给上层的调用者,一种是异常导致的方法结束,这种情况是不会传返回值给上层的调用方法。
方法的的一次调用就对应着栈帧在虚拟机栈中的一次入栈出栈操作,因此方法退出时可能做的事情包括:恢复上层方法的局部变量表以及操作数栈,如果有返回值的话,就把返回值压入到调用者栈帧的操作数栈中,还会把PC计数器的值调整为方法调用入口的下一条指令。
7.3 本地方法栈
与虚拟机栈类似,虚拟机栈是为了执行Java方法而存在的,本地方法栈是为了执行native方法入栈出栈的
7.4 堆
是JVM所管理的内存中最大的一块。被所有线程共享,在虚拟机启动时创建,用于存放对象实例,-Xms(最小值)和-Xmx(最大值),默认是计算机物理内存的1/64
堆是GC主要活动的区域,因为GC基本采用分代收集算法,因此又分为新生代和老年代。新生代,程序新创建的对象都是从新生代分配内存,经过多次GC仍然存货的对象会进入老年代,
新建的对象也可能直接进入老年代,大对象或者大的数组对象,且数组内部无引用外部对象
7.5 方法区
存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
8. GC算法相关
GC一般系统随机触发以及system.gc(),且当eden分配新对象而内存不足时触发minorgc,或者eden向old转移但是old内存不够时触发fullgc
对于新生代,一般采用复制算法,一般内存划分为一个较大的eden和2个survivor,每次使用eden和1个survivor,默认8:1,每次讲eden和survivor中存活的对象存入另一个survivor,
需要老年代担保(Handle Promotion)
对于老年代,一般采用标记-整理算法,讲可用对象向内存的一边偏移,然后清除掉边界以外的内存
9. GC时候如何判断一个对象为垃圾
可以通过引用计数器来判断,但是无法解决循环引用的问题
可达性分析法,GC root与这个对象没有可达路径
循环引用demo如下
Class C { public Object x; } C obj1、obj2 = new C(); obj1.x = obj2; obj2.x = obj1; obj1、obj2 = null;
这种情况下,obj1和obj2的计数器都不为0
10.sql优化
- 尽量避免全表扫描,尽量在where和order by上建立索引
- where中避免null的判断、少使用!=或者<>操作符,避免or 换成union all,少用in可用exists代替,where字句中少用函数