字符串
String s1 = \"aaa\";
String s2 = \"aa\" + new String(\"a\");
String s3 = new String(\"aaa\");
System.out.println(s1.intern().equals(s1)); //true
System.out.println(s1.equals(s2)); //true
System.out.println(s3.equals(s1)); //true
System.out.println(s1.intern() == s1); //true
System.out.println(s1 == s2); //false
System.out.println(s3 == s1); //false
如何实现字符串的反转及替换?(答案有很多,递归解法)
public static String reverse(String originStr) {
if(originStr == null || originStr.length() <= 1)
return originStr;
return reverse(originStr.substring(1)) + originStr.charAt(0);
}
反射
获取类对象的方式
- 类名.class 如String.class
- 对象.class 如“a”.getClass()
- Class.forname(“类的全路径”),如 Class.forname(“java.lang.String”)
使用类对象创建实例对象
- 类对象.newInstance()
- 类对象.getDeclaredConstructor(传入参数类型的类对象)获取指定的构造器对象Constructor,然后再用构造器对象.newInstance(传入参数)
将获取到的私有字段或者私有方法变得可访问
setAccessible(true)
调用对象方法
getMethod(方法名),调用method.invoke(obj, arg)
集合
ArrayList、Vector、LinkList
ArrayList底层实现是数组,线程不安全,扩容大小是n+(n>>1),1.5倍,读取某个索引元素快,初始容量为10
Vector底层实现也是数组,线程安全,扩容大小是2n,读取某个索引元素快,初始容量为10
LinkList底层实现是双向链表,线程不安全,添加删除快,读取某个索引元素慢
HashMap和Hashtable
HashMap底层实现是数组+链表+红黑树(双向链表),数组初始容量为16,扩容为2n,链表元素大于等于8个(数组长度小于64则使用扩容来防止链表过长,否则使用红黑树)时变成红黑树,线程不安全,key和value可为空
Hashtable线程安全,但是效率极低,如果需要使用线程安全类建议使用ConcurrentHashMap代替
千万不要用可变类型做HashMap和HashSet键值
@Test
public void test2(){
HashSet<StringBuilder> stringBuilders = new HashSet<StringBuilder>();
StringBuilder aaa = new StringBuilder(\"aaa\");
StringBuilder bbb = new StringBuilder(\"aaabbb\");
stringBuilders.add(aaa);
stringBuilders.add(bbb);
System.out.println(stringBuilders); //[aaabbb, aaa]
StringBuilder s3 = aaa;
s3.append(\"bbb\");
System.out.println(stringBuilders); //[aaabbb, aaabbb] 破坏了hashset的值唯一性
}
HashMap底层原理详述
底层数据结构
1.7底层是数组+链表实现
1.8底层是数组+链表+红黑树(+双向链表)实现
如何解决hash碰撞
解决hash碰撞的方法:重新hash,开放地址法(即在hash地址往上或者往下一个存放),公共溢出区(开辟一个新区域专门存放碰撞的值),链地址法
HashMap使用先扰动函数,再链地址法解决碰撞问题,hash(key)&(n-1)=hash(key)%n(在n是2的幂次方时成立)
为什么数组长度必须是2的幂次方?
- 取模运算比位运算慢,2的幂次方可以将取模运算转换成位运算
- 使用位运算公式hash(key)&(n-1)计算值存放位置的时候,如果不是2的幂次方则会使得部分位置永远不会存放数据,浪费空间
- 扩容之后仍是2的幂次方,则n-1之后的二进制数只是在原本长度的二进制数位上高位进位1,即如果原本n-1的二进制数为1111,则扩容之后的二进制数为11111,则与hash(key)进行与运算之后要么还在数组原本的位置(hash(key)进位数为0),要么在原本位置加上扩容前数组长度的位置(hash(key)进位数为1),即用hash(key)&n(n为旧的数组长度)判断是等于0则放在新数组原位置,大于0则放在新数组原位置+n的位置
扩容机制
1.7超过扩容阈值(0.75*n)则先扩容再插入,头插法
从链表头开始,先将当前节点指向新数组位置,然后移动到新数组位置,依次循环遍历旧数组的链表完成向新数组的转移
1.8先插入,尾插法,再判断是否超过阈值进行扩容
红黑树加入双向链表结构是为了扩容方便,无论是双向链表还是单向链表,扩容的时候都是从链表头开始,先将当前的链表分为原数组位置和原数组位置+n两条链表,如果是单向链表则在分配好之后一次性放入这两个位置,如果是红黑树+双向链表则分配好之后再重新生成红黑树或者变成单向链表(链表长度<=6)
多线程下扩容问题
1.7扩容时的头插法会导致循环引用
1.8扩容时则可能会导致数据丢失
来源:https://www.cnblogs.com/jaz8912/p/16227721.html
本站部分图文来源于网络,如有侵权请联系删除。