【Java】集合类知识总结

类集:保存数据的集合,类集 = 数据结构 + 线程安全
为了解决数组定长问题,产生类集思想 - Collection(理解为一个动态数组)

1. Collection 接口常用操作方法

Collection接口是Java中保存单个对象的最顶层接口。
Collection有两个子接口:LIst与Set。
List接口:允许数据重复
Set接口:不允许数据重复。

  • 添加元素:
    boolean add(E e)
  • 删除元素:
    boolean remove(Object o)
  • 查找元素:(如果有返回true,没有返回false)
    boolean contains(Object o);
  • 转为数组:
    Object[] toArray();
  • 取得类集的迭代器(用于集合遍历)
    Iterator iterator();
  • Collection.EMPTY_LIST,这是一个空集合。

2. List接口

2.1 常用方法

  • 根据下标返回元素:
    E get(int index);
  • 根据下标修改元素,返回修改前的元素
    E set(int index,E element);
  • 根据下标删除元素
    E remove(int index); E remove(new Integer(int value));

例:

import java.util.ArrayList;
import java.util.List;

public class ListTest {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);//添加元素
        list.add(3);
        list.add(5);
        list.add(7);
        list.add(1);
        System.out.println(list);//输出数组
        System.out.println(list.size());//返回数组大小
        System.out.println(list.contains(7));//判断是否有这个元素
        System.out.println(list.get(3));//返回数组下标为3的数
        System.out.println(list.set(3,9));//将数组下标为3的数修改为9
        System.out.println(list.get(3));//再次返回数组下标为3的数,看是否修改成功
        System.out.println(list.remove(4));//删除的是索引位置的数
        System.out.println(list.remove(new Integer(5)));//删除数字5
        System.out.println(list);//打印数组
    }
}

结果:在这里插入图片描述

2.2 List集合中添加自定义类,及比较方法

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

class Person{
    private String name;
    private Integer age;
    
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

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

    public String getName() {
        return name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class ListTest {
    public static void main(String[] args) {
        List<Person> people = new LinkedList<>();
        Person per1 = new Person("ll",20);
        Person per2 = new Person("hh",30);
        Person per3 = new Person("fwb",1);
        people.add(per1);
        people.add(per2);
        people.add(per3);
        System.out.println(people.contains(new Person("ll",20)));
        System.out.println(people.remove(new Person("zs",20)));
    }
}

这样添加的结果是输出了两个false,因为他需要覆写equals方法以获得比较元素的支持:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

class Person{
    private String name;
    private Integer age;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

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

    public String getName() {
        return name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    /*
    这是原方法中所调用的equals方法,只是比较地址是否相同
    public boolean equals(Object obj) {
        return (this == obj);
    }
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        //instanceof用来判断是否是这类的对象  
        if (!(obj instanceof Person))
            return false;
        //向下转型取出属性值比较
        Person per = (Person)obj;
        return this.age.equals(per.age) && this.name.equals(per.name);
    }
}
public class ListTest {
    public static void main(String[] args) {
        List<Person> people = new LinkedList<>();
        Person per1 = new Person("aaa",20);
        Person per2 = new Person("bbb",30);
        Person per3 = new Person("ccc",1);
        people.add(per1);
        people.add(per2);
        people.add(per3);
        //Object obj = new Person("aaa",20);
        //父类 父类引用 = new 子类();这是向上转型
        System.out.println(people.contains(new Person("aaa",20)));
        System.out.println(people.remove(new Person("张三",20)));
    }
}

此时的输出结果是true与false是我们想要的结果。


2.3 List接口中ArrayList,LinkedList,Vector的区别

2.3.1 ArrayList: JKD1.2

  1. 数组实现
    1.1 初始化策略:Lazy-Load(懒加载策略):只有当对象第一次被使用(add)时,内部的数组才会初始化为长度为10的数组。
    1.2 扩容策略:每次扩容为原先数组的1.5倍
  2. 线程不安全

源码:

private int size;
private static final int DEFAULT_CAPACITY = 10;
1.
public boolean add(E e) {
   	ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

2.
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//二者取最大值
    }

    ensureExplicitCapacity(minCapacity);
}

3.
private void ensureExplicitCapacity(int minCapacity) {
   modCount++;

   // overflow-conscious code
   if (minCapacity - elementData.length > 0)
       grow(minCapacity);
}

4.
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        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.3.2 Vector: JDK1.0

  1. 数组实现
    1.1 初始化策略:当产生Vector对象时,内部数组就初始化为大小为10的数组(不管用不用)
    1.2 扩容:每次扩容为原先数组的2倍。
  2. 线程安全性:使用synchronized同步方法来确保线程安全性。
    但是因为使用了synchronized同步方法所以效率会很低(读写互斥)(锁的是当前整个Vector对象,操作任意一个方法,其他线程全部阻塞了,无法实现多个线程同时读这个Vector对象)
  3. 特殊点:JDK内置的Stack继承Vector。Java中自定义的栈是它的子类。
  4. Vector独有枚举输出。

源码:

1.
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.
public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

3.
private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

4.
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        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);
    }
    

2.3.3 LinkedList: JDK1.2

  1. 基于双向链表的动态数组(所以不存在初始化,扩容问题),可以被当作栈、队列或双端队列来使用
  2. 线程不安全
  3. 特殊:JDK Queue(队列)的一个实现子类
    在这里插入图片描述在这里插入图片描述.

2.3.4 ArrayList与LinkedList的选择:

ArrayList 适合查找(取得)和更改。LinkedList适合添加和删除。但是在自己测试的时候,低数量级的数据(1w以下)是ArrayList更快,之后更大量级的都是LinkedList更快。同时,在使用LinkedList时需要考虑到他是链表结构的使用了大量的小对象,因此会影响整个进程的性能。
可以看看这个问题:https://stackoverflow.com/questions/322715/when-to-use-linkedlist-over-arraylist-in-java/322742#322742

3. Set接口(本质是一个Map接口)

set接口中没有扩充任何方法,set中有的方法都是Collection中有的方法。
源码:

//HashSet底层用来存储元素的结构,实际上使用HashMap来存储
private transient HashMap<E,Object> map;
 
//HashMap中的value值,HashSet只关注key值,所以所有的value值都为Object对象
private static final Object PRESENT = new Object();
 
 
//HashSet的无参构造,直接创建了一个HashMap对象
public HashSet() {
        map = new HashMap<>();
}
 
//指定初始化容量和负载因子
public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
}
 
//给定初始化容量
public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
}
 
public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
}

HashSet的构造方法底层都是调用 HashMap的构造方法, 所以HashSet底层实际上是使用 HashMap 来作为存储结构.

当使用无参构造创建 HashSet对象时, 其实调用了 HashMap的无参构造创建了一个 HashMap对象, 所以 HashSet 的初始化容量也为16, 负载因子也为 0.75.

再来看看 HashSet 的 add() 方法的实现:
在这里插入图片描述
HashSet 的 add() 方法底层实际也是调用了 HashMap 的 put() 方法, 这里的key为我们传入的将要添加到 set集合中的元素, 而value值则为 PERSENT,其实就是上面分析的 HashSet类中的一个静态字段, 默认为 Object对象.

HashSet并不关注value元素, 只使用 HashMap来存储 key元素, 这就使得 HashSet判断元素相等的条件与 HashMap中 key相等的条件其实是一样的, 两个元素的 hashCode值相同且通过equals()方法比较返回 true.

所以HashSet应该重写 equals()和hashCode()方法, 两个元素的 HashCode相同, 保证通过equals() 方法比较返回 true.

1. Set下的两个实现类类:

1.HashSet(无序存储),底层实现哈希表 + 红黑树(JDK1.8以后),JDK1.8以前只有哈希表。

public class SetTest {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        set.add("Hello");
        set.add("Java");
        set.add("SE");
        set.add("EE");
        set.add("Hello");//重复元素
        set.add(null);
        set.add(null);//重复元素
        System.out.println(set);
    }
}

结果:
在这里插入图片描述


2.TreeSet(如果没有自定义类则按照ASCII码,升序存储),不允许存放null,底层实现:红黑树

public class SetTest {
    public static void main(String[] args) {
        Set<String> set = new TreeSet<>();
        set.add("e");
        set.add("a");
        set.add("a");        
        set.add("c");
        set.add("A");
        System.out.println(set);
    }
}

在这里插入图片描述


2. TreeSet中自定义类如何保证元素升序与不重复?

首先看一下使用自定义类时,直接使用Java定义好的工具类什么都不做出现的结果:

class Person{
    private Integer age;
    private String name;

    public Person(Integer age, String name) {
        this.age = age;
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

public class SetTest {
    public static void main(String[] args) {
        Set<Person> set = new TreeSet<>();
        set.add(new Person(20,"张三"));
        set.add(new Person(25,"李四"));
        System.out.println(set);
    }
}

结果:自定义的类如果不实现Comparable接口,或者comparator接口则不能比较。
非自定义的类可以比较是因为已经自己实现了Compara接口。
在这里插入图片描述

3. 所以想让自定义类升序有两种方法:

1.java.lang.Comparable:内部排序接口
int compareTo(T o);
大于0:大于目标对象
等于0:等于目标对象
小于0:小于目标对象
类现了Comparable接口就表示此类具备可比较的性质,所以我们自定义的Person类想要能比较就需要实现Comparable接口。

class Person implements Comparable<Person>{
    private Integer age;
    private String name;

    public Person(Integer age, String name) {
        this.age = age;
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    //首先比较年龄,如果年龄相同,则比较名字(名字按ASCII码排序)
    public int compareTo(Person o) {
        if (this.age < o.age)
            return -1;
        if (this.age > o.age)
            return 1;
        return this.name.compareTo(o.name);
    }
}

public class SetTest {
    public static void main(String[] args) {
        Set<Person> set = new TreeSet<>();
        set.add(new Person(25,"王五"));
        set.add(new Person(25,"李四"));
        set.add(new Person(20,"张三"));
        System.out.println(set);
    }
}

结果:
2.java.util.Comparator:外部排序接口(推荐使用)策略模式,不用修改源代码
类本身不具备可比较的特性,专门有一个类来比较自定义类的大小,此类专门用于比较两个Person的大小,以后如果需要更换比较策略,只需再写一个新的类就可以,不需要再次更改原代码。
int cpmpare(T o1,T o2);

package fwb.collection.Set;

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

class Person2 {
    private Integer age;
    private String name;

    public Person2(Integer age, String name) {
        this.age = age;
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

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

    @Override
    public String toString() {
        return "Person2{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

/*
此类专门用于比较两个Person的大小,以后如果需要更换比较策略,
只需再写一个新的类就可以,不需要再次更改原代码
 */
class PersonAgeSec implements Comparator<Person2>{

    @Override
    public int compare(Person2 o1, Person2 o2) {
        if(o1.getAge() < o2.getAge()){
            return -1;
        }
        if (o1.getAge() > o2.getAge()){
            return 1;
        }
        return 0;
    }
}

//若以后需要更改比较策略,只需要重新写一个类即可
class PersonAgeDesc implements Comparator<Person2>{

    @Override
    public int compare(Person2 o1, Person2 o2) {
        if(o1.getAge() < o2.getAge()){
            return 1;
        }
        if (o1.getAge() > o2.getAge()){
            return -1;
        }
        return 0;
    }
}


public class SetTest2 {
    public static void main(String[] args) {
        PersonAgeSec personAgeSec = new PersonAgeSec();
        //TreeSet有一个构造方法可以接收Comparator的子类
        Set<Person2> set = new TreeSet<>(personAgeSec);
        set.add(new Person2(25,"王五"));
        set.add(new Person2(25,"李四"));
        set.add(new Person2(20,"张三"));
        System.out.println(set);
    }
}

结果:
在这里插入图片描述
总结:
自定义类要想使用TreeSet:

  1. 自定义类自己实现了Comparable
  2. 要么是从外部传入一个该类的比较器对象(实现了Comparator接口)
    通过compare的返回值来判断是否相等,如果为0则重复

4. HashSet如何保证元素不重复?

HashSet使用equals与hashCode共同来判断元素是否重复
equals:判断两个对象属性是否相同。
hashCode:对象在内存中的地址根据Hash算法转为int。
所以如果自定义类需要覆写equals与hashCode方法:

package fwb.collection.Set;

import java.util.*;

/**
 * @program: collection
 * @description: HashSet
 * @author: fwb
 * @create: 2019-07-15 15:06
 **/

class Person3 {
    private Integer age;
    private String name;

    public Person3(Integer age, String name) {
        this.age = age;
        this.name = name;
    }
	
	//覆写equals方法
    @Override
    public boolean equals(Object obj){
        //当前对象 == obj(自己和自己比)
        if (this == obj){
            return true;
        }
        //如果obj不是当前类对象
        if (!(obj instanceof Person3)){
            return false;
        }
        //传进来的时此类对象,并且和自己不是同一个地址
        Person3 per = (Person3) obj;
        return this.age.equals(per.age)&& this.name.equals(per.name);
    }

	//覆写hashCode()方法
    @Override
    public int hashCode() {
        return Objects.hash(name,age);
    }

    public Integer getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

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

    @Override
    public String toString() {
        return "Person3{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

public class SetTest3 {
    public static void main(String[] args) {
        Set<Person3> set = new HashSet<>();
        set.add(new Person3(25,"王五"));
        set.add(new Person3(25,"李四"));
        set.add(new Person3(20,"张三"));
        set.add(new Person3(20,"张三"));
        System.out.println(set);
    }
}

注:

  1. equlas相同,hashCode一定返回相同
  2. hashCode相同,equals不一定相同,因为hash算法不同。例:
//hash算法1:
//x = 10
hash(int i){
	return i + 1;
}
//hash算法2:
//x = 20
hash(int i){
	return i - 9;
}

4. 集合遍历与输出

Collection的输出:

1.使用Iterator迭代器输出(Collection的迭代器接口,专门用来输出Collection接口内容)

Iterator<E> iterator();  //取得该Collection接口对象的迭代器。
boolean hasNext();  //判断当前集合是否还有未遍历的元素。
E next();  //取得当前正在遍历的内容。

2.foreach:
Collection接口可以使用foreach输出的本质在于,所有的子类都实现了Iterator接口。(foreach输出其实就是一种语法糖(只存在于代码编译阶段),本质和第一种方式一样)

3.LIstIterator(双向迭代输出——只有List接口有)
扩展了两个方法:
boolean hasPrevious();
E previous();
要想使用从后向前输出,必须先从前向后走一遍。(要使用previous方法必须先试用next方法)

4.Enumeration(枚举输出——只有Vector及其子类Stack才有)
boolean hasMoreElements();
E nextElement();

代码:
1. 使用迭代器,for—each,双向输出

package fwb.collection.iterator_test;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class ListIteratorTest {
    public static void main(String[] args) {

        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("hello");
        list.add("fwb");

        System.out.println("1. 使用迭代器输出:");
        Iterator<String> iterator = list.iterator();//取得迭代器
        while(iterator.hasNext()){
            String str = iterator.next();
            System.out.println(str);
        }

        System.out.println("2. 使用foreach:");
        for (String str: list
             ) {
            System.out.println(str);
        }

        System.out.println("-------------------------");
        System.out.println("3. 双输出,只有list接口有");
        ListIterator<String> iterator2 = list.listIterator();
        System.out.println("正向");
        while(iterator2.hasNext()){
            String str = iterator2.next();
            System.out.println(str);
        }
        System.out.println("反向");
        while (iterator2.hasPrevious()){
            String str = iterator2.previous();
            System.out.println(str);
        }
    }
}

运行结果:
在这里插入图片描述
2.使用remove删除

public class ListIteratorTest2 {
    public static void main(String[] args) {

        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("hello");
        list.add("fwb");
       // list.add("1998");
        Iterator<String> iterator = list.iterator();//取得迭代器
        while(iterator.hasNext()){
            String str = iterator.next();
            if (str.equals("fwb")){
                //list.remove(str);
                iterator.remove();
            }
            System.out.println(str);
        }


        Iterator<String> iterator2 = list.iterator();
        System.out.println("----------");
        while(iterator2.hasNext()){
            String str = iterator2.next();
            System.out.println(str);
        }

    }
}

结果:使用remove()删除的时候,虽然会将删除的元素打印出来,但实际上是删除了的。
在这里插入图片描述

5. 栈和队列

栈是一种先进后出的数据结构。
Java集合中提供Stack类,他是 Vector的子类。

  1. 入栈 : public E push(E item)
  2. 出栈 : public synchronized E pop()
  3. 观察栈顶元素 : public synchronized E peek()
public class StackTest {
    public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        stack.push(1);
        stack.push(3);
        stack.push(5);
        System.out.println(stack.peek());
        System.out.println(stack.pop());
        System.out.println(stack.peek());
    }
}

结果:
在这里插入图片描述
Queue:(有一个子类是linkedlist)

  1. public E element() 普通 找到链表的表头。
  2. public boolean offer(E o) 普通 将指定元素增加到链表的表尾。
  3. public E peek() 普通 找到但并不删除链表的头。
  4. public E poll() 普通 找到并删除链表的头。
  5. public E remove() 普通 检索并移除表头
public class QueueTest {
    public static void main(String[] args) {
        Queue<Integer> queue = new LinkedList<>();
        queue.add(1);
        queue.add(2);
        queue.add(3);
        System.out.println(queue.peek());
        System.out.println(queue.poll());
        System.out.println(queue.peek());
    }
}

在这里插入图片描述

6. Map接口

Map集合中会一次性保存两个对象,且这两个对象的关系: key=value结构。这种结构最大的特点是可以通过key找到对应的value内容。
Map接口中有如下常用方法:
在这里插入图片描述
几个常用的子类有以下4个:
HashMap:HashSet就是一个value值相同的map结构,key和value都可以为空,但是key只能有一个相同的。
JDK1.2,线程不安全,底层实现是:哈希表 + 红黑树(JDK8)
HashMap问总结
TreeMap:key值不可以为空,value可以为空
JDK1.2,线程不安全,底层实现:红黑树
Hashtable:key与value均不为空
线程安全,JDK1.0,底层实现是:哈希表
ConcurrentHashMap
map常用方法实例:

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class HashMapTest {
    public static void main(String[] args) {
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"hello");
        map.put(2,"java");
        map.put(3,"hello");
        map.put(4,"fwb");
        map.put(4,"ffwwwbbbb");
        map.put(null,null);
        map.put(null,"哈哈");
        System.out.println(map.get(4));
        System.out.println(map.get(7));
        System.out.println(map);
        System.out.println(map.get(null));
        Set<Integer> keySet = map.keySet();//取得key信息,//key不能重复
        Iterator<Integer> iterator = keySet.iterator();
        while(iterator.hasNext()){
            Integer i = iterator.next();//key
            System.out.println(i + "=" + map.get(i));
        }
    }
}

结果:
如果同一个key有多个value,以最后一个为准。
如果没有put进去,那么get输出为空
key为空,value不为空,可以添加
在这里插入图片描述

7. Map集合的输出

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class HashMapTest2 {
    public static void main(String[] args) {
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"hello");
        map.put(2,"java");
        map.put(3,"hello");
        map.put(4,"fwb");
        map.put(4,"ffwwwbbbb");
        map.put(null,null);
        map.put(null,"哈哈");
        // map -> set
        // map标准输出
        Set<Map.Entry<Integer,String>> set = map.entrySet();
        Iterator<Map.Entry<Integer,String>> iterator = set.iterator();
        while (iterator.hasNext()){
            Map.Entry<Integer,String> entry = iterator.next();
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
    }
}

8. properties属性文件操作

保存的都是键值对 key = value,key与value均为String类型
key = value

  1. 设置属性 : public synchronized Object setProperty(String key, String value)
  2. 取得属性 : public String getProperty(String key),如果没有指定的key则返回null
  3. 取得属性 : public String getProperty(String key, String defaultValue),如果没有指定的key则返回默认值
  4. 将属性资源持久化到外部:store(OutputStream out, String comments)
  5. 从外部资源中读取配置 void load(InputStream inStream)

9. Collection工具类

Collections是一个集合操作的工具类,包含有集合反转、排序等操作。

import java.io.IOException;
import java.util.ArrayList; 
import java.util.Collections; import java.util.List;

public class TestDemo { .
	public static void main(String[] args) throws IOException { 				
	 	List<String> list = new ArrayList<>() ; 
	 	// 相当于调用了三次add()方法   
	 	Collections.addAll(list,"A","B","C") ;
	 	System.out.println(list) ; 
	 	// 集合反转 
	 	Collections.reverse(list) ;
	 	System.out.println(list) ; 
 	}
}

10. Stream数据流

最简单的一些操作:

public class StreamTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"Java","python","C++","JavaScript");
        //返回list集合中带Java关键字的关键字的个数
        System.out.println(list.stream().filter((e) -> 
        				e.contains("Java")).count());
        //返回list集合中带Java关键字的关键字的个数,并形成新的List集合
        List<String> resultList =  list.stream().
                filter((e -> e.contains("Java"))).
                collect(Collectors.toList());
        System.out.println(resultList);
    }
}

11.快速失败策略(fail - fast)

即为优先处理产生异常的情况,当异常情况发生时,直接向用户抛出异常,程序终止。
例:

public static int div(int a,int b){
        if (b == 0){
            throw new IllegalArgumentException("除数不能为0");
        }
        return a / b;
    }

当我们执行这种代码的时候会出现如下情况:

public class ListIteratorTest2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        //modcount = 1
        list.add("hello");
        //modcount = 2
        list.add("java");
        //modcount = 3
        list.add("hello");
        //modcount = 4
        list.add("fwb");
        // expectedModCount = modcount (4)
        Iterator<String> iterator = list.iterator();//取得迭代器
        while(iterator.hasNext()){
            String str = iterator.next();
            if (str.equals("fwb")){
                list.remove(str);
                //modcount++(5)
            }
            System.out.println(str);
        }
    }
}

在这里插入图片描述

java.util.ConcurrentModificationException,产生原因:当迭代输出集合时,并发修改了集合的结构。
源码中的 protected transient int modCount 记录了当前集合被修改的(add,remove等方法)次数。.
源码中当使用迭代器的时候,会执行这样一个代码int expectedModCount = modCount;
以及这个方法:

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

当我们执行: list.remove(str);这行代码的时候 modCount++,则modCount != expectedModCount,产生了异常,之所以前四个可以正常输出是因为在remove之后modcount才加1,所以在此之前都是正常的。(见代码)
意义:例如有3个线程在同时运行,2个线程在读取ArrayList的内容,1个线程修改了该集合的值,那么这两个线程读取的内容就有可能是不正确的。fail—fast机制保证集合在多线程场景下读到最新的值。 java.util 的集合类大多都采用此策略(如:ArrayList、Vector、LinkedList、HashSet,读写都在同一个副本中)
与其相关的有:
fail—safe:
java.util.concurrent:CopyOnWriteArrayList/conCurrentHashMap
CopyOnWriteArrayList采用读写分离,所有的读为异步操作,写为同步操作,且读写不在同一个副本。
如何处理:
1.迭代输出的时候,不要修改集合的内容
2.使用迭代器的修改方法(在源码中,他虽然最终调用的还是ArrayList的方法。但是在此之后将expectModCount重新赋值了一次)

if (str.equals("fwb")){
   //list.remove(str);
   iterator.remove();
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我顶得了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值