JAVA集合(笔记)

集合简介

  • 概念:对象的容器,定义了对多个对象进项操作的的常用方法。可实现数组的功能。
  • 和数组的区别
  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() //姜此集合转换成数组。
    COPY/*** Collection接口的使用(一)* 1.添加元素* 2.删除元素* 3.遍历元素* 4.判断*/
    public class Demo1{pubic 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){System.out.println(object);}//3.2 使用迭代器(迭代器专门用来遍历集合的一种方式)//hasnext();判断是否有下一个元素//next();获取下一个元素//remove();删除当前元素Iterator iterator=collection.Itertor();while(iterator.hasnext()){String object=(String)iterator.next();System.out.println(s);//删除操作//collection.remove(s);引发错误:并发修改异常//iterator.remove();应使用迭代器的方法
    //      * 4.判断System.out.println(collection.contains("西瓜"));//trueSystem.out.println(collection.isEmpty());//false}}
    }
    
    COPY/*** 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);可重复添加相同对象System.out.println("元素个数:"+collection.size());System.out.println(collection.toString());//2.删除数据collection.remove(s1);System.out.println("删除之后:"+collection.size());//3.遍历数据//3.1 增强forfor(Object object:collection) {Student student=(Student) object;System.out.println(student.toString());}//3.2迭代器//迭代过程中不能使用collection的删除方法Iterator iterator=collection.iterator();while (iterator.hasNext()) {Student student=(Student) iterator.next();System.out.println(student.toString());}//4.判断和上一块代码类似。}
    }
    
    COPY/*** 学生类*/
    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;}@Overridepublic 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之间的集合元素。
    COPY/*** List子接口的使用(一)* 特点:1.有序有下标 2.可以重复* * 1.添加元素* 2.删除元素* 3.遍历元素* 4.判断* 5.获取位置*/
    public class Demo3 {public static void main(String[] args) {List list=new ArrayList<>();//1.添加元素list.add("tang");list.add("he");list.add(0,"yu");//插入操作System.out.println("元素个数:"+list.size());System.out.println(list.toString());//2.删除元素list.remove(0);//list.remove("yu");结果同上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 使用增强forfor(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 listIterator=list.listIterator();//从前往后while (listIterator.hasNext()) {System.out.println(listIterator.next());		}//从后往前(此时“遍历指针”已经指向末尾)while(listIterator.hasPrevious()) {System.out.println(listIterator.previous());}//4.判断System.out.println(list.isEmpty());System.out.println(list.contains("tang"));//5.获取位置System.out.println(list.indexOf("tang"));}
    }
    
    COPY/*** List子接口的使用(二)* 1.添加元素* 2.删除元素* 3.遍历元素* 4.判断* 5.获取位置*/
    public class Demo4 {public static void main(String[] args) {List list=new ArrayList();//1.添加数字数据(自动装箱)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);//list.remove(20);很明显数组越界错误,改成如下//list.remove(Object(20));//list.remove(new Integer(20));System.out.println("元素个数:"+list.size());System.out.println(list.toString());//3-5不再演示,与之前类似//6.补充方法subList,返回子集合,含头不含尾List list2=list.subList(1, 3);System.out.println(list2.toString());	}
    }
    

List实现类

ArrayList[重点]

  • 数组结构实现,查询块、增删慢;
  • JDK1.2版本,运行效率快、线程不安全。
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cqGEjbf9-1614751593954)(C:\Users\王东梁\AppData\Roaming\Typora\typora-user-images\image-20210228215642063.png)]
COPY/*** ArrayList的使用* 存储结构:数组;* 特点:查找遍历速度快,增删慢。* 1.添加元素* 2.删除元素* 3.遍历元素* 4.判断* 5.查找*/
public class Demo5 {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));}
}

:Object里的equals(this==obj)用地址和当前对象比较,如果想实现代码中的问题,可以在学生类中重写equals方法:

COPY@Override
public boolean equals(Object obj) {//1.是否为同一对象if (this==obj) {return true;}//2.判断是否为空if (obj==null) {return false;}//3.判断是否是Student类型if (obj instanceof Student) {Student student=(Student) obj;//4.比较属性if(this.name.equals(student.getName())&&this.age==student.age) {return true;}}//不满足,返回falsereturn false;
}

ArrayList源码分析

  • 默认容量大小:private static final int DEFAULT_CAPACITY = 10;

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

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

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

    COPY//这是一个空的数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    

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

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

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

    假设你new了一个数组,当前容量为0,size当然也为0。这时调用add方法进入到ensureCapacityInternal(size + 1);该方法源码如下:

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

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

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

    上文说过,elementData就是存放元素的数组,当前容量为0,if条件成立,返回默认容量DEFAULT_CAPACITY也就是10。这个值作为参数又传入ensureExplicitCapacity()方法中,进入该方法查看源码:

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

    我们先不要管modCount这个变量。

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

    COPYprivate void grow(int minCapacity) {// overflow-conscious codeint 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);
    }
    

    这个方法先声明了一个oldCapacity变量将数组长度赋给它,其值为0;又声明了一个newCapacity变量其值为oldCapacity+一个增量,可以发现这个增量是和原数组长度有关的量,当然在这里也为0。第一个if条件满足,newCapacity的值为10(这就是默认的容量,不理解的话再看看前面)。第二个if条件不成立,也可以不用注意,因为MAX_ARRAY_SIZE的定义如下:

    COPYprivate static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    

    这个值太大了以至于第二个if条件没有了解的必要。

    最后一句话就是为elementData数组赋予了新的长度,Arrays.copyOf()方法返回的数组是新的数组对象,原数组对象不会改变,该拷贝不会影响原来的数组。copyOf()的第二个自变量指定要建立的新数组长度,如果新数组的长度超过原数组的长度,则保留数组默认值。

    这时候再回到add的方法中,接着就向下执行elementData[size++] = e;到这里为止关于ArrayList就讲解得差不多了,当数组长度为10的时候你们可以试着过一下源码,查一下每次的增量是多少(答案是每次扩容为原来的1.5倍)。


Vector

  • 数组结构实现,查询快、增删慢;

  • JDK1.0版本,运行效率慢、线程安全。

    COPY/*** Vector的演示使用* *1.添加数据*2.删除数据*3.遍历*4.判断*/
    public class Demo1 {public static void main(String[] args) {Vector vector=new Vector<>();//1.添加数据vector.add("tang");vector.add("he");vector.add("yu");System.out.println("元素个数:"+vector.size());//2.删除数据/** vector.remove(0); vector.remove("tang");*///3.遍历//使用枚举器Enumeration enumeration=vector.elements();while (enumeration.hasMoreElements()) {String s = (String) enumeration.nextElement();System.out.println(s);}//4.判断System.out.println(vector.isEmpty());System.out.println(vector.contains("he"));//5. Vector其他方法//firstElement()  lastElement()  ElementAt();}
    }
    

LinkedList

  • 链表结构实现,增删快,查询慢。
COPY/*** LinkedList的用法* 存储结构:双向链表* 1.添加元素* 2.删除元素* 3.遍历* 4.判断*/
public class Demo2 {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(new Student("唐", 21));* System.out.println(linkedList.toString());*///3.遍历//3.1 使用forfor(int i=0;i<linkedList.size();++i) {System.out.println(linkedList.get(i));}//3.2 使用增强forfor(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;
  • (指向)最后一个结点/尾结点:transient Node<E> last;

关于Node类型我们再进入到类里看看:

COPYprivate 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带参构造方法的三个参数分别是前一个结点、存储的数据、后一个结点,调用这个构造方法时将它们赋值给当前对象。

LinkedList是如何添加元素的呢?先看看add方法:

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

进入到linkLast方法:

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

假设刚开始new了一个LinkedList对象,first和last属性都为空,调用add进入到linkLast方法。

首先创建一个Node变量 l 将last(此时为空)赋给它,然后new一个newNode变量存储数据,并且它的前驱指向l,后继指向null;再把last指向newNode。如下图所示:

img

如果满足if条件,说明这是添加的第一个结点,将first指向newNode:

img

至此,LinkedList对象的第一个数据添加完毕。假设需要再添加一个数据,我们可以再来走一遍,过程同上不再赘述,图示如下:

img


ArrayList和LinkedList区别

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

img


泛型概述

  • Java泛型是JDK1.5中引入的一个新特性,其本质是参数化类型,把类型作为参数传递。
  • 常见形式有泛型类、泛型接口、泛型方法。
  • 语法:
    • <T,…> T称为类型占位符,表示一种引用类型。
  • 好处:
    • 提高代码的重用性。
    • 防止类型转换异常,提高代码的安全性。

泛型类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JNlbroFK-1614751593971)(C:\Users\王东梁\AppData\Roaming\Typora\typora-user-images\image-20210301155839838.png)]

COPY/*** 泛型类* 语法:类名<T>* T是类型占位符,表示一种引用类型,编写多个使用逗号隔开* */
public class myGeneric<T>{//1.创建泛型变量//不能使用new来创建,因为泛型是不确定的类型,也可能拥有私密的构造方法。T t;//2.泛型作为方法的参数public void show(T t) {System.out.println(t);}//泛型作为方法的返回值public T getT() {return t;}
}
COPY/*** 注意:* 1.泛型只能使用引用类型* 2.不同泛型类型的对象不能相互赋值*/
public class testGeneric {public static void main(String[] args) {//使用泛型类创建对象myGeneric<String> myGeneric1=new myGeneric<String>();myGeneric1.t="tang";myGeneric1.show("he");myGeneric<Integer> myGeneric2=new myGeneric<Integer>();myGeneric2.t=10;myGeneric2.show(20);Integer integer=myGeneric2.getT();}
}

泛型接口

COPY/*** 泛型接口* 语法:接口名<T>* 注意:不能创建泛型静态常量*/
public interface MyInterface<T> {//创建常量String nameString="tang";T server(T t);
}
COPY/*** 实现接口时确定泛型类*/
public class MyInterfaceImpl implements MyInterface<String>{@Overridepublic String server(String t) {System.out.println(t);return t; }
}
COPY//测试
MyInterfaceImpl myInterfaceImpl=new MyInterfaceImpl();
myInterfaceImpl.server("xxx");
//xxx
COPY/*** 实现接口时不确定泛型类*/
public class MyInterfaceImpl2<T> implements MyInterface<T>{@Overridepublic T server(T t) {System.out.println(t);return t;}
}
COPY//测试
MyInterfaceImpl2<Integer> myInterfaceImpl2=new MyInterfaceImpl2<Integer>();
myInterfaceImpl2.server(2000);
//2000

泛型方法

COPY/*** 泛型方法* 语法:<T> 返回类型*/
public class MyGenericMethod {public <T> void show(T t) {System.out.println("泛型方法"+t);}
}
COPY//测试
MyGenericMethod myGenericMethod=new MyGenericMethod();
myGenericMethod.show("tang");
myGenericMethod.show(200);
myGenericMethod.show(3.14);

泛型集合

  • 概念:参数化类型、类型安全的集合,强制集合元素的类型必须一致。

  • 特点

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

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

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

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


Set集合概述

Set子接口

  • 特点:无序、无下标、元素不可重复。
  • 方法:全部继承自Collection中的方法。
COPY/*** 测试Set接口的使用* 特点:1.无序,没有下标;2.重复* 1.添加数据* 2.删除数据* 3.遍历【重点】* 4.判断*/
public class Demo1 {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 使用增强forfor (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,则拒绝后者存入。
COPY/*** 人类*/
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;}@Overridepublic String toString() {return "Peerson [name=" + name + ", age=" + age + "]";}
}
COPY/*** HashSet集合的使用* 存储结构:哈希表(数组+链表+红黑树)* 1.添加元素* 2.删除元素* 3.遍历* 4.判断
*/
public class Demo3 {public static void main(String[] args) {HashSet<Person> hashSet=new HashSet<>();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);//3.遍历//3.1 增强forfor (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());//直接new一个相同属性的对象结果输出是false,不难理解。//注:假如相同属性便认为是同一个对象,该怎么做?System.out.println(hashSet.contains(new Person("tang", 21)));}
}

:hashSet存储过程:

  1. 根据hashCode计算保存的位置,如果位置为空,则直接保存,否则执行第二步。
  2. 执行equals方法,如果方法返回true,则认为是重复,拒绝存储,否则形成链表。

存储过程实际上就是重复依据,要实现“注”里的问题,可以重写hashCode和equals代码:

COPY@Override
public int hashCode() {final int prime = 31;int result = 1;result = prime * result + age;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;Person other = (Person) obj;if (age != other.age)return false;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;return true;
}

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

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

TreeSet

  • 基于排序顺序实现不重复。
  • 实现了SortedSet接口,对集合元素自动排序。
  • 元素对象的类型必须实现Comparable接口,指定排序规则。
  • 通过CompareTo方法确定是否为重复元素。
COPY/*** 使用TreeSet保存数据* 存储结构:红黑树* 要求:元素类必须实现Comparable接口,compareTo方法返回0,认为是重复元素 */
public class Demo4 {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);//注:直接添加会报类型转换错误,需要实现Comparable接口System.out.println(persons.toString());//2.删除元素persons.remove(p1);persons.remove(new Person("he", 22));System.out.println(persons.toString());//3.遍历(略)//4.判断System.out.println(persons.contains(new Person("yu", 21)));}
}

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

COPYpublic 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的构造方法,使用匿名内部类来实现它:

COPY/*** TreeSet的使用* Comparator:实现定制比较(比较器)*/
public class Demo5 {public static void main(String[] args) {TreeSet<Person> persons=new TreeSet<Person>(new Comparator<Person>() {@Overridepublic 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());}
}

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

COPY/*** 要求:使用TreeSet集合实现字符串按照长度进行排序* helloworld tangrui hechengyang wangzixu yuguoming* Comparator接口实现定制比较*/
public class Demo6 {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体系集合

  • Map接口的特点:

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

  • 方法

    • V put(K key,V value)//将对象存入到集合中,关联键值。key重复则覆盖原值。

    • Object get(Object key)//根据键获取相应的值。

    • Set<K>//返回所有的key

    • Collection<V> values()//返回包含所有值的Collection集合。

    • Set<Map.Entry<K,V>>//键值匹配的set集合

    COPY/*** Map接口的使用* 特点:1.存储键值对 2.键不能重复,值可以重复 3.无序*/
    public class Demo1 {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");System.out.println(map.toString());//3.遍历//3.1 使用keySet();for (String key : map.keySet()) {System.out.println(key+" "+map.get(key));}//3.2 使用entrySet();效率较高for (Map.Entry<String, Integer> entry : map.entrySet()) {System.out.println(entry.getKey()+" "+entry.getValue());}}
    }
    

Map集合的实现类

HashMap【重点】

  • JDK1.2版本,线程不安全,运行效率快;允许用null作为key或是value。

    COPY/*** 学生类*/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;}@Overridepublic String toString() {return "Student [name=" + name + ", age=" + id + "]";}}
    
    COPY/*** HashMap的使用* 存储结构:哈希表(数组+链表+红黑树)*/public class Demo2 {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 (Entry<Student, String> entry : hashMap.entrySet()) {System.out.println(entry.getKey()+" "+entry.getValue());}//4.判断//注:同上System.out.println(hashMap.containsKey(new Student("he", 10)));System.out.println(hashMap.containsValue("成都"));}}
    

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

    COPY@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + id;result = prime * result + ((name == null) ? 0 : name.hashCode());return result;}@Overridepublic 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的指针(以下只截取了部分源码):

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

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

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

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

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

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

    put方法把key和value传给了putVal,同时还传入了一个hash(Key)所返回的值,这是一个产生哈希值的方法,再进入到putVal方法(部分源码):

    COPYfinal 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{//略}}
    

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

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

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

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

    COPYfinal 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以后链表是尾插入。


HashSet源码分析

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

COPYpublic 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方法:

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

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

Hashtable

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

  • 初始容量11,加载因子0.75。

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

Properties

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

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

TreeMap

  • 实现了SortedMap接口(是Map的子接口),可以对key自动排序。
COPY/*** TreeMap的使用* 存储结构:红黑树*/
public class Demo3 {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);//不能直接打印,需要实现Comparable接口,因为红黑树需要比较大小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接口:

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

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

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

TreeSet源码

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

COPYpublic 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的存储结构实际上就是TreeMap,再来看其存储方式:

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

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


Collections工具类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iJZH8Shy-1614751593973)(C:\Users\王东梁\AppData\Roaming\Typora\typora-user-images\image-20210303135614014.png)]

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

  • 方法

    • public static void reverse(List<?> list)//反转集合中元素的顺序
    • public static void shuffle(List<?> list)//随机重置集合元素的顺序
    • public static void sort(List<T> list)//升序排序(元素类型必须实现Comparable接口)
    COPY/*** 演示Collections工具类的使用**/
    public class Demo4 {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);//copy复制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]);System.out.println(arr.length);//补充:数组转成集合 String[] nameStrings= {"tang","he","yu"};//受限集合,不能添加和删除List<String> list3=Arrays.asList(nameStrings);System.out.println(list3);//注:基本类型转成集合时需要修改为包装类}
    }
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/327139.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

公司技术需求备忘录

业务现状领导要求 1) 部署环境要求: 公有云&#xff0c;私有云&#xff0c;原有院内系统。三套环境&#xff0c;兼容部署&#xff0c;一套代码多环境支持。2) 数据库要求&#xff1a;sqlserver&#xff0c;orcale&#xff0c;mysql要兼容&#xff0c;一套代码多库运行。3) 性能…

equals 和 hashCode 到底有什么联系?一文告诉你

转载自 equals 和 hashCode 到底有什么联系&#xff1f;一文告诉你 写在前面 Java的基类Object提供了一些方法&#xff0c;其中equals()方法用于判断两个对象是否相等&#xff0c;hashCode()方法用于计算对象的哈希码。equals()和hashCode()都不是final方法&#xff0c;都可…

如何将Excel的单元格设置成下拉选项?-excel设置下拉菜单

https://www.qiaoshan022.cn/excel/excel14169.html 如何将Excel的单元格设置成下拉选项&#xff1f;-excel设置下拉菜单 作者&#xff1a;乔山办公网日期&#xff1a;2019-09-24 21:13:23 返回目录&#xff1a;excel表格制作 在用Excel表录入数据时&#xff0c;有时需要限制…

写一个高性能的敏感词检测组件

最近写了一个高性能的敏感词检测组件【ToolGood.Words】。 一、高性能&#xff0c;它的效率到底有多快&#xff1f; 如果将正则表达式的算法效率设为1&#xff0c;高性能可达到正则表达式的1.5万倍。 二、选一个巧妙的算法&#xff1a; AC自动机&#xff08;Aho-Corasick Autom…

win10系统excel2019单元格显示完整的年月日时分秒设置方法

https://www.pianshen.com/article/88671983757/ win10系统excel2019单元格显示完整的年月日时分秒设置方法 技术标签&#xff1a; Windows excel显示年月日时分秒 excel显示完整的详细时间 excel2019显示完整时间 excel2019显示具体时间 excel显示具体的时间 描述&#…

Visual Studio现可使用EditorConfig

Visual Studio 2017的首个候选发布版&#xff08;VS2017RC&#xff09;中提供了支持EditorConfig标准的特性。秉承该标准可使开发人员一次性地定义一种编码风格&#xff0c;即可轻易地在不同的编辑器中使用该风格。另一显著优点是对于在EditorConfig文件中定义的风格&#xff0…

分库分表的事务处理机制

转载自 分库分表的事务处理机制 分布式事务 由于我们将单表的数据切片后存储在多个数据库甚至多个数据库实例中&#xff0c;所以依靠数据库本身的事务机制不能满足所有场景的需要。但是&#xff0c;我们推荐在一个数据库实例中的操作尽可能使用本地事务来保证一致性&#xff0…

如何优雅的使用RabbitMQ

RabbitMQ无疑是目前最流行的消息队列之一&#xff0c;对各种语言环境的支持也很丰富&#xff0c;作为一个.NET developer有必要学习和了解这一工具。消息队列的使用场景大概有3种&#xff1a; 1、系统集成&#xff0c;分布式系统的设计。各种子系统通过消息来对接&#xff0c;这…

hutool中身份证工具-IdcardUtil

JAVA工具例大全--根据身份编号获取户籍省份 发布于 2020-10-10 | 后端技术 | 浏览&#xff08;61&#xff09; | 评论&#xff08;0&#xff09;开场语(刷新后不一样):人生若只如初见&#xff0c;何事秋风悲画扇。作为一名IT人&#xff0c;你当然也想有自己一片天地&…

[Asp.Net Core轻量级Aop解决方案]AspectCore Project 介绍

AspectCore Project 介绍 什么是AspectCore Project ? AspectCore Project 是适用于Asp.Net Core 平台的轻量级 Aop(Aspect-oriented programming) 解决方案&#xff0c;它更好的遵循Asp.Net Core的模块化开发理念&#xff0c;使用AspectCore可以更容易构建低耦合、易扩展的We…

放松眼球的网站

http://www.spielzeugz.de/html5/liquid-particles-3D/

异步广度优先搜索算法

为什么要异步&#xff1f; CPU的工艺越来越小&#xff0c;Cannon Lake架构的Intel CPU已经达到10nm技术&#xff0c;因此在面积不变的情况下&#xff0c;核心数可以明显提升。单纯的提升主频将造成发热量大、需要的电压大、功耗大的问题。而传统的算法与数据结构是针对单核心单…

开箱即用 - jwt 无状态分布式授权

基于JWT(Json Web Token)的授权方式 JWT 是JSON风格轻量级的授权和身份认证规范&#xff0c;可实现无状态、分布式的Web应用授权&#xff1b; 从客户端请求服务器获取token&#xff0c; 用该token 去访问实现了jwt认证的web服务器。 token 可保存自定义信息&#xff0c;如用户基…

Java类加载器总结

转载自 Java类加载器总结 1.类的加载过程 JVM将类加载过程分为三个步骤&#xff1a;装载&#xff08;Load&#xff09;&#xff0c;链接&#xff08;Link&#xff09;和初始化(Initialize)链接又分为三个步骤&#xff0c;如下图所示&#xff1a; 1) 装载&#xff1a;查找并…

MyBatis-Plus EntityWrapper的使用 wrapper le ge

https://blog.csdn.net/shujuelin/article/details/99568651 MyBatis-Plus EntityWrapper的使用 脚丫先生 2019-08-14 14:43:43 2660 收藏 分类专栏&#xff1a; javaee 版权 调度Airflow 本专刊主要以调度系统Airflow详细讲解(会把工作中对于调度系统的docker容器化部署、…

又踩.NET Core的坑:在同步方法中调用异步方法Wait时发生死锁(deadlock)

之前在将 Memcached 客户端 EnyimMemcached 迁移 .NET Core 时被这个“坑”坑的刻骨铭心&#xff08;详见以下链接&#xff09;&#xff0c;当时以为只是在构造函数中调用异步方法&#xff08;注&#xff1a;这里的异步方法都是指基于Task的&#xff09;才会出线死锁&#xff0…

jvm类加载器以及双亲委派

转载自 jvm类加载器以及双亲委派 首先来了解几个概念&#xff1a; 类加载&#xff1a; 概念&#xff1a;虚拟机把描述类的数据从Class文件加载到内存&#xff0c;并对数据进行校验--转换解析--初始化&#xff0c;最终形成能被java虚拟机直接使用的java类型&#xff0c;就是jvm…

分布式系统搭建:服务发现揭秘

CAP理论 加州大学终身教授与著名计算机科学家Eric Allen Brewer在90年代末提出了CAP理论&#xff0c;理论断言任一个基于网络的分布式系统&#xff0c;最多只能满足“数据一致性”、“可用性”、“分区容错性”三要素中的两个要素。 该理论后被MIT证明可行&#xff0c;故架构师…