集合
集合的概念
程序运行时,数据数量是变化的,但是数组长度一旦给定,就不能改变。数组的优点是查询速度快
开发实践中需要对数据的保存的逻辑可能各种各样,于是就有了各种各样的数据结构。Java中对于各种数据结构的实现,就是我们用到的集合。
集合API
集合体系概述
Java的集合框架是由很多接口、抽象类、具体类组成的,都位于java.util包中
Collection接口
Collection接口,作为单列集合中顶级接口,里面定义了单列集合共有的方法
方法以增、删、改、查、判断、转换为主
Collection,ArrayList后面是jdk5之后的语法–泛型
可以通过泛型语法,为集合设置一个集合,这样就只能存储设置的数据类型
集合中建议存储同一类型数据
集合是容器,可以存储不同的数据,严格上将集合中是可以存储任何类型(只能存储引用类型)
c.add("g")//向集合c中添加元素"g"
c.addAll(c1)//把c1集合添加到c集合
c.clear();//清空c集合
c.equals(c1)//比较两个集合中的元素是否相等
c.isEmpty()//判断集合c是否为空
c.remove("h")//删除指定元素"h",删除成功返回ture,失败返回false
c.retainAll(c1)//在集合c中保留c和c1的交集元素,集合c发生变化返回ture,没发生变化返回flase
c.size()//返回集合的长度或元素个数
Object[] objc=c.toArray()//将集合c转为Object类型数组
String[] s=c.toArray(new String[c.size]))//将集合c转为指定类型(这里的指定类型指的是c集合存储的数据元素类型)数组
List接口
共有的特点:有序(按照添加顺序排序),可以有重复元素
List继承了Collection接口,有三个实现的类
- ArrayList :数组列表,数据采用数组方式存储。
- LinkedList :链表
- Vector 数组列表,添加同步锁,线程安全的
ArrayList
底层是通过数组实现的,是可以变长的,在内存中分配连续的空间
遍历元素和随机访问元素的效率比较高,中间增删慢
常用方法
list.add(0,"x")//向指定位置添加元素
list.get(3)//获取指定位置元素
list.remove(0)//删除指定位置元素并输出删除元素
list.set(2,"k")//替换指定位置元素
list.size();//返回list集合中实际存储元素的个数
add();向集合中添加元素时,底层会默认创建一个长度为10的Object类型数组,当数组装满时,再次添加元素,会创建一个为原来数组长度1.5倍的新数组,将原数组内容复制过来,最后将新数组地址赋给底层的数组
LinkedList
底层是链表实现,查询慢(必须从头/尾开始查找,直到找到),中间增删快,只需要改变后继节点位置
常用方法:
lists.add("a")//向链表lists中添加元素a
lists.get(3)//查找链表lists指定位置的元素,查找指定的位置小于size/2,从头结点开始找,否则从尾节点开始找
lists.addFirst("x")//向链表lists的头节点添加元素x
lists.addLast("y")//向链表lists的尾节点添加元素y
lists.removeFirst()//删除并返回链表lists的头节点元素
lists.removeLast()//删除并返回链表lists的尾节点元素
Vector
底层是通过数组实现,线程安全的
for循环遍历集合时,可以从中删除元素
注意:删除元素后,元素向前移动,索引++,可能会出现错漏问题
增强for循环遍历集合
在便利过程中不能删除元素,如果删除会抛出ConcurrentModificationException(并发修改异常)
迭代器遍历
Iterator
Iterator<String> it=list.iterator();
it.hasNext();//判断集合中是否还有元素,有返回true,否则返回false
iterator();返回了一个ArraysList中的内部类对象,实现Iterator接口
此内部类,专门用作对集合进行遍历时的控制
如果在遍历中删除元素,请使用迭代器中的remove()
ListIterator
是针对List接口下的集合类
ListIterator<String> it=list.listIterator(2);//从指定的位置开始遍历
it.hasPrevious();//从集合末尾判断集合中是否还有元素,有返回true,否则返回false
it.previous();//返回集合末尾元素
Set
特点:不能存储重复元素,无序(不会按照添加元素的顺序排列)
HashSet
存储元素顺序不固定且元素不重复,底层使用的是HashMap实现的
HashSet<> set=new HashSet<>();
在底层中使用hashCode()和equals()方法来判断内容是否重复
判断内容是是否重复时,先调用hashCode方法来计算对象内容的哈希值,哈希值不相等,则内容不同
,哈希值相等时,再调用equals()判断内容是否相等。
hashCode()
hashCode()是Object类的方法
public native int hashCode();native修饰的方法为本地方法,java没有实现,是调用的操作系统中的方法
Object类中的hashCode()获取的对象在内存中的地址,其他类中重写的hashCode(),都不是对象的地址,而是根据对象内容计算出来的哈希值,但是此方法存在问题,对象内容不同计算出的哈希值可能相同,不安全
equals();除了Object类中的equals是比较对象地址,其他类中都是比较内容是否相等,很安全但是equals的效率很低。
TreeSet:可以按照元素的自然顺序排序(c,b,a–>>a,b,c)且不能存储重复元素,添加元素类型,必须实现排序接口
TreeSet<> set=new TreeSet<>();
Map接口
双列接口,键不能重复,值可以重复
HashMap
键是无序的,可以存储一个为null键
底层存储:java8
有三种数据结构:数组(哈希表)、链表、红黑树
添加元素过程:1.先计算出元素它的哈希值(例如a-97),再计算出元素再数组中的位置,将元素封装到一个Node对象中,将对象存储到对应的位置。
2.在存储元素的过程中,如果元素有着和之前存入元素相同的位置,那就将新的元素存入之前元素的下一位,当链表长度达到一定条件时,链表会转为红黑树
哈希数组的长度默认是16
负载因子是0.75
哈希数组如果发生扩容,每次扩容为原来的两倍
当哈希数组负载因子超过0.75时,数组就开始扩容
元素在哈希数组中位置的计算方法是数组-1与运算元素的哈希值(n-1&hash相当于hash%length)
当链表长度为8且哈希数组大于64时,链表会转为红黑树
当链表长度大于等于8时,会调用转红黑树的方法,但是不一定会,需要数组的长度大于等于64,如果不满足这个条件此方法只会把数组扩容,而不会把链表转为红黑树
HashMap<String><String> map=new HashMap<>();
map.put("a",aa)//向map集合中添加键值对
map.remove("a")//删除并返回集合map的Key值a
map.clear();//清空集合map
map.containsKey("a");//判断map集合中是否包含key值a
map.containsValue("aa");//判断map集合是否包含value值aa
map.isEmpty();//判断map集合的键值对是否为空
map.size();//返回map集合中的键值对个数
Collection<String> c=map.values();//将map集合中的value值提取出来
Set<String> set=map.keySet();//将map集合中的key值提取出来
map.replace("b","xxxx");//将集合map中key值为b的value值替换为xxxx
Hashtable
线程安全的Map,不可以存储一个为null的键和值;
底层实现和HashMap一致,
TreeMap
可以通过键进行排序,作为键类型的类必须实现排序接口
TreeMap根据key值排序,key值需要实现Comparable接口, 重写compareTo方法。TreeMap根据compareTo的逻辑,对 key进行排序。
键是红黑树结构,可以保证键的排序和唯一性
Collections
int…a 可变长度参数,本质上是数组
Collections.addAll(list,1,1,1)//向list集合中添加元素
Collections.sort(list)//排序
Collections.binarySearch(list,4)//用二分法,查找元素在集合中的位置
Collections.swap(list,0,2)//交换list集合中两个位置的元素
Collections.copy(dlist,list)//将list中的元素复制到dlist中,会覆盖dlist中的元素dlist.size()>list.size()
Collections.emptyList()//返回的是一个内部类EmptyList,此集合不能操作使用,用于逻辑判断
Collections.fill(list,1)//用指定元素1,将list集合填满
Collections.Max(list)//返回集合中最大的元素
Collections.shuffle(list)//将集合list中元素的位置打乱输出
泛型
为什么用泛型
集合中如果存储的是Object类型数据,会引发数据转换问题,集合建议存储同一类型数据
什么是泛型
泛型是jdk5引入的,泛型相当于参数化类型,把类型作为参数传递,创建类的对象时,可以传入任意类型,jdk在编译时,就会对类型进行检测,实际传入的参数类型必须为是引用类型,可以有多个泛型,如果没有传入参数类型,那它默认类型为Object.
子类继承父类,父类类型明确了,子类可以不是泛型
子类和父类都是泛型类
类型通配符
?表示实际船传入的参数的泛型类型,?表示可以是任意的泛型类型,也称无界通配符
Demo<? extends T> 类型通配符上限 实际传入的类型,只能是T或者T的子类
Demo<? super T>类型通配符上限,实际传入的类型,只能是T或者T的父类