阿里巴巴开发手册解析个人笔记(三)集合处理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010498753/article/details/84931169

文章目录

1. 【强制】 关于 hashCode 和 equals 的处理,遵循如下规则:
1) 只要重写 equals,就必须重写 hashCode。
2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的
对象必须重写这两个方法。
3) 如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals。
说明: String 重写了 hashCode 和 equals 方法,所以我们可以非常愉快地使用 String 对象
作为 key 来使用。

解析:

实例程序

public static void main(String[] args) {
		HashSet a = new HashSet();
		a.add("test");
		a.add("test");
		a.add("test");
		a.add("test");
		
	}

在Set中有这样的源码

      Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    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;
            }

可见一直在判断e.hash和key是否Equals,所以看到重写的重要性

下一个

2. 【强制】 ArrayList的subList结果不可强转成ArrayList,否则会抛出 ClassCastException
异常, 即 java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。
说明: subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList 而是 ArrayList
的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。
3. 【强制】在 subList 场景中, 高度注意对原集合元素的增加或删除, 均会导致子列表的遍历、
增加、删除产生 ConcurrentModificationException 异常

我们来查看一下ArrayList.subList的源码
实例程序

		public static void main(String[] args) {
		ArrayList array = new ArrayList();
		array.add(1);
		array.add(2);
		array.add(3);
		array.add(4);
		List s =array.subList(0, 3);
		System.out.println(s);
	}

源码

  public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }

查看继承机构确实SubList是ArrayList的内部类,必须依赖外部接口的实现。
但他返回的是一个AbstractList对象,ArrayList也是返回了一个public class ArrayList extends AbstractList对象

  private class SubList extends AbstractList<E> implements RandomAccess {
        private final AbstractList<E> parent;
        private final int parentOffset;
        private final int offset;
        int size;

        SubList(AbstractList<E> parent,
                int offset, int fromIndex, int toIndex) {
            this.parent = parent;
            this.parentOffset = fromIndex;
            this.offset = offset + fromIndex;
            this.size = toIndex - fromIndex;
            this.modCount = ArrayList.this.modCount;
        }

这算是一个适配器模式,要注意的是
SubList也实现了add,remove方法

扫描二维码关注公众号,回复: 4529147 查看本文章

查看add方法

  public E remove(int index) {
            rangeCheck(index);
            checkForComodification();
            E result = parent.remove(parentOffset + index);
            this.modCount = parent.modCount;
            this.size--;
            return result;
        }

checkForComonfication()会检查是否有别的线程修改了内容

    private void checkForComodification() {
            if (ArrayList.this.modCount != this.modCount)
                throw new ConcurrentModificationException();
        }

ArrayList的add和remove方法

  rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        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;

modCount 是检查是否有其他线程同时修改了里面的内容
所以要subList时, 不能够在原来的ArrayList随意的remove和add

详细阅读:https://www.jianshu.com/p/c5b52927a61a

  1. 【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全
    一样的数组,大小就是 list.size()。
    在这里插入图片描述
    正解
List<String> list = new ArrayList<String>(2);
list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);

下一个

6. 【强制】泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方
法, 而<? super T>不能使用 get 方法,作为接口调用赋值时易出错。

我也不会

下一个

实例程序

public class MainTest {
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		list.add("1");
		list.add("2");
		Iterator<String> iterator = list.iterator();
		while (iterator.hasNext()) {
			String item = iterator.next();
			if (item.equals("1")) {
				iterator.remove();
			}
		}
		list.add("1");
		for (String item : list) {
			if ("1".equals(item)) {
				list.remove(item);
			}
		}
	}

}

查看字节码发现
在这里插入图片描述
上面调用的是iterator.remove()方法,foreach下面调用的是List.remove方法
我们来看看里面的内容
List.remove方法里有

 public static void main(String[] args) {
			List<String> list = new ArrayList<>();
			list.add("1");
			list.add("2");
			Iterator<String> iterator = list.iterator();
			while (iterator.hasNext()) {
				String item = iterator.next();
				if (item.equals("1")) {
					iterator.remove();
				}
			}
			list.add("1");
			Iterator<String> iterator2 = list.iterator();
			while (iterator2.hasNext()) {
				String item = iterator.next();
				if (item.equals("1")) {
					list.remove(item); //这里变化了
				}
			}
		}

iterator里有一个操作数记录器modCount=0;
而list也有一个操作记录数modCount=0; list.remove(item)中并没有修改到Iteartor的修改书modCount,所以Iterator以为是并发修改了数组里的内容,所以发生了
Exception in thread "main" java.util.ConcurrentModificationException

下一个

8. 【强制】 在 JDK7 版本及以上, Comparator 实现类要满足如下三个条件,不然 Arrays.sort,
 Collections.sort 会报 IllegalArgumentException 异常。Collections.sort 会报 IllegalArgumentException 异常。
说明: 三个条件如下
1) x, y 的比较结果和 y, x 的比较结果相反。
2) x>y, y>z, 则 x>z。
3) x=y, 则 x, z 比较结果和 y, z 比较结果相同

这个里面的内容比较复杂,我没看懂。。
实践程序也正常

public class MainTest {
	public static void main(String[] args) {
		ArrayList<Student> a = new ArrayList<Student>();
		Student s = new Student(1,"david");
		Student s2 = new Student(1,"s2");
		Student s3 = new Student(2,"s3");
		Student s4 = new Student(3,"s4");
		Student s5 = new Student(4,"s5");
		Student s6 = new Student(5,"s6");
		
		a.add(s);
		a.add(s2);
		a.add(s3);
		a.add(s4);
		a.add(s5);
		a.add(s6);
		
		Collections.sort(a,new Comparator<Student>() {
			@Override
			public int compare(Student o1, Student o2) {
				return o1.getId() > o2.getId() ? 1 : -1;
			}
		});
		System.out.println(a);
		
		
	}

}

class Student {
	Integer id;
	String name;

	public Student(Integer id, String name) {
		super();
		this.id = id;
		this.name = name;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Student [id=" + id + ", name=" + name + "]";
	}
	
	

}
9. 【推荐】 集合泛型定义时, 在 JDK7 及以上,使用 diamond 语法或全省略。
说明: 菱形泛型,即 diamond, 直接使用<>来指代前边已经指定的类型。
正例:
// <> diamond 方式
HashMap<String, String> userCache = new HashMap<>(16);
// 全省略方式
ArrayList<User> users = new ArrayList(10);

个人解析:
感觉还是写全语法比较好

下一个

10. 【推荐】集合初始化时, 指定集合初始值大小。
说明: HashMap 使用 HashMap(int initialCapacity) 初始化。
正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loader
factor) 默认为 0.75, 如果暂时无法确定初始值大小,请设置为 16(即默认值) 。
反例: HashMap 需要放置 1024 个元素, 由于没有设置容量初始大小,随着元素不断增加,容
量 7 次被迫扩大, resize 需要重建 hash 表,严重影响性能。

已下降为解析转载来自:
https://cloud.tencent.com/info/9bdbd5c9a03e27a5b6005c005d854829.html

实例程序

int aHundredMillion = 10000000;

        Map<Integer, Integer> map = new HashMap<>();

        long s1 = System.currentTimeMillis();
        for (int i = 0; i < aHundredMillion; i++) {
            map.put(i, i);
        }
        long s2 = System.currentTimeMillis();

        System.out.println("未初始化容量,耗时 : " + (s2 - s1));

        Map<Integer, Integer> map1 = new HashMap<>(aHundredMillion / 2);

        long s5 = System.currentTimeMillis();
        for (int i = 0; i < aHundredMillion; i++) {
            map1.put(i, i);
        }
        long s6 = System.currentTimeMillis();

        System.out.println("初始化容量5000000,耗时 : " + (s6 - s5));

        Map<Integer, Integer> map2 = new HashMap<>(aHundredMillion);

        long s3 = System.currentTimeMillis();
        for (int i = 0; i < aHundredMillion; i++) {
            map2.put(i, i);
        }
        long s4 = System.currentTimeMillis();

        System.out.println("初始化容量为10000000,耗时 : " + (s4 - s3));

查看源码发现
为什么是7次?
他的初始容量是16,每次增大两倍并且重组hashMap
所以16+32+64+128+256+512=944 +1024 才能够装1024个数据,但重组了7次浪费了很多的效率

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
                    // 判断是否超过最大容量值
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                                         // 容量扩大为原来的两倍,oldCap大于等于16
                newThr = oldThr << 1; // 双倍
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // 当没设置值时初始化变量
            newCap = DEFAULT_INITIAL_CAPACITY;  //默认值是16,下面是16*0.75=12
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        ....

下一个

11. 【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。
说明: keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出
key 所对应的 value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效
率更高。如果是 JDK8,使用 Map.foreach 方法。
正例: values()返回的是 V 值集合,是一个 list 集合对象; keySet()返回的是 K 值集合,是
一个 Set 集合对象; entrySet()返回的是 K-V 值组合集合。

参考了文章https://blog.csdn.net/chajinglong/article/details/79194967

实例程序

Map map = new HashMap();
		map.put("1", "david");
		map.put("2", "way");
		map.put("3", "test");
		map.put("4", "hehe");
		map.put("5", "bgg");
		
		Iterator iter = map.entrySet().iterator(); 
		while (iter.hasNext()) { 
		    Map.Entry entry = (Map.Entry) iter.next(); 
		    Object key = entry.getKey(); 
		    Object val = entry.getValue(); 
		    System.out.println("key=");
		}
		
		Map map2 = new HashMap(); 
		Iterator iter2 = map2.keySet().iterator(); 
		while (iter2.hasNext()) { 
		    Object key = iter.next(); 
		    Object val = map.get(key); 
		} 

12.【推荐】高度注意 Map 类集合 K/V 能不能存储 null 值的情况,如下表格:


![在这里插入图片描述](https://img-blog.csdnimg.cn/20181210003549800.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA0OTg3NTM=,size_16,color_FFFFFF,t_70)

后面探讨一下为什么线程不安全和不能为null

13.【参考】合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和
不稳定性(unorder)带来的负面影响。
说明: 有序性是指遍历的结果是按某种比较规则依次排列的。 稳定性指集合每次遍历的元素次
序是一定的。 如: ArrayList 是 order/unsort; HashMap 是 unorder/unsort; TreeSet 是
order/sort。

这个我没看懂

下一个

14.【参考】利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的
contains 方法进行遍历、对比、 去重操作。

这个不太需要解析,但提供一个代码说明

List list = new ArrayList(set);
Set set = new HashSet(list);


猜你喜欢

转载自blog.csdn.net/u010498753/article/details/84931169