集合
框架图
泛型
为什么会有泛型呢?
早期的Object类型可以接收任意的对象类型,但是在实际的使用中,会有类型转换的问题。也就存在这隐患,所以Java提供了泛型来解决这个安全问题。
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。
参数化类型,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可
以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
puimport java.util.ArrayList;
/*
泛型:类型参数化,参数化类型,将类型作为参数传入
<任意的单个大写字母,可以有多个>
泛型的传入的类型只能是类类型
如果没有写,默认是Object
*/
public class TypeDemo<T,E>{
T name;
E color;
public T test(E e){
return null;//引用类型的默认值可以是null
}
TypeDemo<String,Integer> t = new TypeDemo<String,Integer>();
String s = t.test(1);
public static void main(String[] args) {
//没有定义类型,默认是Object,存比较方便
ArrayList alist = new ArrayList();
alist.add("a");
alist.add(1);
alist.add(true);
for(int i=0;i<alist.size();i++){
Object obj = alist.get(i);
if(obj instanceof String){//在具体的使用时,需要向下转型,类型不确定
String s = (String) obj;
}
if(obj instanceof Integer){
Integer s1 = (Integer) obj;
}
if(obj instanceof Boolean){
Boolean s2 = (Boolean) obj;
}
}
}
}
1、泛型的类型参数只能是类类型(包括自定义类)
2、泛型的类型参数可以有多个。
如果没有定义具体类型,默认为Object
集合概念
数组用来保存一组类型相同(基本类型或引用类型)的元素,一旦定义,长度不能再改变
集合:能够动态增长长度的容器。
1.集合类的长度可变.
2.不同的数据存储,操作方式不同. 底层有数组,链表,树,哈希表实现.
3.集合只能是引用类型
集合体系
Java的集合框架是由很多接口、抽象类、具体类组成的,都位于java.util包中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UE8o8JCi-1612264565180)(C:\Users\25919\AppData\Roaming\Typora\typora-user-images\1611901653028.png)]
Collection 单列集合
List: 元素可重复
ArrayList: 底层是数组实现 LinkedList:底层是链表实现 Vector:底层也是数组实现,线程安全
Set:不可重复
HashSet:底层hash表实现 TreeSet:底层是红黑树实现
Map 双列集合
HashMap TreeMap Hashtable
迭代器
Collections类
Collection接口
Collection 接口-定义了存取一组对象的方法,其子接口Set和List分别定义了存储方式。
Set 中的数据对象没有顺序且不可以重复。
List 中的数据对象有顺序且可以重复。
在Collection 中定义的方法是集合中共有的基本方法:
public static void main(String[] args) {
/*
集合中默认可以存储任意数据类型,建议使用泛型,存储同一类型的数据
*/
Collection<String> c = new ArrayList<String>();//默认Object类
c.add("a");
c.add("b");
c.add("c");
c.add("d");
// c.clear();//清空
c.remove("a");//删除指定元素,有则返回true,没有返回false
c.contains("a");//判断是否包含某元素
c.isEmpty();//是否为空
c.removeIf(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.equals("a");//删除的过滤条件
}
});
//删除满足给定谓词的此集合的所有元素。
c.stream().forEach((a)->System.out.println(a));//返回以此集合作为源的顺序 Stream 。
//流的方式遍历集合
test(1,2,3,4);
}
//int...a可变长度的参数,
// 本质是一个数组,
// 一个参数列表只能由一个可变长度的参数,
// 并且只能放在末尾
public static void test(int a,int...b){
System.out.println(a);//1
System.out.println(b);//[2,3,4]
}
集合与数组的转换
//集合转数组
Object[] obj = c.toArray();//默认object
String[] s = c.toArray(new String[c.size()]);//用给定的类型转数组
//数组转集合
int[] a = {1, 2, 3, 4};
int[] b = {1, 2, 3, 4};
Collection c1 = Arrays.asList(a,b);
//Collection c2 = Arrays.asList(1,2,3,4);
//System.out.println(c2.remove(2));//不支持的操作2表示索引
List c2 = Arrays.asList(1,2,3,4);//返回List类型,本质是Arrays$ArrayList(Arrays类中的一个内部类ArrayList)
ArrayList alist = new ArrayList<>(c2);//将List类型的集合,通过ArrayList构造方法转为集合
System.out.println(alist.remove(2));//第二个元素3
System.out.println(alist);//[1,2,4]
System.out.println(c1);
可变长度参数
public static void main(String[] args){
test(1,2,3,4);
}
//int...a可变长度的参数,
// 本质是一个数组,
// 一个参数列表只能由一个可变长度的参数,
// 并且只能放在末尾
public static void test(int a,int...b){
System.out.println(a);//1
System.out.println(b);//[2,3,4]
}
1-List接口
List继承了Collection接口,可以存储重复元素,有三个实现的类
ArrayList: 数组列表,数据采用数组方式存储。
LinkedList:链表
Vector:数组列表,添加同步锁,线程安全的
ArrayList
实现了长度可变的数组,在内存中分配连续的空间。
遍历元素和随机访问元素的效率比较高
底层是数组实现,查询快,增,删慢
创建之初,在底层创建一个默认长度的数组,当数组内容添加满了之后,再继续添加时,会扩容一个新数组,为原来的1.5倍
缺点:扩容后,元素不能存满,空间浪费
构造方法
/*
调用默认无参的构造方法,创建ArrayList对象时,并没有实际的创建数组
第一次添加元素时创建,默认长度为10
*/
ArrayList<String> alist = new ArrayList<String>();
alist.add("aaa");
alist.add("bbb");
alist.add("ccc");
alist.add("ddd");//[aaa,bbb,ccc,ddd,null,null,null,null,null,null]
/*
调用有参的构造方法,创建ArrayList对象时,创建一个指定长度的数组
this.elementData = new Objiect[initialCapacity];
*/
ArrayList<String> alist1= new ArrayList<String>(10);
添加元素
ArrayList<String> alist = new ArrayList<String>();
alist.add("aaa");
alist.add("bbb");
alist.add("ccc");
alist.add("ddd");//[aaa,bbb,ccc,ddd,null,null,null,null,null,null]
alist.add(3,"A");//向指定位置添加
/*private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
容量最大值为Integer最大值-8
*/
//当add添加元素时,数组满了后,会扩容,为原来的1.5倍,返回一个新数组
/*add()当数组中不足以放下元素时,调用grow()进行扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
*/
获取元素
alist.get(2);//获取指定位置元素
/*get()
public E get(int index) {
rangeCheck(index);
return elementData(index);
}直接返回索引处的元素
*/
删除元素
alist.remove(3);//删除第三个元素,会把要删除的元素返回
alist.remove("c");//根据内容删除//有就返回true没有就返回false
public class MyArrayList extends ArrayList {
public static void main(String[] args) {
MyArrayList mlist = new MyArrayList();
mlist.add("a");
mlist.add("a");
mlist.add("a");
mlist.add("a");
mlist.add("a");
mlist.removeRange(0,3);//删除指定区间的元素,
//removeRange()受保护的方法protect,只能在子类中访问
}
}
两个集合之间的操作
ArrayList<String> alist = new ArrayList<String>();
alist.add("a");
alist.add("b");
alist.add("c");
alist.add("d");
alist.add("e");
alist.add("a");
ArrayList<String> alist1 = new ArrayList<String>();
alist1.add("a");
alist1.add("b");
alist1.add("c");
alist1.add("d");
alist1.add("e");
alist1.add("a");
alist.addAll(alist1);//把一个集合里的元素全部加到一个集合
alist.containsAll(alist1);//判断一个集合是否包含另一个集合里的元素
alist.removeAll(alist1);//如果有就删除相同的元素,返回true,没有就返回false
alist.retainAll(alist1);//保留两个集合中相同的元素,有变化返回true
补充方法
ArrayList<String> alist = new ArrayList<String>();
alist.add("a");
alist.add("b");
alist.add("c");
alist.add("d");
alist.add("e");
alist.add("a");//可以有重复元素
//排序
alist.sort(new Comparator<String>() {
//使用匿名内部类,定义排序规则
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
alist.forEach((a)->System.out.println(a));//以流的形式遍历
System.out.println(alist);
alist.set(2,"C");//替换指定位置的元素
alist.indexOf("a");//从前往后找指定元素第一次出现的索引
alist.lastIndexOf("d");//从后往前找第一次出现的元素的索引
alist.subList(2,5);//截取指定区间的元素,返回一个新的集合
集合迭代
1-以流的方式遍历
list.stream().forEach((a)->System.out.println(a));
2-for循环遍历
//for循环遍历,可以从集合中删除元素,删除后下标与集合中的内容位置就对不上了
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
if(list.get(i).equals("d")){
list.remove(i);//删除某个元素
}
}
3-增强for循环实现遍历
//增强for循环实现遍历,理论上不允许删除集合中的数据,
// 如果需要可以删除一次并立即终止循环
for (String temp:list) {//ConcurrentModificationException
if(temp.equals("d")){
list.remove(temp);
// break;//可以删掉一个后终止循环
}
}
4-迭代器遍历-iterator()
/*1.
hasNext(),判断集合中还有没有元素,有就返回true,没有就false
remove();删除元素,删除后里面的指针(记录遍历的元素序号)会向回退一下
*/
Iterator<String> i =list.iterator();
System.out.println(i);//java.util.ArrayList$Itr@1540e19d
while (i.hasNext()){//返回true就进入循环
String e = i.next();//向下走一次,获得一个元素
if(e.equals("e")){
i.remove();//删除元素e,必须使用迭代器中提供的remove
}
}
System.out.println(list);//[a, a, b, c, d]
5-迭代器遍历-listIterator()
1
/*2.
listIterator();只适用于list接口实现类
遍历时还可以添加,设置,替换,移除,获取元素下标
*/
ListIterator listit = list.listIterator();
while (listit.hasNext()){
System.out.println(listit.next()+";"+listit.nextIndex());
listit.next();
listit.add("a");
listit.set("a");
listit.remove();
}
System.out.println(list);
2从后往前输出元素
/*
listIterator(list.size())
hasPrevious()有没有上一个元素
previous()//获得上一个元素
*/
ListIterator listit = list.listIterator(list.size());
while (listit.hasPrevious()){
System.out.println(listit.previous());//从后往前输出元素
}
System.out.println(list);
LinkedList
采用链表存储方式。插入、删除元素时效率比较高
底层是链表实现,查询慢,增删快
元素查找时从第一个节点向后查找
增加,删除元素时其他位置元素不动,只需要改变指针域的值即可
获取元素
llist.get(2);//返回第2个元素,2不同于数组的索引,只代表元素序号
/*LinkedList查找某个元素,从头结点或者为节点开始查找,查询效率低
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
*/
添加元素
LinkedList<String> llist = new LinkedList<String>();
llist.add("a");
llist.add("b");
llist.add("c");
llist.add("d");
llist.add("e");
补充
public static void main(String[] args) {
LinkedList<String> llist = new LinkedList<String>();
//LinkedList里面提供了许多关于链表头尾操作得方法,用于模拟队列结构,栈结构操作
llist.addLast("a");
llist.addLast("b");
llist.addLast("c");
llist.addLast("d");
llist.addLast("e");//从队尾添加
llist.clear();
System.out.println(llist.pollFirst());//返回null
System.out.println(llist.peekFirst());//返回null
/*集合内没有元素返回null
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
*/
System.out.println(llist.removeFirst());//删除队首元素,
/*集合内没有元素会报错
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
*/
System.out.println(llist);
llist.addFirst("1");//返回值void
llist.offerFirst("1");//在队首插入指定元素,返回值Boolean类型
llist.offerLast("w");//在末尾插入指定元素,返回值Boolean类型
System.out.println(llist);
}
2-set接口
Set接口继承了Collection接口。
Set中所存储的元素是不重复的,但是是无序的, Set中的元素是没有索引的
Set接口有两个实现类
1-HashSet
HashSet的底层是HashMap,HashSet只是使用了HashMap键的那一列,是从HashMap扩展出来的一格单列存储
的类
// HashSet 无序存储(不是按照添加顺序排列)
HashSet<String> set = new HashSet();//底层是HashMap
/*public HashSet(){
map = new HashMap<>();
}
*/
底层数据结构:首先是哈希表(数组) 数组类型时Node(数据域,指针域)
数组+链表+红黑树
第一次添加元素时,会在底层创建一个长度为16的哈希表
putVal(hash(key),key,value,false,true);
将key值传递到一个hash()中
return(key == null)?0:(hash = key.hashCode())^(h>>>16);
用内容的hash值,计算此元素在hash表中的位置
hash表默认长度为16,当容量添加为长度的0.75时,会自动扩容为原来的2倍
(16*0.75 = 12 )当链表长度大于8时,链表转为红黑树
HashSet类中的元素不能重复,即彼此调用equals方法比较,都返回false。
HashSet<String> set = new HashSet();//底层是HashMap
set.add("a");
set.add("a");
set.add("b");
set.add("w");
set.add("x");
set.add("s");
System.out.println(set);//[a, b, s, w, x]
1.哈希结构的底层是如何存储数据
哈希表本质也是数组
2.HashSet是如何判断内容重复
1.每次添加内容时,会用内容的hash值来判断是否相同,但是hash值这种方法不安全,可能会出现内容不同,hash值相同(快)
2.当出现内容不同hash值相同(hash碰撞,hash冲突)时,使用equals方法比较,比较的是内容是否相同(安全)
双保险:保证效率,又保证了安全
哈希表依赖于哈希值存储
3.hash值怎么来:
调用hashCode()方法,
两种情况
1.类中已经重写hashCode(),例如String类, 根据内容来计算hash值
2.类中没有重写hashCode(),调用Object类中hashCode()
2-TreeSet
可以给Set集合中的元素进行指定方式的排序。存储的对象必须实现Comparable接口。
TreeSet底层数据结构是二叉树(红黑树是一种自平衡的二叉树)
public static void main(String[] args) {
/*
Set不能存储重复元素
TreeSet可以按照元素的自然顺序排序
底层是红黑树
*/
TreeSet<String> tset = new TreeSet<>();
tset.add("b");
tset.add("a");
tset.add("d");
tset.add("c");
tset.add("c");
System.out.println(tset);//[a, b, c, d]
}
红黑树与二叉树
3-Set遍历
1.流的方式**
tset.stream().forEach((a)->System.out.println(a));
2.增强for循环
for(String s:tset){
System.out.println(s);
}
3.迭代器
Iterator<String> it = tset.iterator();
while (it.hasNext()){
String e= it.next();
System.out.println(e);
}
Map接口
Map接口
将键映射到值的对象(key->value)
一个映射不能包含重复的键
每个键最多只能映射到一个值
Collection values()方法
Collection<String> list = hm.values();
System.out.println(list);//获得值那一列[n, a, b, c]
HashMap
HashMap中元素的key值不能重复,即彼此调用equals方法,返回为false。排列顺序是不固定的。
put(key,value),value可以重复,key不能重复
键值对存储
无序存储
允许加进来一个为null的键
public static void main(String[] args) {
HashMap<String,String> hm = new HashMap<>();
/*put(key,value),value可以重复,key不能重复
键值对存储
无序存储
*/
hm.put("a","a");
hm.put("a","b");//如果有重复的键,后面的值会覆盖掉前面的值
hm.put("b","b");
hm.put("c","c");
hm.put(null,"n");//允许加进来一个为null的键
System.out.println(hm);//{null=n, a=b, b=b, c=c}
hm.remove("a");//删除键及对应的值
hm.clear();//清空
hm.containsKey("c");//是否包含键
hm.containsValue("c");//是否包含值
hm.size();//3,map集合里面有几组键值对
hm.get("a");//获得键的值
}
TreeMap
适用于按自然顺序或自定义顺序遍历键(key)。
TreeMap根据key值排序,key值需要实现Comparable接口,
重写compareTo方法。TreeMap根据compareTo的逻辑,对key进行排序。
键是红黑树结构,可以保证键的排序和唯一性
键值对,键不能重复
可以根据键得自然顺序排序
指定的键的类型的类必须实现Comparable接口,排序使用
public static void main(String[] args) {
/*
键值对,键不能重复
可以根据键得自然顺序排序
指定的键的类型的类必须实现Comparable接口,排序使用
*/
TreeMap<Integer,String> tm = new TreeMap<>();
tm.put(2,"a");
tm.put(1,"b");
tm.put(4,"c");
tm.put(1,"d");
tm.put(3,"a");
System.out.println(tm);//{1=d, 2=a, 3=a, 4=c}
}
HashTable
实现了同步。
无序存储
初始容量是11
线程安全的
不允许加为null的键
public static void main(String[] args) {
/*
无需存储
初始容量是11
线程安全的
*/
Hashtable<String,String> ht = new Hashtable<>();
ht.put("a","a");
ht.put("a","b");
ht.put("c","c");
ht.put("d","d");
// ht.put(null,"n");//不允许加为null的键
System.out.println(ht);//{a=b, d=d, c=c}无序存储
}
Map集合遍历
1-流的方式遍历
hm.forEach((k,v)->System.out.println(k+":"+v));
2-keyset()
keyset()先获取到m集合中所有的键,存储到set集合中
再对set集合进行遍历,通过每一次拿到的key获得对应的值
Set<String> keyset = hm.keySet();
for (String key:keyset){
System.out.println(key+":"+hm.get(key));
}
3-entryset()
entryset()将hm集合中的键值对封装到每一个entry对象中
Set<Entry<String,String>> entrySet = hm.entrySet();
//增强for
for(Entry<String,String> entry:entrySet){
System.out.println(entry.getKey()+":"+entry.getValue());
}
//迭代器
Iterator<Entry<String,String>> it =entrySet.iterator();
while (it.hasNext()){
Entry<String,String> entry = it.next();
System.out.println(entry.getKey()+":"+entry.getValue());
}
Collections类
是集合类的工具类,与数组的工具类Arrays类似
定义了大量静态方法
同步集合对象的方法
对List排序的方法
添加
ArrayList<String> list = new ArrayList<>();
list.add("x");
Collections.addAll(list,"a","b","c");
排序
Collections.sort(list);
二分查找
System.out.println(Collections.binarySearch(list,"c"));
覆盖
ArrayList<String> list = new ArrayList<>();
list.add("x");
Collections.addAll(list,"a","b","c");
ArrayList<String> list2 = new ArrayList<>();
list2.add("a");
list2.add("f");
list2.add("d");
list2.add("n");
Collections.copy(list,list2);//后面集合的内容把前面集合的内容覆盖掉,后面集合容量小于等于前面集合
其余常用方法
List<Object> list1 = Collections.emptyList();
//list1.add("a");
/*public void add(int index, E element) {
throw new UnsupportedOperationException();
}
不能给里面添加元素
*/
Collections.fill(list,"K");//用K把里面所有的内容填充掉
System.out.println(list);//[K, K, K, K]
System.out.println(Collections.max(list));//最大值
Collections.replaceAll(list,"a","X");
Collections.reverse(list);//逆序输出
Collections.swap(list,0,2);//交换两个位置的元素