代码味道(持续更新)

UUID
  1. 基于随机数的版本4的UUID
/*
     * The random number generator used by this class to create random
     * based UUIDs. In a holder class to defer initialization until needed.
     */
    private static class Holder {
        static final SecureRandom numberGenerator = new SecureRandom();
    }

private保证外部无法访问,static保证唯一生成随机数,静态内部类懒加载(对比全局静态变量来说的好处)。UUID有多种生成方式,有基于随机数、时间戳、name、String等。

ArrayList
  1. 使用transient修饰elementData
transient Object[] elementData; // non-private to simplify nested class access

比如,现在实际有了8个元素,那么elementData数组的容量可能是8x1.5=12,如果直接序列化elementData数组,那么就会浪费4个元素的空间,特别是当元素个数非常多时,这种浪费是非常不合算的。
所以ArrayList的设计者将elementData设计为transient,然后在writeObject方法中手动将其序列化,并且只序列化了实际存储的那些元素,而不是整个数组。

  1. 数组的最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

数组的最大长度为Integer.MAX_VALUE - 8,数组作为一个对象,需要存储一些对象头信息,减少一些内存溢出几率。但是存在一个hugeCapacity,可以扩展到Integer.MAX_VALUE。我们要尽可能地使minCapacity与实际存储大小size接近。

  1. indexOf(Object o)不同情况的判断
public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++) {
                if (elementData[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = 0; i < size; i++) {
                if (o.equals(elementData[i]))
                    return i;
            }
        }
        return -1;
    }

为了防止空指针异常,我们用== 代替equals来判断是否为null。

  1. replac temp with query(以查询代替临时变量)
E elementData(int index) {  return (E) elementData[index];}
  1. 资源释放,不要的资源要及时释放
 public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);
        // 这里的判断是如果要删除最后一个元素的话,不需要调用数组的拷贝,直接置null即可
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
  1. 临时变量记录初始状态来进行状态的检查
 private void writeObject(java.io.ObjectOutputStream s) throws IOException {
        int expectedModCount = modCount;
        //  Write the non-static and non-transient fields of the current class to this stream.
        s.defaultWriteObject();
        s.writeInt(size);
        for (int i = 0; i < size; i++) {
            s.writeObject(elementData[i]);
        }
        // 确保在写的过程中没有数据丢失,没有对数组进行修改
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
HashMap
  1. hash表防止碰撞, 将高16位与低16位异或
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

详解地址

  1. 计算大于输入参数且最近的2的整数次幂的数
 static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

通过移位使第一个1后面的所有位变为1,最后 n + 1,即可得到最接近的2的倍数。详解地址

  1. HashMap数据结构
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 如果table为空,resize()
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 如果hash位置为空,直接放入node
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            // 如果存在映射,直接返回value
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k)))) // 先判断==,再判断equals
                e = p;
			// 如果存在Treenode说明已经树化了,该往树里存
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            	// 查找链表里的值
                for (int binCount = 0; ; ++binCount) {
                	// 如果走到链表最后还没找到,新创建一个node
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        // 在链表中如果超过树化阈值需要树化
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
					// 不断遍历链表
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
  1. resize()函数
    关于(e.hash & oldCap)的解析
 final Node<K, V>[] resize() {
        // 先对oldTab进行判断,主要进行容量和阈值的计算
        Node<K, V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        // 如果oldCap大于0,先判断有没有超过最大容量
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // 如果没有超过最大容量,进行移位后再判断超过最大容量
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITAL_CAPACITY)
                newThr = oldThr << 1;
        }
        // 如果容量为0,但是有初始的threshold值,可以不用默认值
        else if (oldThr > 0)
            newCap = oldThr;
        else {
            newCap = DEFAULT_INITAL_CAPACITY;
            newThr = (int) (DEFAULT_INITAL_CAPACITY * DEFAULT_INITAL_CAPACITY);
        }
        // 重新处理阈值
        if (newThr == 0) {
            float ft = (float) newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        // new一个新的数组,可以开始搬数据了
        Node<K, V>[] newTab = (Node<K, V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K, V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        // 如果只有单个node,直接存入新数组
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        // 如果是treeNode,重新拆解node
                        ((TreeNode<K, V>)e).split(this, newTab, j, oldCap);
                    else {
                        // 为什么需要这么麻烦?为了保证拷贝过去的链表保持原顺序
                        Node<K, V> loHead = null, loTail = null;
                        Node<K, V> hiHead = null, hiTail = null;
                        Node<K, V> next;
                        do {
                            next = e.next;
                            // 原索引
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            // 原索引 + oldCap
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next ) != null);
                        // 先组成链表,再放入桶内
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }

        }
        return newTab;
    }
  1. putTreeVal(),通过比较key的hash值来确定放入左子树还是右子树
final TreeNode<K, V> putTreeVal(MyHashMap<K, V> map, Node<K, V>[] tab, int h, K k, V v) {

            Class<?> kc = null;
            boolean searched = false;
            TreeNode<K, V> root = (parent != null) ? root(): this;
            for (TreeNode<K ,V> p = root;;) {
                int dir, ph; K pk;
                // 此节点的hash值与h不同,h是调用hash()函数算出来的,而p.hash是节点真正的hash值,在此处比较是为了方便放入左子树还是右子树
                if ((ph = p.hash) > h )
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                // 同一个对象或者值相等都可以
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;
                // 判断是否实现了compareble接口,hash值相等,但是不可比较进这里
                else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0)) {
                // 先搜寻一遍是否已经有相同的node存在了
                    if (!searched) {
                        TreeNode<K, V> q, ch;
                        searched = true;
                        // 如果找到了直接返回结果
                        if (((ch = p.left) != null &&
                                (q = ch.find(h, k, kc)) != null) ||
                                ((ch = p.right) != null &&
                                        (q= ch.find(h, k, kc)) != null))
                            return q;
                    }
                    // 没找到的话需要确定往左子树还是右子树存放,因为没有compareable接口,所以只能通过本地的hash方法来比较
                    dir = tieBreakOrder(k, pk);
                }
                // 到这一步已经确定了树里面没有与插入node相同的key
                TreeNode<K, V> xp = p;
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    Node<K, V> xpn = xp.next;
                    TreeNode<K, V> x = map.newNode(h, k, v, xpn);
                    if (dir <= 0)
                        xp.left =x;
                    else
                        xp.right = x;
                    xp.next = x;
                    x.parent = x.prev = xp;
                    if (xpn != null)
                        ((TreeNode<K, V>)xpn).prev = x;
                    moveRootToFront(tab, balanceInsertion(root, x));
                    return null;
                }
            }
        }
 static int tieBreakOrder(Object a, Object b) {
            int d;
            if (a == null || b == null || (d = a.getClass().getName().compareTo(b.getClass().getName())) == 0)
                // identityHashCode调用的是默认的用内存地址计算的hash值,而不管是否重写了hash函数
                d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
                -1 : 1);
            return d;
        }
  1. Node和TreeNode的关系
    继承关系
    Node里面只有单连接的next属性,而TreeNode里面则有pre的双链接属性,这里设计的很巧妙,因为在没有树化之前,只需要使用单链表维护就可以,而在需要树化之前,需要先将单链表变为双向链表,并且在整个树化过程中一直维护着双向链表和红黑树这两种数据结构。
  2. HashMap中几种hash值的用法
    1)hash()函数。上面HashMap第一条已经讲到了。主要用于在插入或者是寻找Map中的节点时来用来比较
    2)Node重写的hashCode()函数,将键和值的hash值异或。
 static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
        // 传进来的hash值是通过hash()函数算出来的
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
 public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
}

final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
           // 这里除了要比较hash值,还要比较key的实际值
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
  1. 遍历Map时为什么要用entrySet而不用keySet?
 @Test
    public void testMapIterator() {
        Map<String, Integer> map = new HashMap<>();
        map.put("gavin", 1);
        map.put("bob", 2);
        // entrySet
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println("key=" + entry.getKey() + ",value=" + entry.getValue());
        }
        
        // keySet
        for (String key : map.keySet()) {
            System.out.println("key=" + key + ",value=" + map.get(key));
        }
    }

  public SimpleEntry(Entry<? extends K, ? extends V> entry) {
            this.key   = entry.getKey();
            this.value = entry.getValue();
        }

其实keySet遍历了两次,而entrySet只遍历了一次。keySet第一次遍历拿到所有key值,再通过key值再去查找value,而entrySet只遍历一次就将key和value封装到了一起。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值