Java基础入门之集合容器篇

一、集合

面试题
https://blog.csdn.net/ThinkWon/article/details/104588551

1.1 集合的概述

集合是指存储多个对象,比数组结构复杂,功能更加强大的结构。

  • 集合由来

    • 数组的长度是固定的,去做元素的增加和删除时,效率非常低下。而集合的长度是可变的。
  • 集合和数组的区别

    • 数组既可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型,会自动将基本数据类型转换为包装类
    • 数组的长度是固定的;集合的长度是可变的
  • 集合体系

在这里插入图片描述

在这里插入图片描述

1.2 Collection接口的常用功能

  • 方法说明

add
remove
clear
contains
size
isEmpty

1.3 Collection集合遍历之转数组

  • 遍历集合,并将姓名为"张三"的学生对象从集合中移除
  • 代码实现
public class Demo10 {
	
	public static void main(String[] args) {
		//创建Collection对象
		Collection c = new ArrayList();
		//添加元素
		c.add(new Student("张三", 18));
		c.add(new Student("张三", 18));
		c.add(new Student("李四",19));
		c.add(new Student("王五", 20));
		//将集合转换为数组
		Object[] array = c.toArray();
		//强制类型转换:必须要有继承关系

		//错误做法!!!!
//		Student[] stus = (Student[]) array;
//		for (int i = 0; i < stus.length; i++) {
//			Student stu = stus[i];
//			System.out.println("name : " + stu.getName() + " ,age : " + stu.getAge());
//		}
		
		for (int i = 0; i < array.length; i++) {
			Student stu = (Student) array[i];
			if ("张三".equals(stu.getName())) {
				boolean remove = c.remove(stu);
//				System.out.println(remove);
			}
//			System.out.println("name : " + stu.getName() + " ,age : " + stu.getAge());
		}
		array = c.toArray();
		
		for (int i = 0; i < array.length; i++) {
			Student stu = (Student) array[i];
			System.out.println("name : " + stu.getName() + " ,age : " + stu.getAge());
		}
	}

}

1.4 Collection接口遍历之迭代器

  • 代码实现
public class Demo11 {

	public static void main(String[] args) {
		Collection c = new ArrayList();
		// 添加元素
		c.add(new Student("张三", 18));
		c.add(new Student("张三", 18));
		c.add(new Student("李四", 19));
		c.add(new Student("王五", 20));
		// 获取对应的迭代器
		// hashNext
		// next
		// remove
		Iterator iterator = c.iterator();
		
		while (iterator.hasNext()) {
			Object object = iterator.next();
			System.out.println(object);
		}
	}

}

1.5 增强for循环

  • 概述

for循环的语法糖
底层由迭代器支持

  • 语法
for(数据类型 变量名 : 集合/数组){
	
}
  • 代码实现
public class Demo12 {

	public static void main(String[] args) {
		Collection c = new ArrayList();
		// 添加元素
		c.add(new Student("张三", 18));
		c.add(new Student("张三", 18));
		c.add(new Student("李四", 19));
		c.add(new Student("王五", 20));
		//增强for循环
		for(Object obj : c){//obj就是c中的每一个元素
			Student stu = (Student) obj;
			System.out.println("name : " + stu.getName() + " ,age : " + stu.getAge());
		}
		
	}
}

注意:仅适用于集合的遍历

1.6 List子接口及其实现类

List子接口具备有以下特点:
有序
有下标
元素可以重复

  • ArrayList

ArrayList是List接口最常用的一个实现类。特点是底层以数组的方式实现。
特点是:可以使用下标访问,遍历速度快,添加,删除元素性能较差,需要扩容,复制。
非线程安全

  • 简单使用
public static void main(String[] args) {
    ArrayList list = new ArrayList<>(); // 创建一个集合
    list.add("hello"); // 添加元素
    list.add(2); // 实际添加的是包装类的对象,而不是int,因为集合不能添加基本数据类型
    Student stu = new Student("1", "张三", 20);
    list.add(stu);
    Integer in = (Integer)list.get(1); // 根据下标获取数据
    System.out.println(in);
    System.out.println(list.get(2));
    System.out.println("集合元素个数:" + list.size());
    // 删除元素
    list.remove(0); // 根据下标删除
    System.out.println("集合元素个数:" + list.size());
    // 直接覆盖某个下标上的元素
    list.set(1, 3); 
    // 遍历元素
    for (int i = 0; i < list.size(); i++) {
        System.out.println(list.get(i));
    }
    // 判断集合是否为空
    System.out.println(list.isEmpty());
    // 判断集合是否包含元素
    System.out.println(list.contains(3));
    // 创建集合
    ArrayList list1 = new ArrayList<>();
    // 创建一个新的对象,此对象与上面的stu内容一致
    Student stu1 = new Student("1", "张三", 20);
    list1.add(stu1); // 添加元素stu1
    //判断集合是否包含元素stu,通过该对象的equals方法判断
    System.out.println("是否包含stu:" + list1.contains(stu)); 
    // 清空集合
    list1.clear();
    // 通过截取一个集合的部分元素形成一个新的集合
    List list2 = list.subList(0, 1); // 截取0号元素到1号元素
    for (int i = 0; i < list2.size(); i++) {
        System.out.println(list2.get(i));
    }
}

原理部分:
1、创建对象时,创建长度为多少的数组?
如果通过无参构造方法创建,则创建长度为0的数组,如果通过有参构造方法,int类型的参数的大小即为创建数组的长度。

transient Object[] elementData; // ArrayList中存放对象的数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 空数组
private static final Object[] EMPTY_ELEMENTDATA = {}; // 空数组

// 无参构造方法,指定当前数组为空数组
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 有参构造方法指定当前数组长度为传入的长度
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

2、无参构造创建的空数组,第一次添加元素,会如何扩容?
直接扩容为10个大小。
如果添加元素到达数组能存放的最大大小时,如何扩容?
扩容1/2,最终大小为原来的1.5倍

private int size; // 当前数组中存放的元素个数

private static final int DEFAULT_CAPACITY = 10; // 默认指定的大小

public int size() {
    return size;
}

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 扩容
    elementData[size++] = e; // 将元素放入到数组中
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    // 如果是通过无参构造方法创建的集合,并且第一次添加元素,直接指定大小默认大小(10个)
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    // 扩容方法
    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0) // 判断指定的大小是否比数组大小大
        grow(minCapacity); // 空间扩容
}

/*
扩容并复制元素
*/
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 每次扩容扩大1/2,即最终大小为原来的1.5倍
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0) // 如果超过保留大小,则直接指定为int的最大值
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity); // 复制元素
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}
  • Vector

使用与ArrayList基本一样。线程安全,效率低。现在基本被废弃。

  • LinkedList

数组和链表:
数组特点是遍历性能好,添加,删除性能差。
而链表结构与数组相反,添加、删除性能好,但是遍历性能相对差。(单向和双向)

基本用法:

public static void main(String[] args) {
    LinkedList list = new LinkedList();
    // 链表结构向头尾添加性能最优
    list.add("hello"); // 直接使用add会自动添加到尾部,相当于addLast
    list.addLast("world");// 向尾部添加
    list.getLast(); // 得到尾部的元素
    list.getFirst(); // 得到头部的元素
    // 通过下标查找,性能不好,需要循环遍历
    // 代码有相应的优化,即如果该下标小于中间值,则从头开始向下找
    // 如果该下标大于中间值,则从尾部开始向上找
    list.get(1); // 得到第一号元素
    list.remove(1); // 根据下标删除
    list.removeLast();// 删除最后一个
}

总结:
LinkedList会有一个first,即头节点,last即尾节点。
每次向头部添加元素,只需要构造一个节点对象,并将该节点的next指向原list的first,并将list的first指向当前节点即可。
每次向尾部添加元素,只需要构造一个节点对象,并将原list的last节点的next指向该节点,并将该节点的prev指向原list的last,并将list的last指向当前节点即可。

四、泛型

泛型的作用,主要是限定在使用某些类或者方法时,对应类型。
例如,泛型集合。在使用泛型创建的集合中,该集合必须使用该泛型类型的元素,获取时不需要强制转换。

  • 自定义泛型:
public class MyList<E> {
	public void m(E e) {
		
	}
}
  • 集合泛型的使用:
public static void main(String[] args) {
    // 当没有使用泛型时,向集合中添加元素时,可以添加任意类型
    // 当从集合中取出元素时,需要转换成相应的类型
    ArrayList list = new ArrayList();
    list.add("hello");
    list.add(2); // Integer
    Integer in = (Integer) list.get(1);
    for (int i = 0; i < list.size(); i++) {
        Object obj = list.get(i);
    }

    // 对集合使用泛型
    // 通过泛型要求该集合只能使用String类型
    // 添加了泛型后获取元素不用强制转换
    ArrayList<String> list1 = new ArrayList<>();
    list1.add("hello");
    list1.add("world");
    String str = list1.get(1);
    System.out.println(str);

    // foreach循环
    for (String string : list1) {
        System.out.println(string);
    }

    MyList<String> my = new MyList<>();
    my.add("hello");
}

面试题:为什么说Java里的泛型是伪泛型?
Java中的泛型主要是编译前的代码检查和赋值时类型的改变,在实际运行时并没有泛型。

五、Collections工具类

Collections工具类是针对集合的操作编写的工具类。可以对集合进行排序、倒置、打乱等操作。
注意:如果集合元素是自定义的元素,排序之前需要实现java.lang.Comparable接口。

public static void main(String[] args) {
    // 直接将几个元素一次性放入一个集合中
    List<String> list = Arrays.asList("hello", "world", "test", "aaa");
    // 倒置
    Collections.reverse(list);
    System.out.println(list);
    // 随机打乱
    Collections.shuffle(list);
    System.out.println(list);
    // 排序
    Collections.sort(list);
    System.out.println(list);


    List list1 = Arrays.asList(
        new Product(5, "牙膏"),
        new Product(4, "牙刷"),
        new Product(1, "毛巾"),
        new Product(2, "杯子"),
        new Product(3, "衣架")
    );
    // 排序
    // 如果集合中是自定义元素,需要实现java.lang.Comparable接口
    Collections.sort(list1);
    System.out.println(list1);
}
public class Product implements Comparable<Product>{
	private int id;
	private String name;
	// 省略getter、setter、构造、等方法的重写
    
	@Override
	public int compareTo(Product o) {
		if(o != null) {
			if(this.id > o.id) {
				return 1;
			}else if(this.id < o.id){
				return -1;
			}else {
				return 0;
			}
		}
		return 0;
	}
}

六、Map接口及其实现类

map接口使用key-value方式来保存数据。
常见方法:
//添加
put(key, value)
//删除
remove
clear
//判断
containsKey
containsValue
isEmpty
//获取
entrySet
keySet()
valus()
get(key)
size

6.1 HashMap

使用key-value方式存储。线程不安全。

public static void main(String[] args) {
    // 使用key-value的形式存储
    // key键 value值
    // key不能重复,如果重复,值覆盖
    // key可以是任意类型,但是推荐使用字符串
    // key可以为null,但是只能有一个,value可以是null
    HashMap<String, Song> map = new HashMap<>();
    // 添加元素
    map.put("a", new Song("点歌的人", "海来阿木", 200));
    // 取值,如果通过一个没有的名称获取值,返回null
    Song s = map.get("a");
    System.out.println(s.play());
    // 尝试key重复的情况,会覆盖
    map.put("a", new Song("大雨还在下", "邱老板", 200));
    // 取值
    Song s1 = map.get("a");
    System.out.println(s1.play());
    // keySet 键集(所有的键的集合)
    Set<String> keys = map.keySet();
    // 借助键集来循环
    for (String string : keys) {
        System.out.println("key:" + string + ", value:" + map.get(string).play());
    }
    // 值集
    Collection<Song> values = map.values();
    // 循环值集
    for (Song song : values) {
        System.out.println(song.play());
    }
    // 大小
    int size = map.size();
    // 循环键值(使用entrySet)
    for (Entry<String, Song> entry : map.entrySet()) {
        System.out.println("key:" + entry.getKey() + ", value:" + entry.getValue().play());
    }

    // 删除,如果删除一个没有的名称,对应原集合没有任何影响
    map.remove("b");

    // 是否包含key
    map.containsKey("a");
    // 是否包含值
    Song song = new Song("", "", 100);
    map.containsValue(song); // 是通过equals来判断
}

原理:
以数组+链表结构组合而成。既有数组的优点,又有链表的优点。
添加元素时,根据key的hash值,来确定添加到哪一个数组的元素节点后。
查询元素时,根据key的hash值,来提升查找的性能。
添加元素时,当元素的总个数大于64,且单条链表上的元素超过8个时,会将该条链表转换成红黑树(JDK1.8),以进一步提升查找的性能。
扩容因子:0.75
扩容容量x2
即当元素的个数达到容量的0.75时,进行扩容,扩容大小为原来的两倍。

和Collection区别

  • Collection是单列集合,Map是双列集合
  • Collection中的数据结构针对元素有效,Map中的数据结构针对键有效

6.2 Hashtable

用法基本与HashMap相似,但是是线程安全的,key和value都不能为null。性能较差,已基本不用。

6.3 Properties

继承自Hashtable,但是添加限制键值类型为String的操作方法。
setProperty()
getProperty()
作用是为了操作配置文件.properties文件而存在。

  • config.properties
username=zhangsan
password=1234567890
public static void main(String[] args) throws IOException {
    // 存放配置信息
    Properties prop = new Properties();
    prop.setProperty("path", "c:/users/");
    prop.setProperty("username", "root");
    prop.setProperty("password", "root");
    System.out.println(prop.getProperty("password"));
    // 很多时候配置信息是写在工程的某一个文件中
    // 读取properties文件信息
    Properties prop1 = new Properties();
    // 读取配置文件并加载文件中的信息
    InputStream is = MyPropertiesDemo.class.getResourceAsStream("/config.properties");
    prop1.load(is); // 加载信息
    System.out.println(prop1.getProperty("username"));
    System.out.println(prop1.getProperty("password"));
}

6.4 有序、无序和排序

有序是指会按照添加的顺序规律来保存数据。循环输出时会体现该规律。反之则是无序。
会将添加的元素按照指定的排序规则进行排序,取值或循环显示时会按照排序后的结果显示。

6.5 TreeMap

实现了SortedMap接口,要求所有的key应该实现java.lang.Comparable接口,会在添加元素时按照指定的排序规则进行排序。

public static void main(String[] args) {
    // 添加元素时,会按照指定排序规则将添加的元素key进行排序
    TreeMap<Song, String> map = new TreeMap<>();
    map.put(new Song("", "", 200), "zhangsan");
    map.put(new Song("", "", 220), "lisi");
    map.put(new Song("", "", 120), "wangwu");
    map.put(new Song("", "", 60), "zhaoliu");
    Set<Song> keySet = map.keySet();
    for (Song song : keySet) {
        System.out.println(song.play());
    }
}

七、Set接口及其实现类

  • 概述

无序、无下标、元素不重复

7.1 HashSet

  • 元素不重复是使用hashCode来计算的,所以要实现该功能,需要元素对应的类型重写equals和hashCode方法。
  • 添加相同元素,后面添加的会无法添加,保留之前的元素,不会覆盖。
  • HashSet是由哈希表支持的元素不可重复
  • 当哈希码值相同时,执行equals方法比较,否则不执行equals方法比较
  • 基本用法:
public static void main(String[] args) {
    // 无序、元素不能重复
    HashSet<String> set = new HashSet<>();
    set.add("f");
    set.add("a");
    set.add("g");
    set.add("c");
    set.add("c");
    for (String string : set) {
        System.out.println(string);
    }

    // 小技巧:如果遇到需要去重功能,可以将元素放入set中
    // 注意元素是否重复是根据hashCode和equals来判断,需要重写这些方法
    HashSet<Student> set1 = new HashSet<>();
    set1.add(new Student(1, "zhangsan"));
    set1.add(new Student(1, "zhangsan"));
    set1.add(new Student(1, "zhangsan"));
    for (Student student : set1) {
        System.out.println(student);
    }

    System.out.println("=====去重前======");
    // 去重功能
    ArrayList<Student> list = new ArrayList<>();
    list.add(new Student(1, "zhangsan"));
    list.add(new Student(1, "zhangsan"));
    list.add(new Student(2, "lisi"));
    list.add(new Student(2, "lisi"));
    list.add(new Student(3, "wangwu"));
    list.add(new Student(3, "wangwu"));
    for (Student student : list) {
        System.out.println(student);
    }
    System.out.println("=====去重后======");
    HashSet<Student> set2 = new HashSet<>(list);
    for (Student student : set2) {
        System.out.println(student);
    }
}

原理:使用HashMap的key作为set的元素存储空间。

7.2 LinkedHashSet

有序。(添加元素的顺序,使用链表实现<双向>)
LinkedHashSet继承自HashSet,所以具备有HashSet的基本使用方式。
不同的是,实现来自于LinkedHashMap,LinkedHashMap继承自HashMap,加了一个链表来实现了添加有序功能。

public static void main(String[] args) {
    // 有序(插入顺序)、元素不能重复
    LinkedHashSet<String> set = new LinkedHashSet<>();
    set.add("f");
    set.add("a");
    set.add("g");
    set.add("c");
    set.add("c");
    for (String string : set) {
        System.out.println(string);
    }

    // 小技巧:如果遇到需要去重功能,可以将元素放入set中
    // 注意元素是否重复是根据hashCode和equals来判断,需要重写这些方法
    LinkedHashSet<Student> set1 = new LinkedHashSet<>();
    set1.add(new Student(1, "zhangsan"));
    set1.add(new Student(1, "zhangsan"));
    set1.add(new Student(1, "zhangsan"));
    for (Student student : set1) {
        System.out.println(student);
    }

    System.out.println("=====去重前======");
    // 去重功能
    ArrayList<Student> list = new ArrayList<>();
    list.add(new Student(1, "zhangsan"));
    list.add(new Student(1, "zhangsan"));
    list.add(new Student(2, "lisi1"));
    list.add(new Student(2, "lisi2"));
    list.add(new Student(3, "wangwu"));
    list.add(new Student(3, "wangwu"));
    for (Student student : list) {
        System.out.println(student);
    }
    System.out.println("=====去重后======");
    LinkedHashSet<Student> set2 = new LinkedHashSet<>(list);
    for (Student student : set2) {
        System.out.println(student);
    }
}

7.3 TreeSet

元素需要实现java.lang.Comparable接口,会自动将元素排序的集合。
底层是使用TreeMap来实现。
基本使用方法跟HashSet基本一样。

  • 分类
    • 自然排序
    • Comparator排序
  • 排序原理
    • 返回值为0,意味着元素相同
    • 返回值为负数,意味着添加的元素比已经在集合中的元素小
    • 返回值为正数,意味着添加的元素比已经在集合中的元素大
7.3.1 TreeSet自然排序练习
  • 需求
    • 使用TreeSet存储Student对象,先按照age的从小到大排序,当年龄相同时,按照姓名的字典顺序排序
  • 代码实现
    • Student.java
public class Student implements Comparable<Student>{
	
	
	private int id;//身份证号码
	private String name;//姓名
	private int age;

	@Override
	public int compareTo(Student stu) {
		//this : 添加的元素
		//stu  : 已经在集合中的元素
		return this.age - stu.getAge() == 0 ? this.name.compareTo(stu.getName()) : this.age - stu.getAge() ;
	}
	
}
7.3.2 TreeSet比较器排序练习
  • 需求
    • 使用TreeSet存储Person对象,先按照age的从小到大排序,当年龄相同时,按照姓名的字典顺序排序
  • 代码实现
public class Demo19 {

	public static void main(String[] args) {
		// 1,创建TreeSet对象
		TreeSet<Person> ts = new TreeSet<>(new Comparator<Person>() {

			@Override
			public int compare(Person p1, Person p2) {
				//p1 : 添加的元素
				//p2 : 已经在集合中的元素
				return p1.getAge() - p2.getAge() == 0 ? p1.getName().compareTo(p2.getName()) : p1.getAge() - p2.getAge();
			}
		});
		// 2,添加元素
		ts.add(new Person("郑飞宇", 38));
		ts.add(new Person("a老邱", 28));
		ts.add(new Person("b李轩", 28));
		ts.add(new Person("范家伟", 18));
		//3,打印集合
		System.out.println(ts);

	}

}

7.3.3 TreeSet的自然排序和比较器排序的优先级
  • 需求
    • 自然排序:按照年龄的从小到大
    • 比较器排序:按照年龄的从大到小
  • 总结
    • 为什么要有比较器排序?
      • 可以对一些系统类的进行自然排序规则以外的排序

八、总结

  • List、Set、Map三者区别。

List 是可重复集合,Set是不可重复集合,这两个接口都实现了 Collection 父接口。

Map 未实现 Collection接口,而是独立的接口,Map 是一种把键对象和值对象进行映射的集合,它的每一个元素都包含了一对键对象和值对象,Map中存储的数据是没有顺序的,其 key 是不能重复的,它的值是可以有重复的。

List 和 Map 区别?

一个是存储单列数据的集合,另一个是存储键和值这样的双列数据的集合,List 中存储的数据是有顺序的,并且允许重复;Map 中存储的数据是没有顺序的,其 key 是不能重复的,它的值是可以有重复的。

List 和 Set 区别?

list是有序集合

  • ArrayList和linkedList都实现了List接口,ArrayList是线性结构,根据下标对数据进行操作,ArrayList查询快,增删慢。
  • LinkedList是链表结构,增加和删除快,查询慢。

Set是无序的

  • HashSet和TreeSet都实现了Set接口,内容不可重复,HashSet底层是哈希表,通过hashcode和eq来保证数据的一致性。
  • TreeSet底层是二叉树,根据比较的返回值确定元素的一致性。
  • LinkedHashSet:底层是链性+二叉树+哈希表。由链表保证元素有序,哈希表保证元素唯一

List中的数组、链表的实现。

https://blog.51cto.com/10983206/2547330

Set中无序、有序、排序的实现。

https://blog.csdn.net/qq_43298772/article/details/90118530?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2allsobaiduend~default-1-90118530.nonecase&utm_term=set%E6%8E%A5%E5%8F%A3%E5%AE%9E%E7%8E%B0&spm=1000.2123.3001.4430

Map中的无序、有序、排序实现。

https://www.cnblogs.com/chen-lhx/p/8432422.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值