百木园-与人分享,
就是让自己快乐。

Java基础

字符串

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);
}

反射

获取类对象的方式

  1. 类名.class 如String.class
  2. 对象.class 如“a”.getClass()
  3. Class.forname(“类的全路径”),如 Class.forname(“java.lang.String”)

使用类对象创建实例对象

  1. 类对象.newInstance()
  2. 类对象.getDeclaredConstructor(传入参数类型的类对象)获取指定的构造器对象Constructor,然后再用构造器对象.newInstance(传入参数)

将获取到的私有字段或者私有方法变得可访问

setAccessible(true)

调用对象方法

getMethod(方法名),调用method.invoke(obj, arg)

集合

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的幂次方?

  1. 取模运算比位运算慢,2的幂次方可以将取模运算转换成位运算
  2. 使用位运算公式hash(key)&(n-1)计算值存放位置的时候,如果不是2的幂次方则会使得部分位置永远不会存放数据,浪费空间
  3. 扩容之后仍是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
本站部分图文来源于网络,如有侵权请联系删除。

未经允许不得转载:百木园 » Java基础

相关推荐

  • 暂无文章