AI智能
改变未来

Java基础(六)——集合


一、概述

1、介绍

  为什么出现集合?
  答:面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,对对象进行存储,集合就是存储对象最常用的一种方式。
  数组和集合类同是容器,有何不同?
  答:数组虽然也可以存储对象,但长度是固定的,集合长度是可变的。数组中可以存储基本数据类型,集合中只能存储对象(引用类型,基本类型的包装类型)。
  为什么会出现这么多的容器呢?
  答:因为每一个容器对数据的存储方式都有不同。这个存储方式称之为:数据结构。
  什么是迭代器呢?
  答:其实就是集合的取出元素的方式。

2、集合框架

  简图:

  Collection:

  Map:

二、Collection<E>(jdk1.2)

  集合中存储的都是对象的引用(地址)。

1、常用方法

  add(Object obj):添加。
  addAll(Collection coll):添加。
  int size():获取有效元素的个数。
  void clear():清空集合。
  boolean isEmpty():是否是空集合。
  boolean contains(Object obj):是否包含某个元素,通过元素的equals方法来判断是否是同一个对象。
  boolean containsAll(Collection c):也是调用元素的equals方法来比较的。两个集合的元素挨个比较。

  boolean remove(Object obj) :删除,通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素。
  boolean removeAll(Collection coll):删除,取当前集合的差集。
  boolean retainAll(Collection c):取交集,把交集的结果存在当前集合中,不影响c。
  boolean equals(Object obj):集合是否相等。
  Object[] toArray():转成对象数组。
  hashCode():获取集合对象的哈希值。
  iterator():返回迭代器对象,用于集合遍历。

  代码示例:基本方法

public class Main {public static void main(String[] args) {Collection<Object> coll = new ArrayList<>();coll.add(\"AA\");coll.add(\"BB\");coll.add(123); // 自动装箱coll.add(new Date());System.out.println(coll.size());  // 4Collection<Object> coll1 = new ArrayList<>();coll1.add(456);coll1.add(\"CC\");coll1.add(123);coll.addAll(coll1);System.out.println(coll);coll.clear(); // 清空集合元素System.out.println(coll.isEmpty()); // true}}

  代码示例:初始化类

// 预先定义的实体类public class Person {private String name;private int age;// 无参构造器// 有参构造器// getter & setter// toString()@Overridepublic boolean equals(Object o) {System.out.println(\"Person equals()....\");if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return age == person.age &&Objects.equals(name, person.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}}// 初始化的集合.下面的代码示例,集合都是这个.public Collection<Object> init() {Collection<Object> coll = new ArrayList<>();coll.add(123);coll.add(456);coll.add(new Person(\"Jerry\", 20));coll.add(\"Tom\");coll.add(false);return coll;}
public void method1() {System.out.println(coll.hashCode());// 判断当前集合中是否包含obj// 想要按 Person 对象的属性来比较,需要复写 equals(Object o)// false --> trueSystem.out.println(coll.contains(new Person(\"Jerry\", 20)));// 当且仅当形参中的所有元素都存在于当前集合中.true// trueSystem.out.println(coll.containsAll(Arrays.asList(123, 456)));coll.remove(1234);coll.remove(new Person(\"Jerry\", 20));// 差集:A - Bcoll.removeAll(Arrays.asList(123, 4567));System.out.println(coll);// 两个集合完全相同,包括顺序相同(这个又具体子类决定)// System.out.println(coll.equals(coll1));// 交集coll.retainAll(Arrays.asList(123, 666));System.out.println(coll);}// 结果-1200490100Person equals()....Person equals()....Person equals()....truetruePerson equals()....Person equals()....Person equals()....[456, Tom, false][]

  结果分析:
  ①判定一个集合中是否包含某个对象,contains(Object o)方法,会调用equals(Object o)去比较,所以需要复写equals(Object o)方法。
  ②对象Person是第3个被加入到集合中的,比较的时候需要一个一个比较,所以equals()方法被调用了3次。
  ③remove(Object o)时同样equals()方法被调用了3次。

  代码示例:集合 <–> 数组

public void method2() {// 集合 --> 数组final Object[] objects = coll.toArray();final Object[] array = coll.toArray(new Object[0]);// 数组 --> 集合final List<String> strings = Arrays.asList(\"A\", \"B\", \"C\");final List<Integer> integers = Arrays.asList(123, 456);}

2、Iterator<E>(迭代器)

  GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于\”公交车上的售票员\”、\”火车上的乘务员\”、\”空姐\”。
  一种集合的取出元素的方式。定义在集合的内部,用于直接访问集合内 部的元素,是一个内部类。集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。Iterator主要用于遍历Collection,不包括map。
  用foreach遍历Collection,底层还是使用的迭代器。迭代器原理:

  JDK8源码:

  代码示例:迭代器的使用

// 注意:这里是迭代器里的remove,不是集合里面的。public void method3() {Iterator<Object> iterator = coll.iterator();while (iterator.hasNext()) {// next():①指针下移 ②将下移以后集合位置上的元素返回System.out.println(iterator.next());}// 返回一个全新的迭代器iterator = coll.iterator();while (iterator.hasNext()) {// iterator.remove(); 报错IllegalStateExceptionfinal Object next = iterator.next();if (\"Tom\".equals(next)) {iterator.remove();// iterator.remove(); 报错IllegalStateException}}}

3、List<E>、Queue<E>、Set<E>(重点)

  List<E>元素是有序的,可重复,因为该集合体系有索引。实现类:
  ArrayList:底层使用的数组数据结构。特点:查询速度很快,但是增删稍慢(会移动后面的元素)。初始长度10,50%延长。线程不同步的。jdk1.2
  LinkedList:底层使用的链表数据结构。特点:增删速度很快,查询稍慢。线程不同步的。
  Vector:底层使用的数组数据结构。初始长度10,100%延长。线程同步的。后被ArrayList替代了。jdk1.0

  Queue<E>有序的,可重复。实现类:
  Deque<E>(接口):
  LinkedList:底层使用的链表数据结构。特点:增删速度很快,查询稍慢。线程不同步的。

  Set<E>元素是无序的,不重复。没有索引。实现类:
  HashSet:底层使用的HashMap(数组+链表)数据结构。无序的,不可重复,线程不同步。可以存储null值。
  TreeSet:底层使用的TreeMap(排序二叉树,红黑树)数据结构。可以对set集合中的元素按指定属性进行排序。有序的。查询速度比List快。

三、List<E>

1、ArrayList<E>

  代码示例:迭代器的使用

// 其他常用方法不演示public class Main {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add(\"A\");list.add(\"B\");list.add(\"C\");list.add(\"D\");final Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {System.out.print(iterator.next());}}}// 结果// A B C D

2、Vector<E>

  枚举就是Vector特有的取出方式,和迭代器很像,其实是一样的,由于名称过长,后被迭代器取代了。

public static void main(String[] args) {Vector<String> vector = new Vector<>();vector.add(\"A\");vector.add(\"B\");// 返回此向量的组件的枚举.final Enumeration<String> elements = vector.elements();while (elements.hasMoreElements()) {System.out.println(elements.nextElement());}}// 结果// A B

3、Stack<E>(栈)

// 先进后出public class Main {public static void main(String[] args) {Stack<String> stack = new Stack<>();// 压栈stack.push(\"A\");stack.push(\"B\");stack.push(\"C\");while (!stack.empty()) {// 从栈顶弹出一个.会删除元素System.out.println(stack.pop());}// 从栈顶弹出一个.不会删除元素// stack.peek();}}// 结果C B A

四、Queue<E>

1、Queue<E>(队列)

// 先进先出public class Main {public static void main(String[] args) {Queue<Integer> queue = new LinkedList<>();// 添加.入队queue.offer(1);queue.offer(10);queue.offer(5);queue.offer(9);System.out.println(queue);// 出队final Integer poll = queue.poll();System.out.println(poll);System.out.println(queue);// 获取.队头final Integer peek = queue.peek();System.out.println(peek);System.out.println(queue);}}// 结果[1, 10, 5, 9]1[10, 5, 9]10[10, 5, 9]

2、Deque<E>(双端队列)

3、LinkedList<E>(双向链表)

  对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高。

  特有方法
  addFirst():在第一个位置添加元素。
  addLast()
  getFirst():获取第一个元素,但不删除元素。若没有,出现异常。
  getLast()
  removeFirst():获取元素,但是删除元素。若没有,出现异常。
  removeLast()

  在jdk1.6以后出现了替代方法。
  offerFirst():在第一个位置添加元素。
  offerLast()
  peekFirst():获取元素,但不删除元素。若没有,返回null。
  peekLast()
  pollFirst():获取元素,但是删除元素。若没有,返回null。
  pollLast()

五、Set<E>

1、介绍

  Set<E>元素是不重复。没有索引。实现类:
  HashSet:底层使用的HashMap(数组+链表)数据结构。无序的,不可重复,线程不安全。可以存储null值。
  LinkedHashSet:底层使用的 LinkedHashMap。无序的,不可重复。
  TreeSet:底层使用的TreeMap(排序二叉树,红黑树)数据结构。可以对set集合中的元素按指定属性进行排序。有序的。查询速度比List快。

  以HashSet为例理解无序、不重复:
  无序的:不等于随机性(并不是说打印的顺序没有按照添加的顺序),是指的存储的数据在底层数组中并非按照数组索引的顺序依次添加,而是根据数据的哈希值确定的索引位置。
  不重复:调用对象equals()方法保证相同元素只添加一次。
  要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()。重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码。

  重写 hashCode() 方法的基本原则:
  (1)程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
  (2)当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等。
  (3)对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

  重写 equals() 方法的基本原则:
  当一个类有自己特有的\”逻辑相等\”概念,需要重写equals()的时候,总是要重写hashCode(),根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode()方法,它们仅仅是两个对象。因此,违反了\”相等的对象必须具有相等的散列码\”。
  结论:复写equals方法的时候一般都需要同时复写hashCode方法。通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算。

2、HashSet<E>

  HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找和删除性能。
  底层数据结构:看JDK8的源码,HashSet底层是一个HashMap。而HashMap底层是数组+链表(7),数组+链表+红黑树(8)。
  元素添加过程:由于HashSet底层就是HashMap,所以它的原理只需要了解HashMap即可。

// API使用public class Main {public static void main(String[] args) {Set<String> set1 = new HashSet<>();Set<String> set2 = new HashSet<>();set1.add(\"a\");set1.add(\"b\");set1.add(\"c\");set2.add(\"c\");set2.add(\"d\");set2.add(\"e\");// 取交集set1.retainAll(set2);System.out.println(set1); // [c]// 取并集// set1.addAll(set2);// System.out.println(set1); // [a, b, c, d, e]// 取差集// set1.removeAll(set2);// System.out.println(set1); // [a, b]}}

3、LinkedHashSet<E>

  作为HashSet的子类,LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置(依然是无序的), 但它同时使用双向链表维护元素的添加次序,这使得元素看起来是以插入顺序保存的。也可以按照添加的顺序遍历。
  对于频繁的遍历操作,LinkedHashSet效率高于HashSet。LinkedHashSet插入性能略低于 HashSet。
  代码示例:

public class Main {public static void main(String[] args) {Set set = new LinkedHashSet();set.add(\"AA\");set.add(456);set.add(456);set.add(new User(\"刘德华\", 60));Iterator iterator = set.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}}}// 结果:按照添加的顺序遍历.AA456User{name=\'刘德华\', age=60}// 若Set set = new HashSet();则遍历不按照添加的顺序

  底层结构:

4、TreeSet<E>

  底层是排序二叉树,所以①必须是相同类型的对象。②必须可排序。自然排序(实现Comparable接口)和定制排序(Comparator)。
  自然排序中,判断两个对象是否相同的标准为:compareTo()返回0,不是equals()。
  定制排序中,判断两个对象是否相同的标准为:compare()返回0。不是equals()。
  若compareTo始终返回0,则add始终只有一个元素。因为后面的都认为与前面的相同,因此没有加入进来。判断两个对象是否相等的标准是compareTo返回值相同。
  底层数据结构:看JDK8的源码,TreeSet底层是一个TreeMap。采用红黑树的存储结构。有序的。查询速度比List快。
  元素添加过程:
  代码示例:让User具有可比性,自然排序:Comparable

public class User implements Comparable<User> {private String name;private int age;// 无参构造器// 有参构造器// getter & setter// toString()// 可以不复写equals、hashCode@Overridepublic int compareTo(User user) {System.out.println(this.name + \"-------------\" + user.getName());if (this.age > user.age) {return 1;}if (this.age < user.age) {return -1;}return 0;}}// 测试类public static void main(String[] args) {TreeSet<User> treeSet = new TreeSet<>();treeSet.add(new User(\"java1\", 30));treeSet.add(new User(\"java2\", 20));treeSet.add(new User(\"java3\", 31));treeSet.add(new User(\"java4\", 60));final Iterator<User> iterator = treeSet.iterator();while (iterator.hasNext()) {final User user = iterator.next();System.out.println(user.getName() + \"---------\" + user.getAge());}}// 结果java1-------------java1 // 第一次java2-------------java1 // 2和1比,2在左边java3-------------java1 // 3和1比,3在右边java4-------------java1 // 4和1比,4在右边java4-------------java3 // 4和3比,4在右边java2---------20java1---------30java3---------31java4---------60

  结果分析:不难理解打印结果的比较次数。

  代码示例:指定比较器,定制排序:Comparator

// 比较器public static void main(String[] args) {final Comparator<User> comparator = new Comparator<User>() {// 按照年龄从小到大排列@Overridepublic int compare(User o1, User o2) {return Integer.compare(o1.getAge(), o2.getAge());}};TreeSet<User> treeSet = new TreeSet<>(comparator);treeSet.add(new User(\"java1\", 30));treeSet.add(new User(\"java2\", 20));treeSet.add(new User(\"java3\", 31));treeSet.add(new User(\"java4\", 60));treeSet.add(new User(\"java5\", 60));final Iterator<User> iterator = treeSet.iterator();while (iterator.hasNext()) {final User user = iterator.next();System.out.println(user.getName() + \"---------\" + user.getAge());}}// 结果java2---------20java1---------30java3---------31java4---------60

六、Map<K,V>

1、介绍(重点)

  双列数据,存储key-value对的数据。用作键的对象必须实现hashCode方法和equals方法。实现类:
  HashMap:底层使用的(数组+链表7+红黑树8)数据结构。特点:可以存入null键null值。线程不同步,效率高。jdk1.2
  Hashtable:底层使用的(数组+链表)数据结构。特点:不可以存入null键null值。线程同步,效率低。jdk1.0
  TreeMap:底层使用的(排序二叉树,红黑树)数据结构。实现了SortedMap有序序列接口,特点:可以用于给map集合中的键按指定属性进行排序。线程不同步。
  WeakHashMap:底层使用的(数组+链表)数据结构。特点:键是\”弱键\”。

2、HashMap<K,V>

  HashMap中的Entry对象是无序排列的。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

  代码示例:三种遍历方式

public class Main {public static void main(String[] args) {Map<String, String> map = new HashMap<>();map.put(\"A\", \"1\");map.put(\"B\", \"2\");map.put(\"c\", \"3\");// 1.获取键集:keySet()final Set<String> keySet = map.keySet();for (String key : keySet) {System.out.println(\"keySet()----\" + key + \"----\" + map.get(key));}// 2.获取值集:values()final Collection<String> values = map.values();for (String value : values) {System.out.println(\"values()----\" + value);}// 3.获取entry集:entrySet()final Set<Map.Entry<String, String>> entries = map.entrySet();for (Map.Entry<String, String> entry : entries) {System.out.println(\"entrySet()----\" + entry.getKey() + \"----\" + entry.getValue());}}}

3、Hashtable<K,V>

4、TreeMap<K,V>

  向TreeMap中添加key-value,要求key①必须是相同类型的对象。②必须可排序。用于给map集合中的键按指定属性进行排序。
  代码示例:让User具有可比性,自然排序:Comparable

public class User implements Comparable<User> {private String name;private int age;// 无参构造器// 有参构造器// getter & setter// toString()// 可以不复写equals、hashCode// 年龄从小到大排列@Overridepublic int compareTo(User user) {System.out.println(this.name + \"---------\" + user.getName());return Integer.compare(this.age, user.age);}}// 测试类public static void main(String[] args) {TreeMap<User, Integer> treeMap = new TreeMap<>();User u1 = new User(\"Tom\", 23);User u2 = new User(\"Jerry\", 32);User u3 = new User(\"Jack\", 20);User u4 = new User(\"Rose\", 18);treeMap.put(u1, 98);treeMap.put(u2, 89);treeMap.put(u3, 76);treeMap.put(u4, 100);for (Map.Entry<User, Integer> entry : treeMap.entrySet()) {System.out.println(entry.getKey() + \"---->\" + entry.getValue());}}// 结果Tom---------TomJerry---------TomJack---------TomRose---------TomRose---------JackUser{name=\'Rose\', age=18}---->100User{name=\'Jack\', age=20}---->76User{name=\'Tom\', age=23}---->98User{name=\'Jerry\', age=32}---->89

  结果分析:按键 User 指定属性 age 排序,不难理解打印结果的比较次数。排序二叉树:

  代码示例:指定比较器,定制排序:Comparator

// 比较器public static void main(String[] args) {TreeMap<User, Integer> treeMap = new TreeMap<>(new Comparator<User>() {@Overridepublic int compare(User u1, User u2) {return Integer.compare(u1.getAge(), u2.getAge());}});User u1 = new User(\"Tom\", 23);User u2 = new User(\"Jerry\", 32);User u3 = new User(\"Jack\", 20);User u4 = new User(\"Rose\", 18);treeMap.put(u1, 98);treeMap.put(u2, 89);treeMap.put(u3, 76);treeMap.put(u4, 100);for (Map.Entry<User, Integer> entry : treeMap.entrySet()) {System.out.println(entry.getKey() + \"---->\" + entry.getValue());}}// 结果.和自然排序的结果一致.User{name=\'Rose\', age=18}---->100User{name=\'Jack\', age=20}---->76User{name=\'Tom\', age=23}---->98User{name=\'Jerry\', age=32}---->89

5、Properties

  常用来处理配置文件。key和value都是String类型。
  代码示例:读取属性文件

// jdbc.propertiesname=Tompassword=abc123// 未关闭资源public static void main(String[] args) throws Exception {Properties pros = new Properties();FileInputStream fis = new FileInputStream(\"jdbc.properties\");pros.load(fis);String name = pros.getProperty(\"name\");String password = pros.getProperty(\"password\");System.out.println(\"name = \" + name + \", password = \" + password);}// 结果name = Tom, password = abc123

6、ConcurrentHashMap<K,V>

  请查看标签:JDK源码。

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Java基础(六)——集合