-
什么是集合
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)
-
集合和数组的区别
数组在存储多个数据方面的特点
> 一旦初始化以后,其长度就确定了。
> 数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。比如:String[] arr;int[] arr1;Object[] arr2;只能存储对于定义的类型元素
数组在存储多个数据方面的缺点(集合可以很好的解决)
> 一旦初始化以后,其长度就不可修改。
> 数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
> 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
> 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。
总结
>数组是固定长度的;集合可变长度的。
>数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。
>数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型(其实集合一般存储的数据也是同一类型的)。
-
集合框架(常用集合类)
Collection 单列集合,用来存储一个一个的对象
|——- List 接口:元素按进入先后有序保存,可重复(”动态“数组)
|—————- LinkedList 接口实现类, 底层是链表实现的,增和删比较快,查找和修改比较慢,没有同步, 线程不安全
|—————- ArrayList 接口实现类, 底层是数组实现的, 随机访问,查找和修改快,增和删比较慢, 没有同步,线程不安全
|—————- Vector 接口实现类 底层是数组实现的,同步,线程安全
|———————- Stack 是Vector类的实现类
|——- Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
|—————- HashSet 使用hash表(数组)存储元素
|————————- LinkedHashSet 链表维护元素的插入次序,可以保证存储的元素唯一
|—————-TreeSet 底层是二叉树算法实现,元素排好序
Map接口:双列集合,用来存储一对(key - value)一对的数据 -->高中函数:y = f(x)
|———- Hashtable 接口实现类, 同步, 线程安全
|———- HashMap 接口实现类 ,没有同步, 线程不安全-
|—————–- LinkedHashMap 双向链表和哈希表实现
|—————–- WeakHashMap
|——–- TreeMap 红黑树对所有的key进行排序
|———- IdentifyHashMap
-
Collection接口中的方法的使用
在集合末尾添加元素——boolean add(E e)
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add("aaa");
coll.add(123);//自动装箱
coll.add(new Date());
System.out.println(coll);
}
获取添加的元素的个数——int size()
System.out.println(coll.size());//3
将集合中的元素添加到当前的集合中——boolean addll(Collection c)
Collection coll1 = new ArrayList();
coll1.add("bbb");
coll.addAll(coll1);
System.out.println(coll);//[aaa, 123, Fri Jun 10 15:30:17 CST 2022, bbb]
清空集合中所有的元素——void clear()
coll.clear();
System.out.println(coll);//[]
判断当前集合是否为空——boolean isEmpty()
System.out.println(coll.isEmpty());//true
判断集合中是否包含某元素——boolean contains(Object o)
Collection coll = new ArrayList();
coll.add(123);
coll.add(new String("嘻戏i"));
coll.add(new Person("嘻戏i",31));
System.out.println(coll.contains(123));//true
System.out.println(coll.contains(new String("嘻戏i")));//true
//我们在判断时会调用obj对象所在类的equals()。Person类未重写equals方法则该处为false,反之为true
System.out.println(coll.contains(new Person("嘻戏i",31)));//false -->true
判断某一集合中的所有元素是否都存在于当前集合中——boolean containsAll(Collection c)
Collection coll1 = Arrays.asList(123,"嘻戏i");
System.out.println(coll.containsAll(coll1));//true
从集合中移除某一元素——boolean remove(Object o)
coll.remove(123);
System.out.println(coll);//[嘻戏i, Person{name='嘻戏i', age=31}]
从集合中移除某一集合中所有的元素(差集)——boolean removeAll(Collection c)
coll.removeAll(coll1);
System.out.println(coll);//[Person{name='嘻戏i', age=31}]去除和coll1相同的元素
获取当前集合和另一集合的交集,并返回给当前集合boolean retainAll(Collection c)
Collection coll = new ArrayList();
coll.add(123);
coll.add("嘻戏i");
coll.add(new Person("嘻戏i",31));
coll.add(false);
Collection coll1 = Arrays.asList(123,"嘻戏i","aaa");
coll.retainAll(coll1);
System.out.println(coll);//[123, 嘻戏i]
System.out.println(coll1);//[123, 嘻戏i, aaa]
判断当前集合和形参集合是否相同——boolean equals(Object o)
boolean equals = coll.equals(coll1);
//要想返回true 需要当前集合和形参集合的元素都相同。(因为我们这里使用的是collection子类ArrayList是有序的,所有元素的位置也需要一致)
System.out.println(equals);
返回当前对象的哈希值——int hashcode()
int i = coll.hashCode();
System.out.println(i);
集合转数组——Object[] toArray()
Collection coll = new ArrayList();
coll.add("嘻戏i");
coll.add(123);
coll.add(new Person("嘻戏i",31));
Object[] objects = coll.toArray();
for (int i = 0; i < objects.length; i++) {
System.out.println(objects[i]);
}
数组转集合——调用Arrays类的静态方法asList()
List list = Arrays.asList(new String[]{"aa","bb","cc"});
System.out.println(list);//[aa, bb, cc]
//基本数据类型的数组转化成集合时,会将整个数组默认一个整体
List list1 = Arrays.asList(new int[]{111,222,333});
System.out.println(list1);//[[I@3d82c5f3]
List list2 = Arrays.asList(new Integer[]{111,222,333});
System.out.println(list2);//[111, 222, 333]
遍历集合元素:迭代器——Iterator iterator()
public void test5() {
Collection coll = new ArrayList();
coll.add("嘻戏");
coll.add(123);
coll.add(new Person("嘻戏", 28));
Iterator iterator = coll.iterator();//获取迭代器
while (iterator.hasNext()) { //hasNext():判断是否还有下一个元素
System.out.println(iterator.next()); //next():①指针下移 ②将下移以后集合位置上的元素返回
}
}
-
遍历集合元素的方法:
使用迭代器Iterator
public void test5() {
Collection coll = new ArrayList();
coll.add("嘻戏");
coll.add(123);
coll.add(new Person("嘻戏", 28));
Iterator iterator = coll.iterator();//获取迭代器
//方式一:不推荐
// for (int i = 0; i < coll.size(); i++) {
// System.out.println(iterator.next());
// }
//方式二:推荐
while (iterator.hasNext()) { //hasNext():判断是否还有下一个元素
System.out.println(iterator.next()); //next():①指针下移 ②将下移以后集合位置上的元素返回
}
使用迭代器Iterator的错误方式
下面三种错误方式代码用到的集合
Collection coll = new ArrayList();
coll.add("嘻戏");
coll.add(123);
coll.add(new Person("嘻戏", 28));
错误方式1: 报异常:NoSuchElementException
//方式1.1
Iterator iterator1 = coll.iterator();
while (iterator1.next() != null){
System.out.println(iterator1.next());
}
//方式1.2
System.out.println(iterator.next());
System.out.println(iterator.next());
//报异常:NoSuchElementException
System.out.println(iterator.next());
错误方式2 一直无限循环下去
//集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
while (coll.iterator().hasNext()){
System.out.println(coll.iterator().next());
}
//错误方式3 关于Iterator中的remove()
//如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。
Iterator iterator2 = coll.iterator();
while (iterator2.hasNext()){
// iterator2.remove(); //报异常:IllegalStateException
//正确操作:删除集合中的new Person("嘻戏",28)
Object obj = iterator2.next();
if (new Person("嘻戏",28).equals(obj)){
iterator2.remove();
}
}
iterator2 = coll.iterator();
while (iterator2.hasNext()){
System.out.println(iterator2.next());
}
增强for循环遍历集合元素
Collection coll = new ArrayList();
coll.add(123);
coll.add("嘻戏");
coll.add(new Person("嘻戏",28));
//for(集合元素的类型 局部变量 : 集合对象)
//内部仍然调用了迭代器。
for (Object obj: coll) {
System.out.println(obj);
}
System.out.println("*****************");
//for循环和增加for循环
String[] arr = new String[]{"MM","MM","MM"};
for (int i = 0; i < arr.length; i++) {
// arr[i] = "GG";//改变了字符串arr
}
for (String s :
arr) {
s = "GG";//只改变了局部变量s,未改变字符串arr
// System.out.println(s);
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
-
List接口框架
|----Collection接口:单列集合,用来存储一个一个的对象
|----List接口:存储有序的、可重复的数据。 -->“动态”数组,替换原有的数组
|----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
|----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
|----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
ArrayList的源码分析
1 jdk 7情况下
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
list.add(123);//elementData[0] = new Integer(123);
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
2 jdk 8中ArrayList的变化:
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
后续的添加和扩容操作与jdk 7 无异。
3 小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
LinkedList的源码分析
LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象。
其中,Node定义为:体现了LinkedList的双向链表的说法
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
Vector的源码分析
jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。
总结
ArrayList、LinkedList、Vector三者的异同:
同:三个类都是实现了List接口,存储数据的特点相同:存储有序的、可重复的数据
异:通过上面的源代码分析
List接口中的常用方法
增:
在index位置插入元素 ——void add(int index, Object ele)
list.add(1,456);
从index位置开始将另外集合中的所有元素添加进来 ——boolean addAll(int index, Collection eles)
List list1 = Arrays.asList(1,2,3);
//从list索引2的位置开始将list1中的所有元素添加进来
list.addAll(2,list1);
//从list末尾的位置开始将list1中的所有元素当做一个整体添加进来
list.add(list1);
删:
移除指定索引位置的元素,并返回此元素——Object remove(int index) / boolean remove(Object obj)
//删除list集合中索引为2的元素,并返回该元素
Object Obj = list.remove(2);
System.out.println(Obj);
//删除list集合中list1集合,有就返回true,没有返回false
boolean obj1 = list.remove(list1);
System.out.println(obj1);
改:
设置指定索引位置的元素为其他元素——Object set(int index, Object ele)
Object obj2 = list.set(3, 33);
System.out.println(obj2);
查:
获取指定索引位置的元素 ——Object get(int index)
Object obj3 = list.get(3);
System.out.println(obj3);
获取集合长度——int size()
int size = list.size();
返回obj在集合中首次出现的位置(集合中不存在查询的元素返回-1)——int indexOf(Object obj)
int i = list.indexOf(123);
返回obj在当前集合中末次出现的位置——int lastIndexOf(Object obj)
int i2 = list.lastIndexOf(123);
返回从fromIndex到toIndex位置的子集合(左闭右开)——List subList(int fromIndex, int toIndex)
List list2 = list.subList(1, 4);
List集合遍历
Iterator迭代器方式
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
增强for循环
for (Object obj : list) {
System.out.println(obj);
}
普通for循环(List集合有索引,结合get方法,可以用普通for循环)
for (int j = 0; j < list.size(); j++) {
System.out.println(list.get(j));
}
-
Set接口的框架
|----Collection接口:单列集合,用来存储一个一个的对象
|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
|----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
|----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
|----TreeSet:可以按照添加对象的指定属性,进行排序。
说明
1. Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。
2.1 要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
2.2要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码 重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
Set集合存储元素的特征
存储无序的、不可重复的数据
1. 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
2. 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。
Set集合添加元素的过程分析(以HashSet为例)
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断
数组此位置上是否已经有元素:
如果此位置上没有其他元素,则元素a添加成功。 --->情况1
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
如果hash值不相同,则元素a添加成功。--->情况2
如果hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功。--->情况3
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。(七上)
jdk 8 :原来的元素在数组中,指向元素a(八下)
HashSet底层:数组+链表的结构。
LinkedHashSet
LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。
优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
public void test2(){
Set set = new LinkedHashSet();
set.add(456);
set.add(123);
set.add(123);
set.add("AA");
set.add("CC");
set.add(new User("Tom",12));
set.add(new User("Tom",12));
set.add(129);
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
TreeSet
说明
1.可以按照添加对象的指定属性,进行排序。但向TreeSet中添加的数据,要求是相同类的对象。
//添加失败:不能添加不同类的对象
TreeSet set = new TreeSet();
set.add(123);
set.add("aaa");
set.add(new Person("嘻戏",28));
2.两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator)
3.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
4.定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().
自然排序
person类:
public class Person implements Comparable {
private String name;
private int age;
public Person(){}
public Person(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 "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;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
//按照姓名从大到小排列,年龄从小到大排列
@Override
public int compareTo(Object o) {
if (o instanceof Person){
Person p = (Person) o;
int compareNum = -this.name.compareTo(p.name);
if (compareNum != 0){
return compareNum;
}else {
return Integer.compare(this.age,p.age);
}
}else {
throw new RuntimeException("输入的类型不匹配");
}
}
}
TreeSet集合:
TreeSet set = new TreeSet();
set.add(new Person("嘻戏",33));
set.add(new Person("张三",28));
set.add(new Person("李四",28));
set.add(new Person("王二",55));
set.add(new Person("嘻戏",26));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());//java.lang.ClassCastException
}
定制排序
Comparator comparator = new Comparator() {
//按照年龄从小到大排列
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Person && o2 instanceof Person){
Person p1 = (Person) o1;
Person p2 = (Person) o2;
return Integer.compare(p1.getAge(),p2.getAge());
}else {
throw new RuntimeException("输入的数据类型不匹配");
}
}
};
TreeSet set = new TreeSet(comparator);
set.add(new Person("嘻戏",26));
set.add(new Person("嘻戏",33));
set.add(new Person("张三",28));
set.add(new Person("李四",28));//该条元素未存储进去
set.add(new Person("王二",55));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());//java.lang.ClassCastException
}