您的当前位置:首页正文

Java第十一章集合

2024-11-24 来源:个人技术集锦

集合大致分为四个体系:Set、List、Queue、Map

  • Set:包括无序,不可重复的集合,主要包括HashSet实现类、TreeSet实现类、LinkedHashSet实现类
  • List:有序可重复的集合,主要包括ArrayList、LinkedList实现类
  • Map:代表有映射关系的集合,主要包括HashMap、TreeMap实现类
  • Queue:一堆队列集合实现

主要由两个接口Collection和Map实现

1.Collection接口

collection接口中的实现类的对象添加的数据都是object类型,都是对象 ,因此对象都有其所在类,不可以添加基本数据类型,添加的基本数据类型也都是装箱之后的包装类

单列集合,用来存储一个一个的对象
Collection接口中有List接口和Set接口。
List接口:存储有序的、可重复的数据
Set接口:存储无序的、不可重复的数据
因此set和list接口的实现类可以使用collection接口中的方法,collection接口(父接口)的方法,在子接口以及实现类中都可以使用

  • 1.add(Object obj) 添加元素;addAll(Collection coll)
  • 2.int size() 获取有效元素的个数
  • 3.void clear() 清空集合
  • 4.boolean isEmpty() 是否是空集合
  • 5.boolean contains(Object obj) 是否包含某个元素,是通过元素的equals方法来判断是否是同一个对象;boolean containsAll(Collection c):也是调用元素的equals方法来比较,拿两个集合的元素挨个比较
  • 6.boolean remove();removeAll
  • 7.retrainAll(Collection coll1) 求交集,获取当前集合和coll1集合的交集,并返回给当前集合
  • 8.equals(Object obj) :调用当前对象所在类的equals方法
  • 9.hashcode():返回当前对象的哈希值
  • 10.coll.toArray()返回的是object类型,把集合转为数组
  • 11.Arrays.aslist(new String[]{}),调用Arrays类的静态方法asList,把数组转为集合

add用法: collection接口中的实现类的对象添加的数据都是object类型,都是对象 ,因此对象都有其所在类,不可以添加基本数据类型,添加的基本数据类型也都是装箱之后的包装类
contains用法: 要求collect接口实现类的对象中添加数据obj时候,要求obj所在类要重写equals方法。

Collection coll = new ArrayList();
//1.add
coll.add(new String("TOM");
//2.contains
coll.contains(new String("TOM"))//true,因为String类中重写了equals方法
coll.add(new Person("jerry",10));
coll.contains(new Person("jerry",10));
//如果没有重写equals方法,调用的就是父类Object的equals,Object中的equals就是==,也就是会比较地址值,因此输出false
//因此,需要定义的Person类重写equals方法,输出true
Collection coll1 = Arrays.asList(123,4567);
//3.containsAll
sout(coll.containsAll(coll1));//判断coll1中的所有元素是否都在coll中
//4.remove(Object obj)
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
//remove调用equals方法
coll.remove(1234);
coll.remove(123);
//5.retrainAll(Collection coll1):交集,获取当前集合和coll1集合的交集,并返回给当前集合,没有返回值,直接修改原数组
//6.equals(Object obj):要想返回true,当前集合和形参集合的元素都相同,区分array集合和hash集合,注意顺序
//7.hashcode
//8.集合——数组
Object[] arr = coll.toArray();
//9.数组——集合
List<String> list = Arrays.asList(new String[]{"AA","BB","CC"});
//注意
List arr1 = Arrays.asList(new int[]{1,2})//识别为1个元素,打印出来是地址
List arr1 = Arrays.asList(new Integer[]{1,2})//识别为2个元素
//iterator():返回Iterator接口的实例,用于遍历集合元素

集合元素的遍历Iterator

1.内部的方法:hasNext()和next()方法
2.集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前
3.内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于collection中的remove

while(iterator.hasNext()){
  sout(iterator.next());
}

  • iterator本身不是容器,只是迭代器,操作仍然是在coll集合上,集合是个容器
  • 指针先下移,再返回
  • 错误写法一:跳着输出,并且会报异常
Iterator iterator = coll.iterator();
while((iterator.next())!=null){
   sout(iterator.next());
}
  • 错误写法二:死循环,一直输出第一个元素,因为每调一次iterator,都会返回一个新的迭代器对象,新的迭代器对象指向第一个元素
while(coll.iterator().hasNext()){
    sout(coll.iterator().next());
}

for each循环

用于遍历集合和数组

//for(集合元素类型 局部变量 :集合对象)
//内部仍然调用了迭代器
for(Object obj:coll){
  sout(obj);
}

String []arr = new String[]{"mm","mm","mm"};
//方式一:普通for赋值,输出是gg
fot(int i=0;i<arr.length;i++){
   arr[i] = "gg";
}
//方式二:for each增强for循环,输出是gg
for(String s:arr){
  s="gg";
}
//相当于把arr重新赋值给s,因此arr的值不变,s的值为gg
for(int i =0;i<arr.length;i++){
  sout(arr[i]);
}

集合和数组存储数据: 集合和数组都是对多个数据进行存储操作的结构,简称Java容器
不同之处
1.数组:

  • 一旦初始化之后,长度就确定了
  • 数组一旦定义好,元素的类型也确定了
String[] arr;
int [] arr1;
Object [] arr2;
  • 数组的弊端:提供的方法有限,对于添加、删除、插入操作不便;对于无序、不可重复的需求不能满足;获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用

2.集合的优点

List接口

存储有序的、可重复的数据
动态数据,替换原有的数组
常用的实现类有:ArrayList,LinkedList,Vector
三者的异同?

  • 相同点:都实现了List接口,存储数据特点相同
  • 不同点
    ArrayList:主要实现类,线程不安全,效率高,底层使用Object[] elementData 数组存储(String底层使用char[] value 型数组存储)
    LinkedList:对于频繁的插入、删除操作,使用此类效率高,底层使用双向链表存储
    Vector:古老实现类,线程安全的,效率低,底层使用Object[]数组存储

源码分析

  • 1.ArrayList(jdk7)
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)

  • 1.1ArrayList(jdk8)
ArrayList list = new ArrayList();//底层Object[]elementData初始化为{},并没有创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建了长度为10的数组,并将数据123添加到elementData[0]

小结:jdk7中ArrayList对象的创建类似于单例的饿汉式,jdk8中ArrayList对象的创建类似于单例的懒汉式,延时了数组的创建,节省内存

  • vector
    源码分析:底层创建了长度为10的数组,扩容时扩容为原来长度的2倍

LinkedList

源码分析

LinkedList list = new LinkedList();//内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象

其中,Node定位体现了LinkedList的双向链表的说法

使用LinkedList可以实现队列和栈

add

  • boolean add(E e):在链表后添加一个元素,如果成功,则返回true,否则返回false;
  • void addFirst(E e):在链表头部插入一个元素;
  • void addLast(E e):在链表尾部添加一个元素;
  • void add(int index, E element):在指定位置插入一个元素。
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("first");
linkedList.add("second");
linkedList.add("third");
System.out.println(linkedList);//[first,second,third]

linkedList.addFirst("addFirst");
System.out.println(linkedList);//[addFirst,first,second,third]

linkedList.addLast("addLast");
System.out.println(linkedList);//[addFirst,first,second,third,addLast]

linkedList.add(2, "addByIndex");
System.out.println(linkedList);//[addFirst,first,addByIndex,second,third,addLast]

remove

  • E remove();移除链表中第一个元素;
  • boolean remove(Object o):移除链表中指定的元素;
  • E remove(int index):移除链表中指定位置的元素;
  • E removeFirst():移除链表中第一个元素,与remove类似;
  • E removeLast():移除链表中最后一个元素;
  • boolean removeFirstOccurrence(Object o):移除链表中第一次出现所在位置的元素;
  • boolean removeLastOccurrence(Object o):移除链表中最后一次出现所在位置的元素;
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("first");
linkedList.add("second");
linkedList.add("second");
linkedList.add("third");
linkedList.add("four");
linkedList.add("five");
System.out.println(linkedList);//[first,second,second,third,four,five]

linkedList.remove();
System.out.println("remove: " + linkedList);//remove: [second,second,third,four,five]

linkedList.remove("second");
System.out.println("remove(Object): " + linkedList);//remove(Object):[second,third,four,five]

linkedList.remove("six");
System.out.println("remove(Object) not exist: " + linkedList);//remove(Object) not exist: [second,third,four,five]

linkedList.remove(2);
System.out.println("remove(index): " + linkedList);//[second,third,five]

linkedList.removeFirst();
System.out.println("removeFirst: " + linkedList);//[third,five]

linkedList.removeLast();
System.out.println("removeLast:" + linkedList);//[third]

linkedList.clear();//

linkedList.add("first");
linkedList.add("second");
linkedList.add("first");
linkedList.add("third");
linkedList.add("first");
linkedList.add("five");
System.out.println(linkedList);//[first,second,first,third,first,five]

linkedList.removeFirstOccurrence("first");
System.out.println("removeFirstOccurrence: " + linkedList);//[second,first,third,first,five]

linkedList.removeLastOccurrence("first");
System.out.println("removeLastOccurrence: " + linkedList);//[second,first,third,five]
 

get

  • E get(int index):按照下标获取元素;
  • E getFirst():获取第一个元素;
  • E getLast():获取最后一个元素;

链表前后没有发生变化,只是取出来数

push、pop、poll

  • void push(E e):与addFirst一样,实际上它就是addFirst;
  • E pop():与removeFirst一样,实际上它就是removeFirst,返回remove的值;
  • E poll():查询并移除第一个元素;
LinkedList<String> linkedList = new LinkedList<>();
linkedList.push("first");
linkedList.push("second");
linkedList.push("second");
linkedList.push("third");
linkedList.push("four");
linkedList.push("five");
System.out.println("linkedList: " + linkedList);//[five,four,third,second,second,first]

System.out.println("pop: " + linkedList.pop());//pop: first
System.out.println("after pop: " + linkedList);//after pop:[four,third,second,second,first]

System.out.println("poll: " + linkedList.poll());//poll:four
System.out.println("after poll: " + linkedList);//after poll:[third,second,second,first]

push和pop的操作接近stack的操作
poll和pop的区别

  • poll输出null
  • pop产生异常
LinkedList<String> linkedList = new LinkedList<>();
System.out.println("poll: " + linkedList.poll());//poll:null
System.out.println("pop: " + linkedList.pop());//Exception in thread "main" java.util.NoSuchElementException

peek

  • E peek():获取第一个元素,但是不移除;
  • E peekFirst():获取第一个元素,但是不移除;
  • E peekLast():获取最后一个元素,但是不移除;
LinkedList<String> linkedList = new LinkedList<>();
linkedList.push("first");
linkedList.push("second");
linkedList.push("second");
linkedList.push("third");
linkedList.push("four");
linkedList.push("five");
System.out.println("linkedList: " + linkedList);//linkedList:[five,four,third,second,second,first]
System.out.println("peek: " + linkedList.peek());//peek:five
System.out.println("peekFirst: " + linkedList.peekFirst());//peekFirst:five
System.out.println("peekLast: " + linkedList.peekLast());//peekLast:first

System.out.println("linkedList: " + linkedList);//linkedList:[five,four,third,second,second,first]
  • peek如果没找到对应的元素,统统输出null:

offer

  • boolean offer(E e):在链表尾部插入一个元素;
  • boolean offerFirst(E e):与addFirst一样,实际上它就是addFirst;
  • boolean offerLast(E e):与addLast一样,实际上它就是addLast;
LinkedList<String> linkedList = new LinkedList<>();
linkedList.push("first");
linkedList.push("second");
linkedList.push("second");
linkedList.push("third");
linkedList.push("four");
linkedList.push("five");
System.out.println("linkedList: " + linkedList);//linkedList:[five,four,third,second,second,first]
linkedList.offer("six");
System.out.println("linkedList: " + linkedList);//linkedList:[five,four,third,second,second,first,six]
linkedList.offerFirst("zero");
System.out.println("linkedList: " + linkedList);//linkedList:[zero,five,four,third,second,second,first,six]
linkedList.offerLast("seven");
System.out.println("linkedList: " + linkedList);//linkedList:[zero,five,four,third,second,second,first,six,seven]

其他常用方法中,只有set会改变原链表

System.out.println("linkedList: " + linkedList);
//linkedList:[five, four, third, second, second, first]
System.out.println("linkedList.contains(\"second\"): " + linkedList.contains("second"));//linkedList.contains("second"):true
System.out.println("linkedList.contains(\"six\"): " + linkedList.contains("six"));//false
System.out.println("linkedList.element(): " + linkedList.element());//5个元素
System.out.println("linkedList: " + linkedList);
System.out.println("linkedList.set(3, \"set\"): " + linkedList.set(3, "set"));
System.out.println("linkedList: " + linkedList);//linkedList: [five, four, third, set, second, first]
System.out.println("linkedList.subList(2,4): " + linkedList.subList(2,4));//linkedList.subList(2,4): [third, set]
System.out.println("linkedList: " + linkedList);

Set接口

一、存储无序、不可重复的数据
具体实现类:

  • HashSet:主要实现类,线程不安全,可以存储null值
    • LinkedHashSet:作为hashset的子类;遍历内部数据时,可以按照添加的顺序遍历
    • TreeSet:可以按照添加对象的指定属性,进行排序

set接口中没有额外定义新的方法,使用的都是collection中的方法
要求:向set中添加的数据,其所在的类一定要重写hashcode()和equals()
重写的hashcode()和equals()尽可能保持一致性,相等的对象必须具有相等的散列码

  • 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值
  • 不可重复性:保证添加的元素按照equals方法判断时不能返回true,相同的元素只能添加一次

二、添加元素的过程
首先调用元素所在类的hashCode方法,计算元素a的hash值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为索引位置),判断数组此位置上是否已经有元素存在

  • 如果此位置上没有其他元素存在,则元素a添加成功
  • 如果此位置上有其他元素b存在(或以链表形式存在的多个元素),则比较元素a与元素b的hash值
    • 如果hash值不相同,则元素a添加成功
    • 如果hash值相同,进而调用元素a所在类的equals方法
      • equals返回true,添加失败
      • equals返回false,添加成功
      • 对于添加成功的情况而言:元素a与已经存在的索引位置上以链表的方式存储
      • jdk7:元素a放到数组中,指向原来的元素
      • jdk8:原来的元素在数组中,指向元素a

三、TreeSet
1.向TreeSet中添加的数据,要求是相同类的对象
2.两种排序方式:自然排序(实现compareTo接口)和定制排序
3.自然排序中,比较两个对象是否相同的标准为:compareTo返回0,不再是equals

Map

存储双列数据,存储key-value对的数据
实现类:
HashMap: 作为Map的主要实现类;线程不安全,效率高;可以存储null的key和value

  • LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素,对于频繁的遍历操作,此类执行效率高于HashMap

TreeMap: 有序存储。保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定义排序。底层使用红黑树
Hashtable: 作为Map的古老实现类;线程安全,效率低;有sychronized,不能存储null的key和value

  • Properties:常用来处理配置文件。key和value都是String类型
    Hash

key和value

key:不重复,无序的。用set存储
value:可重复,无序。用collection存储
entry键值对:其中有两个对象k和v,无序,不可重复。用set存储

HashMap的底层实现原理

JDK7:
HashMap map = new HashMap();
实例化之后,底层创建了长度是16的一维数组Entry[] table。
map.put(key1,value1);
首先,调用key1所在类的hashcode()计算key1的哈希值,此哈希值经过某种算法计算之后,得到entry数组中的存放位置。
如果此位置上的数据为空,此时的entry添加成功
如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:

  • 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1和value1添加成功
  • 如果key1的哈希值和已经存在的某一个数据的哈希值相同,继续比较:调用key1所在类的equals(key2):
    • 如果equals返回false:此时key1-value1添加成功
    • 如果返回true:使用value1替换value2

补充:如果数据不为空时,则新添加的数据和原来的数据以链表的方式存储
扩容方式:扩容为原来的两倍,并把原有数据复制过来

jdk8和jdk7的不同:
1.new HashMap():底层没有创建一个长度为16的数组
2.底层的数组是Node(),而非Entry()
3.首次调用put()方法时候,底层创建长度为16的数组
4.jdk7底层为数组+链表,jdk8底层为数组+链表+红黑树
当数组的某一个索引位置上的元素以链表形式存在的数据个数>8且当前数组的长度>64时,此时此索引位置上的所有数据改为使用红黑树存储。

Map接口中定义的方法

map放数据使用put 函数,是尾插法。

//添加删除和修改
Map map = new Map();
//添加
map.put("AA",123);
map.put(45,123);
map.put("bb",56);
//修改
map.put("AA",87) 
sout(map);
Map map1 = new Map();
map1.put("CC",123);
map1.put("DD",123);

map.putAll(map1);
//remove(key)修改
Object value = map.remove("CC");//移除指定key的键值对,并返回value,如果key值不存在,则返回null

//clear()
map.clear();//只是清空数据,map仍然存在
sout(map.size())//不会报空指针
sout(map)//{}

//元素查询的方法
map.get("CC");//获取指定key对应的value
//是否包含指定的key
boolean isExist = map.containsKey("BB");//是否包含指定的key
boolean isExist2 = map.constainsValue(123);//是否包含指定的value
//int size()返回map中key-value的个数

//boolean isEmpty()判断当前map是否为空

遍历
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection
Set entrySet():返回所有key-value对构成的Set集合

Map map = new Map();
map.put("AA",123);
map.put(45,123);
map.put("bb",56);
//遍历所有的key集:keySet()
Set set = map.keySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
   System.out.println(iterator.next());
}

//遍历所有的value集:values
Collection values = map.values();
for(Object obj:values){
   sout(obj)
}
//遍历所有的key-value
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while(iterator1.hasNext()){
   
}
显示全文