一.String
1.1string源码中,几个变量 :
String类有final修饰,所以String类不能被继承
@Stable
private final byte[] value; final 是不可变的;因为private是私有的,并且没有提供修改value数组的方法,所以一旦确定值是不可变的;
构造函数:
public String() {
this.value = "".value;
this.coder = "".coder;
}
public String(String original) {
this.value = original.value;
this.coder = original.coder;
this.hash = original.hash;
}
1.2 方法:
1.2.1 equals 方法
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
latin1码下的比较
public static boolean equals(byte[] value, byte[] other) {
if (value.length == other.length) {
for (int i = 0; i < value.length; i++) {
if (value[i] != other[i]) {
return false;
}
}
2.StringBuffer 继承自abstractStringBuffer
变量:
byte[] value;
不是final的可以改变,并且权限是默认的;
继承自abstractStringBuilder
append方法:
扩容的新容量为当前value的容量2倍加2,如果扩容后的容量还是比需要的最小容量小,则直接扩容为需要的最小容量,再将当前value内容复制给一个新的长度为newCapacity的字符数组,再将value指向这个扩容后的新数组。即扩容是通过开辟新数组完成的,返回的也是新创建的新数组。
先扩容,将原先的数组复制到新数组中;
*/
public AbstractStringBuilder append(String str) {
if (str == null) {
return appendNull();
}
int len = str.length();
ensureCapacityInternal(count + len);
putStringAt(count, str);
count += len;
return this;
}
*/
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
int oldCapacity = value.length >> coder;
if (minimumCapacity - oldCapacity > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity) << coder);
}
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = value.length >> coder;
int newCapacity = (oldCapacity << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
int SAFE_BOUND = MAX_ARRAY_SIZE >> coder;
return (newCapacity <= 0 || SAFE_BOUND - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
3.String与StringBuffer与StringBuilder三者介绍和三者的区别:
string 是不可变的,每次对string的操作其实是生成一个新的string对象;
tringBuffer与StringBuilder,是对自己本身操作的,并不产生新对象;
因为在源码中,string的值存放在value的char[] 数组中,这个value变量值final的并且是private,并且源码中并没有可以改变value数组的方法,同时stringbuffer的value数组权限是默认的且非final的;
StringBuilder是线程非安全的,stringbuffer是线程安全方法加锁的;
二.list
1.ArrayList
底层由数组实现,
最主要的方法:
add(): 初始 默认是10; 先查询是否进行扩容,如果扩容Array.copyof()方法,扩到1.5倍,;
remove() 其实底层用的是system.arraycopy;
public int size() {
return size;
}
返回的size是变量,是list的长度,而不是数组的长度;
2. ArrayList 的安全问题
ArrayList 采用fail-fast 机制,是java集合的一中失败检测机制,在迭代过程中报错;
具体场景是:
List<String> list = new ArrayList<>();
for (int i = 0 ; i < 10 ; i++ ) {
list.add(i + "");
}
Iterator<String> iterator = list.iterator();
int i = 0 ;
while(iterator.hasNext()) {
if (i == 3) {
list.remove(3); //改成iterator.remove(3)方法代替就可以解决
}
System.out.println(iterator.next());
i ++;
}
//这个时候就会报错,因为当ArraList在添加或者删除操作时,会有个专门的记录修改次数的变量+1,而在执行iterator.next()操作时候,会 进行一个判读 expectedModCount和门的记录修改次数的变量的判断,这个时候就会报错;
解决方法 : list.remove(3); //改成iterator.remove(3)方法代替就可以解决;
2.linkedList
双向链表
变量:
Node : first last;
方法 :
get(int index) //获取指定位置的节点:
会把index 与size/2比,小于,从头开始遍历查找,大于从尾部查找开始;
也同时实现了普通队列和栈的功能:
//出队(从前端),如果不存在会返回null,存在的话会返回值并移除这个元素(节点)
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
//出队(从前端),如果不存在会抛出异常而不是返回null,存在的话会返回值并移除这个元素(节点)
public E remove() {
return removeFirst();
}
构造函数:
其中有个构造函数
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
addAll方法把c转换成数组,然后遍历数组一个个添加,
三. map
1.hashMap
hashMap 是数组+链表的形式,根据key的hashcode 来确定放到哪个桶,当两个key的hashcode相同时候,需要放在一个桶中,这个时候就用链表,如果链表长度超过(默认),就转成红黑树;
如果数组越长 那么hash冲突的概率就越小,而查找效率越快,但空间的占用就越多,所以这个时候,出现了扩容机制;
扩容机制是 根据默认负载因子0.75,threshold=负载因子 * length; //判断是否需要扩容,每次扩容*2;HashMap是先插入数据再进行扩容的,但是如果是刚刚初始化容器的时候是先扩容再插入数据。
扩容之后的hash计算 :只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”,
put()方法
检测table[]是否为空,
检测table[i]是否为插入过,选择直接插入,或者遍历
然后看是否链表超过8,编程红黑树;
如果key相同覆盖,如果不是插入,
2.hashtable
hashtable 相对来说是线程安全的,加了锁; 初始容量11 每次扩容2n+1;ashMap的迭代器(Iterator)是fail-fast迭代器;
Map m = Collections.synchronizeMap(hashMap);(让hashmap变线程安全);
3.ConcurrentHashMap
1.7中采用的是分段锁,解决了hashmap中的线程安全问题,可以多个线程同时访问不同的segement。
ConCurrentHashMap1.7中的实现是
segement[]数组,里面是enrty[]数组,而entry又是链表;
segement是充当锁,实现的是ReentrantLock锁。
put方法
先根据hash找到对应的segement,
然后尝试获取锁,如果获取锁失败就说明有竞争存在,然后就自旋尝试获取,当自旋锁获取到了一定次数,改为独占锁,阻塞,确保一定可以获取到锁。
在获取到锁之后,在通过hash找到对应的entry,如果存在hash,则进行比较key是否相等,如果相等则进行覆盖,如果不相等则插入。如果不存在则插入一个新的entry,并且判断是否需要扩容。
get()方法,不加锁的
直接两侧hash。
jdk1.8
取消了分段锁,直接cas+synchronize 保证线程安全。
基本单位换成了node,变量仍然是用volatile修饰。
put方法()
判断是否进行初始化
根据hash定位到Node,如果为空,则用cas进行尝试写入,写入失败则进行自旋。
如果hashcode =-1 则进行扩容操作;
如果不为空,则加synchronize锁进行写操作。
如果链表数量大于阙值则进行转换成红黑树。