1.数组与集合的区别
数组不是面向对象的,存在明显的缺陷,集合弥补了数组的缺点,比数组更灵活更实用,而且不同的集合框架类可适用不同场合。如下:
(1)数组能存放基本数据类型和对象,而集合类存放的都是对象,集合类不能存放基本数据类型。数组和集合存放的对象皆为对象的引用地址。
(2)数组容易固定无法动态改变,集合类容量动态改变。
(3)数组无法判断其中实际存有多少元素,length只告诉了数组的容量,而集合的size()可以确切知道元素的个数
(4)集合有多种实现方式和不同适用场合,不像数组仅采用顺序表方式
(5)集合以类的形式存在,具有封装、继承、多态等类的特性,通过简单的方法和属性即可实现各种复杂操作,大大提高了软件的开发效率
2.集合的框架结构
我们可以发现一个特点,上述所有的集合类,除了 map 系列的集合,即左边集合都实现了 Iterator 接口,这是一个用于遍历集合中元素的接口,主要hashNext(),next(),remove()三种方法。它的一个子接口 ListIterator 在它的基础上又添加了三种方法,分别是 add(),previous(),hasPrevious()。也就是说如果实现 Iterator 接口,那么在遍历集合中元素的时候,只能往后遍历,被遍历后的元素不会再被遍历到,通常无序集合实现的都是这个接口,比如HashSet;而那些元素有序的集合,实现的一般都是 LinkedIterator接口,实现这个接口的集合可以双向遍历,既可以通过next()访问下一个元素,又可以通过previous()访问前一个 元素,比如ArrayList。
还有一个特点就是抽象类的使用。如果要自己实现一个集合类,去实现那些抽象的接口会非常麻烦,工作量很大。这个时候就可以使用抽象类,这些抽象类中给我们提供了许多
现成的实现,我们只需要根据自己的需求重写一些方法或者添加一些方法就可以实现自己需要的集合类,工作量大大降低。
3.集合详解
(1)List集合
List
是Java集合框架的一个重要接口,它代表一个有序的集合,可以包含重复的元素。List
接口扩展了Collection
接口,除了基本的集合操作外,还提供了按索引位置进行操作的方法,比如添加、删除、修改指定位置的元素,以及获取指定位置的元素。List
接口的主要实现包括:
-
ArrayList:基于动态数组实现,提供了快速的随机访问,但插入和删除元素(尤其是列表中间的位置)相对较慢,因为它可能需要移动后续的元素。当您需要频繁地访问元素,而插入和删除操作较少时,
ArrayList
是一个好选择。 -
LinkedList:基于双向链表实现,每个元素都指向其前一个和后一个元素,这使得插入和删除操作非常高效(特别是在列表的开始和结束位置),但随机访问速度较慢,因为需要从头或尾开始遍历链表。如果您的应用需要大量的插入和删除操作,并且对随机访问的速度要求不高,那么
LinkedList
会更适合。 -
Vector:是早期Java版本中提供的线程安全的动态数组实现,与
ArrayList
相似,但在多线程环境下提供了额外的安全性,不过由于同步带来的开销,性能上通常不如ArrayList
。现代Java开发中,更倾向于使用ArrayList
配合显式同步或使用CopyOnWriteArrayList
等其他线程安全集合来替代Vector
。
List
接口提供的一些常用方法包括:
add(E element)
:在列表末尾添加元素。add(int index, E element)
:在指定位置插入元素。get(int index)
:返回指定位置的元素。set(int index, E element)
:用新元素替换指定位置的元素。remove(int index)
:移除指定位置的元素。size()
:返回列表中的元素数量。- 还有其他诸如
contains
,indexOf
,addAll
,clear
等方法。
使用List
时,根据具体的应用场景和性能需求选择合适的实现类是非常重要的。
(2)Set集合
Set
也是Java集合框架中的一个重要接口,它代表一个不包含重复元素的集合。与List
不同,Set
不保证元素的顺序,即它是无序的。这意味着你不能通过索引来访问Set
中的元素,而且每次遍历时元素的顺序可能都会有所不同。Set
主要用于需要确保所有元素唯一性的场景。Set
接口的主要实现包括:
-
HashSet:这是最常用的
Set
实现之一,它基于哈希表实现,提供了快速的插入、删除和查找操作(平均时间复杂度接近O(1))。为了确保元素的唯一性,存储在HashSet
中的元素必须正确覆写hashCode()
和equals()
方法。如果元素的顺序无关紧要,并且需要高效的性能,HashSet
是一个很好的选择。 -
LinkedHashSet:它继承自
HashSet
,同时维护了一个双向链表来保持元素的插入顺序。这意味着遍历LinkedHashSet
时,元素会按照它们被插入的顺序出现,尽管它仍然是一个集合,不支持通过索引访问。当你需要保持插入顺序的同时避免重复元素时,可以使用LinkedHashSet
。 -
TreeSet:基于红黑树实现,自然排序或自定义比较器排序使
TreeSet
中的元素能够按照升序排列。这意味着TreeSet
不仅可以确保元素的唯一性,还可以提供有序的访问。如果你需要一个既能去重又能排序的集合,TreeSet
是理想的选择。使用TreeSet
同样要求元素正确实现Comparable
接口或在创建集合时提供Comparator
。
Set
接口提供的一些核心方法包括:
add(E element)
:添加元素。如果该元素已经存在于集合中,则不会添加,集合不变。remove(Object o)
:移除指定元素。contains(Object o)
:判断集合是否包含指定元素。size()
:返回集合中的元素数量。clear()
:清空集合中的所有元素。
由于Set
不允许重复元素,所以在添加元素时,如果试图添加的元素与集合中已有的元素相等(根据equals()
方法判断),则添加操作将被忽略。
Set
集合是不允许重复元素的,否则将会引发各种奇怪的问题。那么HashSet
如何判断元素重复呢?
HashSet
需要同时通过equals
和HashCode
来判断两个元素是否相等,具体规则是,如果两个元素通过equals
为true
,并且两个元素的hashCode
相等,则这两个元素相等(即重复)。
package Day0512;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
public class SetDemo2 {
public static void main(String[] args) {
//set集合的特点:
//1.添加元素的顺序不一定与输出顺序相同
//创建一个set集合对象
HashSet hashSet = new HashSet();
//添加元素
hashSet.add("hello");
hashSet.add("world");
hashSet.add("java");
hashSet.add("python");
hashSet.add(new student("Tom", 20));
hashSet.add(new student("Tom", 20));
hashSet.add(new student("Tom", 21));
System.out.println(hashSet);
}
static class student {
private String name;
private int age;
public student(String name, int age) {
this.name = name;
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);
}
}
}
运行结果:
这里我们把name
和age
都相同则视为重复元素。
(3)Map集合
在Java中,Map
接口是一种存储键值对的数据结构。与List
和Set
不同,Map
中的每个元素都由一个唯一的键(Key)和与之关联的值(Value)组成,键用于查找特定的值。这意味着Map
中的键必须是唯一的,但值可以重复。Map
的主要用途是在给定键的情况下快速查找或存储对应的值,它不适合用于序列化数据或者维持元素的插入顺序(虽然某些Map
的实现可以保持插入顺序)。
Map
接口有多种实现,每种实现都有其特点和适用场景:
-
HashMap:这是最常用的
Map
实现,它基于哈希表实现,提供了快速的插入、删除和查找操作(平均时间复杂度接近O(1))。为了高效地工作,作为键的对象需要正确的实现hashCode()
和equals()
方法。如果不需要保证顺序,并且重视性能,HashMap
是一个好选择。 -
LinkedHashMap:它继承自
HashMap
,同时使用双向链表维护了插入顺序。这意味着遍历LinkedHashMap
时,可以按照元素插入的顺序进行。如果你需要保持插入顺序,同时利用到HashMap
的高效性能,可以使用LinkedHashMap
。 -
TreeMap:基于红黑树实现,自然排序或自定义比较器排序使得
TreeMap
中的键值对能够按照键的升序排列。如果你需要一个根据键排序的映射关系,可以选择TreeMap
。使用TreeMap
时,键必须实现Comparable
接口或在创建TreeMap
时提供一个Comparator
。 -
Hashtable:这是一个较老的实现,功能上类似于
HashMap
,但在多线程环境下是线程安全的。然而,由于同步机制,它的性能通常比HashMap
低。现代应用更倾向于使用ConcurrentHashMap
来替代Hashtable
,以获得更好的并发性能。
Map
接口提供的一些核心方法包括:
put(K key, V value)
:添加键值对。如果键已经存在,则旧值会被新值替换。get(Object key)
:根据键获取对应的值。containsKey(Object key)
:判断集合是否包含指定的键。containsValue(Object value)
:判断集合是否包含指定的值。remove(Object key)
:根据键移除对应的键值对。size()
:返回集合中的键值对数量。clear()
:清空集合中的所有键值对。
需要注意的是,从Java 8开始,Map
接口还引入了一些新的默认方法,如forEach
, putIfAbsent
, remove
, replace
, 和compute
系列方法,这些方法增强了对集合的操作能力,使得在处理映射数据时更加灵活。
4.Map和Set集合的关系
1、都有几个类型的集合。HashMap 和 HashSet ,都采 哈希表算法;TreeMap 和 TreeSet 都采用 红-黑树算法;LinkedHashMap 和 LinkedHashSet 都采用 哈希表算法和红-黑树算法。
2、分析 Set 的底层源码,我们可以看到,Set 集合 就是 由 Map 集合的 Key 组成。
5.集合的遍历
(1)增强for循环 for(Obj o:c){syso(o)}
(2)使用iterator , Iterator it=c.iterator;
while(it.hasNext()){Object o = it.next()}