PartOne——集合

集合学习

一、通用与特定方法

1.遍历

1)迭代器遍历

迭代器本质是一种设计模式,为了解决不同的集合类提供统一的遍历操作接口。

迭代器结构

public interface Iterator<E> {
    boolean hasNext();
    E next();
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
    //对剩余集合剩余元素执行操作
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

运用

public void testIteratorAndForEachRemaining() {
        int count = 0;
        Collection<String> coll = new ArrayList<String>();
        coll.add("abc1");
        coll.add("abc2");
        coll.add("abc3");
        coll.add("abc4");

        Iterator<String> it = coll.iterator();
        //while截断在第二个位置
        while (it.hasNext()) {
            if (count > 1) {
                break;
            }
            System.out.println(it.next());
            count++;
        }
        //使用迭代器的forEachRemaining方法传入有个拼接好的事件:从第三个位置之后先一遍大写后一遍小写输出
        it.forEachRemaining(consumerString(s -> System.out.print(s.toUpperCase()), s -> System.out.println(s.toLowerCase())
        ));
    }
    //拼接两个Consumer
    private static Consumer consumerString(Consumer<String> one, Consumer<String> two) {
        return one.andThen(two);
    }
    //消费指定泛型
     private static void consumerString(Consumer<String> function) {
        function.accept("Hello");
    }
2)Map遍历
table 与 entrySet 与keySet 与 values

1.transient Node<K,V>[] table 与 transient Set< Map.Entry<K,V>> entrySet

*0.transient关键字:短暂的,即序列化的时候不会被序列化

1.为了使得遍历方便(Map.Entry接口中有getKey和getValue方法)会将table中对应的Node(一个Node结点的内容见上)存入一个Set集合,达到维护效果。

2.为什么Node类型数据可以存入Map.Entry是因为Node本身就实现了Map.Entry

3.怎么存入的,设定指针指向

发生时机:调用entrySet时,源码如下:

public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }

会创建一个final修饰的EntrySet对象,当调用该对象的ForEach时装填table里的数据:

 public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (int i = 0; i < tab.length; ++i) {
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }

2.Collection vs = values;Set ks = keySet;

1.本质和上面一致就是获取table中单独的key集合和values集合

		    log.info("1.通过entrySet");
        map.entrySet().forEach(System.out::println);
        log.info("简略版:底层一致");
        map.forEach((k, v) -> System.out.println(k + " : " + v));
        log.info("2.通过keySet");
        Set<String> strings = map.keySet();
        for (String string : strings) {
            System.out.println(map.get(string));
        }
        log.info("3.通过Values");
        map.values().forEach(System.out::println);

2.TreeSet(TreeMap)与比较器

TreeMap 它还实现了NavigableMap 接⼝和 SortedMap 接⼝。

//TreeSet构造函数实现了一个比较器对象,底层就是把这个比较器丢给TreeMap
public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }
@Data
@AllArgsConstructor
@NoArgsConstructor
class Person implements Comparable {
    Integer age;
    String name;
    @Override
    public int compareTo(@NotNull Object o) {
        if (o instanceof Person) {
            Person p = (Person) o;
          //返回1则交换位置:0与-1比较大于返回1则说明要交换即this.name<p.name说明要交换
            return Integer.compare(0,this.name.compareTo(p.name));
        }
        return 0;
    }
}

1.TreeMap是用比较器排序的Map,其他Map都不行,它可以支持两种比较器实现排序。当所有比较结果都相同时前者就会被后来的替换,如下:

Map<Person, String> treeMap = new TreeMap<>();
        treeMap.put(new Person(1, "a"), "person1");
        treeMap.put(new Person(3, "aa"), "person2");
        treeMap.put(new Person(2, "aa"), "person3");
        treeMap.put(new Person(4, "aaaa"), "person4");
        treeMap.forEach((key, value) -> System.out.println(key+" "+value));

结果:

Person(age=4, name=aaaa) person4
Person(age=3, name=aa) person3//person2和person3的名字一致被替换
Person(age=1, name=a) person1

2.但条件可以满足互斥时会保留,用一个Comparator重新排序就会在原来基础上再排序一次:

return Integer.compare(0,this.name.compareTo(p.name));
===========================================
Map<Person, String> treeMap = new TreeMap<>(Comparator.comparingInt(Person::getAge).reversed());
        treeMap.put(new Person(1, "a"), "person1");
        treeMap.put(new Person(3, "aa"), "person2");
        treeMap.put(new Person(2, "aa"), "person3");
        treeMap.put(new Person(4, "aaaa"), "person4");
      treeMap.forEach((key, value) -> System.out.println(key+" "+value));

结果:

Person(age=4, name=aaaa) person4
Person(age=3, name=aa) person2
Person(age=2, name=aa) person3
Person(age=1, name=a) person1
*2.1Comparator与Camparable使用

一个类实现了Camparable接口则表明这个类的对象之间是可以相互比较的,这个类对象组成的集合就可以直接使用sort方法排序。
Comparator可以看成一种算法的实现,将算法和数据分离,Comparator也可以在下面两种环境下使用:
1、类的设计师没有考虑到比较问题而没有实现Comparable,可以通过Comparator来实现排序而不必改变对象本身
2、可以使用多种排序标准,比如升序、降序等。

总结一下,两种比较器Comparable和Comparator,后者相比前者有如下优点:
1、如果实现类没有实现Comparable接口,又想对两个类进行比较(或者实现类实现了Comparable接口,但是对compareTo方法内的比较算法不满意),那么可以实现Comparator接口,自定义一个比较器,写比较算法。
2、实现Comparable接口的方式比实现Comparator接口的耦合性要强一些,如果要修改比较算法,则需要修改Comparable接口的实现类,而实现Comparator的类是在外部进行比较的,不需要对实现类有任何修改。从这个角度说,实现Comparable接口的方式其实有些不太好,尤其在我们将实现类的.class文件打成一个.jar文件提供给开发者使用的时候。实际上实现Comparator 接口的方式后面会写到就是一种典型的策略模式。

对于本身实现了Comparator接口的方法(一般集合都有)就直接内部类或者Lambda表达式写排序逻辑即可。

//对于List或者Array需要调用对应的sort之后在类中重写的compareTo才会生效
List<Person> people = new ArrayList<>();
        people.add(new Person(3, "aaaaa"));
        people.add(new Person(2, "aa"));
        people.add(new Person(1, "aaa"));
        people.add(new Person(4, "aab"));
        Collections.sort(people);

结果:

Person(age=4, name=aab)
Person(age=3, name=aaaaa)
Person(age=1, name=aaa)
Person(age=2, name=aa)
//也可以实现Comparator覆盖
List<Person> people = new ArrayList<>();
        people.add(new Person(3, "aaaaa"));
        people.add(new Person(2, "aa"));
        people.add(new Person(1, "aaa"));
        people.add(new Person(4, "aab"));
        people.sort((o1, o2) -> Integer.compare(0, o1.age.compareTo(o2.age)));

结果:

Person(age=4, name=aab)
Person(age=3, name=aaaaa)
Person(age=2, name=aa)
Person(age=1, name=aaa)
List<Person> personList = new ArrayList<>();

        personList.add(new Person("Sherry", 9000, 24, "female", "New York"));
        personList.add(new Person("Tom", 8900, 22, "male", "Washington"));
        personList.add(new Person("Jack", 9000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 8800, 26, "male", "New York"));
        personList.add(new Person("Alisa", 9000, 26, "female", "New York"));

        // 按工资升序排序(自然排序)
        List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName)
                .collect(Collectors.toList());
        // 按工资倒序排序
        List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
                .map(Person::getName).collect(Collectors.toList());
        // 先按工资再按年龄升序排序
        List<String> newList3 = personList.stream()
                .sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName)
                .collect(Collectors.toList());
        // 先按工资再按年龄自定义排序(降序)
        List<String> newList4 = personList.stream().sorted((p1, p2) -> {
            if (p1.getSalary() == p2.getSalary()) {
                return p2.getAge() - p1.getAge();
            } else {
                return p2.getSalary() - p1.getSalary();
            }
        }).map(Person::getName).collect(Collectors.toList());

        System.out.println("按工资升序排序:" + newList);
        System.out.println("按工资降序排序:" + newList2);
        System.out.println("先按工资再按年龄升序排序:" + newList3);
        System.out.println("先按工资再按年龄自定义降序排序:" + newList4);

3.fail-fast和fail-safe

快速失败(fail-fast) 是 Java 集合的⼀种错误检测机制。在使⽤迭代器对集合进⾏遍历的时候,我们在多线程下操作⾮安全失败(fail-safe)的集合类可能就会触发 fail-fast 机制,导致抛出
ConcurrentModificationException ConcurrentModificationException 异常。 另外,在单线程下,如果在遍历过程中对集合对象的内容进⾏了修改的话也会触发 fail-fast 机制。
举个例⼦:多线程下,如果线程 1 正在对集合进⾏遍历,此时线程 2 对集合进⾏修改(增加、删除、修改),或者线程 1 在遍历过程中对集合进⾏修改,都会导致线程 1 抛出ConcurrentModificationException 异常。
为什么呢?
每当迭代器使⽤ hashNext() / next() 遍历下⼀个元素之前,都会检测 modCount 变量是否为等于expectedModCount 值,是的话就返回遍历;否则抛出异常,终⽌遍历。
如果我们在集合被遍历期间对其进⾏修改的话,就会改变 modCount 的值,进⽽导致 modCount !=expectedModCount ,进⽽抛出 ConcurrentModificationException 异常。

Iterator 源码:

       @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

每次调用next()方法,在实际访问元素前,都会调用checkForComodification方法,该方法源码如下:

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

单线程错误:

 list.add("a"); list.add("b");list.add("c");
        for (String s : list) {
            list.remove("a");
        }

结果:

java.util.ConcurrentModificationException

多线程错误:

 List<String>list=getList();
        //多线程操作失败
        new Thread(()->{
            for (String str :list) {
                System.out.println(str+" thead1");
                list.remove(str);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"a").start();
        new Thread(()->{
            for (String str :list) {
                System.out.println(str+" thead2");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"b").start();
//===============================结果====================
a thead1
bb thead2
Exception in thread "a" java.util.ConcurrentModificationException
ccc thead2
d thead2
ee thead2
fff thead2
g thead2
h thead2
i thead2

解决方法一:在单线程的遍历过程中,如果要进行remove操作,可以调用迭代器的remove方法而不是集合类的remove方法。看看ArrayList中迭代器的remove方法的源码:

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
 
            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

可以看到,该remove方法并不会修改modCount的值,并且不会对后面的遍历造成影响,因为该方法remove不能指定元素,只能remove当前遍历过的那个元素,所以调用该方法并不会发生fail-fast现象。

阿⾥巴巴⼿册相关的规定:不要在foreach循环里进行元素的remove/add操作,remove元素使用Iterator,如果并发需要对Iterator上锁。

迭代器方法删除:

Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            if("a".equals(iterator)){
                iterator.remove();
            }
            iterator.next();
        }

结果:

[b, c]

另外removeIf方法满足迭代安全条件,源码如下:

default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        final Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }

测试:

			 list.add("a");
        list.add("b");
        list.removeIf(s->s.equals("a"));
        log.info(list.toString());

结果:

[b]

有了前⾯讲的基础,我们应该知道:使⽤ Iterator 提供的 remove ⽅法,可以修改到
expectedModCount 的值。所以,才不会再抛出 ConcurrentModificationException 异常。
什么是安全失败(fail-safe)呢?
明⽩了快速失败(fail-fast)之后,安全失败(fail-safe)我们就很好理解了。
采⽤安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,⽽是先复制原有集合内容,在拷⻉的集合上进⾏遍历。所以,在遍历过程中对原集合所作的修改并不能被迭代器检测到,故不会抛ConcurrentModificationException 异常。在JUC包下可以采用满足fail-fast的容器CopyOnWriteArrayList,ConcurrentHashMap:

Fail Fast IteratorFail Safe Iterator
Throw ConcurrentModification ExceptionYesNo
Clone objectNoYes
Memory OverheadNoYes
ExamplesHashMap,Vector,ArrayList,HashSetCopyOnWriteArrayList,ConcurrentHashMap

二、扩容机制与初始化

1.ArrayList<?>(数组)

1.初始化

ArrayList<?>底层维护一个transient Object[] elementData ,数组初次创建未设定ArrayList的initialCapacity参数时容量为0,第一次添加扩容为10,再次扩容(第一个对象)时为elementData的1.5倍。初始设定为指定大小则也变为1.5倍。

//初始化 Collection<String> col = new ArrayList<>(2);
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
          //没有initialCapacity就长度扩容为10
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
//扩容
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
2.扩容

迭代扩容之前执行ensureCapacityInternal(size + 1)进行扩容,当elementData的大小不够则调用grow(int minCapacity):minCapacity(当前元素+1后的长度)

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
      //1+0.5=1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
      //最大为Intager最大值为(2^31)-1
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

2.Vecotor扩容(数组)(线程安全)

1.初始化

基础方法使用了synchronized关键字可以实现线程安全,默认是10,扩容为两倍,初始化时候可以设定初始大小和扩容数量

public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }
2.扩容

扩容机制与ArrayList差不多

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
  		 //capacityIncrement为初始设定的增长量,没有设置默认为0
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

3.LinkedList(双向链表)

1.初始化

用一个Node维护一个双向循环链表

private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
2.扩容

add方法为尾插法

/**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
      //用来记录列表被操作次数
        modCount++;
    }

扩容:不用说了吧

增删多用LinkedList链表查询的时候需要从头开始检索,改查多用ArrayList数组增删需要改变剩余所有索引

4.HashSet(HashMap)

初始大小+负载因子

HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }
public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

第一次添加默认为16的Node数组,临界值为数组长度*负载因子(0.75)到达临界值扩容到(原长度两倍 * 负载因子):16:12->32:24->64:48…

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
  //定义辅助变量
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
          //计算扩容大小,并初始化扩容(16个null的tab数组)
            n = (tab = resize()).length;
  //hash计算该key需要存的数组位置:此处的hash算法默认为
  //hash=(h = key.hashCode()) ^ (h >>> 16)
  //用key的hashCode高低位相亦或使得数字更随机(更散列)减少冲突
  //因此当key为String且相等时候必会hash冲突
  //其他情况要产生冲突就要满足hash值相等很难的吧
  //首先判断这个位置上是不是为null(没存过东西)
        if ((p = tab[i = (n - 1) & hash]) == null)
          //为没存过的位置直接放入结点
            tab[i] = newNode(hash, key, value, null);
        else {
          //该hash位置上有了就是hash了就得分以下三种情况
          //先定义一些辅助变量
            Node<K,V> e; K k;
          //情况一:原位置上的结点p与新加入结点的hash和key(用==和equals都进行一次比较,满足其中一个就行)都相等则放弃。
          //(可重写手动制造冲突:重写hashCode方法使hash值一样equals不一样就可以链化树化)
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
      		//情况二:原位置上的p是一棵红黑树:在树上添加当前结点并赋值一份给e结点(添加成功则返回null;失败(树上已经有了)则返回结点对象)
            else if (p instanceof TreeNode)
           e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        	//情况三:是个链表就比较和其中的每个结点比较
        //1.都不相同则挂在最后,e==p.next==null
        //再判断是否大于等于8个结点要树化了或者扩容
        //(所以默认16初始大小的链表的极限就是7+3=10:扩容三次:每次扩容会重写计算hash)
        //treeifyBin源码代码:
        //if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
        //  resize();
        //2.遇到地址值和hash值都相等的或者equals的就break将此处的结点已赋值给e
          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;
                }
            }
          //e不为空就是已经存在了就别加进来了
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                  //此处替换value:
                  //hashmap来说就是后来value替换前面的,hashset的话没差
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
  //每加入一个结点:链表或者数组都算 size会++;threshold数组实际长度
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

5.HashTable(HashMap)

底层是一个Entry数组,不允许键值为null,线程安全(内部方法大部分有synchronized修饰)

rehash():初始化为11:8之后扩容也是负载因子0.75,key相同value替换。

int newCapacity = (oldCapacity << 1) + 1//乘以二+1
//骚一点的遍历
 treeMap.entrySet().stream().forEach(personStringEntry -> {
 System.out.println(personStringEntry.getValue());
 });

与或非位运算

*5.1Properties类

继承自HashTable,可以读取Properties类文件

    //方法二
    private static void readProperty2() {
        Properties properties = new Properties();
        try {
            properties = PropertiesLoaderUtils.loadAllProperties("code.properties");
            //遍历取值
            Set<Object> objects = properties.keySet();
            for (Object object : objects) {
                System.out.println(new String(properties.getProperty((String) object).getBytes("iso-8859-1"), "gbk"));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
*5.2properties配置文件与yml配置文件

0.参考文献

1.区别:基本上格式上的区别罢了。

2.一些基本的配置springboot会自动的读取,比如:datasource的配置会自动装配连接池

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useAffectedRows=true&serverTimezone=Asia/Shanghai&allowMultiQueries=true
    username: root
    password: root

3.自定义的读取

1.依赖

<!--spring默认使用yml中的配置,但有时候要用传统的xml或properties配置,就需要使用spring-boot-configuration-processor-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

2.配置:

ly:
  upload:
    baseUrl: http://image.leyou.com/
    allowTypes:
      - image/jpeg
      - image/jpg
      - image/png

3.读取:

类上

@Data
@ConfigurationProperties(prefix = "ly.upload")
public class UploadProperties {
    private String baseUrl;
    private List<String> allowTypes;
}

方法上

@Configuration //相当于配置文件,在本项目中使用@Autowired注入在此用@Bean注入的类时会启用这里的配置
public class WXPayConfiguration {
    //注入配置类,在方法上读取配置文件信息完成注入
    @Bean
    @ConfigurationProperties(prefix = "ly.pay")
    public PayConfig payConfig(){
        return new PayConfig();
    }
    //注入官方提供的支付类
    @Bean
    public WXPay wxPay(PayConfig payConfig){
        return new WXPay(payConfig, WXPayConstants.SignType.HMACSHA256);
    }

}

4.使用:

4.1.在配置类 @Bean上的@ConfigurationProperties可以直接用@Autowired注入

4.2.在类上的@ConfigurationProperties使用配置类需

@EnableConfigurationProperties(UploadProperties.class)

在这里插入图片描述

三、泛型

1.作用和意义

类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),

然后在使用/调用时传入具体的类型(类型实参)。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
泛型类
@Data
//在实例化泛型类时,可以指定T的具体类型,也可以不指定
class Generic<T> {
    private int age;
    private String name;
    private T key;

    public void Generic(T key) {
        this.key = key;
    }
}

指定后就限定了此对象的T的类型,没指定就可以是任意类型

在这里插入图片描述

在这里插入图片描述

注意:

  • 泛型的类型参数只能是类类型,不能是简单类型。
  • 不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。

if(ex_num instanceof Generic){ }

泛型接口
/**
 * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
 * 即:class FruitGenerator<T> implements Generator<T>{
 * 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
 */
class FruitGenerator<T> implements Generator<T>{
    @Override
    public T next() {
        return null;
    }
}

2.通配符

当实现泛型接口的类,传入泛型实参时:

/**
 * 传入泛型实参时:
 * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
 * 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
 * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
 * 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
 */
public class FruitGenerator implements Generator<String> {

    private String[] fruits = new String[]{"Apple", "Banana", "Pear"};

    @Override
    public String next() {
        Random rand = new Random();
        return fruits[rand.nextInt(3)];
    }
}

同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。在逻辑上Generic不能视为Generic的父类。

public void showKeyValue1(Generic<Number> obj){
    Log.d("泛型测试","key value is " + obj.getKey());
}
Generic<Integer> gInteger = new Generic<Integer>(123);
Generic<Number> gNumber = new Generic<Number>(456);
showKeyValue(gNumber);
// showKeyValue这个方法编译器会为我们报错:Generic<java.lang.Integer> 
// cannot be applied to Generic<java.lang.Number>
// showKeyValue(gInteger);

解决:使用通配符“?”

public class GenericTest {

    public static void main(String[] args) {

        Box<String> name = new Box<String>("corn");
        Box<Integer> age = new Box<Integer>(712);
        Box<Number> number = new Box<Number>(314);

        getData(name);
        getData(age);
        getData(number);
    }

    public static void getData(Box<?> data) {
        System.out.println("data :" + data.getData());
    }

}

3.类型通配符上限和类型通配符下限

类型通配符上限通过形如<? extends Number>形式定义(必须为Number的子类),相对应的,类型通配符下限为<? super Number>形式(必须是Number的父类),其含义与类型通配符上限正好相反

				Box<String> name = new Box<String>("corn");
        Box<Integer> age = new Box<Integer>(712);
        Box<Number> number = new Box<Number>(314);

        getData(name);
        getData(age);
        getData(number);
			 //在代码//1处调用将出现错误提示,而//2 //3处调用正常。
        //getUpperNumberData(name); // 1
        getUpperNumberData(age);    // 2
        getUpperNumberData(number); // 3

四、结构

1.ConcurrentHashMap

HashMap的死循环

Java并发:volatile

jdk1.7

640?wx_fmt=png

put

首先是通过 key 定位到 Segment,之后在对应的 Segment 中进行具体的 put。

        Segment<K,V> s;
        int hash = hash(key);
        int j = (hash >>> segmentShift) & segmentMask;
        s = ensureSegment(j);
        return s.put(key, hash, value, false);

虽然 HashEntry 中的 value 是用 volatile 关键词修饰的,但是并不能保证并发的原子性,所以 put 操作时仍然需要加锁处理。

HashEntry<K,V> node = 
  tryLock() ? null :scanAndLockForPut(key, hash, value);
finally {
  unlock();
}
get

只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。ConcurrentHashMap 的 get 方法是非常高效的,因为整个过程都不需要加锁

size

在 JDK1.7 中,第一种方案他会使用不加锁的模式去尝试多次计算 ConcurrentHashMap 的 size,最多三次,比较前后两次计算的结果,结果一致就认为当前没有元素加入,计算的结果是准确的。 第二种方案是如果第一种方案不符合,他就会给每个 Segment 加上锁,然后计算 ConcurrentHashMap 的 size 返回。

jdk1.8

640?wx_fmt=png

抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。

1.8 在 1.7 的数据结构上做了大的改动,采用红黑树之后可以保证查询效率(O(logn)),甚至取消了 ReentrantLock 改为了 synchronized,这样可以看出在新版的 JDK 中对 synchronized 优化是很到位的。

put
  • 根据 key 计算出 hashcode 。
  • 判断是否需要进行初始化。
  • 为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
  • 如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。
  • 如果都不满足,则利用 synchronized 锁写入数据。
  • 如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。
get
  • 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
  • 如果是红黑树那就按照树的方式获取值。
  • 就不满足那就按照链表的方式遍历获取值。
size

Map 的 size 可能超过 MAX_VALUE, 所以还有一个方法 mappingCount(),JDK 的建议使用 mappingCount() 而不是size()。无论是 size() 还是 mappingCount(), 计算大小的核心方法都是 sumCount()。

final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
long sum = baseCount;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}

ConcurrentHashMap 提供了Long baseCount、CounterCell [] counterCells 两个辅助变量和一个 CounterCell 辅助内部类。sumCount() 就是beseCount的值+迭代 counterCells 来统计 sum 的过程。
put 操作时,肯定会影响 size(),在 put() 方法最后会调用 addCount() 方法。
addCount() 代码如下:如果 counterCells == null, 则对 baseCount 做 CAS 自增操作。
如果并发导致 baseCount CAS 失败了使用 counterCells。如果counterCells CAS 失败了,在 fullAddCount 方法中,会继续死循环操作,直到成功。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值