集合

集合概述


  • 概念:对象的容器,定义了对多个对象进行操作的常用方法。可实现数组的功能。
  • 和数组的区别
  1. 数组长度固定,集合长度不固定。
  2. 数组可以存储基本类型和引用类型,集合只能存储引用类型。如果使用集合存储基本类型,必须将基本类型装箱为对应的引用类型
  • 位置: java.util.*;

Collection体系集合


img

Collection父接口


  • 特点:代表一组任意类型的对象,无序、无下标、不能重复。

  • 方法

    • boolean add(Object obj) //添加一个对象。
    • boolean addAll(Collection c) //讲一个集合中的所有对象添加到此集合中。
    • void clear() //清空此集合中的所有对象。
    • boolean contains(Object o) //检查此集合中是否包含o对象。
    • boolean equals(Object o) //比较此集合是否与指定对象相等。
    • boolean isEmpty() //判断此集合是否为空。
    • boolean remove(Object o) //在此集合中移除o对象。
    • int size() //返回此集合中的元素个数。
    • Object[] toArray() //将此集合转换成数组。
    /**
     * Collection接口的使用(一)
     * 1.添加元素
     * 2.删除元素
     * 3.遍历元素
     * 4.判断
     */
         public static void main(String[] args){
            //创建集合
            Collection collection=new ArrayList();  //接口回调
    //      * 1.添加元素
            collection.add("苹果");
            collection.add("西瓜");
            collection.add("榴莲");
            System.out.println("元素个数:"+collection.size());
            System.out.println(collection);//[苹果, 西瓜, 榴莲]
    //      * 2.删除元素
            collection.remove("榴莲");
            System.out.println("删除之后:"+collection.size());
    //      * 3.遍历元素
            //3.1 使用增强for
            for(Object object : collection){   //   ArrayList 返回的是Object类型
                System.out.println(object);
            }
            //3.2 使用迭代器(迭代器专门用来遍历集合的一种方式)
            //hasnext();判断是否有下一个元素
            //next();获取下一个元素   读取的也是Object类型
            //remove();移除迭代器返回的最后一个元素
            //it.next();it.next();it.next();remove()会删除最后一个也就是第三个返回的元素
            Iterator it=collection.iterator();
            while(it.hasNext()){
                String s=(String)it.next();
                System.out.println(s);
                //删除操作
                //collection.remove(s); 使用此方法会引发并发修改异常
                //it.remove(); 使用迭代器的方法,移除迭代器返回的最后一个元素
            }
            // * 4.判断
            System.out.println(collection.contains("西瓜"));//true
            System.out.println(collection.isEmpty());//false
        }
    
    /**
     * Collection接口的使用(二)
     * 1.添加元素
     * 2.删除元素
     * 3.遍历元素
     * 4.判断
     */
    public class Demo2 {
    	public static void main(String[] args) {
    		Collection collection=new ArrayList();
    		Student s1=new Student("张三",18);
    		Student s2=new Student("李四", 20);
    		Student s3=new Student("王五", 19);
    		//1.添加数据
    		collection.add(s1);
    		collection.add(s2);
    		collection.add(s3);
    		//collection.add(s3);可重复添加相同对象,因为new ArrayList();
    		System.out.println("元素个数:"+collection.size());
    		System.out.println(collection.toString());// 此处toString()方法调用的是Student类重
    		//2.删除数据                               //写的方法
    		collection.remove(s1);
    		System.out.println("删除之后:"+collection.size());
    		//3.遍历数据
    		//3.1 增强for
    		for(Object object:collection) {
    			Student student=(Student) object;
    			System.out.println(student.toString());
    		}
    		//3.2迭代器
    		//迭代过程中不能使用collection的删除方法
            //next()返回的是Object类型
    		Iterator iterator=collection.iterator();
    		while (iterator.hasNext()) {
    		System.out.println(iterator.next().toString());//上转型对象
    		}
    		//4.判断和上一块代码类似。
    	}
    }
    
    /**
     * 学生类
     */
    public class Student {
    	private String name;
    	private int age;
    	public Student(String name, int age) {
    		super();
    		this.name = name;
    		this.age = age;
    	}
    	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;
    	}
    	@Override
    	public String toString() {
    		return "Student [name=" + name + ", age=" + age +"]";
    	}
    }
    

Collection子接口


List集合

  • 特点:有序(添加的顺序和遍历获取的顺序是一致的)、有下标、元素可以重复。

  • 方法

    • void add(int index,Object o) //在index位置插入对象o。
    • boolean addAll(index,Collection c) //将一个集合中的元素添加到此集合中的index位置。
    • Object get(int index) //返回集合中指定位置的元素。
    • List subList(int fromIndex,int toIndex) //返回fromIndex和toIndex之间的集合元素。
    /**
     * List子接口的使用(一)
     * 1.添加元素
     * 2.删除元素
     * 3.遍历元素
     * 4.判断
     * 5.获取位置
     */
    	 public static void main(String[] args) {
            List list=new ArrayList();
            //1.添加元素
            list.add("chen");
            list.add("shu");
            list.add(0,"bo");// 插入操作
            System.out.println("元素个数:"+list.size());
            System.out.println(list.toString());//ArrayList继承的AbstractCollection类的方法
            //2.删除元素
            list.remove(0); // list.remove("bo");结果相同
            System.out.println("删除之后:"+list.size());
            System.out.println(list.toString());
            //3.遍历元素
            //3.1  使用for遍历
            for(int i=0;i<list.size();i++) {
                System.out.println(list.get(i));
            }
            //3.2 使用增强for
            for(Object object:list) {
                System.out.println(object);
            }
            //3.3 使用迭代器
            Iterator iterator=list.iterator();
            while (iterator.hasNext()) {
                System.out.println(iterator.next());
            }
            //3.4使用列表迭代器,listIterator可以双向遍历,添加、删除及修改元素。
            ListIterator lt=list.listIterator();
            //从前往后
            while (lt.hasNext()) {
                System.out.println(lt.nextIndex()+":"+lt.next());
            }
            //从后往前(此时“遍历指针”已经指向末尾元素的后面)
            while(lt.hasPrevious()) {
                System.out.println(lt.previousIndex()+":"+lt.previous());
            }
            //4.判断
            System.out.println(list.isEmpty());
            System.out.println(list.contains("tang"));
            //5.获取位置
            System.out.println(list.indexOf("shu"));
        }
    
    /**
     * List子接口的使用(二)
     * 1.添加元素
     * 2.删除元素
     * 3.遍历元素
     * 4.判断
     * 5.获取位置
     */
    	 public static void main(String[] args) {
            List list=new ArrayList();
            //1.添加数字数据(隐含自动装箱Integer)
            list.add(20);
            list.add(30);
            list.add(40);
            list.add(50);
            System.out.println("元素个数:"+list.size());
            System.out.println(list.toString());
            //2.删除元素
            //list.remove(0);下标为0
            //list.remove(20);很明显数组越界错误,程序识别为移除下标为20的元素,
            //list.remove((Object)20); 正确
            list.remove(new Integer(20));  //正确
            System.out.println("元素个数:"+list.size());
            System.out.println(list.toString());
            //3-5不再演示,与之前类似
            //6.方法subList,返回子集合,[1,3);
            List list2=list.subList(1, 3);
            System.out.println(list2.toString());
        }
    

List实现类

ArrayList
  • 查找遍历速度快,增删慢;
  • 存储结构:数组
  • 运行效率快、线程不安全。
/**
 * ArrayList的使用
 * 1.添加元素
 * 2.删除元素
 * 3.遍历元素
 * 4.判断
 * 5.查找
 */
	 public static void main(String[] args) {
        ArrayList arrayList=new ArrayList();
        //1.添加元素
        Student s1=new Student("唐", 21);
        Student s2=new Student("何", 22);
        Student s3=new Student("余", 21);
        arrayList.add(s1);
        arrayList.add(s2);
        arrayList.add(s3);
        System.out.println("元素个数:"+arrayList.size());
        System.out.println(arrayList.toString());
        //2.删除元素
        arrayList.remove(s1);
        //arrayList.remove(new Student("唐", 21));
        //不可以这样删除,属性相同但是显然这是两个不同的对象,堆中地址不同。
        //3.遍历元素
        //3.1使用迭代器
        Iterator iterator=arrayList.iterator();
        while(iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        //3.2使用列表迭代器
        ListIterator listIterator=arrayList.listIterator();
        //从前往后遍历
        while(listIterator.hasNext()) {
            System.out.println(listIterator.next());
        }
        //从后往前遍历
        while(listIterator.hasPrevious()) {
            System.out.println(listIterator.previous());
        }
        //4.判断
        System.out.println(arrayList.isEmpty());
        //System.out.println(arrayList.contains(new Student("何", 22)));
        //属性相同但是显然这是两个不同的对象,堆中地址不同
        //5.查找
        System.out.println(arrayList.indexOf(s1));
    }
ArrayList源码分析
  • 默认容量大小:private static final int DEFAULT_CAPACITY = 10;

  • 存放元素的数组:transient Object[] elementData;

  • 实际元素个数:private int size;

  • 创建对象时调用的无参构造函数:

    // 空数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    

    这段源码说明当你没有向集合中添加任何元素时,集合容量为0。那么默认的10个容量怎么来的呢?

    这就得康康add方法的源码了:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    

    new一个ArrayList之后,当时容量为0,size也为0。这时调用add方法进入到ensureCapacityInternal(size + 1);该方法源码如下:

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    

    该方法中的参数minCapacity传入的值为size+1也就是 1,接着我们再进入到calculateCapacity(elementData, minCapacity)里面:

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    

    if条件成立,返回10。这个值作为参数又传入ensureExplicitCapacity()方法中,进入该方法查看源码:

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

    先不管modCount(修改次数)这个变量。

    因为elementData数组长度为0,所以if条件成立,调用grow方法,重要的部分来了,我们进入到grow方法的源码中:

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);// 右移一位相当于除2
        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);
    }
    
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    // Integer.MAX_VALUE  是int的最大值
    

    Arrays.copyOf()方法返回的数组是新的数组对象,原数组对象不会改变,该方法不会影响原来的数组。Arrays.copyOf()的第二个自变量指定要建立的新数组长度,如果新数组的长度超过原数组的长度,则保留数组默认值。


Vector
  • 查找遍历速度快、增删慢;

  • 存储结构:数组

  • 实现可增长的对象数组

  • 运行效率慢、线程安全。

    /**
     * Vector的演示使用
     *1.添加数据
     *2.删除数据             
     *3.遍历
     *4.判断
     */
     public static void main(String[] args) {
            Vector vec=new Vector();
            //1.添加数据
            vec.add("chen");
            vec.add("shu");
            vec.add("bo");
            System.out.println("元素个数:"+vec.size());
            //2.删除数据
            // vec.remove(0); 下标索引
            // vec.remove("chen"); Object类型
            //3.遍历
            //使用枚举器
            Enumeration enumeration=vec.elements();
            while (enumeration.hasMoreElements()) {
                String s = (String) enumeration.nextElement(); // 返回Object类型
                System.out.println(s);
            }
            //4.判断
            System.out.println(vec.isEmpty());
            System.out.println(vec.contains("bo"));
            //5. Vector其他方法
            vec.firstElement(); // 第一个元素
            vec.lastElement(); // 最后一个元素
            vec.elementAt(1); // 指定 1 下标的元素
        }
    

LinkedList
  • 增删快,查找遍历慢。
  • 存储结构:双向链表
/**
 * LinkedList的用法
 * 1.添加元素
 * 2.删除元素
 * 3.遍历
 * 4.判断
 */
	 public static void main(String[] args) {
        LinkedList linkedList=new LinkedList();
        Student s1=new Student("唐", 21);
        Student s2=new Student("何", 22);
        Student s3=new Student("余", 21);
        //1.添加元素
        linkedList.add(s1);
        linkedList.add(s2);
        linkedList.add(s3);
        linkedList.add(s3);
        System.out.println("元素个数:"+linkedList.size());
        System.out.println(linkedList.toString());
        //2.删除元素
        // linkedList.remove(s1); //  Object类型
        // linkedList.remove(1);  // 下标索引
        //3.遍历
        //3.1 使用for
        for(int i=0;i<linkedList.size();i++) {
            System.out.println(linkedList.get(i));
        }
        //3.2 使用增强for
        for(Object object:linkedList) {
            Student student=(Student) object;
            System.out.println(student.toString());
        }
        //3.3 使用迭代器
        Iterator iterator =linkedList.iterator();
        while (iterator.hasNext()) {
            Student student = (Student) iterator.next();
            System.out.println(student.toString());
        }
        //3.4 使用列表迭代器
        //4. 判断
        System.out.println(linkedList.contains(s1));
        System.out.println(linkedList.isEmpty());
        System.out.println(linkedList.indexOf(s3));
    }
LinkedList源码分析

LinkedList有三个属性:

  • 链表大小:transient int size = 0;
  • (指向)第一个结点/头结点: transient Node<E> first; // 初始为null
  • (指向)最后一个结点/尾结点:transient Node<E> last; // 初始为null

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;
    }
}

item存放的是当前元素;next指向下一个结点;prev指向上一个结点。

Node的有参构造方法的三个参数分别是前一个结点、存储的元素、后一个结点,调用这个构造方法构造出当前对象。

添加元素的add方法(尾插法):

public boolean add(E e) {
    linkLast(e);
    return true;
}

进入到linkLast方法:

void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

ArrayList和LinkedList区别
  • ArrayList:必须开辟连续空间,查询快,增删慢。
  • LinkedList:无需开辟连续空间,查询慢,增删快。

img


泛型概述

  • 泛型其本质是参数化类型,把类型作为参数传递。

  • 常见形式有泛型类、泛型接口、泛型方法。

  • 语法:

    • 类名 < T >,T是类型占位符,表示一种引用类型,编写多个时用逗号隔开
    • 泛型只能使用引用类型
    • 不同泛型类型的对象之间不能相互赋值
    • 声明泛型变量,不能使用new来创建,因为泛型是不确定的类型,也可能拥有私密的构造方法。
    • 不能创建泛型静态常量,因为不明确类型,无法初始化
  • 好处:

    • 提高代码的重用性。(比如代替方法重载)

    • 防止类型转换异常,提高代码的安全性:

    •     ArrayList arrayList=new ArrayList();
          arrayList.add("xxx");
          arrayList.add("yyy");
          arrayList.add(22);
          arrayList.add(33);
        
          for (Object object:arrayList) {
              String s= (String)object; // 异常 ---> Integer 不能转为 String
              System.out.println(s);
          }
      
泛型类
/**
 * 泛型类
 */
class myGeneric<T>{
        //1.创建泛型变量
        T t;
        //2.泛型作为方法的参数
        public void show(T t) {
            System.out.println(t);
        }
        //泛型作为方法的返回值
        public T getT() {
            return t;
        }
    }
    public class testFX {
        public static void main(String[] args) {
            //使用泛型类创建对象
            myGeneric<String> myGeneric1=new myGeneric<String>();
            myGeneric1.t="tang";
            myGeneric1.show("he");
            System.out.println(myGeneric1.getT());
            
            myGeneric<Integer> myGeneric2=new myGeneric<Integer>();
            myGeneric2.t=10;
            myGeneric2.show(20);
            System.out.println(myGeneric2.getT());
        }
    }
-----------执行结果-------
he
tang
20
10
泛型接口
/**
 * 泛型接口
 * 语法:接口名<T>
 */
public interface MyInterface<T> {
    //创建常量
	String nameString="tang";
    
	T server(T t);
}
/**
 * 实现接口时确定泛型类
 */
public class MyInterfaceImpl implements MyInterface<String>{
	@Override
	public String server(String t) {
		System.out.println(t);
		return t; 
	}
}

//测试类
MyInterfaceImpl myInterfaceImpl=new MyInterfaceImpl();
System.out.println(myInterfaceImpl.server("xxx"));
----------执行结果-------
xxx              泛型类中输出的
xxx              测试类中输出的,因为返回了 t 
 
/**
 * 实现接口时不确定泛型类
 */
public class MyInterfaceImpl2<T> implements MyInterface<T>{
	@Override
	public T server(T t) {
		System.out.println(t);
		return t;
	}
}
//测试类
MyInterfaceImpl2<Integer> myInterfaceImpl2=new MyInterfaceImpl2<Integer>();
myInterfaceImpl2.server(2000);
----执行结果----
2000
泛型方法
/**
 * 泛型方法
 * 语法:<T> 返回类型
 */
public class MyGenericMethod {
	public <T> void show(T t) {  // 可以是静态的,也可以不是
		System.out.println("泛型方法"+t);
	}
}
//测试类
MyGenericMethod myGenericMethod=new MyGenericMethod();
myGenericMethod.show("tang");  //泛型方法tang
myGenericMethod.show(200);      //泛型方法200
myGenericMethod.show(3.14);    //泛型方法3.14
泛型集合
  • 概念:参数化类型、类型安全的集合,强制集合元素的类型必须一致。

  • 特点:

    • 编译时即可检查,而非运行时抛出异常。
    • 访问时,不必类型转换。
    • 不同泛型之间引用不能相互赋值,泛型不存在多态

之前我们在创建LinkedList类型对象的时候并没有使用泛型,但是进到它的源码中会发现:

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{略}

它是一个泛型类,而之前使用的时候并没有传递,说明java语法是允许的,这个时候传递的类型是Object类,虽然它是所有类的父类,可以存储任意的类型,但是在遍历、获取元素时需要原来的类型就要进行强制转换。这个时候就会出现一些问题,假如往链表里存储了许多不同类型的数据,在强转的时候就要判断每一个原来的类型,这样就很容易出现错误。

public static void main(String[] args) {

        ArrayList<String> arrayList=new ArrayList<String>();
        arrayList.add("xxx");
        arrayList.add("yyy");
        //arrayList.add(22); integer类型不能加入

        for (String string:arrayList) {  // 少一步强制类型转换  Object-> String
            System.out.println(string);
        }

        ArrayList<Student>  arrayList1=new ArrayList<Student>();
        Student s1=new Student("张三",18);
        Student s2=new Student("李四", 20);
        Student s3=new Student("王五", 19);

        arrayList1.add(s1);
        arrayList1.add(s2);
        arrayList1.add(s3);

        Iterator<Student> it=arrayList1.iterator();
        while (it.hasNext()){    
            Student s=it.next();   // 少一步强制类型转换  Object-> Student
            System.out.println(s.toString());
        }

    }

Set集合概述

Set子接口
  • 特点:无序、无下标、元素不可重复。
  • 方法:全部继承自Collection中的方法。
/**
 * 测试Set接口的使用
 * 1.添加数据
 * 2.删除数据
 * 3.遍历【重点】
 * 4.判断
 */
	public static void main(String[] args) {
		Set<String> set=new HashSet<String>();
		//1.添加数据
		set.add("tang");
		set.add("he");
		set.add("yu");
		System.out.println("数据个数:"+set.size());
		System.out.println(set.toString());// 无序输出
		//2.删除数据
		/*
		 * set.remove("tang"); System.out.println(set.toString());
		 */
		//3.遍历【重点】
		//3.1 使用增强for
		for (String string : set) {
			System.out.println(string);
		}
		//3.2 使用迭代器
		Iterator<String> iterator=set.iterator();
		while (iterator.hasNext()) {
			System.out.println(iterator.next());
		}
		//4.判断
		System.out.println(set.contains("tang"));
		System.out.println(set.isEmpty());
	}

Set实现类

HashSet【重点】
  • 基于HashCode实现元素不重复。
  • 当存入元素的哈希码相同时,会调用equals进行确认,如结果为true,则拒绝后者存入。
  • 存储结构:哈希表(数组+链表+(红黑树))
/**
 * 人类
 */
public class Person {
	private String name;
	private int age;
	public Person(String name,int age) {
		this.name = name;
		this.age = age;
	}
	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;
	}
	@Override
	public String toString() {
		return "Peerson [name=" + name + ", age=" + age + "]";
	}
}
/**
 * HashSet集合的使用
 * 1.添加元素
 * 2.删除元素
 * 3.遍历
 * 4.判断
*/
	public static void main(String[] args) {
		HashSet<Person> hashSet=new HashSet<Person>();
		Person p1=new Person("tang",21);
		Person p2=new Person("he", 22);
		Person p3=new Person("yu", 21);
		//1.添加元素
		hashSet.add(p1);
		hashSet.add(p2);
		hashSet.add(p3);
        hashSet.add(p3); //重复,添加失败
        //直接new一个相同属性的对象,依然会被添加进去,因为不是同一个对象。
        // 重写之后就不能添加进去
        hashSet.add(new Person("yu", 21));
		System.out.println(hashSet.toString());
		//2.删除元素
		hashSet.remove(p2); 
        hashSet.remove(new Person("yu", 21)); // 重写之后就能删除
		//3.遍历
		//3.1 增强for
		for (Person person : hashSet) {
			System.out.println(person);
		}
		//3.2 迭代器
		Iterator<Person> iterator=hashSet.iterator();
		while (iterator.hasNext()) {
			System.out.println(iterator.next());		
		}
		//4.判断
		System.out.println(hashSet.isEmpty());
		System.out.println(hashSet.contains(new Person("tang", 21)));
		 //直接new一个相同属性的对象结果输出是false,因为不是同一个对象。
        //重写之后就能输出true
	}

hashSet存储过程:

  1. 根据哈希码(Object中的native hashcode()方法)计算保存的位置,如果位置为空,则直接保存,否则执行第二步。

  2. 执行equals方法(继承自Object,比较的是地址),如果方法返回true,认为是重复则拒绝存储;如果方法返回false,顺利存储形成链表。


  • 对象调用hashCode()方法获得自身哈希码,不同对象可能有相同哈希码也可能不同哈希码;相同对象一定有相同哈希码;相同哈希码不一定相同对象;不同哈希码一定不相同对象
  • 哈希码相同,位置一定相同;哈希码不同,位置有可能相同有可能不同;
  • 要实现“相同属性便认为是同一个对象”,同时重写hashCode和equals方法:
// 快捷键:右键Generate(alt+insert)--> equals() and hashCode()
// 在Person类中重写这两个方法
@Override
    public int hashCode() {
        int n1 = this.age;
        int n2 = this.name.hashCode();
        return n1+n2;
    }
@Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (obj instanceof Person){
            Person p = (Person) obj;
            if((p.name==this.name)&&(p.age==this.age)){
                return true;
            }
        }
        return false;
    }

快捷键的hashCode方法里为什么要使用31这个数字大概有两个原因:

  1. 31是一个质数,这样的数字在计算时可以尽量减少散列冲突。
  2. 可以提高执行效率,因为31*i=(i<<5)-i,31乘以一个数可以转换成移位操作,这样能快一点

TreeSet
  • 基于排序顺序实现元素不重复。
  • 实现了SortedSet接口,对集合元素自动排序。
  • 元素对象的类型必须实现Comparable接口,指定排序规则。
  • 通过CompareTo方法确定是否为重复元素,compareTo方法返回0,认为是重复元素
  • 存储结构:红黑树(一种二叉查找树、弱平衡二叉树)
/**
 * 使用TreeSet保存数据
 */
	public static void main(String[] args) {
        TreeSet<Person> persons=new TreeSet<Person>();
        Person p1=new Person("tang",21);
        Person p2=new Person("he", 22);
        Person p3=new Person("yu", 21);
        //1.添加元素
        persons.add(p1);
        persons.add(p2);
        persons.add(p3);
        //注:直接添加会报类型转换错误,需要Student类实现并重写
        //  Comparable泛型接口中的compareTo()方法,可选择是否指定泛型类型
        System.out.println(persons.toString());
        System.out.println(persons.size());
        //2.删除元素
        //persons.remove(p1); // 能删除
        persons.remove(new Person("he", 22));//能删除 因为compareTo比较姓名和年龄
        System.out.println(persons.toString());
        System.out.println(persons.size());
        //3.遍历(略)
        //4.判断
        System.out.println(persons.contains(new Person("yu", 21)));// true 因为compareTo()
    }

查看Comparable接口的源码,发现只有一个compareTo抽象方法,在Person类中实现它:

public class Person implements Comparable<Person>{
    @Override
	//1.先按姓名比
	//2.再按年龄比
	public int compareTo(Person o) {
		int n1=this.getName().compareTo(o.getName());
		int n2=this.age-o.getAge();
		return n1==0?n2:n1;        
	}
}

除了实现Comparable接口里的比较方法,TreeSet也提供了一个带比较器Comparator的构造方法,使用匿名内部类来实现它:

/**
 * TreeSet的使用
 * Comparator:实现定制比较(比较器)
 */
	public static void main(String[] args) {
	     // 创建集合时就定制了比较规则
		TreeSet<Person> persons=new TreeSet<Person>(new Comparator<Person>() {
			@Override
			public int compare(Person o1, Person o2) {
				// 先按年龄比较
				// 再按姓名比较
				int n1=o1.getAge()-o2.getAge();
				int n2=o1.getName().compareTo(o2.getName());
				return n1==0?n2:n1;
			}			
		});
		Person p1=new Person("tang",21);
		Person p2=new Person("he", 22);
		Person p3=new Person("yu", 21);
		persons.add(p1);
		persons.add(p2);
		persons.add(p3);
		System.out.println(persons.toString());
	}

接下来我们来做一个小案例:

/**
 * 要求:使用TreeSet集合实现字符串按照长度进行排序
 * helloworld tangrui hechengyang wangzixu yuguoming
 * Comparator接口实现定制比较
 */
	public static void main(String[] args) {
		TreeSet<String> treeSet=new TreeSet<String>(new Comparator<String>() {
			@Override
			//先比较字符串长度
			//再比较字符串
			public int compare(String o1, String o2) {
				int n1=o1.length()-o2.length();
				int n2=o1.compareTo(o2);
				return n1==0?n2:n1;
			}			
		});
		treeSet.add("helloworld");
		treeSet.add("tangrui");
		treeSet.add("hechenyang");
		treeSet.add("yuguoming");
		treeSet.add("wangzixu");
		System.out.println(treeSet.toString());
        //输出[tangrui, wangzixu, yuguoming, hechenyang, helloworld]
	}

Map集合

  • Map接口的特点:

    1. 用于存储任意键值对(Key-Value)。
    2. 键:无序、无下标、不允许重复(唯一)。
    3. 值:无序、无下标、允许重复。


Map父接口

  • 特点:存储一对数据(Key-Value),无序、无下标,键不可重复,值可重复。

  • 方法

    • V put(K key,V value); //将对象存入到集合中,关联键值。key重复则覆盖原值
    • Object get(Object key); //根据键获取相应的值。
    • Set<K> keySet(); //返回此映射中所有key的 Set 集合
    • Collection<V> values(); //返回包含所有值的Collection集合。
    • Set<Map.Entry<K,V>> entrySet(); // 返回此映射中包含的映射关系的 Set 集合
    /**
     * Map接口的使用
     */
    	public static void main(String[] args) {
    		Map<String,Integer> map=new HashMap<String, Integer>();
    		//1.添加元素
    		map.put("tang", 21);
    		map.put("he", 22);
    		map.put("fan", 23);
    		System.out.println(map.toString());
    		//2.删除元素
    		map.remove("he");  //  remove(Object key);
    		System.out.println(map.toString());
    		//3.遍历
    		//3.1 使用keySet();
    		for (String key : map.keySet()) {
    			System.out.println(key+" "+map.get(key));
    		}
    		//3.2 使用entrySet();     效率较高   把键值对包装成一个 Entry
    		for (Map.Entry<String, Integer> entry : map.entrySet()) {
    			System.out.println(entry.getKey()+" "+entry.getValue());
    		}
    	}
    

Map集合的实现类

HashMap【重点】
  • 线程不安全(单线程使用),运行效率快;允许用null作为key或是value。

  • 存储结构:哈希表(数组+链表+(红黑树))

    /**
       * 学生类
       */
      public class Student {
      	private String name;
      	private int id;	
      	public Student(String name, int id) {
      		super();
      		this.name = name;
      		this.id = id;
      	}
      	public String getName() {
      		return name;
      	}
      	public void setName(String name) {
      		this.name = name;
      	}
      	public int getId() {
      		return id;
      	}
      	public void setId(int id) {
      		this.id = id;
      	}
      	@Override
      	public String toString() {
      		return "Student [name=" + name + ", age=" + id + "]";
      	}
      }
    
    /**
       * HashMap的使用
       */
      	public static void main(String[] args) {
      		HashMap<Student, String> hashMap=new HashMap<Student, String>();
      		Student s1=new Student("tang", 36);
      		Student s2=new Student("yu", 101);
      		Student s3=new Student("he", 10);
      		//1.添加元素
      		hashMap.put(s1, "成都");
      		hashMap.put(s2, "杭州");
      		hashMap.put(s3, "郑州");
      		// hashMap.put(s3,"上海"); //添加失败,但会更新值
      		hashMap.put(new Student("he", 10),"上海");//添加成功,不过两个属性一模一样
      		System.out.println(hashMap.toString());
      		//2.删除元素
      		hashMap.remove(s3);
      		System.out.println(hashMap.toString());
      		//3.遍历
      		//3.1 使用keySet()遍历
      		for (Student key : hashMap.keySet()) {
      			System.out.println(key+" "+hashMap.get(key));
      		}
      		//3.2 使用entrySet()遍历
      		for (Map.Entry<Student, String> entry : hashMap.entrySet()) {
                System.out.println(entry.getKey()+" "+entry.getValue());
            }
      		//4.判断
      		System.out.println(hashMap.containsKey(new Student("he", 10)));//重写之前false  重写之后true
      		System.out.println(hashMap.containsValue("成都")); //true
      	}
    

    注:和之前说过的HashSet类似,重复依据是hashCode和equals方法,重写即可:

    // 在Student类中重写这两个方法
    @Override
      public int hashCode() {
          final int prime = 31;
          int result = 1;
          result = prime * result + id;
          result = prime * result + ((name == null) ? 0 : name.hashCode());
          return result;
      }
      @Override
      public boolean equals(Object obj) {
          if (this == obj)
              return true;
          if (obj == null)
              return false;
          if (getClass() != obj.getClass())
              return false;
          Student other = (Student) obj;
          if (id != other.id)
              return false;
          if (name == null) {
              if (other.name != null)
                  return false;
          } else if (!name.equals(other.name))
              return false;
          return true;
      }
    
HashMap源码分析
  • 默认初始化容量:static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

  • 最大容量:static final int MAXIMUM_CAPACITY = 1 << 30;

  • 默认加载因子:static final float DEFAULT_LOAD_FACTOR = 0.75f;

  • 链表调整为红黑树的链表长度最小阈值(JDK1.8):static final int TREEIFY_THRESHOLD = 8;

  • 红黑树调整为链表的链表长度最大阈值(JDK1.8):static final int UNTREEIFY_THRESHOLD = 6;

  • 链表调整为红黑树的哈希数组最小阈值(JDK1.8):static final int MIN_TREEIFY_CAPACITY = 64;

  • HashMap存储的数组:transient Node<K,V>[] table;

  • HashMap存储的元素个数:transient int size;

    • 默认加载因子是什么?
      • 就是判断数组是否扩容的一个因子。假如数组容量为100,如果HashMap的存储元素个数超过了100*0.75=75,那么就会进行扩容。
    • 链表调整为红黑树的链表长度阈值是什么?
      • 假设在数组中下标为3的位置已经存储了数据,当新增数据时通过哈希码得到的存储位置又是3,那么就会在该位置形成一个链表,当链表过长时就会转换成红黑树以提高执行效率,这个阈值就是链表转换成红黑树的最小链表长度;
    • 红黑树调整为链表的链表长度阈值是什么?
      • 当红黑树的元素个数小于该阈值时就会转换成链表。
    • 链表调整为红黑树的数组最小阈值是什么?
      • 并不是只要链表长度大于8就可以转换成红黑树,在前者条件成立的情况下,数组的容量必须大于等于64才会进行转换。

    HashMap的数组table存储的就是一个个的Node<K,V>类型,很清晰地看到有一对键值,还有一个指向next的指针(以下只截取了部分源码):

    static class Node<K,V> implements Map.Entry<K,V> {
          final K key;
          V value;
          Node<K,V> next;
      }
    

    之前的代码中在new对象时调用的是HashMap的无参构造方法,进入到该构造方法的源码查看一下:

    public HashMap() {
          this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
      }
    

    发现没什么内容,只是赋值了一个默认加载因子;而在上文我们观察到源码中table和size都没有赋予初始值,说明刚创建的HashMap对象没有分配容量,并不拥有默认的16个空间大小,这样做的目的是为了节约空间,此时table为null,size为0。

    当我们往对象里添加元素时调用put方法:

    public V put(K key, V value) {
          return putVal(hash(key), key, value, false, true);
      }
    

    put方法把key和value传给了putVal,同时还传入了一个hash(Key)所返回的值,这是一个产生哈希值的方法,再进入到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)  // 把table赋值给tab
              n = (tab = resize()).length;
          if ((p = tab[i = (n - 1) & hash]) == null)  // 找位置,判断是否为空,为空直接加入
              tab[i] = newNode(hash, key, value, null);
          else{
              //略
          }
      }
    

    这里面创建了一个tab数组和一个Node变量p,第一个if实际是判断table是否为空,而我们现在只关注刚创建HashMap对象时的状态,此时tab和table都为空,满足条件,执行内部代码,这条代码其实就是把resize()所返回的结果赋给tab,n就是tab的长度,resize顾名思义就是重新调整大小。查看resize()源码(部分):

    final Node<K,V>[] resize() {
          Node<K,V>[] oldTab = table;
          int oldCap = (oldTab == null) ? 0 : oldTab.length;
          int oldThr = threshold;
          if (oldCap > 0);
          else if (oldThr > 0);
          else {               // zero initial threshold signifies using defaults
              newCap = DEFAULT_INITIAL_CAPACITY;
              newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
          } 
          @SuppressWarnings({"rawtypes","unchecked"})
          Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
          table = newTab;
          return newTab;
      }
    

    该方法首先把table及其长度赋值给oldTab和oldCap;threshold是扩容阈值的意思(16*0.75),此时为0,所以前两个if先不管,最后else里newCap的值为默认初始化容量16;往下创建了一个newCap大小的数组并将其赋给了table,刚创建的HashMap对象就在这里获得了初始容量。然后我们再回到putVal方法,第二个if就是根据哈希码得到的tab中的某个位置是否为空,为空便直接添加元素,此时数组中无元素所以直接添加。至此HashMap对象就完成了第一个元素的添加。当添加的元素超过(16乘0.75=)12时,就会进行扩容:

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict){
          if (++size > threshold)
              resize();
      }
    

    扩容的代码如下(部分):

    final Node<K,V>[] resize() {
          int oldCap = (oldTab == null) ? 0 : oldTab.length;
          int newCap;
          if (oldCap > 0) {
              if (oldCap >= MAXIMUM_CAPACITY) {//略}
              else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                       oldCap >= DEFAULT_INITIAL_CAPACITY)
          }
      }
    

    核心部分是else if里的左移操作,也就是说每次扩容都是原来大小的两倍

  • 额外说明的一点是在JDK1.8以前链表是头插入,JDK1.8以后链表是尾插入。

  • HashMap刚创建时,table为null,为了节省空间,当添加第一个元素时,table容量调整为16


HashSet源码分析

了解完HashMap之后,再回过头来看之前的HashSet源码,为什么放在后面写你们看一下源码就知道了(部分):

public class HashSet<E>
      extends AbstractSet<E>
      implements Set<E>, Cloneable, java.io.Serializable
  {
      private transient HashMap<E,Object> map;
      private static final Object PRESENT = new Object();
      public HashSet() {
          map = new HashMap<>();
      }
  }

可以看见HashSet的存储结构就是HashMap,那它的存储方式是怎样的呢?可以看一下add方法:

public boolean add(E e) {
      return map.put(e, PRESENT)==null;
  }

发现它的add方法调用的就是map的put方法,把元素作为map的key传进去的。。

Hashtable

  • 线程安全,运行效率慢;不允许null作为key或是value。

    (这个集合在开发过程中已经不用了,稍微了解即可)

Properties

  • Hashtable的子类,要求key和value都是String。通常用于配置文件的读取。

它继承了Hashtable的方法,与流关系密切,此处不详解。

TreeMap

  • 实现了SortedMap接口(是Map的子接口),可以对key自动排序。
  • 存储结构:红黑树
/**
 * TreeMap的使用
 */
	public static void main(String[] args) {
		TreeMap<Student, Integer> treeMap=new TreeMap<Student, Integer>();
		Student s1=new Student("tang", 36);
		Student s2=new Student("yu", 101);
		Student s3=new Student("he", 10);
		//1.添加元素
		treeMap.put(s1, 21);
		treeMap.put(s2, 22);
		treeMap.put(s3, 21);
        treeMap.put(new Student("he", 10), 91);// 添加失败,因为compareTo根据id进行比较,会认为这是同一个对象,但是value会覆盖 即91覆盖21
		//不能直接打印,需要实现Comparable接口,因为红黑树需要比较大小,比较key
		System.out.println(treeMap.toString());
		//2.删除元素
		treeMap.remove(new Student("he", 10));// 
		System.out.println(treeMap.toString());
		//3.遍历
		//3.1 使用keySet()
		for (Student key : treeMap.keySet()) {
			System.out.println(key+" "+treeMap.get(key));
		}
		//3.2 使用entrySet()
		for (Entry<Student, Integer> entry : treeMap.entrySet()) {
			System.out.println(entry.getKey()+" "+entry.getValue());
		}
		//4.判断
		System.out.println(treeMap.containsKey(s1));
		System.out.println(treeMap.isEmpty());		
	}

在学生类中实现Comparable接口:

public class Student implements Comparable<Student>{
    @Override
    public int compareTo(Student o) {
        int n1=this.id-o.id;
        return n1;
}

除此之外还可以使用比较器来定制比较:

TreeMap<Student, Integer> treeMap2=new TreeMap<Student, Integer>(new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        // 略
        return 0;
    }			
});

TreeSet源码

和HashSet类似,放在TreeMap之后讲便一目了然(部分):

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{
    private transient NavigableMap<E,Object> m;
    private static final Object PRESENT = new Object();
    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }
    public TreeSet() {
        this(new TreeMap<E,Object>());  // 调用TreeSet的构造方法
    }
}

TreeSet的存储结构实际上就是TreeMap,再来看其存储方式:

public boolean add(E e) {
    return m.put(e, PRESENT)==null;
}

它的add方法调用的就是TreeMap的put方法,将元素作为key传入到存储结构中。


Collections工具类

  • 概念:集合工具类,定义了除了存取以外的集合常用方法。

  • 方法

    • public static void reverse(List<?> list)//反转集合中元素的顺序
    • public static void shuffle(List<?> list)//随机重置集合元素的顺序
    • public static void sort(List<T> list)//升序排序(元素类型必须实现Comparable接口)
    /**
     * 演示Collections工具类的使用
     * 
     */
    	public static void main(String[] args) {
    		List<Integer> list=new ArrayList<Integer>();
    		list.add(20);
    		list.add(10);
    		list.add(30);
    		list.add(90);
    		list.add(70);
    		
    		//sort排序
    		System.out.println(list.toString());
    		Collections.sort(list);
    		System.out.println(list.toString());
    		System.out.println("---------");
    		
    		//binarySearch二分查找  返回目标下标
    		int i=Collections.binarySearch(list, 10);
    		System.out.println(i);
    		
    		//复制,该方法要求目标元素容量大于等于源目标,多余的位置保留类型默认值
    		List<Integer> list2=new ArrayList<Integer>();
    		for(int i1=0;i1<5;i1++) {
    			list2.add(0);
    		}
    		Collections.copy(list2, list);
    		System.out.println(list2.toString());
    		
    		//reserve反转
    		Collections.reverse(list2);
    		System.out.println(list2.toString());
    		
    		//shuffle 打乱
    		Collections.shuffle(list2);
    		System.out.println(list2.toString());
    		
    		//补充:list转成数组
    		Integer[] arr=list.toArray(new Integer[0]);// 主要是用这个Integer类型
    		System.out.println(arr.length);
    		
            //补充:数组转成集合
    		String[] nameStrings= {"tang","he","yu"};
           //受限集合,不能添加和删除
    		List<String> list3=Arrays.asList(nameStrings);
    		System.out.println(list3);
            
            //  int[] nums={1,2,4,5}; 基本类型数组转成集合时需要修改为包装类
            Integer[] num={1,2,4,5};
            List<Integer> list5=Arrays.asList(num);
            System.out.println(list5);
    	}
    
posted @   bobochen  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示