zoukankan      html  css  js  c++  java
  • java-Collection

    集合框架

    collection.jpg
    collection2.jpg

    1. 三大接口:Iterator,Collection,Map
    2. 工具类:Collections Arrays

    Java提供的默认排序方法

    1.Arrays.sort()
    2.Collections.sort()(底层是调用 Arrays.sort())

    1.对于原始数据类型,目前使用的是所谓双轴快速排序(Dual-Pivot QuickSort),是一种改
    进的快速排序算法,早期版本是相对传统的快速排序
    DualPivotQuicksort.java
    typically faster than traditional (one-pivot) Quicksort implementations

    DualPivotQuicksort

    2.对象数据类型 使用TimSort 归并和二分插入排序(binarySort)结合的优化排序算法
    思路:
    查找数据集中已经排好序的分区(这里叫 run),
    然后合并这些分区来达到排序的目的。

    Collections.sort->list::sort->Arrays.sort->TimSort.sort

    {1, 2, 3, 4, 5, 9, 7, 8, 10, 6} 输出 6
    {9,8,7,6,5,4, 10}输出6 并且reverse(0,6)->[4, 5, 7, 6, 8, 9, 10]

    展开代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    private static <T> int (T[] a, int lo, int hi,
    Comparator<? super T> c) {
    assert lo < hi;
    int runHi = lo + 1;
    if (runHi == hi)
    return 1;

    if (c.compare(a[runHi++], a[lo]) < 0) { // Descending
    while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
    runHi++;
    reverseRange(a, lo, runHi);
    } else { // Ascending
    while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
    runHi++;
    }
    return runHi - lo;
    }
    private void reverseRange(Object[] a, int lo, int hi) {
    hi--;
    while (lo < hi) {
    System.out.println(Arrays.toString(a));
    Object t = a[lo];
    a[lo++] = a[hi];
    a[hi--] = t;
    }
    }

    3.java8的parallelSort ForkJoin框架


    package java.util;里的常用类

    java_collections.jpg

    • Vector,Arraylist,LinkedList implements List
    • LinkedList implents Queue
    • List,Queue,Set implememts Collection

    java9 of静态工厂不可变

    Java 9 中,Java 标准类库提供了一系列的静态工厂方法,比如,List.of()、Set.of(),大大简
    化了构建小的容器实例的代码量。不可变的,符合我们对线程安全的需求

    Vector ArryList LinkedList的区别

    collect.jpg

    同:
    1.都实现集合框架中的 List,有序集合。
    2.都可以按照位置进行定位、添加或者删除的操作,都提供迭代器以遍历其内容。

    不同:
    1.Vector在扩容时会提高 1 倍,而 ArrayList 则是增加 50%。
    vector:

    1
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);

    2.只有Vector是线程安全的。

    ArrayList

    fianl修饰的变量,JVM也会提前给我们初始化好。???

    1
    2
    3
    4
    5
    6
    7
    8
    //变量
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] elementData;

    //构造函数,避免反复创建无用数组 指向同一个缓存Object[]数组
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    if (initialCapacity == 0) {
    this.elementData = EMPTY_ELEMENTDATA;}

    • .add(e)
    1. 创建Object数组,并拷贝 ->ensureCapacityInternal(size+1)
      ->ensureExplicitCapacity(size+1);
      ->grow(size+1)->Arrays.copyOf
      ->new Object[newLength],System.arraycopy
    2. elementData[size++] = e;
    • object的长度为10,size为逻辑长度,不是数组长度,minCapacity = Math.max(DEFAULT_CAPACITY=10, minCapacity
    • 第一次扩容:就一个元素,在堆内存中占了10个位置
    • 之后扩容:int newCapacity = oldCapacity + (oldCapacity >> 1);//>>=/2

    • .remove(index)

    1.将index+1后的numMoved个元素从index开始复制obj->obj

    1
    2
    System.arraycopy(elementData, index+1, elementData, index,
    numMoved);

    2.长度-1,最后一个null elementData[--size] = null;


    • .romove(Object o) 循环查找o==null!=null->fastremove(index)(基本同上)
    1
    2
    3
    4
    //当o!=null,按List规范重写equal!!
    if(o.equal(elementData[index])){
    fastRemove(index)
    }

    Arrays

    • @SuppressWarnings("unchecked")编译器消除警告
      • unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型。
    • Arrays.

    vector

    1.ArrayListhe和Vector在用法上完全相同addElement(Object obj)add(Object obj)没什么区别

    1
    2
    3
    4
    5
      public synchronized void addElement(E obj) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = obj;
    }

    1
    2
    3
    4
    5
    6
      public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
    }

    Vector里有一些功能重复的方法,这些方法中方法名更短的是属于后来新增的方法.更长的是原先vector的方法.而后来ArrayList是作为List的主要实现类.

    线程同步不应该使用Vector 应该使用
    java.util.concurrent.CopyOnWriteArrayList


    class Stack<E> extends Vector<E>

    Deque 接口及其实现提供了 LIFO 堆栈操作的更完整和更一致的 set

    Deque<Integer> stack = new ArrayDeque<Integer>();
    LinkedList<E> implements List<E>, Deque<E>,


    ArrayDeque循环数组

    https://github.com/CarpenterLee/JCFInternals/blob/master/markdown/4-Stack%20and%20Queue.md

    head指向首端第一个有效元素,tail指向尾端第一个可以插入元素的空位


    void addFirst(E e)

    1
    2
    elements[head = (head - 1) & (elements.length - 1)] = e;//越界处理
    if (head == tail) doubleCapacity();

    1.head前有空位 2.head是0,加到最后,如果最后是tail则扩容:

    1. elements.length必需是2的指数倍,elements - 1就是二进制低位全1
    2. 跟head - 1相与之后就起到了【取模】的作用
    3. 当head-1=-1;相当于对其取相对于elements.length的补码(正数就是本身)
    1
    2
    3
    4
    int head = 10;
    int length = 8;
    //8->1000 ;7->0111;10-1=9->1001 ;->1
    head = (head - 1) & (length - 1);

    addLast

    1
    2
    elements[tail] = e;
    if ( (tail = (tail + 1) & (elements.length - 1)) == head) doubleCapacity();

    void doubleCapacity()

    System.arraycopy

    1
    2
    3
    4
    5
    native void arraycopy(Object src, //原数组 
    int srcPos,//原数组起始位置
    Object dest, //目标数组
    int destPos, //起始
    int length); //长度

    doubleC.jpg

    1
    2
    3
    4
    5
    6
    7
    int p = head;
    int n = elements.length;
    int r = n - p; // head右边元素的个数
    //复制右半部分,对应上图中绿色部分
    System.arraycopy(elements, p, a, 0, r);
    //复制左半部分,对应上图中灰色部分
    System.arraycopy(elements, 0, a, r, p);


    pollFirst()删除并返回Deque首端(head)元素

    展开代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     public E pollFirst() {
    int h = head;
    ("unchecked")
    E result = (E) elements[h];
    // Element is null if deque empty
    if (result == null)
    return null;
    elements[h] = null; // Must null out slot
    head = (h + 1) & (elements.length - 1);
    return result;
    }

    pollLast()

    int t =(tail-1)&(element.length-1);


    E peekFirst()&E peekLast 返回但步删除


    LinkedList 双向链表

    Queue queue = new LinkedList();
    内部静态类Node


    Arrays.asList

    String可以

    1
    2
    String[] ss = {"da","da"};
    List<String> strings = Arrays.asList(ss);

    基本数据类型不行

    1
    2
    int[] ss = {1,2};
    List<int[]> ints1 = Arrays.asList(ss);

    源码:基本数据类型

    1
    2
    3
    public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
    }

    泛型

    泛型方法:

    类型参数放在修饰符public static后,返回类型之前

    1. 元素限定<T extends AutoCloseable> void closeAll(ArrayList<T> elems)确保Array的元素类型是AutoCloseable的子类;extends表示子类型、类型限定
    • 多个限定T extends Runnable & AutoCloseable,只能有一个限定类,放在第一个,其它都是接口
    1. Manager是Employee子类但ArrayList<Manger>不是ArrayList<Employee>子类
      因为
      1
      2
      3
      ArrayList<Manger> bosses = new ArrayList<>();
      ArrayList<Employee> empls = bosses; //非法
      empls.add(new Employee(...)); //可以在管理员列里添加普通成员

    如果使用数组Manger[]和Employee[] 转型是合法的,但赋值会爆ArrayStoreException

    如果不对ArrayList写操作,转换是安全的。可以用子类通配符

    1. <? extends Employee> staff可以传Arraylist
      1. staff.get(i)可以 可以将<? extends Employee>转成Employee
      2. staff.add(i)不行 不能将任何对象转成<? extends Employee>
    2. 父类通配符<? super Employee>常用于函数式对象参数(用lambda调用)

    泛型类型擦除

    1
    2
    3
    4
    List<String> l1 = new ArrayList<String>();
    List<Integer> l2 = new ArrayList<Integer>();
    //true
    System.out.println(l1.getClass() == l2.getClass());
    1
    2
    3
    4
    5
    List<Integer> l2 = new ArrayList<Integer>();
    l2.add(1);
    Method madd = l2.getClass().getDeclaredMethod("add",Object.class);
    //[1, abc]
    madd.invoke(l2,"abc");

    泛型约束

    1. 类型变量不能实例化 T[] result = new T[n];错误

      1. 以【方法引用】方式提供数组的构造函数Sting[]::new
        IntFunction<T[]> constr T[] result = constr.apply(n)
      2. 反射

        1
        2
        3
        4
        5
        6
        7
        public static <T> T[] repeat(int n,T obj,Class<T> c1){
        //编译器不知道类型,必须转换
        ("unchecked")
        T[] result = (T[])java.lang.reflect.Array.newInstance(c1,n);
        for(int i=0;i<n;i++)result[i]=obj;
        return result;
        }

        调用String[] greetings = repeat(10,"Hi",String.class);

      3. 最简单的方法
        1
        2
        3
        4
        5
         public static <T> ArrayList<T> repeat(int n,T obj){
        ArrayList<T> result = new ArrayList<>();
        for(int i =0;i<n;i++) result.add(obj);
        return result;
        }
    2. 泛型数组Entry<String,Integer>[]是合法的,但是初始化要@SuppressWarnings(“unchecked”)
      正确方法:ArrayList<Entry<String,Integer>>

    1
    2
    3
    4
    //可以
    List<Integer>[] gen = (List<Integer>[]) new ArrayList[10];
    //可以
    List<Integer>[] graph=new ArrayList[numCourses];

    guava组件

    1. ImmutableList<String> ilist = ImmutableList.of("a","b");不可变List
    2. 过滤器
    • 工具类Lists
    1
    List<String> lit = Lists.newArrayList("aaa","ddd","bbb");
    1
    Collection<String> cl =Collections2.filter(lit,(e)->e.toUpperCase().startsWith("a"));
    1. 转换 (有问题)日期

      大专栏  java-Collectionht java">
      1
      2
      Set<Long> set = Sets.newHashSet(20170801L,20980320L,19950730L);
      Collection<String> col = Collections2.transform(set,(e)->new SimpleDateFormat("yyyy-MM-dd").format(e));
    2. 组合函数Functions.compose(f1,f2) 用google的Function

    集合操作

    1. 交集
      SetView<Integer> v1 = Sets.intersection(set, set2);
    2. 差集:只是1中没有在2中的元素
      .difference(s1,s2)
    3. 并集:把重复的只留一份

    Mutiset无序可重复

    1
    2
    3
    4
    5
    //输出[study, dayday, up, good x 2]
    String[] sp = s.split(" ");
    HashMultiset<String> hms = HashMultiset.create();
    for(String str:sp){
    hms.add(str);}

    获取次数

    1
    2
    3
     Set<String> ele = hms.elementSet();
    for(String ss:ele){
    System.out.println(ss+":"+hms.count(ss));}

    Multimap Key 可以重复,一个键对应一个Collection

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //输出:
    // 作者2 [书3]
    // 作者1 [书2, 书1]
    Multimap<String,String> mmap = ArrayListMultimap.create();
    Map<String,String> map = new Hashtable<>();
    map.put("书1","作者1");
    map.put("书2","作者1");
    map.put("书3","作者2");
    Iterator<Map.Entry<String,String>> iter = map.entrySet().iterator();
    while(iter.hasNext()){
    Map.Entry<String,String> entry = iter.next();
    mmap.put(entry.getValue(),entry.getKey());
    }
    for(String key:mmap.keySet()){
    Collection<String> value = mmap.get(key);
    System.out.println(key+" "+value);
    }

    BiMap 双向map 键值都不能重复

    BiMap<String,String> map = HashBiMap.create();
    key和val可以反转map.inverse()

    双键Map 行,列,值

    Table<String,String,Integer> table = HashBasedTable.create();
    获取条settable.cellSet()

    多对多

    拆分成两个一对多Student和Course生成StudentAndCourse

    1
    2
    3
    4
    5
    class StudentAndCourse{
    private int id;
    private int cid;//添加在Course
    private int sid;//添加在Student
    }

    Collections工具类

    排序类:针对List(set不用排序)

    1. shuffle(list)随机打乱
    2. reverse(list)
    3. .sort()/(,Comparator c)
    4. .swap

      1
      2
      3
      public static void swap(List<?> list, int i, int j) {     
      final List l = list;
      l.set(i, l.set(j, l.get(i)));}

      ArrayList: set返回旧值

      1
      2
      3
      4
      5
      6
      public E set(int index, E element) {
      rangeCheck(index);
      E oldValue = elementData(index);
      elementData[index] = element;
      return oldValue;
      }
    5. rotate(list,int)i移动到(i+d)%size

      1. rotate1(list, distance); : 数组实现,并且长度小于100
        if (list instanceof RandomAccess ||list.size() < ROTATE_THRESHOLD)
        leetcode的算法3
        展开代码

        rotate.jpg
        直接放在正确的位置上
        理解:把0位放到正确位置(distance)后,将这个位置继续当作0位,继续移动distance步,直到i回到0位

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        private static <T> void rotate1(List<T> list, int distance) {
        int size = list.size();
        if (size == 0)
        return;
        distance = distance % size;
        if (distance < 0)
        distance += size;
        if (distance == 0)
        return;
        /****以上得的了正确的distance size和distance有一个为零就不动***/
        for (int cycleStart = 0, nMoved = 0; nMoved != size; cycleStart++) {
        //size = 5
        T displaced = list.get(cycleStart);//1.得到[0]
        int i = cycleStart;
        do {
        i += distance; //向前走3步
        if (i >= size)
        i -= size;//对长度取模
        displaced = list.set(i, displaced);//2.赋值给[3],得到[3]的值
        nMoved ++;//3.每位放一次一共执行size次同时退出for循环
        } while (i != cycleStart);
        }
        }

      2. 1
        2
        3
        reverse(list.subList(0, mid));
        reverse(list.subList(mid, size));
        reverse(list);

    查找

    1. binarySearch,max,min(遍历compare/compareTo)
    2. fill(List,o)填充 (遍历调用set)
    3. frequency(c,o)c中与o相等元素数量(遍历equals)
    4. replaceAll(list,old,new)遍历equals,set

    同步重建,加上代码块的锁

    只有vector/hashtable比较古老是安全的

    1. synchronizedList(list)

    设置不可变的集合emptyXXX,singletonXXX,unmodifiableXXX

    1. EmptyList防空指针
    2. UnmodifiableCollection重写了add、remove等方法,直接抛异常
    3. singletonMapsize=1

    其它

    • disjoint(,)没有相同元素返回true
    • addAll(c,T...)全部加入c
    • reverseOrder(Comparator<T> cmp)返回比较器
      1
      2
      //反转排序[5, 4, 3, 2, 1]       
      Collections.sort(list,Collections.reverseOrder());

    Iterator

    1. ListIterator
    2. Enumeration Vector使用public Enumeration<E> elements()
      1
      2
      3
      4
      Enumeration<Integer> es = v.elements();
      while(es.hasMoreElements()){
      System.out.println(es.nextElement());
      }

    迭代器设计模式

    1.迭代器接口 2.迭代器的实现(用构造函数传入(3))3.抽象类 4.具体类(return new迭代器)

    forEach jdk1.8

    1. ArrayList:public void forEach(Consumer<? super E> action)
      Consumer接口:void accept(T t);
      1
      2
      3
      4
      5
      6
      default void forEach(Consumer<? super T> action) {
      Objects.requireNonNull(action);
      for (T t : this) {
      action.accept(t);
      }
      }

    Map

    map.jpg

    1. 键值setSet<Map.Entry<Integer,String>> enry = map.entrySet();
    2. Collection<String> cl = map.values();
    3. forEach(BiConsumer<? super K, ? super V> action)
    4. 权限查询.containsKey()
    5. 遍历:
      1
      for(Map.Entry<String,String> entry:map.entrySet()){}

    Map接口中的新方法

    1. getOrDefault(Object key, V defaultValue)不存在返回默认值

      1
      return (((v = get(key)) != null) || containsKey(key))? v: defaultValue;
    2. V putIfAbsent(K key, V value)不覆盖添加 原本的put覆盖并返回旧值

      1
      2
      V v = get(key);
      if (v == null) {v = put(key, value);}
    3. boolean remove(Object key, Object value)保证key和val都有

      1
      2
      3
      4
      5
      6
      7
       Object curValue = get(key);
      if (!Objects.equals(curValue, value) ||
      (curValue == null && !containsKey(key))) {
      return false;
      }
      remove(key);
      return true;
    4. boolean replace(K key, V oldValue, V newValue)

    5. V compute对key位置上的kv对用BiFunction最后更新并返回计算后的值
      put(key, newValue);

      1
      2
      map.compute(1,(k,v)->v+="1");
      map.computeIfAbsent(5,(k)->"空空空");
    6. merge

      1
      map.merge(1,"222",(oldv,newv)->oldv+newv);

    TreeMap

    TreeSet

    1
    2
    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    HashMap 数组+链表+(链表长度达到8,会转化成红黑树)

    不是同步的,支持 null 键和值

    Ideally, under random hashCodes, the frequency of nodes in bins follows a Poisson distributionwith a parameter of about 0.5 on average for the default resizingthreshold of 0.75,
    负载因子0.75的清空下,bin满足泊松分布(exp(-0.5) * pow(0.5, k) /factorial(k)).
    落在0的桶的概率有0.6

    用树存储冲突hash when bins contain enough nodes to warrant useTREEIFY_THRESHOLD = 8;

    1. 计算hash值 【扰动函数】
      1
      2
      3
      4
      static final int hash(Object key) {
      int h;
      return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
      }

    实验:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    int a = (65535<<2)+1;
    //262141 111111111111111101
    System.out.println(a+" "+Integer.toBinaryString(a));
    //32是表长
    int hash=a^(a>>>16);
    //输出:11 反转最后两位
    //扰动(如果>65536,右移16位)
    //低16与高16位`^`,混合hash的高位低位
    System.out.println(Integer.toBinaryString(a>>>16));
    int index = (32-1)&hash;
    /*
    111111111111111110
    11111//只取了低位
    -------------
    11110->30
    */
    System.out.println(Integer.toBinaryString(hash));
    System.out.println(Integer.toBinaryString(31));
    System.out.println("-------------");
    System.out.println(Integer.toBinaryString(index));
    //262142 30
    System.out.println(hash+" "+index);

    1. put(K key, V value) {return putVal(hash(key), key
    2. putVal

      1
      2
      3
      n = (tab = resize()).length;
      if ((p = tab[i = (n - 1) & hash]) == null)//n=length=16
      tab[i] = newNode(hash, key, value, null);
    3. putVal如果Node链表太长

      1
      2
      if (binCount >= TREEIFY_THRESHOLD - 1) //TREEIFY_THRESHOLD==8
      treeifyBin(tab, hash);//变成红黑树

    TreeNode<K,V> extends LinkedHashMap.Entry<K,V>
    65536(2^16)>>>16 ==1

    1. table uses power-of-two masking
    • 为了用&2^n-1取模 2^n的table长不是素数很容易冲突
    1. *spreads the impact of higher bits downward
    • int类型自带hashCode范围-2147483648到2147483648
    • i = (n - 1) & hash 插入位置 n-1=15是1111低位掩码高位归零
    • int长32>>>16是int的高16位,低16与高16位^,混合hash的高位低位

      key1=0ABC0000 & (16-1) = 0(8个16进制数,共32位)
      key2=0DEF0000 & (16-1) = 0
      hashcode的1位全集中在前16位。key相差很大的pair,却存放在了同一个链表
      把hashcode的“1位”变得“松散”,比如,经过hash函数处理后,0ABC0000变为A02188B,0DEF0000变为D2AFC70

    Hashtable 数组加链表没用二叉树

    数据结构一样的名字不同:
    hashtable:private transient Entry<?,?>[] table; 默认大小11
    hashmap: transient Node<K,V>[] table;

    LinkedHashMap 双重链表

    set

    1. String、Path都有很好的hash函数
    2. 顺序遍历集合TreeSet实现了SortedSetNavigableSet接口
    3. set的元素必须实现Comparable接口或者构造函数中有Comparator

    HashSet

    1. HashMap实现,用空对象占位

      1
      2
      3
      4
      5
      private transient HashMap<E,Object> map;
      private static final Object PRESENT = new Object();
      public boolean add(E e) {
      return map.put(e, PRESENT)==null;
      }
    2. 保持不重复 使用HashMap的 putVal

      1. 比较hashCode相同不一定是同一个对象,再比较equals
    3. 重写放入hash的类的hashCode:

    LinkedHashSet 用链表记录插入的位置

    TreeSet 排序

    1. 需要实现Comparatable或传入Comparator,会根据compare的值覆盖,去重
    • NavigableSet使用元素自然顺序排序,or 用Comparator

  • 相关阅读:
    (转)重识new
    【洛谷习题】连续自然数和
    【AHOI2005】约数研究
    【NOIP2003】麦森数
    康托展开
    【洛谷习题】南蛮图腾
    【洛谷习题】吃奶酪
    【NOIP2002】字串变换
    哈希表
    【NOIP2013】货车运输
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12284928.html
Copyright © 2011-2022 走看看