【java学习】集合框架:Map、Collection(Set、List、Queue)

在这里插入图片描述

1,常用map

AbstractMap 的成员变量:transient, volatile修饰

transient volatile Set<K>        keySet;//keySet, 保存 map 中所有键的 Set
transient volatile Collection<V> values;//values, 保存 map 中所有值的集合

1)HashMap

HashMap 常用方法:

方法描述备注
clear()删除 hashMap 中的所有键/值对
Object remove(Object key)从映像中删除与key相关的映射
clone()复制一份 hashMap
Object put(Object key, Object value)添加。若k存在则覆盖v,并返回关键字的旧值,否则返回null
void putAll(Map t)将来自特定映像的所有元素添加给该映像
putAll()将所有键/值对添加到 hashMap 中。参数为一个Map
putIfAbsent()如果 hashMap 中不存在指定的键,则将指定的键/值对插入到 hashMap 中。
containsKey()检查 hashMap 中是否存在指定的 key 对应的映射关系。
containsValue()检查 hashMap 中是否存在指定的 value 对应的映射关系。
replace()替换 hashMap 中是指定的 key 对应的 value。
replaceAll()将 hashMap 中的所有映射关系替换成给定的函数所执行的结果。
getOrDefault()获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值
forEach()对 hashMap 中的每个映射执行指定的操作。
entrySet()返回 hashMap 中所有映射项的集合集合视图。
keySet()返回 hashMap 中所有 key 组成的集合视图list = new ArrayList(map.value()); map转list
values()返回 hashMap 中存在的所有 value 值。
merge()添加键值对到 hashMap 中
compute()对 hashMap 中指定 key 的值进行重新计算prices.compute(“Shoes”, (key, value) -> value - value * 10/100)如果 key 对应的 value 不存在,则返回该 null,如果存在,则返回重新计算后的值。
computeIfAbsent()对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hasMap 中
computeIfPresent()对 hashMap 中指定 key 的值进行重新计算,前提是该 key 存在于 hashMap 中。
int size()返回当前映像中映射的数量
boolean isEmpty()判断map是否有元素。建议使用

1> 特点

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
  1. 访问速度快
  2. 无序
  3. k、v都可以为null,k不可重复
    对于key为null的值,在talbe[0]链表中存储。
  4. 非线程安全;
    线程安全时用concurrentHashMap。

2>原理—哈希表(数组)+ 单向链表 + 红黑树

  1. 哈希函数:hash(key)&(len-1)
    实现了均匀的散列,但比直接%len效率高,因为len为2的整数次幂,所以len-1为奇数(最后一位为1),保证了hash值通过&运算后,得到和原hash的低位相同,减少碰撞。
  2. 哈希冲突:链表定址法。
    通过equals判断当前key是否存在,存在则覆盖对应key;
    key不冲突,jdk1.7时通过头插法插入链表,新结点作为头结点存入哈希表;jdk1.8时使用尾插法插入链表。
    get步骤:1)计算hash值得到哈希数组下标;2)通过equals方法确定key值;3)从头结点开始遍历链表看key是否相等,相等返回。
    查找和插入复杂度:O(1)
  3. 当hash表长度>64,链表的长度超过8时,链表会自动转化为红黑树
    jdk8的时候加入了红黑树查找时间复杂度由O(N)变成O(lgN)。同时链表采用尾插法。之前的头插法虽然效率高一点,但是并发情况下容易死循环,而头插尾插对红黑树没什么影响。
    两个条件有一个不满足则退化为链表。
  4. 扩容
    hash表初始长度是16,每次扩容长度必须是 2的幂。默认加载因子是0.75(超过75%就会开始扩容,容量变成原来的2倍+1)。
    每一次put新数据时,都会检查当前容量,如果需要扩容,所有元素都需要按照新的hash算法被算一遍。ReHash代价交够。
    因为HashMap的key是int类型,所以最大值是231次方,但是查看源码,当到达 230次方,即 MAXIMUM_CAPACITY之后,便不再进行扩容。
  5. 线程不安全,支持快速失败。

3>高并发情况下,为什么HashMap出现死锁?

ReHash过程:每个元素重新算hash值,将链表翻转(遍历每个bucket上的链表,然后用头插法插入对应的bucket上的链表中)。对于同时准备扩容的两个线程1和2,如下图:
这里写图片描述
源码如下:

            while(null != e) {
                Entry<K,V> next = e.next; //线程1还没有执行这句 中断了
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);//计算hash值
                }
                int i = indexFor(e.hash, newCapacity);//获取扩容后这个结点对应索引位
                e.next = newTable[i];//这三行代码表示头插法插入
                newTable[i] = e;
                e = next;
            }

(1)线程1,中断,线程2 reHash
(2)线程2将原表 bucket 1 处的链表分发到 新表 bucket 1 和 bucket 3 上(hash值的后2位,第一位不同,则不是01就是11),分散到 bucket 3上的值有两个, key(3), key(7),遍历原表Bucket 1 上的 链表,采用头插法,结果就是 链表反转且还属于新表此bucket的元素放到 此bucket上。此时 key(7) -> key(3) -> null
(3)此时线程 2 被中断,线程 1调度。
此时线程 1 中 e 是 key(3)-> null,根据源码继续将e插入对应的table[3]链表,头插法,结果为:key(3)->key(7)->key(3),这是一个循环链表。
e = next, e是null,才能跳出循环。上面e永远不会空,死循环了。

4>常量集合

public static final Map<String, String> myMap = Collections.unmodifiableMap(new HashMap<String, String>() {
        private static final long serialVersionUID = 1L;

        {
            put("1", "11");
            put("2", "22");
        }
    });

5>空集合

//空集合:  这个空集合没有add方法。  emptyList,emptySet同理
return Collections.emptyMap(); 

6>iterator遍历

//可以使用keySet遍历 也可以私用values遍历,推荐使用Entry遍历
Set<Map.Entry<String,String>> entrys = map.entrySet();
Iterator<Map.Entry<String,String>> it = entrys.iterator();
while(it.hasNext()){
	Map.Entry<String,String> entry = it.next();
	System.out.println(entry.getKey());
	System.out.println(entry.getValue());
}

2)Hashtable

JDK1.0。

  1. k v不可为空;
  2. 线程安全但效率低下;
    是线程安全的hashMap,在hash表上加了全局synchronized(并发下只有一个线程能写hash表)。 推荐使用concurentHashMap。
  3. 哈希函数:直接使用对象的hashCode;
  4. 扩容:hash数组默认大小是11,增加的方式是old*2 + 1。

3)ConcurrentHashMap(线程安全且高效)

jdk1.5 java.util.concurrent(简称JUC)包下。

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable {
i>(jdk7):ReentrantLock+segment+HashEntry

在hashmap的基础上,对hash表的每个数组分段加锁(ReentrantLock):

  1. 1个Segment包含了一个HashEntry数组,每个HashEntry又是一个链表结构。
  2. segment继承自ReentrantLock,ReentrantLock用来保证segment的线程安全,分段锁提高了效率
  3. 对数组分段加锁,使用segment分片锁,这样一个线程只会锁住数组的一片,其他线程仍可以访问数组的其他片进行写操作和扩容
ii>(jdk8):synchronized+CAS+红黑树

1.8hashmap的基础上做了优化:

  1. 加锁
    用synchronized对链表头结点加锁;(不影响其它元素读写)
  2. 扩容(阻塞所有的读写、扩容操作)
    多线程并发扩容,对原始数组进行分片,每个线程对分片进行扩容。
  3. size计算
    并发量小的时候,通过CAS来实现size递增;
    如果并发量大,则通过数组维持元素个数做负载。要增加元素个数,随机取数组中一个数来做CAS。
  4. 多线程写数据
    通过CAS修改,失败后(哈希冲突)通过分段synchronized来写。
  5. 多线程读数据
    无锁。
ii>原理

ConcurrentHashMap 的弱一致性主要是为了提升效率,是一致性与效率之间的一种权衡。要成为强一致性,就得到处使用锁,甚至是全局锁,这就与Hashtable 和同步的HashMap 一样了。

  1. 修改数据CAS
    ConcurrentHashMap并发控制的关键在于一个变量,如下所示:private transient volatile int sizeCtl;
    sizeCtl被volatile关键字修饰是一个多线程共享的变量,当它的值为负数的时候说明某个线程正在操作这个Map,想要去操作这个Map的线程就要一直去竞争这个sizeCtl,没有得到这个变量的值就要一直自旋等待这个变量,当占用这个变量的线程操作完成后,要将这个变量的值设置回来,以便让其他线程走出自旋,竞争到该变量。
    这种同步进制事实上是一种CAS的做法。
  2. get()方法的弱一致性
    正是因为get 操作几乎所有时候都是一个无锁操作( get 中有一个readValueUnderLock 调用,不过这句执行到的几率极小),使得同一个Segment 实例上的put 和get 可以同时进行,这就是get 操作是弱一致的根本原因。
  3. clear()方法的弱一致性
    因为没有全局的锁,在清除完一个segment 之后,正在清理下一个segment 的时候,已经清理的segment 可能又被加入了数据,因此clear返回的时候,ConcurrentHashMap 中是可能存在数据的。因此,clear 方法是弱一致的。
  4. ConcurrentHashMap 的弱一致性
    在遍历过程中,如果已经遍历的数组上的内容变化了,迭代器不会抛出ConcurrentModificationException 异常。如果未遍历的数组上的内容发生了变化,则有可能反映到迭代过程中。这就是ConcurrentHashMap 迭代器弱一致的表现。
    在这种迭代方式中,当iterator 被创建后,集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new 新的数据从而不影响原有的数据,iterator 完成后再将头指针替换为新的数据,这样iterator 线程可以使用原来老的数据,而写线程也可以并发的完成改变,更重要的,这保证了多个线程并发执行的连续性和扩展性,是性能提升的关键。

4)TreeMap

  1. key有序(Unicode升序),key非null;
    基于红黑树实现,可以按照自然顺序或者自定义顺序自动排序,不允许插入null值,查找效率比较高,适合需要排序的场景。
  2. 非线程安全

5)LinkedHashMap

  1. key有序(插入顺序)
    LinkedHashMap的构造函数里有个布尔参数accessOrder,当它为true时,LinkedHashMap会以访问顺序为序排列元素,否则以插入顺序为序排序元素。

6)WeakHashMap

  1. 基于HashMap
  2. key不常使用时,允许GC
    WeakReference。

7)ImmutableMap

不可变Map,也就是说初始化之后,不能再往里面put元素了,不然会报异常java.lang.UnsupportedOperationException。
初始化:

public class MapTest {
    Map<String, String> myMapA = ImmutableMap.of("张三", "北京", "李四", "上海");
    // 或者
    Map<String, String> myMapB = ImmutableMap.<String, String>builder()
            .put("张三", "北京")
            .put("李四", "上海")
            .build();
}
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.1-jre</version>
</dependency>

8) ConcurrentSkipListMap

juc下面的类。

  1. 线程安全的有序的哈希表
    相当于线程安全的TreeMap
  2. 原理:跳表

9)LinkedMultiValueMap

一个key对应多个value。

功能方法备注
添加一个k-vvoid add(K, V);
添加一个K对应多个vvoid add(K, List);
设置一个k对应一个vvoid set(K, V);
设置一个k对应多个vvoid set(K, List);
移除一个k及其vList remove(K);
清除所有vvoid clear();
K集合Set keySet();
v集合List values();
查询k对应的某个vV getValue(K, index);
查询k对应的所有vList getValues(K);
map大小int size();
判空boolean isEmpty();
判k的存在boolean containKey(K);

2,常用List

1)常见用法

1>排序

Collections.sort 排序

//简单的list排序
List<String> list = ...
Collections.sort(list);

自定义排序:建立一个第三方类并实现java.util.Comparator接口。

package com.set;

import java.util.Set;
import java.util.TreeSet;

class MySort implements java.util.Comparator<Student2>{

	 //倒序 o2-o1   正序:o1-o2
    public int compare(Student2 o1, Student2 o2) {    
        return o2.id-o1.id;   
    }
}

使用:
        Set<Student2> set = new TreeSet<Student2>(new MySort());
        set.add(student1);
        set.add(student2);

2>初始化

//预防空指针
List<String> list = null;
List<String> newList = Optional.ofNullable(list).orElse(Lists.newArrayList());

3>常用方法

		//是一个private全局变量,在添加和删除时进行修改。作为成员变量是保存在堆中的。
    int size();        
    boolean isEmpty();        
    boolean contains(Object o);        
    //以正确的顺序返回list中元素的迭代器    
    Iterator<E> iterator();
    boolean add(E e);
    boolean addAll(Collection<? extends E> c);
    //在指定位置插入指定集合
    boolean addAll(int index, Collection<? extends E> c);
    //如果指定元素存在list中,移除list中第一次出现的指定元素(实现类可以选择具体的实现)
    boolean remove(Object o);
    //判断list中是否包含某个集合
    boolean containsAll(Collection<?> c);
    //删除list中包含的Collection中的所有元素
    boolean removeAll(Collection<?> c);
    //保留list中包含的Collection中的所有元素
    boolean retainAll(Collection<?> c);
    //将该列表的每个元素替换为将该运算符应用于该元素的结果。
    default void replaceAll(UnaryOperator<E> operator);
    //对list中的元素排列
    default void sort(Comparator<? super E> c);
    void clear();
    boolean equals(Object o);
    int hashCode();
    E get(int index);
    E set(int index, E element);
    //在指定位置上增加指定元素
    void add(int index, E element);
    //删除指定索引上的元素
    E remove(int index);
    //获取对象的第一个索引
    int indexOf(Object o);
    //获取对象的最后一个索引
    int lastIndexOf(Object o);
    //返回list的list 迭代器
    ListIterator<E> listIterator();
    //从指定位置返回list的迭代器
    ListIterator<E> listIterator(int index);
    //返回list的子list
    List<E> subList(int fromIndex, int toIndex);

list转为数组:

Object[] toArray();     
//在list的末尾插入元素(实现类可以选择插入的位置)
String[] array=list.toArray(new String[list.size()]);//可以指定类型

4>com.google.common.collect.Lists

import com.google.common.collect.Lists;
//将list等分成多个list
List<List<User>> parts = Lists.partition(users,  50);
//new LinkedList();
Lists.newLinkedList()
//new ArrayList()并且addAll
Lists.newArrayList();Lists.newArrayList("a","b");

2)ArrayList(推荐)

JDK1.2。

  1. 底层的数据结构:动态数组
  2. 查询快、尾插法修改快
    地址连续,下标随机访问。O(1)
    尾插法快(指定初始容量,性能甚至会超过LinkedList);
    头插法慢(所有元素要后移一位)
  3. 非线程安全;
  4. 扩容
    当ArrayList中的元素超过它的初始大小时,ArrayList只增加50%的大小。(节约内存)
    初始容量默认为10,扩容:((旧容量 * 3) / 2) + 1
  5. 有序(插入顺序);
  6. 可重复;
  7. 比较:equals重写了,比较的是存储的元素相等,而不是直接比较引用。

1>Double Brace Initialization初始化,增强可读性


ArrayList<String> lists2 = new ArrayList<String>(){ //这个括号相当于派生自ArrayList<String>的匿名类。如果我们将该匿名类实例通过函数调用等方式传到该类型之外,那么对该匿名类的保持实际上会导致外层的类型无法被释放,进而造成内存泄露。
        	{ //这个括号:由于匿名类中不能添加构造函数,因此这里的instance initializer实际上等于构造函数,用来执行对当前匿名类实例的初始化
            	add("test1");
            	add("test2");
        	}
        };

实际上,任何类型都可以通过它来执行预初始化:

NutritionFacts cocaCola = new NutritionFacts() {{
    setCalories(100);
    setSodium(35);     
    setCarbohydrate(27);
5 }};

2>直接采用[]、{}的形式存入对象

JDK1.7对Java集合增强支持。
直接采用[]、{}的形式存入对象,采用[]的形式按照索引、键值来获取集合中的对象,如下 :

List<String> list=["item"]; //向List集合中添加元素
      String item=list[0]; //从List集合中获取元素

      Set<String> set={"item"}; //向Set集合对象中添加元素
      Map<String,Integer> map={"key":1}; //向Map集合中添加对象
      int value=map["key"]; //从Map集合中获取对象

3>常量集合

    public static final List<String> myList = Collections.unmodifiableList(new ArrayList<String>() {
        private static final long serialVersionUID = 1L;

        {
            add("a");
            add("b");
        }
    });
    
public static final List<String> myList1 = new ArrayList<String>(Arrays.asList("Tom", "Jerry", "Mike"));

//常量数组  不允许修改 (没有add方法)
//list = Arrays.asList(str1,str2);str1和str2可以是public static final String 
public static final List<String> OBJECTS_LIST = Collections.unmodifiableList(list);

//不可变的集合且长度只有1,可以减少内存空间
List<String> list=Collections.singletonList("111");

//返回的不可变列表组成的n个拷贝的指定对象。<T> List<T> nCopies(int n, T o)
list = Collections.nCopies(3, 0);  //输出list [0, 0, 0]

4>空集合

//空集合:  这个空集合没有add方法。  emptyMap,emptySet同理
return Collections.emptyList(); 

5>判断两个List集合是否相等

list.containsAll(list1)

3)LinkedList

  1. 底层的数据结构:基于双向链表结构;
    常用堆栈与队列的实现,地址不连续,查询慢,增删快(性能主要耗费在创建node节点)。
  2. 非线程安全
  3. 必须使用迭代器进行访问
    使用IndexOf会遍历所有node,性能很差

4)Vector

JDK1.0。

  1. 底层是数据结构:动态数组
    在内存中占用连续的空间;
  2. 线程安全
    是线程安全的ArrayList,通过Synchronized修饰。
  3. 可以设置增长因子;
    当Vector中的元素超过它的初始大小时,Vector会将它的容量翻倍。

6)Stack(栈)

  1. 先进后出(FILO, First In Last Out)。
  2. 线程安全
    继承于Vector。

7)CopyOnWriteArrayList

jdk1.5 java.util.concurrent(简称JUC)包。

i>概念
  1. 线程安全
    相当于线程安全的ArrayList,它实现了List接口。
    add方法是加锁的(ReentrantLock),适用于读多写少的场景。
  2. 写时复制(适用于数据量小的并发)
    当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。
    慎用!如果数据量过大,每次add/set都要重新复制数组,代价高昂容易引起故障。
  3. 读的时候并发写读的是旧数据
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;//加的是lock 锁
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);//将原容器的引用指向新的容器;
            return true;
        } finally {
            lock.unlock();
        }
    }

8)Collections 类中提供的静态工厂方法创建的类

Collections.synchronizedXXX(),可以将指定的集合包装成线程同步的集合。比如,

List  list = Collections.synchronizedList(new ArrayList());
Set  set = Collections.synchronizedSet(new HashSet()); 

3,Set

Java.util.Set。

1)特点

  1. 不可重复
    作用:询问某个对象是否在某个Set;
  2. 允许null值;
  3. 无序。

2)方法

  1. 新建实例
Set<String> set = new LinkedHashSet<String>();
  1. 遍历
    Iterator遍历;
  2. 删除
    移除的是值为"a"的项。不能按索引移除。
set.remove("a");

3)HashSet

  1. 基于hash表实现
    地址不连续。
    采用hashCode计算hash值。
    自定义对象存入hashSet需要重新hashCode和equals方法。
  2. 非线程安全
  3. 无序

4)TreeSet

  1. 有序(Unicode升序)
    存放对象不能排序则报错、可指定排序规则。
    自定义的对象需要实现Comparable接口的 compareTo(object o)方法
  2. 原理:红黑树
  3. 非线程安全
  4. 不允许插入null值

5)LinkedHashSet

  1. 有序(插入顺序)
    根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。

6)CopyOnWriteArraySet

juc包下。

  1. 线程安全
  2. 写时复制,读写分离
    相当于线程安全的HashSet。
    内部包含一个CopyOnWriteArrayList对象。

7)ConcurrentSkipListSet

juc包下。

  1. 线程安全
  2. 写时复制,读写分离
    是线程安全的有序的集合(相当于线程安全的TreeSet);
    通过ConcurrentSkipListMap实现。

4,Arrays数组

1)Array与Arrays的区别

①Array(数组类)

  1. 效率高,但容量固定且无法动态改变。
  2. 它无法判断其中实际存有多少元素,length只是告诉我们array的容量。
  3. 因此除了结构的初始化和销毁之外,数组只有存取元素和修改元素值的操作。
  4. Array可以存放对象类型、基本数据类型的数据,其中的元素的类型必须相同。

②Arrays(静态类)

此静态类专门用来操作array ,提供搜索、排序、复制等静态方法。

2)数组定义

int [] a;//一开始不定义长度
int [] a = new int[10]; 
float f[][] = new float[6][6];
float []f[] = new float[6][6];
float [][]f = new float[6][6];
float [][]f = new float[6][];

3)常用方法

1>Arrays.asList()

将数组转换成java.util.ArrayList类型;

注意:返回的是Arrays的内部类java.util.Arrays A r r a y L i s t ,而不是 j a v a . u t i l . A r r a y L i s t 。 j a v a . u t i l . A r r a y s ArrayList, 而不是java.util.ArrayList。java.util.Arrays ArrayList,而不是java.util.ArrayListjava.util.ArraysArrayList和java.util.ArrayList都是继承AbstractList,remove、add等方法AbstractList中是默认throw UnsupportedOperationException而且不作任何操作。java.util.ArrayList重了写这些方法而Arrays的内部类ArrayList没有重写,所以使用时会抛出异常。

2>Arrays.sort()

数组的排序;

            char[] array = str.toCharArray();
            Arrays.sort(array);
            输出array,此时已排序

3>Arrays.binarySearch()

在排好序的array中二分查找元素。

4>Arrays.equals()

比较两个array是否相等。array拥有相同元素个数,且所有对应元素两两相等。

5>Arrays.fill()

给数组赋初值。

类似C语言中的memset()应用

char *p = new char[90];
memset((void *)p, -2, 90);
//把90个char都赋成-2,因为C++里的char是一个byte(8bit);

java写法:

java.util.Arrays.fill( float[], float) 

6>复制数组

当数据量很大时,复制的效率:System.arraycopy > clone > Arrays.copyOf > for循环

a>System.arraycopy

System类源码中给出了arraycopy的方法,是native方法,也就是本地方法,肯定是最快的。

//数据量小的时候,for可能快。
public static void arraycopy(Object src,  //源数组
                             int srcPos,  //源数组中的起始位置
                             Object dest, //目标数组
                             int destPos, //目标数据中的起始位置
                             int length)  //要复制的数组元素的数量
b>clone

java.lang.Object类的clone()方法为protected类型,不可直接调用,需要先对要克隆的类进行下列操作:
首先被克隆的类实现Cloneable接口;
然后在该类中覆盖clone()方法,并且在该clone()方法中调用super.clone();

这样,super.clone()便可以调用java.lang.Object类的clone()方法。

//被克隆的类要实现Cloneable接口
    class Cat implements Cloneable {
        private String name;
        private int age;

        public Cat(String name, int age) {
            this.name = name;
            this.age = age;
        }

        //重写clone()方法
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }

    public class Clone {
        public static void main(String[] args) throws CloneNotSupportedException {

            Cat cat1 = new Cat("xiaohua", 3);
            System.out.println(cat1);
            //调用clone方法
            Cat cat2 = (Cat) cat1.clone();
            System.out.println(cat2);
        }
    }
c>Arrays.copyOf

Arrays.copyOf有十种重载方法,复制指定的数组,返回原数组的副本。具体可以查看jdk api。

d>Arrays.copyOfRange

将数组拷贝至另外一个数组

arr1 = Arrays.copyOfRange(arr, 0, index);

参数:
original:第一个参数为要拷贝的数组对象
from:第二个参数为拷贝的开始位置(包含)
to:第三个参数为拷贝的结束位置(不包含)

7)字符串和char[] 转换

//str->char[]
            char[] array = str.toCharArray();
//char[]->str  切忌不可array.toString(),转换的是对象名
            String key = new String(array);

8)数组计算

int[] a = {1,2,3};
++a[0]; //2,2,3

4)数组名

数组名不等价于指针,只有数组名作为函数参数时,才退化为指针,此时数组名的sizeof()就是指针大小,除了这种情况外,均是整个指整个数组的大小。

char *string_a=(char *)malloc(100*sizeof(char));//对于64位机:sizeof(string_a)为8
char string_b[100];//sizeof(string_b)为100.

6)Array和List区别

  1. 数组在内存中顺序存储,需要提前知道大小。
  2. List存储数据都转换为对象,存在不安全类型且需要拆箱装箱。

5,Iterator接口

1)概念

  1. Iterator是所有集合的总接口,其他所有接口都继承于它。
  2. 用于集合的安全遍历:移除和添加。

java.util.Iterator其接口定义如下:

public interface Iterator {  
  boolean hasNext(); //判断容器内是否还有可供访问的元素 
  Object next();  //返回迭代器刚越过的元素的引用,返回值是Object,需要强制转换成自己需要的类型
  void remove();  //删除迭代器刚越过的元素
}  

2)使用

①遍历List、Set、Map

	Iterator iterator = list.iterator();  
        while(iterator.hasNext()){  
            String s = iterator.next();  
            if (s.contains("1")) {
                iterator.remove();//安全删除
            }
        }  

②安全删除

Iterator支持从源集合中安全地删除对象(在Iterator上调用remove方法)。
Iterator的remove()方法不仅会删除元素,还会维护一个标志,用来记录目前是不是可删除状态,例如,你不能连续两次调用它的remove()方法,调用之前至少有一次next()方法的调用。

如果在循环的过程中调用集合的remove()方法,就会导致循环出错,引发ConcurrentModificationException异常。
原因:Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast原则 Iterator 会马上抛出java.util.ConcurrentModificationException 异常。所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。正确的做法是不用list.remove(),使用Iterator.remove();

4) ListIterator

这里写图片描述
功能:

  1. 双向遍历:next() 、 previous()
  2. 在遍历时修改 List 的元素;
  3. 遍历时获取迭代器当前游标所在位置。

6,Queue 队列

1)java.util.List(非阻塞队列)

jdk5.0以前使用。

2)PriorityQueue(优先队列)

JDK1.5。

1>特性

  1. 自动排序
    对于基本数据类型的包装类(如:Integer,Long等),默认asc(小顶堆)排序,取最小值出队。
    对于自定义类,需要自定义比较器。
PriorityQueue<MaxNum> pq = new PriorityQueue(new Comparator<MaxNum>(){
            //大于0表示n2>n1
            public int compare(MaxNum n1, MaxNum n2){            
							//降序排列
                return n2.num - n1.num;
            }
        });
  1. 非阻塞队列;
  2. 线程不安全;
  3. 不允许null值。

2>使用

方法说明备注
peek()返回队首元素
poll()返回队首元素,队首元素出队列。默认asc排序,出min
add()添加元素
size()返回队列元素个数
isEmpty()/判断队列是否为空,为空返回true,不空返回false

3>原理

二叉堆、小根堆

Object[] 存储数据;

  1. 跟节点:数组实现的二叉堆,根节点在index=0位置上。
  2. 左孩子:数组n位置上的元素,其左孩子在[2n+1]位置上。
  3. 右孩子:数组n位置上的元素,其右孩子在 2(n+1) 位置上。
  4. 父节点:数组n位置上的元素,其父节点在 (n-1)/2 位置上。
  5. 最大不能超过Integer.MAX_VALUE - 8

4>topN计算

package sort;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Random;
/**
 * 固定容量的优先队列,模拟大顶堆,用于解决求topN小的问题
 * @author 张恩备
 * @date 2016-11-25 下午02:29:31
 */
public class FixSizedPriorityQueue<E extends Comparable> {
    private PriorityQueue<E> queue;
    private int maxSize; // 堆的最大容量

    public FixSizedPriorityQueue(int maxSize) {
        if (maxSize <= 0)
            throw new IllegalArgumentException();
        this.maxSize = maxSize;
        this.queue = new PriorityQueue(maxSize, new Comparator<E>() {
            public int compare(E o1, E o2) {
                // 生成最大堆使用o2-o1,生成最小堆使用o1-o2, 并修改 e.compareTo(peek) 比较规则
                return (o2.compareTo(o1));
            }
        });
    }

    public void add(E e) {
        if (queue.size() < maxSize) { // 未达到最大容量,直接添加
            queue.add(e);
        } else { // 队列已满
            E peek = queue.peek();
            if (e.compareTo(peek) < 0) { // 将新元素与当前堆顶元素比较,保留较小的元素
                queue.poll();
                queue.add(e);
            }
        }
    }


    public List<E> sortedList() {
        List<E> list = new ArrayList<E>(queue);
        Collections.sort(list); // PriorityQueue本身的遍历是无序的,最终需要对队列中的元素进行排序
        return list;
    }

    public void printResult(){
        // 或者直接用内置的 poll() 方法,每次取队首元素(堆顶的最大值)
        while (!queue.isEmpty()) {
            System.out.print(queue.poll() + ", ");
        }
    }
}
 final FixSizedPriorityQueue pq = new FixSizedPriorityQueue<Integer>(3);

        for (int i=0; i<a.length; i++) {
            pq.add(a[i]);
        }
        pq.printResult();

3)阻塞队列(JUC包)

常见阻塞队列对比:

队列是否有界是否加锁是否线程安全
ArrayBlockingQueue有界加锁数组
LinkedBlockingQueue可选有界加锁链表
PriorityBlockingQueue无界加锁数组
DelayQueue无界加锁数组
LinkedTransferQueue无界无锁链表
LinkedBlockingDeque可选有界有锁链表

1>ArrayBlockingQueue

  1. 数据结构:数组
    内部还保存着3个变量,由于cpu缓存一致性协议,可能导致伪共享。
# 出队下标
/** items index for next take, poll, peek or remove */
int takeIndex;
 
# 入队下标
/** items index for next put, offer, or add */
int putIndex;
 
# 队列中元素数量
/** Number of elements in the queue */
int count;
  1. 线程安全
  2. 有界
    初始化时必须指定队列的大小。
  3. 阻塞队列
    生产者和消费者共用一把锁:读写操作上都需要锁住整个容器。==》吞吐量一般

2> LinkedBlockingQueue

  1. 数据结构:单向链表
    按 FIFO(先进先出)排序元素。
  2. 可选有界
    指定队列的大小时,就是有界的;
    不指定队列的大小时,默认是Integer.MAX_VALUE,相当于无界。==》但当生产速度大于消费速度时候,有可能会内存溢出。
  3. 阻塞队列
    生产者和消费者分别采用了独立的锁来控制数据同步==》高并发的情况下put()有putLock锁,take()有putLock;吞吐量较高。
常用方法说明是否阻塞
put()把元素加入到阻塞队列中。
如果阻塞队列没有空间,则调用此方法的线程被阻塞,直到有空间的时候再继续
阻塞
take()方法取出排在阻塞队列首位的对象。若阻塞队列为空,则调用此方法的线程被阻塞,直到有新的对象被加入的时候再继续。阻塞
offer()把元素加入到阻塞队列中,如果可以容纳,则返回true。如果不可以容纳,则返回false。不阻塞
poll()取出排在阻塞队列首位的对象,若阻塞队列为空,则返回null,如果不为空,则返回取出来的那个元素。不阻塞

3>LinkedBlockingDeque

  1. 数据结构:双向链表
  2. 双向并发阻塞队列:同时支持FIFO和FILO。
  3. 可选有界
  4. 有锁

4>ConcurrentLinkedQueue

  1. 数据结构:单向链表:
    FIFO
  2. 无界队列

4>ConcurrentLinkedDeque

  1. 数据结构:双向链表
    同时支持FIFO和FILO。
  2. 无界队列

5>PriorityBlockingQueue

  1. 数据结构:数组;
    按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。
    PriorityBlockingQueue 里面存储的对象必须是实现Comparable 接口,队列通过这个接口的compare 方法确定对象的priority。
    队列的元素并不是全部按优先级排序的,但是队头的优先级肯定是最高的。每取一个头元素时候,都会对剩余的元素做一次调整,这样就能保证每次队头的元素都是优先级最高的元素。
  2. 无界
  3. 加锁:不会阻塞生产者,但会阻塞消费者。

6>DelayQueue

  1. 数据结构:数组;
    DelayQueue = BlockingQueue +PriorityQueue + Delayed。
  2. 无界
  3. 加锁:不会阻塞生产者,但会阻塞消费者。
    放置实现了Delayed 接口的对象,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的Delayed 元素。这个队列里面所存储的对象都带有一个时间参数,采用take 获取数据的时候,如果时间没有到,取不出来任何数据。而加入数据的时候,是不会阻塞的(不会阻塞生产者,但会阻塞消费者)。
    优势:
    如果不使用DelayQueue,那么常规的解决办法就是:使用一个后台线程,遍历所有对象,挨个检查。对象数量过多时,可能存在性能问题;而且做不到按超时的时间顺序处理。
  4. 应用场景:缓存系统的设计。
    缓存中的对象,超过了有效时间,需要从缓存中移出。使用一个线程循环查询DelayQueue,一旦能从DelayQueue 中获取元素时,表示缓存有效期到了。

7>Disruptor(推荐)

github
文档

  1. 数据结构:环形数组;
    当数据填满队列时(2^n - 1)时,再次添加数据会覆盖之前的数据。
    位运算:数组大小必须为2的n次方,通过位运算提高效率。
  2. 有界
  3. CAS无锁
    只有一个游标器Sequencer,它可以保证生产的消息不会覆盖还未消费的消息。一个变量避免伪共享的产生。
    线程间通信性能远远高于ArrayBlockingQueue。

7,快速失败和安全失败

1)快速失败(fail—fast)

①概念

在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
fail-fast是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。

②原理

迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。

③场景

java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。

2)安全失败(fail—safe)

①概念

采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。

②原理

由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。

③缺点

基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

④场景

java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

8,JUC(java.util.concurrent)

在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了设计用于多线程上下文中的 Collection 实现等;

1)copyOn系列的List和Set

2)阻塞队列

3)ConcurrentHashMap

4)Condition

①Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()

②作用

对锁进行更精确的控制。

对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。
例如:
假如多线程读/写同一个缓冲区:当向缓冲区中写入数据之后,唤醒"读线程";当从缓冲区读出数据之后,唤醒"写线程";并且当缓冲区满的时候,"写线程"需要等待;当缓冲区为空时,“读线程"需要等待。
如果采用Object类中的wait(), notify(), notifyAll()实现该缓冲区,当向缓冲区写入数据之后需要唤醒"读线程"时,不可能通过notify()或notifyAll()明确的指定唤醒"读线程”,而只能通过notifyAll唤醒所有线程(但是notifyAll无法区分唤醒的线程是读线程,还是写线程)。 但是,通过Condition,就能明确的指定唤醒读线程。

③方法

// 造成当前线程在接到信号或被中断之前一直处于等待状态。
void await()
// 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 对应Object的wait();
boolean await(long time, TimeUnit unit)
// 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
long awaitNanos(long nanosTimeout)
// 造成当前线程在接到信号之前一直处于等待状态。
void awaitUninterruptibly()
// 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。
boolean awaitUntil(Date deadline)
// 唤醒一个等待线程。对应Object的notify();
void signal()
// 唤醒所有等待线程。对应Object的notifyAll()
void signalAll()

不同的是,Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。

5)Semaphore类

控制某个资源可被同时访问的个数,通过构造函数设定一定数量的许可,通过acquire() 获取一个许可,如果没有就等待,而release() 释放一个许可。

下面的例子只允许5 个线程同时进入执行acquire()和release()之间的代码:

public class SemaphoreTest {
 public static void main(String[] args) {
        // 线程池
        ExecutorService exec = Executors.newCachedThreadPool();
        // 只能5 个线程同时访问
        final Semaphore semp = new Semaphore(5);
        // 模拟20 个客户端访问
        for (int index = 0; index < 20; index++) {
            final int NO = index;
            Runnable run = new Runnable() {
                public void run() {
                    try {
                        // 获取许可
                        semp.acquire();
                        System.out.println("Accessing: " + NO);
                        Thread.sleep((long) (Math.random() * 10000));
                        // 访问完后,释放,如果屏蔽下面的语句,则在控制台只能打印5 条记录,之后线程一直阻塞
                        semp.release();
                    } catch (InterruptedException e) {
                    }
                }
            };
            exec.execute(run);
        } // 退出线程池
        exec.shutdown();
    }
}

6)ReentrantLock类

具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大;

7)Future接口

表示异步计算的结果;

8)java.util.concurrent.CountDownLatch类(锁计数器)

①功能

可以用来在一个线程中等待多个线程完成任务的类。

②原理

CountDownLatch 是通过“共享锁”实现的。

i>创建
CountDownLatch latch = new CountDownLatch(3);

创建时传入一个int 类型参数,表示该“共享锁”最多能被count 个线程同时获取, 这个值只能被设置一次, 而且CountDownLatch 没有提供任何机制去重新设置这个计数值。

ii>await()函数

让线程阻塞等待其他线程,直到CountDownLatch 的计数值变为0,才继续执行之后的操作。
主线程必须在启动其他线程后立即调用await()方法:

WorkerThread t1 = new WorkerThread(latch);
WorkerThread t2 = new WorkerThread(latch);
WorkerThread t3 = new WorkerThread(latch);
t1.start();
t2.start();
t3.start();
latch.await();
iii>countDown()函数

这个函数用来将CountDownLatch 的计数值减1,如果计数达到0,则释放所有等待的线程。
由其他线程调用:latch.countDown();

③场景

一个任务,它需要等待其他的一些任务都执行完毕之后它才能继续执行。
比如:开5 个多线程去下载,当5 个线程都执行完了才算下载成功。

9)java.util.concurrent.CyclicBarrier(循环屏障)

①功能

这个类是一个可以重复利用的屏障类。它允许一组线程相互等待,直到全部到达某个公共屏障点,然后所有的这组线程再同步往后执行。

②原理

await()函数:每被调用一次,计数便会减少1(CyclicBarrier 设置了初始值),并阻塞住当前线程。当计数减至0 时,阻塞解除,所有在此CyclicBarrier 上面阻塞的线程开始运行。

③CountDownLatch 和CyclicBarrier 区别

(1) CountDownLatch 的作用是允许1 个线程等待其他线程执行完成之后,它才执行;而CyclicBarrier 则是允许N 个线程相互等待到某个公共屏障点,然后这一组线程再同时执行。
(2) CountDownLatch 的计数器的值无法被重置,这个初始值只能被设置一次,是不能够重用的;CyclicBarrier 是可以重用的。

④使用

private static final CyclicBarrier cb=new CyclicBarrier(4,new Runnable() {
		public void run()
		{
			System.out.println("寝室四兄弟一起出发去球场");
		}
	});

//cb传入线程中,线程中通过
//线程通过cb.await();使cb计数器减一

10)生产者消费者问题多种实现

①使用阻塞队列 LinkedBlockingQueue实现

  class Producer implements Runnable {
        private final BlockingQueue sharedQueue;

        public Producer(BlockingQueue sharedQueue) {
            this.sharedQueue = sharedQueue;
        }

        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println("Produced: " + i);
                    sharedQueue.put(i);
                } catch (InterruptedException ex) {
                    System.out.println(ex);
                }
            }
        }
    }

    class Consumer implements Runnable {
        private final BlockingQueue sharedQueue;

        public Consumer(BlockingQueue sharedQueue) {
            this.sharedQueue = sharedQueue;
        }

        public void run() {
            while (true) {
                try {
                    int i = (Integer) sharedQueue.take();
                    System.out.println("Consumed: " + i);
                } catch (InterruptedException ex) {
                    System.out.println(ex);
                }
            }
        }
    }

    public class ProducerConsumerPattern {
        public static void main(String args[]) { 
            BlockingQueue sharedQueue = new LinkedBlockingQueue();
           
            Thread prodThread = new Thread(new Producer(sharedQueue));
            Thread consThread = new Thread(new Consumer(sharedQueue));           
           
            prodThread.start();
            consThread.start();
        }
    }

②使用Object 的wait()和notify()实现

 PriorityQueue<Integer> queue = new PriorityQueue<Integer>(10);//充当缓冲区

    class Consumer extends Thread {
        public void run() {
            while (true) {
                synchronized (queue) {
                    while (queue.size() == 0) {//队列空的条件下阻塞
                        try {
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            queue.notify();
                        }
                    }
                    queue.poll(); // 每次移走队首元素
                    queue.notify();
                }
            }
        }
    }

    class Producer extends Thread {
        public void run() {
            while (true) {
                synchronized (queue) {
                    while (queue.size() == 10) {//队列满了的条件下阻塞
                        try {
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            queue.notify();
                        }
                    }
                    queue.offer(1); // 每次插入一个元素
                    queue.notify();
                }
            }
        }
    }

③使用Condition 实现

 private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(10);
    private Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    class Consumer extends Thread {
        public void run() {
            while (true) {
                lock.lock();
                try {
                    while (queue.size() == 0) {
                        try {
                            notEmpty.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    queue.poll(); // 每次移走队首元素
                    notFull.signal();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    class Producer extends Thread {
        public void run() {
            while (true) {
                lock.lock();
                try {
                    while (queue.size() == 10) {
                        try {
                            notFull.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    queue.offer(1); // 每次插入一个元素
                    notEmpty.signal();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值