集合大致分为四个体系: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()){
}