String x="abc"; 和String x=new String("abc") 区别
String x="abc" 内存会去查找永久代(常量池),如果常量池没有则开辟新的区域,将x指针指向此区域地址,如果有此常量,则直接指向此常量地址。
而String str = new String("abc");是在堆中从新new一块儿内存,把指针赋给栈,将新构造出来的String对象的引用赋给str。 因此 只要是new String(),则,栈中的地址都是指向最新的new出来的堆中的地址。
注: String Integer 等重写了 equals方法
String s3 = new String("hello");
String s4 = "hello";
System.out.println(s3 == s4);// false
System.out.println(s3.equals(s4));// true
String s5 = "hello";
String s6 = "hello";
System.out.println(s5 == s6);// true
System.out.println(s5.equals(s6));// true
String s1 = "hello";
String s2 = "world";
String s=s1+s2;
String s3 = "helloworld";
System.out.println(s3 == s1 + s2); //false 两个变量相加会先在常量池中开辟空间,再相加,再存放到此空间
System.out.println(s == s3); //false
System.out.println(s3 == "hello" + "world"); //false //两个常量相加会先相加得到新的常量,再去常量池比较是否存在
为什么重写equals时必须重写hashCode方法?
Object的hashcode方法是本地方法,也就是用c语言或c ++实现的,该方法通常将对象的内存地址转换为整数之后返回。 重写的hashCode 实质内容和对象每个属性的hashCode()值相关
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
重写equals时必须重写hashCode方法只和hash容器有关,如果确保此对象不会被加入到hash容器,则不必重写hashCode(),hashCode() 在散列表中才有用,在其它情况下没用,当两个equals相等的对象没有重写hashCode()被加入到hashSet中,由于hashSet底层是hashMap set中的元素作为Map中的键,所以set加入第二个对象时,会通过对象hashcode值来判断对象加入的位置,如果没有重写的话是一个全新的hashCode值,则会加入到set中,此时则不是想要的结果。(没有重写hashCode则不能根据键的hashCode值来正确的确定在散列表的位置)
java 中只有值传递
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1, num2);
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);
}
public static void swap(int a, int b) {
//调用时候都相当int a=10,b=20
int temp = a;
a = b;
b = temp;
System.out.println("a = " + a);
System.out.println("b = " + b);
}
//输出
a = 20
b = 10
num1 = 10
num2 = 20
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]);
change(arr);
System.out.println(arr[0]);
}
public static void change(int[] array) {
// 将数组的第一个元素变为0
array[0] = 0;
}
//输出
1
0
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
Student s1 = new Student("小张");
Student s2 = new Student("小李");
Test.swap(s1, s2);
System.out.println("s1:" + s1.getName());
System.out.println("s2:" + s2.getName());
}
public static void swap(Student x, Student y) {
Student temp = x;
x = y;
y = temp;
System.out.println("x:" + x.getName());
System.out.println("y:" + y.getName());
}
}
//输出
x:小李
y:小张
s1:小张
s2:小李
hashMap 源码
1.7
默认数组容量 16
默认加载因子 0.75f
阈值 = 容量*加载因子
构造方法
调用this方法 赋值
- put过程
1.1 判断数组是否为空,为空 初始化空的数组
首先确定数组容量为>=传入的容量值 且 是2的幂次方,
注:因为后续计算数组下标公式是hashCode & (length-1),在与运算中为了让下标在0-length-1范围内,lenth-1 是一个必须高位全为0,低位全为1的数,推算 length必须是只有1位是1其他位是0,也就是2的幂次方。
计算阈值
new 数组
1.2 hash(key) 方法获得一个hash值 ,内部通过异或和右移让原始hashCode高位参与运算,提高hashCode的散列性
1.3 indexfor 方法得到存放的数组下标
1.4 遍历从arr[i]开始的链表 如果当前节点的hash值和刚才计算出的hash值相等且(key的内存地址相等或内容相等),则新的value覆盖旧的value,且返回旧的value
1.5 modCount++ 改变次数+1,和异常有关,在遍历会执行expectCount=modeCount,在hashMap.remove()时modCount++,在判断modeCount==expectCount会抛出异常,是一种fast-fail机制,因为hashMap认为在遍历的过程中增减操作时可能存在风险,不允许这么做。
1.6 如果没有相等的则执行addEntry(hash,key,value,i)方法
判断 size > 容量*加载因子 && table[i] !=null
数组扩容
新建一个两倍容量的数组,将数据转移到新的数组,计算阈值
转移方法 链表上的每个节点重新计算出的i可能和原来的i相同或是原来的两倍,所有扩容的目的是数组变长,链表变短
注: 为什么说hashMap是线程不安全的,因为多线程下转移方法中指针指向会出现问题,形成循环链表,耗尽资源
创建节点
头插法并且向下移动,size+1
ConcurrentHashMap
1.7
注 hashTable 也是线程安全的,内部在每一个操作上都加了synchronized 关键字
构造器 (默认参数16 0.75f 16)
DEFAULT_CONCURRENCY_LEVEL :segment 数量
DEFAULT_INITIAL_CAPACITY : hashEntry 数量
算出来每个segment中有一个hashEntry
segment 数量最多为16个
确定segment数量 真正的segment数量是 <传入的level && 是2的幂次方 (1,2,4,8,16)
确定hashEntry数量 每个hashEntry 也必须是2的幂次方 且 > capacity/level
new Segment[]
在索引为0的位置放入segment对象
put的时候 用cas方式只允许一个线程去修改数组