7.java 集合相关

java 集合相关

1 集合框架概述

1.1 出现的背景:

​ 一方面,面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象进行操作,就要对对象进行存储的容器。另外一方面,使用Array来存储对象方面具有一些弊端,,一旦创建了,数组的长度就不可以发生变化,而java集合就像一种容器,可以动态的对集合的大小进行扩充。java集合类可以用于存储数量不等的多个对象,还可以用于保存具有映射关系的关联数组。集合,数组是对多个数据进行存储操作的结构,简称Java容器。

​ 说明:此时的存储,主要是指内存层面的存储,不涉及持久化存储。

2 数组存储

  1. 数组内存存储方面的特点:
  • 数组初始化后,长度就确定了。
  • 数组声明的类型,就决定了进行元素初始化时的类型,即数组元素的类型就已经决定了。
  1. 数组在存储方面的弊端
  • 数组初始化之后,长度就不变了,不便于扩展。
  • 数组中提供的属性方法少,不便于添加,删除,插入等操作,且效率比较低,无法直接获取存储元素的个数(实际元素的个数)。
  • 存储特点单一,数组存储的数据是有序的,可以重复的。对于有些特殊的需求不能满足。

3 java集合

  1. Collection接口:单列数据,定义了存取一组对象的方法的集合
  • list:元素有序,可重复的集合。
  • set:元素无序,不可重复的集合。

image-20201022225041355

  1. Map接口:双列数据,保存具有映射关系的“key-value对”的集合,可以根据key来获取value,但是一个key不能对应多个value。

image-20201022225105085

2 Collection接口

2.1 Collection接口概述

2.1.1 常见方法
  1. 添加
  • add(Object obj):将元素添加到集合中,如果是一个集合则会将这个集合当成一个整体。
  • addAll(Collection coll):如果添加的是一个集合,将参数集合中的元素逐个添加到当前集合。
  1. 获取有效元素的个数
  • int size():获取有效元素的个数。
  1. 清空集合
  • void clear():将集合中的元素指向null,size设置为0
  1. 是否是空集合
  • boolean isEmpty():判断当前的size是否为0
  1. 是否包含某个元素
  • boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象,在判断的时候是逐个进行对比。
  • boolean containsAll(Collection c):也是调用元素的equals方法来比较的。拿两个集合的元素逐个比较,要求形参集合中的元素全部都在当前的集合中则返回true。
  1. 删除
  • boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
  • boolean removeAll(Collection coll):取当前集合的差集
  1. 取两个集合的交集
  • boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c集合。
  1. 是否相等
  • boolean equals(Object obj):需要判断元素是否一一对应相等。
  1. 转成对象数组
  • Object[] toArray()
  1. 获取集合对象的哈希值
  • hashCode()
  1. 遍历
  • iterator():返回迭代器对象,用于集合遍历。不是容器,只是一个迭代器。

    说明:Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。GOF给迭代器模式的定义为:提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部细节==>迭代器模式,就是为容器而生。 要求往Collection中添加的自定义的类要重写equals方法,如果不重写,则会调用Object的equals对比(比较对象的地址值)。

    //执行遍历操作
    while(iterator.hasNext()){
    	Object next = iterator.next();
    	System.out.println(next);
    }
    

    实现原理解析:

image-20201022225125884

2.1.2 遍历Collection集合数组的方式

1)使用迭代器:iterator

@Test
public void testIterator(){
    //Iterator():集合元素的遍历,返回值是一个迭代器接口
    Collection collection=new ArrayList();
    collection.add(123);
    collection.add(456);
    collection.add(new String("Tom"));
    collection.add(false);
    Iterator iterator = collection.iterator();
    //执行遍历操作
    while(iterator.hasNext()){
        Object next = iterator.next();
        if ("Tom".equals(next)){
            iterator.remove();
        }
        System.out.println(next);
    }
    //会进入死循环,无限输出第一个。每次调用iterator()就会返回一个新的iterator,默认游标在第一个之前。
   /* while(collection.iterator().hasNext()){
        System.out.println(collection.iterator().next());
    }*/
    //存在remove方法,可以用来移除集合中的元素。此方法不同于集合调用remove()。
    //如果在next之前调用remove或者调用next之后调用remove然后再次调用remove会出现错误:IllegalStateException
    Iterator iterator1 = collection.iterator();
    while (iterator1.hasNext()){
        System.out.println(iterator1.next());
    }
}

2)JDK5.0新增的foreach循环迭代访问Collection和数组

/**
 * foreach遍历集合
 */
@Test
public void testForeach(){
    Collection collection=new ArrayList();
    collection.add(123);
    collection.add(456);
    collection.add(new String("Tom"));
    collection.add(false);
    //for(集合中元素的类型 局部变量:集合对象)
    for (Object o:collection) {
        System.out.println(o);
    }
}
2.1.3 简单练习
/**
 * 练习题:增强for循环通常用来遍历,无法对数据进行修改。
 */
@Test
public void testForeach02(){
    String[] strings=new String[]{"MM","MM","MM"};
    for (int i = 0; i < strings.length; i++) {
        strings[i]="GG";
    }
    for (int i = 0; i < strings.length; i++) {
        //GG
        System.out.println(strings[i]);
    }
    for (String s:strings) {
        s="HH";
    }
    for (int i = 0; i <strings.length; i++) {
        //GG
        System.out.println(strings[i]);
    }
}

2.2 Collection–List接口

2.2.1 List接口概述
  • 鉴于Java中数组用来存储数据的局限性,我们通常使用List接口的实现类来替代数组
  • List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
  • 底层使用动态数组实现,List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素,此番方式比较快。
  • JDK API中List接口的实现类常用的有:ArrayListLinkedListVector
2.2.2 ArrayList

​ 作为List接口的主要实现类而出现。

  1. 特点
  • 线程不安全的,因此效率比较高。
  • 底层使用Object类型的数组进行封装==>Object[] elementData存储。
  • 对于查询和修改操作比较多的情况下效率比较高,值得是根据下标来进行查找。
  • 因为底层使用数组,因此在内存中的存储是连续的。

2.源码分析:JDK7和8之中有所不同–>1.8懒加载

JDK7版本:

//1.调用空参构造器创建对象。
public ArrayList() {
//2.调用重载的构造器
	this(10);
}

public ArrayList(int initialCapacity) {
	super();
	//3.判断初始化容量是否小于0
	if (initialCapacity < 0)
	throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
	//4.将Object[] elementData进行初始化,初始化容量为10
	this.elementData = new Object[initialCapacity];
}

//1.调用添加数据的方法(不够的情况下)

public boolean add(E e) {
	//2.确认容量是否够,数组容量和已添加的元素个数+1进行比较。
	ensureCapacityInternal(size + 1);  // Increments modCount!!
	//9.添加新的数据到数组中去
	elementData[size++] = e;
    return true;
}

//3.判断容量是否足够的方法
private void ensureCapacityInternal(int minCapacity) {
	modCount++;
	// overflow-conscious code
	//4.如果数组容量不够了
	if (minCapacity - elementData.length > 0)
	//5.开始调用扩容的方法
	grow(minCapacity);
}

//5.调用扩容的方法。
private void grow(int minCapacity) {
	// overflow-conscious code
	int oldCapacity = elementData.length;
	//6.新的容量的计算。例如旧的容量是10==>新的容量10+5==15,扩容为1.5倍
	int newCapacity = oldCapacity + (oldCapacity >> 1);
	//7.1如果新的容量依旧小于之前容量+1==>将之前容量+1赋给新的容量
	if (newCapacity - minCapacity < 0)
		newCapacity = minCapacity;
	//7.2如果新的容量大于MAX_ARRAY_SIZE
	if (newCapacity - MAX_ARRAY_SIZE > 0)
	//7.2.1将容量设置为int的最大值
	newCapacity = hugeCapacity(minCapacity);
	// minCapacity is usually close to size, so this is a win:
	//8.将数组进行复制到新的数组中去
	elementData = Arrays.copyOf(elementData, newCapacity);
}

//7.2.2容量赋值为整形最大值
private static int hugeCapacity(int minCapacity) {
	if (minCapacity < 0) // overflow
	throw new OutOfMemoryError();
	return (minCapacity > MAX_ARRAY_SIZE) ?
			Integer.MAX_VALUE :
			MAX_ARRAY_SIZE;
}

总结: > 如果添加数据导致底层数组的容量不够,则进行扩容,默认为1.5倍扩容。
	  > 开发者中建议使用带参数的构造器。

JDK8**版本**
//1.调用空参构造器
public ArrayList() {
	//2.调用默认属性,并没有直接创建数组对象,类似于单例模式中的懒汉式
	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//2.调用默认属性
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//1.调用**add**方法添加数据
public boolean add(E e) {
	//2.确认容量是否足够
	ensureCapacityInternal(size + 1);  // Increments modCount!!
	elementData[size++] = e;
	return true;
}

//2.**判断容量是否足够的方法*
private void ensureCapacityInternal(int minCapacity) {
	//3.**判断是不是第一次添加数据。*
	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		//4.1**如果是第一次添加数据。进行默认初始化。**DEFAULT_CAPACITY==10*
		minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
	}
	//4.2**不是第一次添加数据,判断容量*
	ensureExplicitCapacity(minCapacity);
}

//4.2**判断容量*
private void ensureExplicitCapacity(int minCapacity) {
	modCount++;  //初始为0
	// overflow-conscious code*
	//4.2.1**如果容量小了*
	if (minCapacity - elementData.length > 0)
		//4.2.2**增加容量*
		grow(minCapacity);
	}
	//**增加容量的方法:和**JDK7.0**一致
private void grow(int minCapacity) {
	// overflow-conscious code
	int oldCapacity = elementData.length;
	//**容量**1.5**倍进行增加。
	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);
}


  1. 结论:

​ JDK8中调用空参构造器的时候底层Object[] elementData初始化的时候并没有创建长度为10的数组。在第一次调用add的时候,创建长度为10的数组,将数据添加到其中操作和扩容操作和JDK7保持一致。1.5倍扩容

JDK8对比JDK7的优点:

  • 延迟了数组的创建时间,节省了内存。
  • JDK7中ArrayList数组的创建类似于饿汉式。
  • JDK8中ArrayList数组的创建类似于懒汉式。
  1. 方法
  • void add(int index,E element):指定的下标出添加元素。(index<0||index>size)
  • E get(int index):根据下标获取指定元素。

    E set(int index E element):修改指定索引处的值为参数值,返回原元素值

  • int indexOf(Object o):获取第一次出现参数值的下标

  • int lastIndexOf(Object o):获取最后一次出现指定参数值的下标。

  • E remove(int index):删除指定下标处的元素

@Test
public void test01() {
    ArrayList<Object> list = new ArrayList<Object>();
    list.add("123");
    list.add("456");
    list.add("789");
    //[123, 456, 789]
    System.out.println(list);
    list.add(1,"hello");
    //[123, hello, 456, 789]
    System.out.println(list);
    System.out.println(list.get(2));
    System.out.println(list.set(2, "world"));
    System.out.println(list.get(2));
    System.out.println(list.indexOf("hello"));
    System.out.println(list.lastIndexOf("hello"));
    System.out.println(list.remove(2));  //world
}
@Test
	public void test() {
		Scanner input=new Scanner(System.in);
		System.out.println("请输入20个字符");
		String str=input.next();
		char[] chars=str.toCharArray();
		ArrayList<Object> list=new ArrayList<>();
		for (int i = 0; i < chars.length; i++) {
			list.add(chars[i]);
		}
		System.out.println("输入的字符串序列为:"+list);
		ArrayList<Object> list2=new ArrayList<>();
		for (int i = 0; i < chars.length; i++) {
			if (list2.contains(chars[i])) {
				continue;	
			}
			list2.add(chars[i]);
		}
		System.out.println("去除重复后的字符集合为:"+list2);
		for (int i = 0; i < list2.size(); i++) {
			int nums=0;
			for (int j = 0; j < chars.length; j++) {
				if ((char)(list2.get(i))==chars[j]) {
					nums++;
				}
			}
			System.out.println(list2.get(i)+"出现了:"+nums+"次");
		}
	}
2.2.3 LinkedList
  1. 特点
  • 底层使用双向链表的方式进行存储。
  • 对于频发的插入,删除操作效率比ArrayList高。
  • 因为底层是双向链表,在内存中的存储是不连续。
  1. 源码分析:在JDK7和JDK8之中没有特别大的区别

    //存储数据的结构是“双向链表”,数据存储在一个个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;
    	}
    }
    
    //构造器
    public LinkedList() {
    
    }
    
    //1.执行添加操作
    public boolean add(E e) {
    	//2.调用linkLast方法来执行添加节点的操作。
    	linkLast(e);
    	return true;
    }
    
    //linkLast方法
    // Links e as last element.
    void linkLast(E e) {
    	final Node<E> l = last;
    	//放入数据,构建新的节点:前一个,当前的,后一个指向null
    	final Node<E> newNode = new Node<>(l, e, null);
    	//将新添的元素作为最后一个。
    	last = newNode;
    	if (l == null)
    		first = newNode;
    	else
    		l.next = newNode;
    	//节点个数++
    	size++;
    	modCount++;
    }
    
  2. 结论:

​ LinkList list=new LinkList(),内部声明了Node类型的first和last属性,头尾节点,便于我们直接对头尾节点进行操作默认值为null,执行添加操作list.add(123)==>实际上是将数据封装到了node节点中,如果节点是第一个元素,将该节点赋值给first节点。

  1. 常见面试题

1)Arraylist和linkedList之间的区别?

​ arraylist底层使用数组实现,linkedlist使用双向链表实现,因此如果对于查询和修改来说,如我们知道该元素的下标,arraylist可以根据下标直接定位到我们需要的元素,可以直接获取或修改其属性,在这种情况下,由于linkedList必须一个一个进行比较,直到获取到我们需要的元素,在这种情况下,arraylist的效率要高于LinkedList。如果直接直接获取或修改头尾节点,两者的效率没有什么差别。如果是删除或者新增的操作,linkedlist的效率要高于arraylist,因为数组的特性是一块连续的存储空间,因此linkedlist需要对删除或新增位置的以后的元素进行移位,比较消耗性能,而linkedlist只要连上修改pre和next引用地址即可。

​ 具体操作为:比如要在ab之间插入c,那么应该c的pre指向a,next指向b,a的next指向c,a的next.next.pre指向c,

2.2.4 Vector

​ 作为List的古老实现类出现,出现于jdk1.0。

  1. 特点
  • 线程安全的,效率比较低
  • 底层使用Object类型的数组进行封装==>Object[] elementData存储。
  1. 源码分析==JDK7和8中没有什么不同
//构造器
public Vector() {
	//1.数组初始化长度为10
	this(10);
}

//2.执行添加数据的操作
public synchronized boolean add(E e) {
	modCount++;
	//3.判断容量是否充足
	ensureCapacityHelper(elementCount + 1);
	elementData[elementCount++] = e;
	return true;
}

//判断容量的方法
private void ensureCapacityHelper(int minCapacity) {
	// overflow-conscious code
	if (minCapacity - elementData.length > 0)
	//扩容
	grow(minCapacity);
}

//扩容的方法
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);
}
  1. 结论:

​ JDK7和JDK8中Vector(向量类)没什么大的改变,默认数组初始化长度为10,首次扩容的时候,默认扩容为之前的两倍。

2.2.5 List接口中方法测试
  • void add(int index, Object ele): 在index 位置插入ele 元素
  • boolean addAll(int index, Collection eles): 从index 位置开始将eles中的所有元素添加进来
  • Object get(int index): 获取指定index 位置的元素
  • int indexOf(Object obj): 返回obj在集合中首次出现的索引位置
  • int lastIndexOf(Object obj): 返回obj在当前集合中末次出现的索引位置
  • Object remove(int index): 移除指定index位置的元素,并返回此元素
  • Object set(int index, Object ele): 设置指定index位置的元素改为ele
  • List subList(int fromIndex, int toIndex): 返回从fromIndex 到toIndex位置的子集合,左闭右开。

总结:常用方法

  • 增:add(Object obj)

  • 删:remove(Int index)/remove(Object o)

  • 改:set(Int index,Object ele)

  • 查:get(Int index)

  • 插:add(Int index,Object obj)

  • 长度:size()

  • 遍历: ①Iterator迭代器方式

    ​ ②增强for循环

    ​ ③普通的循环

@Test
public void testListMethods(){
    ArrayList<Object> list = new ArrayList<>();
    //add(Object o)
    list.add(123);
    list.add(456);
    list.add("AA");
    list.add(new Person("张三",21));
    list.add(456);
    //[123, 456, AA, Person{name='张三', age=21}, 456]
    System.out.println(list);
    //add(int index,Object o)
    list.add(1,"李四");
    //[123, 李四, 456, AA, Person{name='张三', age=21}, 456]
    System.out.println(list);
    list.addAll(2, Arrays.asList("王五","赵六","田七"));
    //[123, 李四, 王五, 赵六, 田七, 456, AA, Person{name='张三', age=21}, 456]
    System.out.println(list);
    //赵六
    System.out.println(list.get(3));
    //5
    System.out.println(list.indexOf(456));
    //8
    System.out.println(list.lastIndexOf(456));
    Object remove = list.remove(6);
    //AA
    System.out.println(remove);
    //[123, 李四, 王五, 赵六, 田七, 456, Person{name='张三', age=21}, 456]
    System.out.println(list);
    list.set(0,"张三");
    System.out.println(list);
    List<Object> sub = list.subList(0, 5);
    //[张三, 李四, 王五, 赵六, 田七]
    System.out.println(sub);
    //遍历:
    //方式一:
    Iterator<Object> iterator = list.iterator();
    while (iterator.hasNext()){
        System.out.print(iterator.next()+"\t");
    }
    System.out.println("**********");
    //方式二
    for (Object o:list) {
        System.out.print(o+"\t");
    }
    System.out.println("**********");
    //方式三
    for (int i = 0; i < list.size(); i++) {
        System.out.print(list.get(i)+"\t");
    }
}
//javaBean
class Person{
    String name;
    int age;

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

    public Person() {
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;

        Person person = (Person) o;

        if (age != person.age) return false;
        return name != null ? name.equals(person.name) : person.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}

2.3 Collection–Set接口

Set接口概述

  • Set接口是Collection的子接口,内部没有提供额外的方法。
  • Set接口存储数据是无序的,不可重复的数据,如果试图添加相同的元素到Set集合中去,则添加操作失败。
  • Set判断两个对象是否相等不是使用的==,而是使用的equals方法。
2.3.1 HashSet

​ 作为Set接口的主要实现类

  1. 特点:
  • 线程不安全的==>效率高。
  • 可以存储Null值。
  • 无序性不代表是随机性,不代表每次遍历集合都不相同。以HashSet为例,插入顺序和读出顺序不一定一致。
  • 插入值的方式:底层使用hashmap实现,即hash的表的结构,首先计算插入元素的hash值,然后hash值和当前哈希桶数量减一相与,获取其在数组中的位置,然后查看改为是否存在元素,如果没有,插入成功,如果存在元素,则逐个对比,如果hashcode值不一致则添加成功,如果一致则使用equals方法进行比较,如果不同,添加成功,如果相同进行替换。
  • 底层结构是:哈希表,即数组+链表
  1. 添加元素的过程:以HashSet为例

    添加过程:调用待添加数据的hashCode()方法,计算hash值,然后此hash值通过某种算法(如散列函数)进行计算存放的位置(索引位置),判断该位置是否存在元素:如果该位置没有元素则存放在此处;当发现存放的位置已经存在元素b了,则进行比较他们的hash值是否相同,不相同则添加成功,一旦相同就调用新元素的equals方法,如果返回值为true==>就认为两个元素是相同的元素,添加失败。不相同的时候则以链表的形式进行添加。某位置存在元素的情况下添加元素的规则:JDK7则将新的元素放到旧元素的位置,旧元素下移,JDK8则相反(7上8下)

注意:

  • Object类中的hashCode值是进行随机计算的==>自定义类进行添加的时候需要重写hashCode()和equals()方法,且这两个方法尽可能的保持一致性==>相同的对象必有相同的散列码。

  • 对象中用作equals()方法的field,都应该被用来计算hashCode()==>生产过程中使用ide工具直接自动生成。

  1. 扩容机制:

​ 底层也是数组进行存储,初始容量为16,当如果使用率超过0.75,(16*0.75=12)就会扩大容量为原来的2倍。(16扩容为32,依次为64,128…等)。

  1. 重写hashCode(),为什么会存在31这个数字?
@Override
public int hashCode() {
	int result = name != null ? name.hashCode() : 0;
	result = 31 * result + age;
	return result;
}

原因:

  • 选择系数的时候,尽量要选择大的系数,因为如果计算出来的hash值越大,所谓的“冲突”就越少,查找起来效率比较高(减少哈希碰撞)。
  • 并且31只占用5个bit,相乘造成数据溢出的概率比较小。
  • 31可以使用1*31==>(1<<5)-1来表示,很多虚拟机里面都有相关的优化(提高算法的效率)
  • 31是一个素数,素数的作用就是如果我用一个数字来乘以这个数,那么这个数最多只能被素数本身和被乘数还有1来整除(减少冲突)。

简单测试一下:

@Test
public void testSet(){
    Set set = new HashSet();
    set.add(123);
    set.add(456);
    set.add("张三");
    set.add("张三");
    set.add("李四");
    System.out.println(set);
    Iterator iterator = set.iterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }
    //重写了hashCode()
    System.out.println("张三".hashCode());
    System.out.println("张三".hashCode());
}

输出结果:
[李四, 张三, 456, 123]
李四
张三
456
123
774889
774889
2.3.2 LinkedHashSet

​ 是HashSet的子类

特点:

  • 依旧是无序的。
  • 在添加数据的同时每个数据还维护了两个引用,记录此数据的前一个和后一个数据,在遍历其内部数据的时候,可以按照添加的顺序进行遍历。
  • 对于频繁的增删操作,效率要高于HashSet。
@Test
public void testLinkedHashSet(){
    Set set = new LinkedHashSet();
    set.add(123);
    set.add(456);
    set.add("张三");
    set.add("张三");
    set.add("李四");
    System.out.println(set);
    Iterator iterator = set.iterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }
}

输出结果:
[123, 456, 张三, 李四]
123
456
张三
李四
2.3.3 TreeSet
  1. 特点:
  • 使用红黑二叉树,平衡二叉树,进行存储==>查询速快。
  • 像TreeSet中添加数据,必须存储同一个类new的对象。
  • 该对象可以排序。
  1. 添加数据:
  • 自然排序:比较两个对象是否相同的标准不再是equals()方法,而是CompareTo()返回0,一旦他们的。
  • 定制排序:
/**
 * TreeSet的测试
 */
@Test
public void testTreeSet(){
    TreeSet<Object> treeSet = new TreeSet<>();
    treeSet.add(123);
    treeSet.add(456);
    //添加失败:java.lang.ClassCastException:
    // java.lang.Integer cannot be cast to java.lang.String
    //treeSet.add("张三");
    treeSet.add(-2);
    treeSet.add(78);
    Iterator<Object> iterator = treeSet.iterator();
    while(iterator.hasNext()){
        //输出是排好顺序的:-2  78 123    456
        System.out.print(iterator.next()+"\t");
    }
    System.out.println("*************************");
    //自然排序:
    TreeSet<Object> treeSet1 = new TreeSet<>();
    treeSet1.add(new Person("Tom",21));
    treeSet1.add(new Person("BB",21));
    treeSet1.add(new Person("Tom",18));
    treeSet1.add(new Person("AA",21));
    treeSet1.add(new Person("Tam",24));
    Iterator<Object> iterator1 = treeSet1.iterator();
    while (iterator1.hasNext()){
        System.out.print(iterator1.next()+"\t");
    }
    System.out.println();
    System.out.println("-----------------");
    //存在定制排序:优先使用定制排序的规则。
    TreeSet<Object> treeSet2 = new TreeSet<>(new Comparator<Object>() {
        @Override
        public int compare(Object o1, Object o2) {
            if (!(o1 instanceof Person&&o2 instanceof Person)){
                try {
                    throw new Exception("传入的参数有误!!");
                } catch (Exception e) {
                    System.out.println(e.getMessage());
                }
            }
            Person person1=(Person) o1;
            Person person2=(Person) o2;
            //如果名字相同,则年龄从大到小进行排列
            if (person1.name.compareTo(person2.name)==0){
                return -(new Integer(person1.age).compareTo(person2.age));
            }
            return person1.name.compareTo(person2.name);
        }
    });
    treeSet2.add(new Person("Tom",21));
    treeSet2.add(new Person("BB",21));
    treeSet2.add(new Person("Tom",18));
    treeSet2.add(new Person("AA",21));
    treeSet2.add(new Person("Tam",24));
    Iterator<Object> iterator2 = treeSet2.iterator();
    while (iterator2.hasNext()){
        System.out.print(iterator2.next()+"\t");
    }
}


class Person implements Comparable {
        private String name;
        private int age;

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

        public Person() {
        }

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

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof Person)) {
                return false;
            }
            Person person = (Person) o;

            if (age != person.age){
                return false;
            }
            return name != null ? name.equals(person.name) : person.name == null;
        }
        @Override
        public int hashCode() {
            int result = name != null ? name.hashCode() : 0;
            result = 31 * result + age;
            return result;
        }

        /**
         * 名字从小到大,名字相同时,年龄从小到大。
         * @param o
         * @return
         */
        @Override
        public int compareTo(Object o) {
            if (!(o instanceof Person)){
                try {
                    throw new Exception("传入的参数有误!!");
                } catch (Exception e) {
                    System.out.println(e.getMessage());
                }
            }
            Person person=(Person) o;
            //如果两者的名字相同,则进行比较其年龄。
            if (this.name.compareTo(person.name)==0){
                return new Integer(this.age).compareTo(person.age);
            }
            return this.name.compareTo(person.name);
        }
    }
  1. 常见方法

E first():获得第一个元素

E last():获取最后一个元素

E floor(E e):获取小于参数的最大元素

E ceiling(E e):获取大于参数的最大元素

总结:

  1. Collection中存储的如果是自定义类的对象,需要自定义类从重写哪个方法?为什么?
  • List接口的实现类:equals()
  • Set接口: HashSet,LinkedHashSet:equals,hashCode()。
  • ​ TreeSet:Comparabe:compareTo(Object o);Comparator:compare(Object o1,Object o2)。
  1. ArrayList,LinkedList,Vector三者的相同点,不同点?
  • 相同点:都实现了List接口,存储都是有序的,可重复的。
  • 不同点:Vector是线程安全的,ArrayList,linkedList线程不安全。参考知识点。
  1. List接口中的常用方法有那些?

增:add(Object o)

删:remove(Object o),remove(Int index)==>ArrayList

改:set(Int index,Object o)

查:get(Int index)

插入:add(Int index,Object o )

长度:size(),

遍历:①Iterator

​ ②foreach

​ ③for

  1. Set存储数据的特点==>无序,不可重复性,常见实现类==>HashSet,TreeSet,LinkedHashSet

  2. 一个简单测试:考察对底层的理解

public static void main(String[] args) {
    HashSet set=new HashSet();
    Person p1 = new Person(1001, "AA");
    Person p2 = new Person(1002, "BB");
    set.add(p1);
    set.add(p2);
    System.out.println(set);
    p1.name="CC";
    //极大的概率没找到,删除失败
    System.out.println(set);
    set.remove(p1);
    //{p1.toString(),p2.toString()}
    System.out.println(set);
    //添加成功,对比了HashCode添加的位置发生了变化
    set.add(new Person(1001,"CC"));
    System.out.println(set);
    //添加成功,对于了Hashcode然后对比了equals()
    set.add(new Person(1001,"AA"));
    //{p1.toString(),p2.toString,CC.toString,AA.toString}
    System.out.println(set);
}

class Person{
    int id;
    String name;

    public Person() {
    }

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {return true;}
        if (!(o instanceof Person)) {return false;}

        Person person = (Person) o;

        if (id != person.id) {return false;}
        return name != null ? name.equals(person.name) : person.name == null;
    }

    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
  1. 定制排序的使用
public static void main(String[] args) {
    TreeSet treeSet = new TreeSet();
    Employee e1 = new Employee("liudehua", 55, new MyDate(1965, 5, 4));
    Employee e2 =new Employee("zhangxueyou",44,new MyDate(1978,5,4));
    Employee e3 =new Employee("guofucheng",44,new MyDate(1978,5,9));
    Employee e4 =new Employee("liming",51,new MyDate(1969,5,4));
    Employee e5 =new Employee("liangchaowei",21,new MyDate(1999,5,4));
    treeSet.add(e1);
    treeSet.add(e2);
    treeSet.add(e3);
    treeSet.add(e4);
    treeSet.add(e5);
    Iterator iterator = treeSet.iterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }
    System.out.println("***********************************************");
    TreeSet treeSet1 = new TreeSet(new Comparator() {
        //按照生日进行比较,可以将date这个排序方式放到MyDate类中作为自然排序。
        @Override
        public int compare(Object o1, Object o2) {
            if (o1 instanceof Employee&&o2 instanceof Employee){
                Employee e1=(Employee)o1;
                Employee e2=(Employee)o2;
                MyDate b1 = e1.getBirthday();
                MyDate b2 = e2.getBirthday();
                int minusYear=b1.getYear()-b2.getYear();
                if (minusYear!=0){
                    return minusYear;
                }
                int minusMonth=b1.getMonth()-b2.getMonth();
                if (minusMonth!=0){
                    return minusMonth;
                }
                return b1.getDay()-b2.getDay();
            }
            throw new RuntimeException("传入的参数有误!");
        }
    });
    treeSet1.add(e1);
    treeSet1.add(e2);
    treeSet1.add(e3);
    treeSet1.add(e4);
    treeSet1.add(e5);
    Iterator iterator1 = treeSet1.iterator();
    while (iterator1.hasNext()){
        System.out.println(iterator1.next());
    }
}

class Employee implements Comparable{
    private String name;
    private int age;
    private MyDate birthday;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public MyDate getBirthday() {
        return birthday;
    }

    public void setBirthday(MyDate birthday) {
        this.birthday = birthday;
    }

    public Employee(String name, int age, MyDate birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public Employee() {
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", birthday=" + birthday +
                '}';
    }
    //按照姓名进行排序,如果是汉字的话就就可能会出现问题,拿的是UTF-8来进行计算,不是使用abcd来计算。特殊需求的话找jar包,根据拼音来比较。
    @Override
    public int compareTo(Object o) {
        if (o instanceof Employee){
            Employee e=(Employee)o;
            return this.name.compareTo(e.name);
        }
        throw new RuntimeException("传入参数有误");
    }
}
class MyDate{
    private int year;
    private int month;
    private int day;

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public MyDate() {
    }

    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }
}

3 Map的测试

3.1 map概述–JDK1.2

​ Map是与Collection并列的存在,用于存储具有映射关系的双列数据,存储key-value键值对,可以实任何类型的引用变量,key-value存在单向一对一的关系,即一个key指向唯一的一个value。

3.2 HashMap

​ map的主要实现类

1 特点:

  • 线程不安全的,效率高。

  • 可以存储null的key和value,相key相同的时候,添加的时候是覆盖的操作。

2 Map的key-value的理解

​ 以HashMap的键值对为例:key使用Set来进行存储,即无序的,不可重复的数据==>自定义类需要重写equals()和hashCode()。value是可以重复的。

Collection存储所有的value==>要重写equals()方法,底层contains方法会调用。实际上我们put的时候,将数据封装到entry对象中(entry:无序,不可重复)。使用Set存储所有的entry。

3 底层结构

​ JDK7及之前:数组+链表

​ JDK8:数组+链表+红黑树

4 HashMap的底层实现原理:

① JDK7版本:

​ HashMap map=new HashMap();>在实例化之后,底层创建了长度为16的一维数组Entry[] table。map.put(key1,value1);>非第一次存放数据,首先调用key1所在类的hashCode()计算key1的哈希值,经过某种算法得到在Entry数组中的存放位置。如果此位置上的数据为空,则表明不存在数据,key1-value1添加成功,如果此位置上的数据不为空,意味着存在一个或者多个数据(链表的方式存在),比较当前的key1和当前位置上的key的hash值进行比较,如果都不相同,则可以存储成功,如果k1和某一个数据的hash值相同了,则使用equals方法进行比较,如果不同,则进行存储。如果相同则使用value1来进行替换原有的数据。

​ 扩容机制:默认的扩容方式是扩容为原来的两倍,然后将原有的数据复制过来。

② JDK8相较于JDK7在底层实现方面的不同。

  • new HashMap():底层没有立即创建一个长度为16的数组。

  • JDK8 底层的数组是Node[ ] ,而非Entry[ ]

  • 首次调用put()方法的时候,底层创建长度为16的数组

  • JDK7底层结构只有:数组+链表。JDK8中底层结构:数组+链表+红黑树。

    注:当数组的某一个索引位置上的元素以链表的形式存在的数据个数>8,且当前数组的长度>64,此时此索引位置上的所有数据改为红黑树进行存储。

5 底层源码==>JDK7和JDK8

//创建HashMap对象
HashMap map=new HashMap();

//1.调用空参构造器
public HashMap() {
	//2.调用有参构造器:
	//DEFAULT_INITIAL_CAPACITY<==>默认初始化长度
	//DEFAULT_LOAD_FACTOR<==>默认加载因子0.75
	this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

//有参构造器
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);
	// Find a power of 2 >= initialCapacity
	int capacity = 1;
	//创建初始化长度为2的多少次幂;例如15,则实际创建16
	while (capacity < initialCapacity)
		capacity <<= 1;
	//加载因子:默认值0.75
	this.loadFactor = loadFactor;
	//计算临界值=当前容量*加载因子,例如使用默认情况下。临界值=16*0.75=12,当超过12的时候,就进行扩容操作
	threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
	//初始化底层数组:Entry<k,v> table
	table = new Entry[capacity];
	useAltHashing = sun.misc.VM.isBooted() &&
		(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
			init();
	}

//put()操作:
public V put(K key, V value) {
	//可以存放null的key值
	if (key == null)
		return putForNullKey(value);
	//计算hash值
	int hash = hash(key);
	//计算存放的位置
	int i = indexFor(hash, table.length);
	for (Entry<K,V> e = table[i]; e != null; e = e.next) {
		Object k;
		if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
		V oldValue = e.value;
		e.value = value;
		e.recordAccess(this);
		return oldValue;
		}
	}
	modCount++;

	addEntry(hash, key, value, i);
	return null;
}

//计算存放位置的方法
static int indexFor(int h, int length) {
	return h & (length-1);
}

//底层存储的操作
void addEntry(int hash, K key, V value, int bucketIndex) {
	//当size>12且该位置不空==>想要生成链表则进行扩容操作。
	if ((size >= threshold) && (null != table[bucketIndex])) {
	//默认扩容为之前的2倍
	resize(2 * table.length);
	//计算hash值,如果是null则hash值为0,否则计算hash值
	hash = (null != key) ? hash(key) : 0;
	//扩容之后重新计算索引位置
	bucketIndex = indexFor(hash, table.length);
	}
	//不需要扩容则执行createEntry()
	createEntry(hash, key, value, bucketIndex);
}

void createEntry(int hash, K key, V value, int bucketIndex) {
	//直接将这个位置上的元素取出来。
	Entry<K,V> e = table[bucketIndex];
	//将新的元素放到该索引位置。将原来数组上的元素放到新的元素next中出现。
	//即新的元素next指向老的元素
	table[bucketIndex] = new Entry<>(hash, key, value, e);
	size++;
}


//底层源码分析==>JDK8版本

//构造器
public HashMap() {
	//区别于7,只有加载因子,并没有调用有参构造器
	this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

//put方法的调用
public V put(K key, V value) {
    //调用putVal的方法
	return putVal(hash(key), key, value, false, true);

}

//putVal方法。
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)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

//创建底层数组的方法
final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

6 底层结构涉及到的几个名词

  • DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16

  • MAXIMUM_CAPACITY : : HashMap的最大支持容量,2^30

  • DEFAULT_LOAD_FACTOR :HashMap的默认加载因子

  • TREEIFY_THRESHOLD :Bucket中链表长度大于该默认值,转化为红黑树

  • UNTREEIFY_THRESHOLD :Bucket中红黑树存储的Node小于该默认值,转化为链表

  • MIN_TREEIFY_CAPACITY :桶中的Node被树化时最小的hash表容量。(当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行(64)

  • resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。

  • table :存储元素的数组,总是2的n次幂

  • entrySet: :存储具体元素的集

  • size :HashMap中存储的键值对的数量

  • modCount :HashMap扩容和结构改变的次数。

  • threshold :扩容的临界值,=容量*填充因子

  • loadFactor: :填充因子

7 为什么默认的加载因子是0.75?

​ 因为满的概念定义的比较模糊,根据统计学得出的结论:加载因子过小则数组利用率比较低,过大的时候链表的长度比较大。最终选定了0.75。

8 常见方法

  • V put(k key,V value):往map中存放数据,如果key存在,返回值为被替换的value;否则为null

  • void clear():删除所有的元素(键值对)

  • boolean containKey(Object key):map中是否包含key

  • boolean containValue(Object value):map中是否包含value

  • V get(Object key):通过key来获取value

  • boolean isEmpty():判断是否为空

  • V remove (Object key):根据键删除键值对,返回值是被删除的value。

  • Set keySet():获取所有的键对应的set

  • Collection values():获取值集合

  • Set<Map.Entry<k,v> entrySet()>( ):键值对对应的Entry对象

3.3 LinkedHashMap

1 特点:

  • 在原有的HashMap的基础上加了两个指针,保证在遍历map元素的时候,可以按照添加的顺序进行遍历。

  • 对于频繁的遍历操作,执行效率要高于HashMap。

2 底层实现原理

static class Entry<K,V> extends HashMap.Node<K,V> {
	Entry<K,V> before, after;可以记录添加的元素的先后顺序。
	Entry(int hash, K key, V value, Node<K,V> next) {
	super(hash, key, value, next);
	}
}

3.4 TreeMap

1 特点:

  • 考虑key的自然排序和定制排序,底层是红黑二叉树的结构==>保证按照添加的key-value来进行排序,实现排序遍历。
  • 向TreeMap中添加key-value,要求key必须是由同一个类创建的对象==>按照key来进行排序。

2 排序的方式:自然排序,定制排序。

/**
 * 排序
 */
@Test
public void testTreeMap(){
    System.out.println("自然排序:::");
    TreeMap treeMap = new TreeMap();
    Person p1 = new Person("Tom", 23);
    Person p2 = new Person("Jerry", 27);
    Person p3 = new Person("Rose",22);
    Person p4 = new Person("Jack",20);
    Person p5 = new Person("James",18);
    Person p6 = new Person("Tom",25);
    treeMap.put(p1,"NBA");
    treeMap.put(p2,"CBA");
    treeMap.put(p3,"nab");
    treeMap.put(p4,"qwe");
    treeMap.put(p5,123);
    treeMap.put(p6,125);
    Set set = treeMap.entrySet();
    Iterator iterator = set.iterator();
    while (iterator.hasNext()){
        Object next = iterator.next();
        Map.Entry entry=(Map.Entry)next;
        System.out.println(entry.getKey()+"="+entry.getValue());
    }
    System.out.println("定制排序:::");
    TreeMap treeMap1 = new TreeMap(new Comparator() {
        //直接按照年龄从小到大进行排序
        @Override
        public int compare(Object o1, Object o2) {
            if (o1 instanceof Person && o2 instanceof Person) {
                Person p1 = (Person) o1;
                Person p2 = (Person) o2;
                return p1.age - p2.age;
            }
            throw new RuntimeException("传入的参数不匹配");
        }
    });
    treeMap1.put(p1,"NBA");
    treeMap1.put(p2,"CBA");
    treeMap1.put(p3,"nab");
    treeMap1.put(p4,"qwe");
    treeMap1.put(p5,123);
    treeMap1.put(p6,125);
    Set set1 = treeMap1.entrySet();
    Iterator iterator1 = set1.iterator();
    while (iterator1.hasNext()){
        Object next = iterator1.next();
        Map.Entry entry=(Map.Entry)next;
        System.out.println(entry.getKey()+"="+entry.getValue());
    }
}

class Person implements Comparable{
    String name;
    int age;

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

    public Person() {
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) {return true;}
        if (!(o instanceof Person)) {return false;}

        Person person = (Person) o;

        if (age != person.age) {return false;}
        return name != null ? name.equals(person.name) : person.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }

    /**
     * 自然排序:按照姓名从大到小,年龄从小到大
     * @param o
     * @return
     */
    @Override
    public int compareTo(Object o) {
        if (o instanceof Person){
            Person p=(Person)o;
            int i = -this.name.compareTo(p.name);
            if (i!=0){
                return i;
            }else {
                return this.age-p.age;
            }
        }
        throw  new RuntimeException("传入的参数有误!!!");
    }
}

3.5 Hashtable

​ map的古老实现类:出现于JDK1.0

1 特点:

  • 线程安全的,效率低。
  • 不可以存储null的key和value。

2 子类之properties:常用来处理配置文件。key和value都是String类型。

默认的识别目录为项目下:当前模块的根目录,非当前项目的根目录

@Test
public void testProperties() throws IOException {
    Properties properties = new Properties();
    //加载流对应的文件
    properties.load(new FileInputStream("JDBC.properties"));
    String name = properties.getProperty("name");
    String password = properties.getProperty("password");
    //james詹姆斯=1997
    System.out.println(name+"="+password);
}

3.6 Map中常用的方法

​ 以HashMap为例

  1. 添加 、 删除、修改操作 :
  • Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中

  • void putAll(Map m):将m中的所有key-value对存放到当前map中

  • Object remove(Object key):移除指定key的key-value对,并返回value

  • void clear():清空当前map中的所有数据

@Test
public void testMap(){
    HashMap hashMap = new HashMap();
    hashMap.put(null,"张三");  //{null=张三}
    hashMap.put(null,"李四");  //null="李四";
    System.out.println(hashMap);  //{null=李四}
    hashMap.put("AA",123);
    hashMap.put("BB",456);
    //{null=李四, AA=123, BB=456}
    System.out.println(hashMap);
    HashMap hashMap1 = new HashMap();
    hashMap1.put("CC",123);
    hashMap1.put("DD",456);
    hashMap.putAll(hashMap1);
    //{null=李四, AA=123, BB=456, CC=123, DD=456}
    System.out.println(hashMap);
    Object remove = hashMap.remove(null);
    System.out.println(remove);      //李四
    System.out.println(hashMap);  //{AA=123, BB=456, CC=123, DD=456}
    //不等同于map=null。清空的是数据
    hashMap.clear();
    System.out.println(hashMap.size()+"\t"+hashMap);    //0    {}
    Object aa = hashMap.get("AA");
    System.out.println(aa); //null
}
  1. 元素 查询的操作:
  • Object get(Object key):获取指定key对应的value
  • boolean containsKey(Object key):是否包含指定的key
  • boolean containsValue(Object value):是否包含指定的value
  • int size():返回map中key-value对的个数
  • boolean isEmpty():判断当前map是否为空
  • boolean equals(Object obj):判断当前map和参数对象obj是否相等
@Test
public void testMap2(){
    HashMap hashMap = new HashMap();
    hashMap.put("AA",123);
    hashMap.put(45,123);
    hashMap.put("BB",56);
    hashMap.put("45",345);
    //{AA=123, BB=56, 45=345, 45=123}
    System.out.println(hashMap);
    Object o = hashMap.get(45);
    //123
    System.out.println(o);
    boolean b = hashMap.containsKey(45);
    //true
    System.out.println(b);
    boolean b1 = hashMap.containsValue(123);
    //true
    System.out.println(b1);
    hashMap.clear();
    //true
    System.out.println(hashMap.isEmpty());
    //equals();判断两个map是否相同
    hashMap.put("AA",123);
    hashMap.put("bb",123);
    hashMap.put("cc",123);
    HashMap hashMap1 = new HashMap();
    hashMap1.putAll(hashMap);
    boolean equals = hashMap.equals(hashMap1);
    //true
    System.out.println(equals);
}
  1. 元视图操作的方法:
  • Set keySet():返回所有key构成的Set集合

  • Collection values():返回所有value构成的Collection集合

  • Set entrySet():返回所有key-value对构成的Set集合

/**
 * 元视图的操作
 */
@Test
public void testMap03(){
    HashMap hashMap = new HashMap();
    hashMap.put("AA",123);
    hashMap.put("bb",123);
    hashMap.put("cc",123);
    System.out.println("遍历map方式一:先获取key,然后根据key获取value。");

    Set set = hashMap.keySet();
    Iterator iterator = set.iterator();
    while (iterator.hasNext()){
        Object next = iterator.next();
        System.out.println(next+"="+hashMap.get(next));
    }
    System.out.println("遍历map方式二:entrySet()");
    Set set1 = hashMap.entrySet();
    Iterator iterator1 = set1.iterator();
    while (iterator1.hasNext()){
        Object next = iterator1.next();
        Map.Entry entry=(Map.Entry)next;
        System.out.println(entry.getKey()+"="+entry.getValue());
    }
}

3.7 练习

  1. 写一个方法键盘输入字符串返回一个map记录时每个字符出现的次数。
@Test
	public void test02() {
		//1.从键盘中获取字符串并转化为char数组
		@SuppressWarnings("resource")
		Scanner input=new Scanner(System.in);
		System.out.println("请输入一串字符串!!!");
		String str=input.next();
		char[] chars=str.toCharArray();
		//2.遍历数组,并存入到HashMap中去
		HashMap<Character,Integer> hashMap = new HashMap<>();
		for (int i = 0; i < chars.length; i++) {
			hashMap.put(chars[i],hashMap.get(chars[i])==null?new Integer(1):hashMap.get(chars[i])+1);
		}
		Iterator<Character> iterator = hashMap.keySet().iterator();
		while (iterator.hasNext()) {
			Character next = iterator.next();
			System.out.println(next+"出现了:"+hashMap.get(next)+"次");
		}
	}
  1. 创建一个学生类:name sex score sid,创建一个map 装10个学生:键是sid值是student对象,给所有女生分数+1,删除分数小于60的键值对,获取平均分。

    @Test
    	public void test03() {
    		HashMap<String, Student> hashMap=new HashMap<>();
    		hashMap.put("1", new Student("1","zhangsan","男",70));
    		hashMap.put("2", new Student("2","zhangsan","男",70));
    		hashMap.put("3", new Student("3","zhangsan","男",50));
    		hashMap.put("4", new Student("4","zhangsan","男",70));
    		hashMap.put("5", new Student("5","zhangsan","男",70));
    		hashMap.put("6", new Student("6","zhangsan","女",70));
    		hashMap.put("7", new Student("7","zhangsan","女",80));
    		hashMap.put("8", new Student("8","zhangsan","女",30));
    		hashMap.put("9", new Student("9","zhangsan","女",70));
    		hashMap.put("10", new Student("10","zhangsan","女",70));
    		System.out.println("初始hashMap:"+hashMap);
    		Set<String> keySet = hashMap.keySet();
    		Iterator<String> iterator = keySet.iterator();
    		ArrayList list=new ArrayList<String>();
    		int sum=0;
    		while (iterator.hasNext()) {
    			String sid=iterator.next();
    			Student student=hashMap.get(sid);
    			if ("女".equals(student.getSex())) {
    				student.setScore(student.getScore()+1);
    				hashMap.put(sid, student);
    			}
    			student=hashMap.get(sid);
    			if (student.getScore()<60) {
    				list.add(student.getSid());
    			}else {
    				sum+=student.getScore();
    			}
    		}
    		System.out.println("修改hashMap:"+hashMap);
    		for (int i = 0; i < list.size(); i++) {
    			hashMap.remove(list.get(i));
    		}
    		System.out.println("删除小于60之后:"+hashMap);
    		System.out.println("avg="+(sum*1.0/hashMap.size()));
    	}
    
    
    class Student{
    	private String sid;
    	private String name;
    	private String sex;
    	private int score;
    	public String getSid() {
    		return sid;
    	}
    	public void setSid(String sid) {
    		this.sid = sid;
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public String getSex() {
    		return sex;
    	}
    	public void setSex(String sex) {
    		this.sex = sex;
    	}
    	public int getScore() {
    		return score;
    	}
    	public void setScore(int score) {
    		this.score = score;
    	}
    	public Student(String sid, String name, String sex, int score) {
    		super();
    		this.sid = sid;
    		this.name = name;
    		this.sex = sex;
    		this.score = score;
    	}
    	public Student() {
    		super();
    	}
    	@Override
    	public String toString() {
    		return "Student [sid=" + sid + ", name=" + name + ", sex=" +
    				sex + ", score=" + score + "]";
    	}
    	
    }
    
    

4 Collections工具类

4.1 Collections概述

​ Collections是操作Collection和Map的工具类。其中提供了一些静态的方法对集合元素进行排序,查询和修改等操作,还提供了对集合对象设置不可变,对集合对象实现同步控制等方法。

4.2 常用的方法

1 排序操作:(均为static方法)

  • reverse(List):反转 List 中元素的顺序
  • shuffle(List):对 List 集合元素进行随机排序
  • sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
  • sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
  • swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
@Test
public void testCollections(){
    ArrayList arrayList = new ArrayList();
    arrayList.add(123);
    arrayList.add(234);
    arrayList.add(423);
    arrayList.add(18);
    //[123, 234, 423, 18]
    System.out.println(arrayList);
    //对集合进行反转操作
    Collections.reverse(arrayList);
    //输出:[18, 423, 234, 123]
    System.out.println(arrayList);
    //进行随机化处理
    Collections.shuffle(arrayList);
    System.out.println(arrayList);
    //对集合中的元素进行升序排序,还有个重载的方法进行定制排序
    Collections.sort(arrayList);
    //[18, 123, 234, 423]
    System.out.println(arrayList);
    //对集合中的两个位置元素进行换位
    Collections.swap(arrayList,1,3);
    //[18, 423, 234, 123]
    System.out.println(arrayList);
}

2 查找、替换

  • Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
  • Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
  • Object min(Collection):找出最小的元素
  • Object min(Collection,Comparator):根据比较器的规则找到最小的元素
  • int frequency(Collection,Object):返回指定集合中指定元素的出现次数
  • void copy(List dest,List src):将src中的内容复制到dest中
  • boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
@Test
public void testCollections02() {
    ArrayList arrayList = new ArrayList();
    arrayList.add(123);
    arrayList.add(234);
    arrayList.add(423);
    arrayList.add(423);
    arrayList.add(423);
    arrayList.add(18);
    //返回指定数的出现的次数
    int frequency = Collections.frequency(arrayList, 423);
    //3
    System.out.println(frequency);
   /* ArrayList list = new ArrayList();
    Collections.copy(list,arrayList);
    //java.lang.IndexOutOfBoundsException: Source does not fit in dest
    //源码
    //int srcSize = src.size();
    //if (srcSize > dest.size())
        //throw new IndexOutOfBoundsException("Source does not fit in dest");
    System.out.println(list);*/
    //正确的写法
    List<Object> objects = Arrays.asList(new Object[arrayList.size()]);
    Collections.copy(objects,arrayList);
    System.out.println(objects);
    objects.set(1,999);
    //java.lang.UnsupportedOperationException:Arrays.asList返回的list集合不可以进行添加操作==>长度不可变。
    System.out.println(objects);
    //replaceAll()
    Collections.replaceAll(objects,18,180);
    System.out.println(objects);
}

3 线程同步的相关操作

@Test
public void testCollections03() {
    ArrayList arrayList = new ArrayList();
    arrayList.add(123);
    arrayList.add(234);
    arrayList.add(423);
    arrayList.add(423);
    arrayList.add(423);
    arrayList.add(18);
    //返回的list1是线程安全的list
    List list = Collections.synchronizedList(arrayList);
}

总结:

每日一考:

1.Map存储数据的特点是什么?并指出key,value,entry存储数据的特点。

​ map:双列数据,存储Key-Value对

​ key:无序,不可重复–>Set存储

​ value:无序,可重复–>Collection存储

​ entry:无序,不可重复–>Set来存储

2.描述HashMap底层实现结构

​ JDK7:数组+链表

​ JDK8:数组+链表+红黑二叉树

3.Map的实现类有那些?各自的特点是什么。

|–HashMap:map的主要实现类,线程不安全的,效率比较高;可以存储key为null的键值对。

|–LinkedHashMap:对map进行遍历的时候可以按照添加的顺序进行遍历==>维持了一对指针。

|–TreeMap:保证按照key-value进行排序,实现排序遍历,key只能是同一类型的数据

|–Hashtable:map的古老实现类,线程安全的,效率低,不能存储null值。

|–properties:常用来处理配置文件,通常key和value是String类型。

4.如何遍历key-value键值对?

HashMap map=new HashMap();
Set set=map.keySet();
Iterator iterator=set.iterator();
while(iterator.hasNext()){
	Object o=iterator.next();    //此时o是map中的key值。
	System.out.printlb(o+"="+map.getValue(o))
}

5.Collection和Collections的区别?

Collection:是单列集合类接口,包含了一些基本操作,是Set和List接口的父接口。

Collections:是操作Collection和map的工具类,存在很多静态方法。

6.想要使用map,还想要保证线程安全,如何做?

​ 1)使用HashTable,线程安全的,但是由于内部的大部分方法都由synchronized所修饰,导致效率并不高。

​ 2)使用Collections的synchronized的方法来保证线程安全,其原理是给各个方法加上synchronized关键字,所以效率也不高。

​ 3)使用ConcurrentHashmap。在1.7及以前,底层采用了数组(分段数组)加链表,使用segment分段锁来保证线程安全,继承了Reentranlock,尝试获取锁的时候存在并发竞争,自旋,阻塞。在jdk1.8的时候,底层采用了数组加链表加红黑树的数据结构,锁采用cas+synchronized,cas失败的时候保证自旋成功,如果再次失败使用synchronized的方式保证成功,保证效率。

image-20201022225159024

CAS:compareAndSwap,比较并替换,相当于一个轻量级的锁,如果并发量比较大的时候,会出现自旋阻塞(存在忙循环),存在ABA的问题,可以使用一个戳或者版本号来进行区分。建议使用状态机或者锁。

image-20201022225239727

7.synchronized的原理

image-20201022225257789

1)偏向锁

image-20201022225311788

2)轻量级的锁(适用于低并发)

image-20201022225319694

3)自旋锁(死循环)此过程是可重入锁

4)重量级锁

image-20201022225332395

n(objects);
}


3 线程同步的相关操作

```java
@Test
public void testCollections03() {
    ArrayList arrayList = new ArrayList();
    arrayList.add(123);
    arrayList.add(234);
    arrayList.add(423);
    arrayList.add(423);
    arrayList.add(423);
    arrayList.add(18);
    //返回的list1是线程安全的list
    List list = Collections.synchronizedList(arrayList);
}

总结:

每日一考:

1.Map存储数据的特点是什么?并指出key,value,entry存储数据的特点。

​ map:双列数据,存储Key-Value对

​ key:无序,不可重复–>Set存储

​ value:无序,可重复–>Collection存储

​ entry:无序,不可重复–>Set来存储

2.描述HashMap底层实现结构

​ JDK7:数组+链表

​ JDK8:数组+链表+红黑二叉树

3.Map的实现类有那些?各自的特点是什么。

|–HashMap:map的主要实现类,线程不安全的,效率比较高;可以存储key为null的键值对。

|–LinkedHashMap:对map进行遍历的时候可以按照添加的顺序进行遍历==>维持了一对指针。

|–TreeMap:保证按照key-value进行排序,实现排序遍历,key只能是同一类型的数据

|–Hashtable:map的古老实现类,线程安全的,效率低,不能存储null值。

|–properties:常用来处理配置文件,通常key和value是String类型。

4.如何遍历key-value键值对?

HashMap map=new HashMap();
Set set=map.keySet();
Iterator iterator=set.iterator();
while(iterator.hasNext()){
	Object o=iterator.next();    //此时o是map中的key值。
	System.out.printlb(o+"="+map.getValue(o))
}

5.Collection和Collections的区别?

Collection:是单列集合类接口,包含了一些基本操作,是Set和List接口的父接口。

Collections:是操作Collection和map的工具类,存在很多静态方法。

6.想要使用map,还想要保证线程安全,如何做?

​ 1)使用HashTable,线程安全的,但是由于内部的大部分方法都由synchronized所修饰,导致效率并不高。

​ 2)使用Collections的synchronized的方法来保证线程安全,其原理是给各个方法加上synchronized关键字,所以效率也不高。

​ 3)使用ConcurrentHashmap。在1.7及以前,底层采用了数组(分段数组)加链表,使用segment分段锁来保证线程安全,继承了Reentranlock,尝试获取锁的时候存在并发竞争,自旋,阻塞。在jdk1.8的时候,底层采用了数组加链表加红黑树的数据结构,锁采用cas+synchronized,cas失败的时候保证自旋成功,如果再次失败使用synchronized的方式保证成功,保证效率。

image-20201022225159024

CAS:compareAndSwap,比较并替换,相当于一个轻量级的锁,如果并发量比较大的时候,会出现自旋阻塞(存在忙循环),存在ABA的问题,可以使用一个戳或者版本号来进行区分。建议使用状态机或者锁。

image-20201022225239727

7.synchronized的原理

image-20201022225257789

1)偏向锁

image-20201022225311788

2)轻量级的锁(适用于低并发)

image-20201022225319694

3)自旋锁(死循环)此过程是可重入锁

4)重量级锁

image-20201022225319694

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值