javaSE基础-集合

集合

集合中类的关系图
image

数组与集合

1、数组与集合数据存储简述

集合、数组都是对多个数据进行存储操作的结构,简称java容器

说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt .jpg等)

2、数组存储的特点

  • 初始化以后,其长度就确定了
  • 数组一旦定义好,其元素的类型就确定了,就只能操作指定类型的数据

3、数组存储数据的劣势

  • 初始化以后,其长度不可改变
  • 数组中提供的方法有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高
  • 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
  • 数组存储数据的特点:有序、可重复。对无序、不可重复的需求不能满足

注:集合的数据存储可以很好解决数组存储方面的劣势

4、集合框架主要的类结构

集合-1

Collection接口

Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。 Collection 接口存储一组不唯一,无序的对象。

实体类 - Person.java

click me
class Person {
    private String name;
    private Integer age;

    public Person() {
    }

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

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Person person = (Person) o;

        if (!Objects.equals(name, person.name)) return false;
        return Objects.equals(age, person.age);
    }

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

常用方法1

集合-2

示例一

click me
@Test
public void test1(){
	Collection coll = new ArrayList();

	//1、add(object e):将元素添加到集合中
	coll.add("AA");
	coll.add("BB");
	coll.add(123);
	coll.add(new Date());

	//2、size():获取集合的元素的个数
	System.out.println(coll.size());//4

	//3、addAll(Collection coll):将集合coll中的元素添加到coll2中
	Collection coll2 = new ArrayList();
	coll2.add(234);
	coll2.addAll(coll);
	System.out.println(coll2);//[234, AA, BB, 123, Tue Aug 30 11:38:26 CST 2022]
	//System.out.println(coll);

	//4、clear():清空集合
	coll2.clear();

	//5、isEmpty():判断当前集合是否为空
	System.out.println(coll2.isEmpty());
}

常用方法2

集合-3

示例二

click me
@Test
public void test2(){
	Collection coll = new ArrayList();
	coll.add(123);
	coll.add(456);
	coll.add(false);
	coll.add(new String("Mary"));
	coll.add(new Person("Tom", 18));

	//6、contains(object obj):判断当前集合是否包含obj对象
	boolean b1 = coll.contains(123);
	System.out.println(b1);//true
	System.out.println(coll.contains(new String("Mary")));//true

	//没有重写Person类对象obj的equals():判断对象的地址是否相同
	//System.out.println(coll.contains(new Person("Tom", 18)));//false

	//重写Person类对象obj的equals():判断内容是否相同
	System.out.println(coll.contains(new Person("Tom", 18)));//true

	//7、containsAll(Collection coll):判断形参coll中的所有元素是否都存在于当前集合中
	System.out.println(coll.containsAll(Arrays.asList(123,456)));//true

}

@Test
public void test3(){
	Collection coll = new ArrayList();
	coll.add(123);
	coll.add(456);
	coll.add(false);
	coll.add(new String("Mary"));
	coll.add(new Person("Tom", 18));

	//8、remove(Object obj):移除当前集合中的obj元素,返回boolean
	coll.remove(123);
	System.out.println(coll);//[456, false, Mary, Person{name='Tom', age=18}]

	//9、removeAll(Collection coll):从当前集合中移除集合coll的元素,返回boolean
	Collection coll2 = Arrays.asList(456, false);
	coll.removeAll(coll2);//相当于求coll与coll2的差集
	System.out.println(coll);//[Mary, Person{name='Tom', age=18}]
}

@Test
public void test4(){
	Collection coll = new ArrayList();
	coll.add(123);
	coll.add(456);
	coll.add(false);
	coll.add(new String("Mary"));
	coll.add(new Person("Tom", 18));

	//10、equals(Object obj):比较两个对象元素是否相同
	Collection coll3 = new ArrayList();
	coll3.add(123);
	coll3.add(456);
	coll3.add(false);
	coll3.add(new String("Mary"));
	coll3.add(new Person("Tom", 18));

	//返回boolean值
	System.out.println(coll.equals(coll3));//true

	//11、retainAll(Collection coll2):获取当前集合和coll1集合的交集
	Collection coll2 = Arrays.asList(123,456,"aaa");
	coll.retainAll(coll2);//交集
	System.out.println(coll);//[123, 456]

}

@Test
public void test5(){
	Collection coll = new ArrayList();
	coll.add(123);
	coll.add(456);
	coll.add(false);
	coll.add(new String("Mary"));
	coll.add(new Person("Tom", 18));

	//12、hashCode():返回当前对象的哈希值
	System.out.println(coll.hashCode());//2024439392

	//13、toArray():集合 --> 数组
	Object[] objects = coll.toArray();
	System.out.println(Arrays.toString(objects));//[123, 456, false, Mary, Person{name='Tom', age=18}]

	//Arrays.asList():将数组 --> 集合
	List list = Arrays.asList("aa0", 123, 343, "bb");
	System.out.println(list);

	//注意:
	List<int[]> arr1 = Arrays.asList(new int[]{123, 1234});
	System.out.println(arr1.size());//1 --> 此处数组是以一个对象添加到List集合中
	System.out.println(arr1);//[[I@1a93a7ca]

	List<Integer> arr2 = Arrays.asList(new Integer[]{124, 7852});
	System.out.println(arr2.size());//2 --> 此处Integer类的每个整数自动装箱成为两个整型对象添加到集合中
	System.out.println(arr2);//[124, 7852]

	//14、iterator():返回Iterator接口的实例,用于遍历集合元素
	Iterator iterator = list.iterator();
	while(iterator.hasNext()){
	System.out.println(iterator.next());
	}
}

注意:向Collection接口的实现类对象中添加数据obj时,要求obj所在的类要重写equals()

Collection集合与数组间的转换

//xxx.toArray(): 集合 --> 数组
Object[] objects = coll.toArray();
System.out.println(Arrays.toString(objects));//[123, 456, false, Mary, Person{name='Tom', age=18}]

//Arrays.asList(): 数组 --> 集合
List list = Arrays.asList("aa0", 123, 343, "bb");
System.out.println(list);

Iterator接口

Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素

1、Collection集合的遍历方式

  • 使用Iterator迭代器
@Test
public void test1(){
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(false);
    coll.add(new String("Mary"));
    coll.add(new Person("Tom", 18));

    Iterator iterator = coll.iterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }
}
  • foreach循环(增强for循环,内部本质上调用迭代器)
@Test
public void test2(){
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(false);
    coll.add(new String("Mary"));
    coll.add(new Person("Tom", 18));

    for (Object obj : coll) {
        System.out.println(obj);
    }

    coll.forEach(System.out::println);
}

2、常用方法

hasNext() 和 next()

remove():移除集合中的元素

示例

@Test
public void test3(){
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(false);
    coll.add(new String("Mary"));
    coll.add(new Person("Tom", 18));

    Iterator iter  = coll.iterator();
    while(iter.hasNext()){
        Object next = iter.next();
        if("Mary".equals(next)){
            //remove():移除集合中的元素
            iter.remove();
        }
    }

    //再一次使用迭代器,需要新的迭代器
    Iterator iter2 = coll.iterator();
    while(iter2.hasNext()){
        System.out.println(iter2.next());
    }
}

迭代器的执行原理

集合-4

注意

集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前

如果还未调用next()或在上一次调用next方法之后已经调用了remove方法,在调用remove都会报错IllegalStateException

拓展 fori 与 foreach 的使用注意

@Test
public void test4(){
    String[] arr = new String[]{"aa","aa"};
    for (int i = 0; i < arr.length; i++) {
        arr[i] = "bb";//将字符串"bb"覆盖原来元素
    }
    for (int i = 0; i < arr.length; i++) {
        System.out.println(arr[i]);//bb bb
    }

    String[] arr2 = new String[]{"aa","aa"};
    for (String obj : arr2) {//将数组中的元素赋值给obj,不改变影响原来的数组arr2
        obj = "bb";
    }
    for (int i = 0; i < arr2.length; i++) {
        System.out.println(arr2[i]);//aa aa
    }
}

List接口

1、List特点

List接口(jdk1.2):存储有序的、可重复的数据。相当于“动态”数组

注:添加的对象所在的类要重写equals()方法

2、常用实现类

ArrayList(jdk1.2): 作为List的主要实现类,其线程是不安全的,查询效率高;底层使用Object[] elementData数组

LinkedList(jdk1.2):对于频繁插入、删除操作,比ArrayList效率高,线程不安全。底层使用双向链表结构

Vector(jdk1.0):作为List的老的接口,线程安全的,效率低;底层使用Object[] elementData

3、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位置的子集合

示例

click me
@Test
public void test1(){
	ArrayList list = new ArrayList();
	//1、void add()
	list.add(123);
	list.add(456);
	list.add("BB");
	list.add(new Person("Mary", 20));
	list.add(99);

	System.out.println(list);//[123, 456, BB, Person{name='Mary', age=20}, 99]

	//2、boolean addAll()
	List list2 = Arrays.asList(12, "GG", "Li");
	list.addAll(1, list2);
	System.out.println(list);//[123, 12, GG, Li, 456, BB, Person{name='Mary', age=20}, 99]
}

@Test
public void test2(){
	ArrayList list = new ArrayList();
	list.add(123);
	list.add(456);
	list.add("BB");
	list.add(new Person("Mary", 20));
	list.add(99);

	//3、Object get()
	Object obj = list.get(2);
	System.out.println(obj);//BB

	//4、int indexOf()
	int index = list.indexOf(99);
	System.out.println(index);//4

	//5、int lastIndexOf()
	int index2 = list.lastIndexOf("BB");
	System.out.println(index2);//2

	//6、Object remove()
	Object removeObj = list.remove(list.size()-1);
	System.out.println(removeObj);//99
	System.out.println(list);//[123, 456, BB, Person{name='Mary', age=20}]

	//7、Object set()
	Object obj2 = list.set(0, "zhang");
	System.out.println(obj2);//123, 注:返回的是当前位置上的元素
	System.out.println(list);//[zhang, 456, BB, Person{name='Mary', age=20}]

	//8、List subList()
	List subList = list.subList(1, 3);
	System.out.println(subList);//[456, BB]
	System.out.println(list);//[zhang, 456, BB, Person{name='Mary', age=20}]
}

Set接口

1、Set特点

Set接口:存储无序的、不可重复的数据

存储无序的:不等于随机性.存储数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值所对应的位置添加

不可重复的:保证添加的元素按照equals()判断时,不能返回true,即:相同的元素只能添加一个

2、常用实现类

HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值

LinkedHashSet:作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历,对于频繁的遍历操作,LinkedHashSet效率高于HashSet。(LinkedHashSet在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据)

TreeSet:可以按照添加对象的指定属性,进行排序

3、常用方法

Set接口中没有额外定义新的方法,使用的都是Collection中声明的方法

示例

click me
//HashSet的使用特点
@Test
public void test1(){
	HashSet set = new HashSet();
	set.add(234);
	set.add(111);
	set.add("NN");
	set.add("MM");
	set.add(new User("Mary", 10));
	set.add(new User("Mary", 10));
	set.add(555);

	Iterator iterator = set.iterator();
	while(iterator.hasNext()){
	System.out.println(iterator.next());
	}
}

//LinkedHashSet的使用特点
@Test
public void test2(){
	HashSet set = new LinkedHashSet();
	set.add(345);
	set.add(345);
	set.add("NN");
	set.add("MM");
	set.add(new User("Mary", 10));
	set.add(new User("Mary", 10));
	set.add(555);

	Iterator iterator = set.iterator();
	while(iterator.hasNext()){
	System.out.println(iterator.next());
	}
}

/**
 * 1、向TreeSet中添加的数据,要求是相同类的对象
 * 2、两种排序的方式:自然排序(实现comparable接口) 和 定制排序(实现comparator接口)
 * 3、自然排序中,比较两个对象是否相同标准为:compareTo()返回0,不再是equals()
 */
@Test
public void test3(){
	TreeSet set = new TreeSet();

	//错误:TreeSet添加的数据所在类的对象必须同一个对象
	//set.add(12);
	//set.add("AA");
	//set.add(34);

	//示例一:同一个整型对象
	//set.add(12);
	//set.add(2);
	//set.add(-12);
	//set.add(52);
	//set.add(1);

	//示例二:同一个User对象,实现comparable接口
	set.add(new User("jack", 62));
	set.add(new User("jim", 11));
	set.add(new User("kim", 67));
	set.add(new User("Mary", 52));
	set.add(new User("Mike", 42));
	set.add(new User("Mary", 31));

	Iterator iterator = set.iterator();
	while (iterator.hasNext()){
	System.out.println(iterator.next());
	}
}

HashSet添加元素的过程

集合-5

注意

向Set中添加的数据,其所在的类一定要重写hashCode()和equals()
重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
重写两个方法的技巧:对象中作用equals()方法比较的Field,都应该用来计算hashCode的值

HashSet内存解析示意图
image

Map接口

1、Map特点

Map:双列数据,存储key-value对的数据

Map数据结构的特点

Map中的key:无序的、不可重复的,使用Set存储所有的key --> key所在的类要重写equals()和hashCode()

Map中的value:无序的、可重复的,使用Collection存储所有的value --> value所在的类要重写equals()

一个键值对:key-value构成了一个Entry对象

Map中的entry:无序的、不可重复的,使用Set存储所有的Entry

2、常用实现类

集合-6

HashMap的底层实现:数组 + 链表(jdk7及之前) / 数组 + 链表 + 红黑树 (jdk8)

3、常用方法

集合-7

示例

click me
@Test
public void test3(){
	HashMap map = new HashMap();
	map.put("AA", 123);
	map.put(23, 12);
	map.put("CC", 98);

	//get()
	System.out.println(map.get(11));//null
	//containsKey()
	System.out.println(map.containsKey("ll"));//false
	//containsValue()
	System.out.println(map.containsValue(77));//false

	map.clear();
	//isEmpty()
	System.out.println(map.isEmpty());//true
}

@Test
public void test2(){
	HashMap map = new HashMap();
	//Object put(Object key, Object value)
	map.put("AA", 123);
	map.put(23, 12);
	//put()修改
	map.put("AA", 77);
	map.put("CC", 98);
	System.out.println(map);//{AA=77, CC=98, 23=12}

	HashMap map2 = new HashMap();
	map2.put("DD", "jack");
	map2.put("FF", 99);
	//void putAll(Map m)
	map.putAll(map2);
	System.out.println(map);//{AA=77, CC=98, DD=jack, FF=99, 23=12}

	//Object remove(Object key)
	Object val = map.remove("LL");
	System.out.println(val);//null
	System.out.println(map);

	//clear()
	map.clear();//不等同于map=null;
	System.out.println(map.size());//0
	System.out.println(map);//{}
}

4、Map集合的遍历方式

@Test
public void test4(){
    HashMap map = new HashMap();
    map.put("AA", 123);
    map.put(23, 12);
    map.put("CC", 98);

    //keySet():遍历所有的key集
    for (Object key : map.keySet()) {
        System.out.println(key);
    }

    //values():遍历所有的value集
    for (Object value : map.values()) {
        System.out.println(value);
    }

    //entrySet():遍历所有的key-value构成Set集合
    //方式一:
    Set set = map.entrySet();//entrySet集合中的元素都是entry
    Iterator iterator = set.iterator();
    while (iterator.hasNext()){
        Map.Entry entry = (Map.Entry) iterator.next();
        System.out.println(entry.getKey() + "===" + entry.getValue());
    }

    //方式二:for (Map.Entry<Integer, String> entry : map.entrySet()){}
    for (Object obj: map.entrySet()) {
        Map.Entry entry = (Map.Entry) obj;
        System.out.println(entry.getKey() + "--->" + entry.getValue());
    }

    //方式三:
    Set set1 = map.keySet();
    Iterator iterator1 = set1.iterator();
    while (iterator1.hasNext()){
        Object key = iterator1.next();
        Object value = map.get(key);
        System.out.println(key + "+++" + value);
    }
}

5、LinkeedHashMap的使用

继承了HashMap类,使用HashMap的put()方法,并重写了newNode(),在newNode()中重新new了一个Entry()方法,该方法又继承HashMap.Node<K,V>

内部源码区别

集合-8

示例

@Test
public void test1(){
    LinkedHashMap<Integer, String> map = new LinkedHashMap<>();
    map.put(456,"aa");
    map.put(123,"mm");
    map.put(789,"nn");
    for(Map.Entry<Integer,String> entry: map.entrySet()){
        System.out.println("k=" + entry.getKey() + ", v=" + entry.getValue());
    }
}
//结果
k=456, v=aa
k=123, v=mm
k=789, v=nn

6、TreeMap排序

按照key的值,进行自然排序、定制排序

7、Properties的使用

常用来处理配置文件,key-value都是String类型

public class PropertiesTest {
    public static void main(String[] args) {
        FileInputStream file = null;
        try {
            Properties pros = new Properties();
            file = new FileInputStream("jdbc.properties");
            pros.load(file);
            String name = pros.getProperty("name");
            String password = pros.getProperty("password");
            System.out.println("name=" + name + " ,password=" + password);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                file.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Collections工具类

1、特点

可以操作List、Set、Map集合的工具类

2、常用方法

集合-9

示例

/**
 * Collections类中提供了多个 synchronizedXxx()方法,
 * 该方法可将指定集合包装成线程同步的集合,从而可以解决多线程
 * 并发访问集合时的线程安全问题
 */
@Test
public void test2(){
    ArrayList list = new ArrayList();
    list.add(123);
    list.add(99);

    //返回list1即为线程安全的List
    List list1 = Collections.synchronizedList(list);

    HashMap map = new HashMap();
    map.put(1, "AA");
    map.put(2, "BB");
    //返回map1即为线程安全的Map
    Map map1 = Collections.synchronizedMap(map);

}

@Test
public void test1(){
    ArrayList list = new ArrayList();
    list.add(123);
    list.add(99);
    list.add(3);
    list.add(36);
    list.add(-3);
    list.add(0);

    //反转
    Collections.reverse(list);
    //随机排序
    Collections.shuffle(list);
    //自然排序
    Collections.sort(list);
    //定制排序
    Collections.sort(list, new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            return -Integer.compare((int)o1, (int)o2);
        }
    });

    //交换
    Collections.swap(list, 1,2);
    System.out.println(list);

    //获取最大值、最小
    System.out.println(Collections.max(list));//123
    System.out.println(Collections.min(list));//-3

    //元素出现的次数
    System.out.println(Collections.frequency(list, 3));//1

    //从src集合复制到dest
    List list1 = Arrays.asList(new Object[list.size()]);
    System.out.println(list1);//[null, null, null, null, null, null]

    Collections.copy(list1, list);
    System.out.println(list1);//[123, 3, 99, 36, -3, 0]
}

ArrayList和HashMap都是线程不安全的,若要求线程安全操作,使用Collections.synchronizedXxx(Xxx)进行转换

其它拓展

1、ArrayList的底层实现原理解析

集合-11

2、LinkedList的源码分析

LinkedList list = new LinkedList();//内部声明了Node类型的first和last属性,默认值为null

list.add(333);//将数据333封装到Node中,创建Node对象

//其中Node定义为:Node的特点体现了双向链表
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

3、Vector的底层实现分析

通过Vector()构造器创建对象,底层创建了长度为10的数组elementData;在扩容方面,默认扩容为原来数组长度的2倍

4、HashMap的底层实现原理解析

HashMap数据结构示意图

HashMap底层数组结构示意图

HashMap数据添加过程

集合-10

说明:

关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储

在不断的添加过程中,涉及到扩容问题,当超出临界值(且要存放的位置为非null),扩容:默认的扩容方式:扩容为原来的2倍,并将原有的数据复制过来

jdk8与jdk7在底层实现方面的不同:

1、new HashMap():底层没有创建一个长度为16的数组

2、jdk8底层的数组是:Node[],而非(jdk7)Entry[]

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

4、jdk7底层结构为: 数组+链表。jdk8中底层结构:数组+链表+红黑树

在形成链表结构时,七上八下(在jdk7中链表上新的元素指向旧的元素,在jdk8中旧的元素指向新的元素)

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

源码常量词:

DEFAULT_INITIAL_CAPACITY: HashMap的默认容量:16

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

DEFAULT_LOAD_FACTOR: HashMap的默认加载因子:0.75

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

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

MIN_TREEIFY_CAPACITY: 桶中的Node被树化时最小的hash表容量。

(当桶中Node的数量大到需要红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行resize扩容操作,这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍)

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

entrySet:存储具体元素的集

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

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

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

loadFactor:填充因子

5、HashMap 与 Hashtable的异同

HashMap是Map接口的主要实现类,其线程不安全的,检索效率高,可以存储null的key和value

Hashtable是Map接口旧的实现类,其线程安全,效率低,不可以存储null的key和value

6、加载因子(LoadFactor)大小对HashMap的影响

加载因子

posted @ 2022-11-14 21:46  Bingeone  阅读(21)  评论(0编辑  收藏  举报