技术学习总结
学习技术一定要制定一个明确的学习路线,这样才能高效的学习,不必要做无效功,既浪费时间又得不到什么效率,大家不妨按照我这份路线来学习。
最后面试分享
大家不妨直接在牛客和力扣上多刷题,同时,我也拿了一些面试题跟大家分享,也是从一些大佬那里获得的,大家不妨多刷刷题,为金九银十冲一波!
[java] view plain copy
-
public V put(K key, V value) {
-
if (key == null)
-
return putForNullKey(value); //处理null值
-
int hash = hash(key.hashCode());//计算hash
-
int i = indexFor(hash, table.length);//计算在数组中的存储位置
-
//遍历table[i]位置的链表,查找相同的key,若找到则使用新的value替换掉原来的oldValue并返回oldValue
-
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
-
Object k;
-
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
-
V oldValue = e.value;
-
e.value = value;
-
e.recordAccess(this);
-
return oldValue;
-
}
-
}
-
//若没有在table[i]位置找到相同的key,则添加key到table[i]位置,新的元素总是在table[i]位置的第一个元素,原来的元素后移
-
modCount++;
-
addEntry(hash, key, value, i);
-
return null;
-
}
a HashMap会对null值key进行特殊处理,总是放到table[0]位置
b计算has值
c 找到在table数组中的索引
1)遍历table[i]位置的链表,查找相同的key,若找到则使用新的value替换掉原来的oldValue并返回oldValue
2)若没有在table[i]位置找到相同的key,则添加key到table[i]位置,新的元素总是在table[i]位置的第一个元素,原来的元素后调用addEntry方法
[java] view plain copy
-
void addEntry(int hash, K key, V value, int bucketIndex) {
-
if ((size >= threshold) && (null != table[bucketIndex])) {
-
resize(2 * table.length);
-
hash = (null != key) ? hash(key) : 0;
-
bucketIndex = indexFor(hash, table.length);
-
}
-
createEntry(hash, key, value, bucketIndex);
-
}
[java] view plain copy
-
void createEntry(int hash, K key, V value, int bucketIndex) {
-
Entry<K,V> e = table[bucketIndex];
-
table[bucketIndex] = new Entry<>(hash, key, value, e);
-
size++;
-
}
所做的工作就是:
1)判断当元素数量达到临界值(capactiy*factor)时,则进行扩容,是table数组长度变为table.length*2
2)当table[index]已存在其它元素时,会在table[index]位置形成一个链表,将新添加的元素放在table[index],原来的元素通过Entry的next进行链接,这样以链表形式解决hash冲突问题,
**2)get方法
**同样当key为null时会进行特殊处理,在table[0]的链表上查找key为null的元素
[java] view plain copy
-
public V get(Object key) {
-
if (key == null)
-
return getForNullKey();
-
Entry<K,V> entry = getEntry(key);
-
return null == entry ? null : entry.getValue();
-
}
get的过程是先计算hash然后通过hash与table.length取摸计算index值,然后遍历table[index]上的链表,直到找到key,
然后返回,找不到返回null
[java] view plain copy
-
final Entry<K,V> getEntry(Object key) {
-
if (size == 0) {
-
return null;
-
}
-
int hash = (key == null) ? 0 : hash(key);
-
for (Entry<K,V> e = table[indexFor(hash, table.length)];
-
e != null;
-
e = e.next) {
-
Object k;
-
if (e.hash == hash &&
-
((k = e.key) == key || (key != null && key.equals(k))))
-
return e;
-
}
-
return null;
-
}
3)remove方法
remove方法和put get类似,计算hash,计算index,然后遍历查找,将找到的元素从table[index]链表移除
[java] view plain copy
-
public V remove(Object key) {
-
Entry<K,V> e = removeEntryForKey(key);
-
return (e == null ? null : e.value);
-
}
[java] view plain copy
-
final Entry<K,V> removeEntryForKey(Object key) {
-
if (size == 0) {
-
return null;
-
}
-
int hash = (key == null) ? 0 : hash(key);
-
int i = indexFor(hash, table.length);
-
Entry<K,V> prev = table[i];
-
Entry<K,V> e = prev;
-
while (e != null) {
-
Entry<K,V> next = e.next;
-
Object k;
-
if (e.hash == hash &&
-
((k = e.key) == key || (key != null && key.equals(k)))) {
-
modCount++;
-
size–;
-
if (prev == e)
-
table[i] = next;
-
else
-
prev.next = next;
-
e.recordRemoval(this);
-
return e;
-
}
-
prev = e;
-
e = next;
-
}
-
return e;
-
}
4)clear()方法
clear方法非常简单,就是遍历table然后把每个位置置为null,同时修改元素个数为0
需要注意的是clear方法只会清楚里面的元素,并不会重置capactiy
[java] view plain copy
-
public void clear() {
-
modCount++;
-
Arrays.fill(table, null);
-
size = 0;
-
}
5)containsKey和containsValue
containsKey方法是先计算hash然后使用hash和table.length取摸得到index值,遍历table[index]元素查找是否包含key相同的值
[java] view plain copy
-
public boolean containsKey(Object key) {
-
return getEntry(key) != null;
-
}
[java] view plain copy
-
final Entry<K,V> getEntry(Object key) {
-
if (size == 0) {
-
return null;
-
}
-
int hash = (key == null) ? 0 : hash(key);
-
for (Entry<K,V> e = table[indexFor(hash, table.length)];
-
e != null;
-
e = e.next) {
-
Object k;
-
if (e.hash == hash &&
-
((k = e.key) == key || (key != null && key.equals(k))))
-
return e;
-
}
-
return null;
-
}
6)containsValue方法就比较粗暴了,就是直接遍历所有元素直到找到value,由此可见HashMap的containsValue方法本质上和普通数组和list的contains方法没什么区别,你别指望它会像containsKey那么高效
[java] view plain copy
-
public boolean containsValue(Object value) {
-
// Same idea as size()
-
if (value == null)
-
throw new NullPointerException();
-
final Segment<K,V>[] segments = this.segments;
-
boolean found = false;
-
long last = 0;
-
int retries = -1;
-
try {
-
outer: for (;😉 {
-
if (retries++ == RETRIES_BEFORE_LOCK) {
-
for (int j = 0; j < segments.length; ++j)
-
ensureSegment(j).lock(); // force creation
-
}
-
long hashSum = 0L;
-
int sum = 0;
-
for (int j = 0; j < segments.length; ++j) {
-
HashEntry<K,V>[] tab;
-
Segment<K,V> seg = segmentAt(segments, j);
-
if (seg != null && (tab = seg.table) != null) {
-
for (int i = 0 ; i < tab.length; i++) {
-
HashEntry<K,V> e;
-
for (e = entryAt(tab, i); e != null; e = e.next) {
-
V v = e.value;
-
if (v != null && value.equals(v)) {
-
found = true;
-
break outer;
-
}
-
}
-
}
-
sum += seg.modCount;
-
}
-
}
-
if (retries > 0 && sum == last)
-
break;
-
last = sum;
-
}
-
} finally {
-
if (retries > RETRIES_BEFORE_LOCK) {
-
for (int j = 0; j < segments.length; ++j)
-
segmentAt(segments, j).unlock();
-
}
-
}
-
return found;
-
}
2 HashTable源码分析
1)构造方法有三个 ,带零个、一个、两个参数的,最终都会调用两个参数的构造方法
其思想也比较简单,检查参数的合法性
public Hashtable(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
initHashSeedAsNeeded(initialCapacity);
}
2)put操作,直接在方法的前面加上synchronized 关键字,简单粗暴,在并发激烈的情况下,效率较低,所以才会有
ConcurrentHashMap的诞生。
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}
modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
3)get操作也一样,直接在访问该方法的时候上锁,确保同一个时刻只有一个线程能访问
public synchronized V get(Object key) {
Entry tab[] = table;
int hash = hash(key);
写在最后
很多人感叹“学习无用”,实际上之所以产生无用论,是因为自己想要的与自己所学的匹配不上,这也就意味着自己学得远远不够。无论是学习还是工作,都应该有主动性,所以如果拥有大厂梦,那么就要自己努力去实现它。
最后祝愿各位身体健康,顺利拿到心仪的offer!
由于文章的篇幅有限,所以这次的蚂蚁金服和京东面试题答案整理在了PDF文档里
hold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
3)get操作也一样,直接在访问该方法的时候上锁,确保同一个时刻只有一个线程能访问
public synchronized V get(Object key) {
Entry tab[] = table;
int hash = hash(key);
写在最后
很多人感叹“学习无用”,实际上之所以产生无用论,是因为自己想要的与自己所学的匹配不上,这也就意味着自己学得远远不够。无论是学习还是工作,都应该有主动性,所以如果拥有大厂梦,那么就要自己努力去实现它。
最后祝愿各位身体健康,顺利拿到心仪的offer!
由于文章的篇幅有限,所以这次的蚂蚁金服和京东面试题答案整理在了PDF文档里
[外链图片转存中…(img-rZPpVGKe-1715304213739)]
[外链图片转存中…(img-1szeFIJm-1715304213739)]
[外链图片转存中…(img-LKSxLZ1Q-1715304213740)]