java 集合

什么是java容器

  • 集合容器主要⽤于保存对象,主要分类有三种List 、Set、Map

    • List 有序 可重复的 集合
    • 常⻅的List有(线程不安全)ArrayList、LinkedList ;(线程安全) Vector、 CopyOnWriteArrayList等类,
    • Set 无序,不可重复 的集合
    • 常见的Set的子类有 HashSet,LinkedSet,TreeSet 等,
    • Map key-value 键值对
    • HashMap,TreeMap,Hashtable,LinkedHashMap 等,
  • 它们与Collection的关系
    请添加图片描述

Collection 接口

  • 常用方法

请添加图片描述

方法名说明
boolean add(E e)向集合添加元素e,若指定集合元素改变了则返回true
boolean addAll(Collection<? extends E> c)把集合C中的元素全部添加到集合中,若指定集合元素改变返回true
void clear()清空所有集合元素
boolean contains(Object o)判断指定集合是否包含对象o
boolean containsAll(Collection<?> c)判断指定集合是否包含集合c的所有元素
boolean isEmpty()判断指定集合的元素size是否为0
boolean remove(Object o)删除集合中的元素对象o,若集合有多个o元素,则只会删除第一个元素
boolean removeAll(Collection<?> c)删除指定集合包含集合c的元素
boolean retainAll(Collection<?> c)从指定集合中保留包含集合c的元素,其他元素则删除
int size()集合的元素个数
T[] toArray(T[] a)将集合转换为T类型的数组

Collection 接口方法跟后面 List ,Set 差不多;

List 接口

  • List 接口 的实现类,可以理解为一个自动扩容的数组,你需要几个元素空间,就动态开辟多少空间;

  • 添加到List集合中的第一个数据下标为0,第二个为1····· ,这个跟数组类似

List实现类介绍

ArrayList

ArrayList底层为数组,所以拥有数组的特性,实现了可变数组的大小

对集合中的元素进行快速的随机访问(查看,修改)

ArrayList 中插入与删除元素的速度相对较慢。

ArrayList 里包含3个构造方法

ArrayList 每次扩容为原来的1.5倍;扩容时机:add元素,并且元素个数超过容量时

ArrayList 容量最大为整数的最大值,Integer.MAX_VALUE

  • 
    /**
     * 构造一个初始容量为0的空列表 
     * 当第一次add时,会构造一个容量为10的空列表,也就是数组
    */
    public ArrayList() {
    
    }
    
    
    /**
     * 构造具有指定初始容量的空列表
    */
    public ArrayList(int initialCapacity) {
    
    }
    
    
    /**
     * 按照集合迭代器返回的顺序,构造一个包含指定集合元素的列表
     * 可以理解为把一个集合的元素赋给构造的集合
    */
    public ArrayList(Collection<? extends E> c) {
    
    }
    
LinkedList

基于的数据结构是链表,⼀个双向链表,链表数据结构的特点是每个元素分配的空间不

必连续

插⼊和删除元素时速度⾮常快,但访问元素的速度较慢

List 常用方法

public int size() ;返回容器元素个数
public boolean isEmpty();判断是否为空
public boolean contains(Object o) ;是否包含(o), 如果是引用类型,调用equals( )进行比较
public int indexOf(Object o);返回列表中指定元素第一次出现的索引,没找到,返回-1
public int lastIndexOf(Object o);返回列表中指定元素最后一次出现的索引,没找到,返回-1
public Object[] toArray();返回一个数组,元素相同,顺序相同
public T[] toArray(T[] a);参数a为一个数组,若传入的数组类型不匹配报错,数组a长度大于列表的话,将元素索引相同的位置依次赋值给数组 ,数组其它索引位置原有的值不会改变,直接返回该数组的引用地址;若长度不够,就开辟一个新的数组copy并返回
public E get(int index)获取对应索引下标的元素
public E set(int index, E element)设置新值,index为下标,element 为新元素
public boolean add(E e)添加元素
public void add(int index, E element)将当前位于该位置的元素和后面的元素依次向后移动,再将新元素插入到该位置
public E remove(int index)删除列表指定位置的元素,将后面元素依次向前移动,并返回删除的元素
public boolean remove(Object o)删除第一个出现的指定元素,若没有,就保持不变;找到并删除,返回true;否则返回false
public void clear()将列表每个元素置为空,并不会释放列表空间
public boolean addAll(Collection<? extends E> c)将指定容器的元素追加到列表末尾,成功追加返回true;若在操作过程中修改指定容器
  • ArrayList 和 LinkedList 是最常用的, 那么两者的区别是?

    • 两个都是List的接⼝,两个都是⾮线程安全的

    • ArrayList是基于动态数组的数据结构,⽽LinkedList是基于链表的数据结构

    • 对于随机访问get和set(查询操作),ArrayList要优于LinkedList,因LinkedList要移动指针

    • 对于增删操作(add和remove),LinkedList优于ArrayList。

Map 接口

HashMap
  • 底层就是⼀个数组结构,数组中的每⼀项⼜是⼀个链表,即数组和链表的结合体

  • Table是数组,数组的元素是Entry

  • Entry元素是⼀个key-value键值对,它持有⼀个指向下⼀个 Entry元素的引⽤,table数组的

每个Entry元素同时也作为当前Entry链表的⾸节点,也指向了该链表的下⼀个Entry元素

请添加图片描述

  • 使⽤put(key, value)存储对象到HashMap中,使⽤get(key)从 HashMap中获取对象。当put()⽅法传递键和值时,会先对键调⽤hashCode()⽅法,计 算并返回的hashCode是⽤于找到Map数组的bucket位置来储存Entry对象的,是⾮线程 安全的,所以HashMap操作速度很快

  • HashMap 是以key–value对的形式存储的,key值是唯一的(可以为null),一个key只能对应着一个value,但是value是可以重复的。

  • HashMap 如果再次添加相同的key值,它会覆盖key值所对应的内容,这也是与HashSet不同的一点,Set通过add添加相同的对象,不会再添加到Set中去。

  • HashMap 提供了get方法,通过key值取对应的value值,但是HashSet只能通过迭代器Iterator来遍历数据,找对象。

  • jdk1.7 和1.8 中HashMap的主要区别

  • 底层实现由之前的 “数组+链表” 改为 “数组+链表+红⿊树”

  • 当链表节点较少时仍然是以链表存在,当链表节点较多时,默认是⼤于8时会转为红⿊ 树

  • HashMap 当我们讨论它的容量时,指的是table数组的大小,初始为 16,

/**
    * The default initial capacity - MUST be a power of two.
    */
   static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  • 链表新节点插入链表的顺序不同(jdk7是插入头结点,jdk8因为要把链表变为红 黑树所以采用插入尾节点)

谈谈HashMap扩容机制

阈值(threshold) = 负载因子(loadFactor) x 容量(capacity) 当HashMap中table数组(也称为桶)长度 >= 阈值(threshold) 就会自动进行扩容。

扩容的规则是这样的,因为table数组长度必须是2的次方数,扩容其实每次都是按照上一次tableSize位运算得到的就是做一次左移1位运算,假设当前tableSize是16的话 16转为二进制再向左移一位就得到了32 即 16 << 1 == 32 即扩容后的容量,也就是说扩容后的容量是当前容量的两倍,但记住HashMap的扩容是采用当前容量向左位移一位(newtableSize = tableSize << 1),得到的扩容后容量,而不是当前容量x2 (位运算比数值运算快)

为什么HashMap的默认负载因子是0.75,而不是0.5或者是整数1呢?

  • 阈值(threshold) = 负载因子(loadFactor) x 容量(capacity) 根据HashMap的扩容机制,他会保证容量(capacity)的值永远都是2的幂 为了保证负载因子x容量的结果是一个整数,这个值是0.75(4/3)比较合理,因为这个数和任何2的次幂乘积结果都是整数。
  • 负载因子越大,导致哈希冲突的概率也就越大,负载因子越小,费的空间也就越大,这是一个无法避免的利弊关系,所以通过一个简单的数学推理,可以测算出这个数值在0.75左右是比较合理的

谈谈HashMap存储过程

  • 获取到传过来的key,调用hashcode()获取到hash值
  • 获取到hash值之后调用indexFor方法,通过获取到的hash值以及数组的长度算出数组的下标 (把哈希值和数组容量转换为二进,再在数组容量范围内与哈希值进行一次与运算,同为1则1,不然则为0,得出数组的下标值,这样可以保证计算出的数组下标不会大于当前数组容量)
  • 把传过来的key和value存到该数组下标当中。
  • 如该数组下标下以及有值了,则使用链表,jdk7是把新增元素添加到头部节点 jdk8则添加到尾部节点。
TreeMap
  • TreeMap 基于平衡二叉树,又叫红黑树;
  • 实现了SortedMap, 所以是有序的集合
  • 默认是按key 的字典顺序进行排序;
  • 也可自定义; Compare接口;

TreeMap , 和 TreeSet 构造方法中都不能指定容量,而且当数据类型 为 类 类型时TreeMap,TreeSet 判断重复元素不用equals 和hashCode方法,而是实现 Compare接口中的比较方法compareTo() 判断相等即重复,以及排序; 或者还可以在构造时,传入 Comparator 比较器作为参数;

Map 常用方法

Map<String,String> map = new HashMap<>();

//往map⾥⾯放key - value;

map.put(“⼩明”,“⼴东⼴州”);

map.put(“⼩东”,“⼴东深圳”);

//根据key获取value

map.get(“⼩东”);

//判断是否包含某个key

map.containsKey(“⼩明”);

//返回map的元素数量

map.size();

//清空容器

map.clear();

//获取所有value集合

map.values();

//返回所有key的集合

map.keySet()

//返回⼀个Set集合,集合的类型为Map.Entry , 是Map声明的⼀个内部接⼝,接⼝为泛型,定

义为Entry<K,V>,

//它表示Map中的⼀个实体(⼀个key-value对),主要有getKey(),getValue⽅法

Set<Map.Entry<String,String>> entrySet = map.entrySet();

//判断map是否为空

map.isEmpty();

Set 接口

  • Set 不保存重复的元素,存储⼀组唯⼀,⽆序(存取的顺序会被打乱,跟添加的顺序不一样)的对象
  • Set的实现都是对应的Map的⼀ 种封装。⽐如HashSet是对HashMap的封装,TreeSet对应TreeMap
  • 所谓封装就是在HashSet 类中,声明了一个私有的HashMap 集合属性
HashSet
  • HashSet底层是⼀个HashMap,由于HashMap的put()⽅法是⼀个键值对,当新放⼊HashMap的 Entry中key 与集合中原有Entry的key相同(hashCode()返回值相等) ,并equals⽐较返回 false,才会添加新的key,若为true,则是重复元素,不添加 ,若元素是自定义类 类型,则需要重写hsahCode 和equals 方法
  • HashSet集合的值放在了内部封装的HashMap集合的key值中,因为在HashMap中key是不允许重复的
  • 允许包含值为null的元素,但最多只能有⼀个null元素,HashMap 也允许null值 和 null键
TreeSet
  • TreeSet 具有去重 和排序的特性
  • 默认是按key 的字典顺序进行排序;
  • 如果是 TreeSet 存的是自定义的类 类型,不需要重写hashCode和equals方法,也能去重;
  • 不允许包含值为null的元素
  • 如果是 TreeSet 存的是自定义的类 类型,那么需要自定义排序规则,即这个自定义类要实现Comparable接口,并从写compareTo 方法; 否则报错 Collection.Collections.Student cannot be cast to java.lang.Comparable
public class Student implements Comparable{

private String name;
private  int age;
public Student(){}

public Student(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 "Student{" +
       "name='" + name + '\'' +
       ", age=" + age +
       '}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}

@Override
public int hashCode() {
return Objects.hash(name, age);
}
//  重写 排序规则
@Override
public int compareTo(Object o) {
Student student = (Student) o;             
   if(this.getAge()==student.getAge())  //排除两个学生对象年龄一样,而姓名不一样的情况
       return 1; 
return this.getAge()- student.getAge();
}

}

这样定义了排序规则后,就可以往TreeSet里添加元素,自动排序了

Comparator 接口 和 Comparable 接口

  1. Comparator 接口一般用于自然排序,自己与自己比较,上面的Student类就是这种情况

  2. Comparable 接口我们称之为比较器, 具体方法为 int compare (T o1, T o2), 一般是两者的比较,源码上有 @FunctionalInterface 函数式编程注解,那么推荐使用函数式编程来使用,例如Collections.sort( ) , Arrays.sort( ) 方法里作为比较器传入参数

  3. 例如 :

     List<Student> list = new ArrayList<>();
            Student s1=new Student("jack",12);
            Student s2=new Student("tom",6);
            Student s3=new Student("jk",1);
            Student s4=new Student("ying",56);
            list.add(s1);
            list.add(s2);
            list.add(s3);
            list.add(s4);
            Collections.sort(list, new Comparator<Student>() {
                @Override
                public int compare(Student o1, Student o2) {
                    return o2.getAge()-o1.getAge();  //降序,升序反过来相减
                }
            });
            System.out.println(list);
    //[Student{name='ying', age=56}, Student{name='jack', age=12}, Student{name='tom', age=6}, Student{name='jk', age=1}]
    // 最大年龄学生
         Student student= Collections.max(list, new Comparator<Student>() {
                @Override
                public int compare(Student o1, Student o2) {
                    return o1.getAge()-o2.getAge();
                }
            });
            System.out.println(student);
    //Student{name='ying', age=56}
    

    但当我们Student 类实现了Comparable 接口,上面的 sort(),max()里可以不传Comparator 比较器,也可以; min,max 方法 ,如果不传比较器,Student 类自定义的排序规则为升序的时候,对应最大值,最小值, 若自定义降序,则相反对应,即 此时min = 集合最大值;

当jdk 不知道如何比较时,就需要我们自定义排序,理论上,Comparator 接口 和 Comparable 接口可以通用,互换;使用时还是遵循该有的规范比较好;

集合遍历

遍历List
  		// 获取index 遍历
		for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        System.out.println("====================");
        //增强for循环 遍历   
        for (Object str: list   //注意泛型与定义时一致,Object通用,若定义时未指定
             ) {                //泛型,这里就不可写 String str :list     
            System.out.println(str);
        }
        System.out.println("====================");
        // 获取迭代器遍历
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }


遍历Set
  		// 迭代器
		Iterator iterator = hashSet.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
        System.out.println("===================");
        //增强for
        for (Object obj:hashSet
             ) {
            System.out.println(obj);
        }

Set 为啥没有get(Index)的遍历方式呢?

  • 无论是HashSet还是TreeSet 都是对 Map结构的封装,map 可以通过 get key 值来获取 value,而Set 是存在 key 值上的,无法通过 key或者value来获取。
遍历Map
 		// keySet 集合获取到key值, get(key) 获取value
        for (Object obj : hashMap.keySet()) {
            System.out.println(hashMap.get(obj));
        }
        System.out.println("================");
        // 用 EntrySet,获取 Entry 来遍历
        Set<Map.Entry<Integer, String>> entries = hashMap.entrySet();
        //Iterator iterator = hashMap.entrySet().iterator();   //也可一步到位
        Iterator iterator = entries.iterator();
        while (iterator.hasNext()){
            Map.Entry e = (Map.Entry)iterator.next();
            System.out.println(e.getKey()+"-->"+e.getValue());
        }
Iterator 迭代器
  • Iterator 接口
  • 迭代器类似于指针,指向要遍历的元素,java中提供了Iterator 接口,以及一些方法

  • boolean hasNext( ) 如果迭代包含更多的元素,则返回true;当前迭代器指向的元素的下一个空间还有元素的话,返回true;

  • E next( ) 将迭代器下移一位, 返回迭代器指向的元素;

  • 为什么是下一个元素呢?

  • 一开始遍历的时候,迭代器指向的是第一个元素的前一个位置,这时我们判断hasNext(),它就会返回true,第一个元素是存在的;是基于这样的一个设计;

  • default void remove( ) 移除迭代器指向的元素,每次调用next方法时,只能调用remove一次,多次调用,相当于重复删除了,迭代器没有下移,指向的还是第一次remove的空间;

  • 迭代器在操作过程中,如果通过其他操作修改了元素,则会出错;

例如 :

Iterator iterator = list.iterator(); //获取迭代器
list.remove(0); // 删除第一个元素
String next= (String)iterator.next();   //获取第一个元素   !! 语句报错
System.out.println(next);  
  • List 集合还有一个 ListIterator迭代器,public interface ListIterator extends Iterator { } 提供了前向遍历的方法 boolean hasPrevious( )判断是否有前一个元素,E previous( ) 返回当前迭代器指向的元素,并向前移动迭代器;
  		System.out.println(list); //  [jack, tom, toney, jeny, 张飞, 李白]
        ListIterator<String> iterator = list.listIterator();
        iterator.next();  
        iterator.next();  
        System.out.println(iterator.previous());  // tom
        System.out.println(iterator.previous());  // jack

提供一种反向遍历的方式:

 		System.out.println(list); //  [jack, tom, toney, jeny, 张飞, 李白]
        ListIterator<String> iterator = list.listIterator(list.size());
        while (iterator.hasPrevious()){
            System.out.println(iterator.previous());
        }

  • 集合的多种删除方式
      // 第一种 通过集合自带方法
		System.out.println(list);
        for (int i = 0; i < list.size(); i++) {
            if(list.get(i).equals("张飞"))
            list.remove(i);
        }
        System.out.println(list);
//       foreach 报错  ConcurrentModificationException 异常
//        System.out.println("====================");
//        for (Object str: list
//             ) {
//            String s = (String) str;
//            if(s.equals("李白"))
//            list.remove(s);
//        }
//        System.out.println(list);
        System.out.println("====================");
// 第二种,通过迭代器删除
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            String next= (String)iterator.next();
            if(next.equals("jeny")){
                //list.remove("jeny");   // 中途修改元素,报错
                iterator.remove();
            }
        }
        System.out.println(list);


结果 :
[jack, tom, toney, jeny, 张飞, 李白]
[jack, tom, toney, jeny, 李白]
====================
[jack, tom, toney, 李白]
  • 为什么 foreach 删除不了元素呢?
  • java中提供的foreach语法糖其底层实现方式主要有两种:对于集合类或实现迭代器的集合使用迭代器的遍历方式,对于数组集合使用数组的遍历方法。
  • 使用foreach进行集合遍历时需要额外注意不能对集合长度进行修改,也就是不能对集合进行增删操作,否则会抛出ConcurrentModificationException异常
  • foreach 遍历集合采用迭代器的方式,如果在遍历过程中使用用集合相关函数对集合进行增删操作操作,就违背了迭代器的使用原则
  • ArrayList 源码里有modCount 记录修改次数的值,在迭代器生成的时候,会将modCount 值赋给迭代器中的expectedModCount,若迭代器遍历过程中modCount的值不等于迭代器的expectedModCount ,那么 就会抛出ConcurrentModificationException异常

对于集合泛型的理解

  • 在我们定义 容器时 ,容器类名后面和应用变量之间的泛型可以不写,注意是不写<> ,而不是写尖括号然后里面不写
  • 容器名后面,写尖括号然后里面不写 ,会报错 ( java: 非法的类型开始)
  • 这里的泛型不写,容器就会接受Object类型的数据
  • new LinkedList<>(); 这里的泛型可写可不写,可只写<>里面啥都不写

Collection  co = new LinkedList<>();
       co.add("asfas");
       co.add(121);
       co.add(23.23);
       System.out.println(co);
       List list1 =new LinkedList<>();
       list1.add("asdas");
       list1.add(5445);
       System.out.println(list1);

       for (Object o : co
            ) {
           System.out.println(o);
       }
       
//以List 为例子 ,有以下正确写法
		List list1 = new LinkedList<>();
       List list2 = new LinkedList();
       List list3 = new LinkedList<String>();  //list3  类型仍然为Object类型
       List <String>list4 = new LinkedList ();
       List <String>list5 = new LinkedList<> ();
       List <String>list6 = new LinkedList<String> ();

!!!总结 : 容器的类型是由 前面的引用变量指定的,new 后面的泛型不起作用,写不写都可,因为写了也没用; 在前面已经指定数据类型的时候,但如果要写的话就得和前面的泛型保持一致,要不然报错 :(java: 不兼容的类型:)

Collections 类

操作集合的工具类,不能 new 实例,通过静态方法调用

  		List<String>list =new ArrayList<>();
        list.add("aa");
        list.add("ll");
        list.add("vv");
        list.add("kk");
        System.out.println("排序前==========");
        System.out.println(list);
        //默认升序  ,小到大 ;
       // Collections.sort(list);
        //指定升序
        Collections.sort(list, Comparator.naturalOrder());  
        System.out.println("排序后==========");
        System.out.println(list);

        //指定降序
        Collections.sort(list, Comparator.reverseOrder());
        System.out.println("排序后==========");
        System.out.println(list);

        //随机排序,类似打乱;
        Collections.shuffle(list);
        System.out.println("打乱后==========");
        System.out.println(list);
        
        //将集合改为只读集合
        List<String> unmodifiableList = Collections.unmodifiableList(list);
        System.out.println(unmodifiableList);

        // 反转元素
		Collections.reverse(list);
        System.out.println(list);

		//统计某一个元素在集合中出现的次数
		System.out.println(Collections.frequency(list,"kk"));

		//拷贝
		List copyList = new ArrayList();
 		Collections.copy(copyList,list);
        System.out.println(copyList);

or.reverseOrder());
System.out.println(“排序后==========”);
System.out.println(list);

    //随机排序,类似打乱;
    Collections.shuffle(list);
    System.out.println("打乱后==========");
    System.out.println(list);
    
    //将集合改为只读集合
    List<String> unmodifiableList = Collections.unmodifiableList(list);
    System.out.println(unmodifiableList);

    // 反转元素
	Collections.reverse(list);
    System.out.println(list);

	//统计某一个元素在集合中出现的次数
	System.out.println(Collections.frequency(list,"kk"));

	//拷贝
	List copyList = new ArrayList();
	Collections.copy(copyList,list);
    System.out.println(copyList);

``

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值