集合框架
-
集合可以看作是一种容器,用来存储对象信息。所有集合类都位于java.util包下,但支持多线程的集合类位于java.util.concurrent包下。
-
数组与集合的区别
- 数组的长度是固定的。集合的长度是可变的
- 数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象,而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。
Collection家族
-
Collection 接口
序号 接口描述 1 Collection 接口
Collection 是最基本的集合接口。一个 Collection 代表一组 Object ,即 Collection 元素
Java不提供直接继承Collection的类,只提供继承于它的子接口(如List,Set)
Collection接口存储一组不唯一,无序的对象2 List 接口
List 是一个有序的 Collection,此接口能够精准的控制每个元素插入的位置,可以通过索引来访问 List 中的元素
第一个元素索引为0,而且允许有相同的元素
List 接口存储一组不唯一,有序(插入顺序)的对象3 Set 接口
Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素4 SortedSet 接口
继承于 Set 的子接口,但是它保存的是唯一有序的集合5 Queue 接口
Queue 就是队列,队列的特点是先进先出,通常,不允许随机访问元素。 -
Set 与 List 的区别
- Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可重复的元素
- Set 检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>
- List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector>
-
Collection 实现类(常用集合类)
序号 类描述 1 List
|➡ArrayList
该类实现了可变大小的数组,为随机访问和遍历元素提供了更好的性能
线程不安全的(异步),多线程情况下不要使用。
ArrayList 扩容长度 50%,插入删除效率低。
底层为数组结构2 List
|➡Vector
该类与ArrayList 类非常相似
线程安全的(同步),多线程情况下,该类允许默认扩容长度100%
底层为数组结构3 List/Queue
|➡LinkedList
该类没有同步方法,如果多线程同时访问要给List,则必须自己实现访问同步
解决方法就是创建List时创造一个同步的List = Collections.synchronizedList(newLinkedList(...));
底层为双向链表结构4 Queue
|➡ArrayQueue
该类是一个循环队列,继承了AbstractList 抽象类
底层为数组结构5 Set
|➡HashSet
该类不允许出现重复元素,不保证集合中元素的顺序,允许包含值为Null的元素,但最多一个
底层为哈希表结构(一个HashMap实例)由数组+链表/红黑树构成(链表长度大于8切换为红黑树)6 Set
|➡TreeSet
该类实现排序等功能
底层为红黑树结构7 Set
|➡HashSet
|➡LinkedHashSet
该类不仅保证了元素的唯一,同时元素存放是由顺序的
底层为链表+哈希表结构 -
特别说明
-
代码表现
public class LinkedListDemo{ public static void main(String[] args) { LinkedList
link = new LinkedList (); //添加元素 link.addFirst("abc1"); link.addFirst("abc2"); link.addFirst("abc3"); System.out.println(link); // 获取元素 System.out.println(link.getFirst()); System.out.println(link.getLast()); // 删除元素 System.out.println(link.removeFirst()); System.out.println(link.removeLast()); while (!link.isEmpty()) { //判断集合是否为空 System.out.println(link.pop()); //弹出集合中的栈顶元素(弹栈) } System.out.println(link); } } /* 输出结果: [abc3, abc2, abc1] abc3 abc1 abc3 abc1 abc2 [] */ public class HashSetDemo { public static void main(String[] args) { //创建 Set集合 HashSet
set = new HashSet (); //添加元素 set.add(new String("cba")); set.add("abc"); set.add("bac"); set.add("cba"); //遍历 for (String name : set) { System.out.println(name); } System.out.println("==========") //创建集合对象 该集合中存储 Student类型对象 HashSet stuSet = new HashSet (); //存储 Student stu = new Student("于谦", 43); stuSet.add(stu); stuSet.add(new Student("郭德纲", 44)); stuSet.add(new Student("于谦", 43)); stuSet.add(new Student("郭麒麟", 23)); stuSet.add(stu); for (Student stu2 : stuSet) { System.out.println(stu2); } } } /* 输出结果: cba abc bac ========== Student [name=郭德纲, age=44] Student [name=于谦, age=43] Student [name=郭麒麟, age=23] tips: 根据结果我们发现字符串“cba”与Student [name=于谦, age=43]只存储了一个,也就是说重复的元素set集合不存储 */ public class LinkedHashSetDemo { public static void main(String[] args) { Set
set = new LinkedHashSet (); set.add("bbb"); set.add("aaa"); set.add("abc"); set.add("bbc"); Iterator it = set.iterator(); while (it.hasNext()) { System.out.println(it.next()); } } } /* 运行结果 bbb aaa abc bbc */
Collections工具
-
java.utils.Collections 是集合工具类,用来对集合进行操作
- public static boolean addAll(Collection c, T... elements) ,往集合中添加一些元素
- public static void shuffle(List> list) 打乱顺序 ,打乱集合顺序
- public static void sort(List list) ,将集合中元素按照默认规则排序
- public static void sort(List list,Comparator super T> ) ,将集合中元素按照指定规则排序
public class CollectionsDemo { public static void main(String[] args) { ArrayList
list = new ArrayList (); //原来写法 //list.add(12); //list.add(14); //list.add(15); //list.add(1000); //采用工具类 完成 往集合中添加元素 Collections.addAll(list, 5, 222, 1,2); System.out.println(list); //排序方法 Collections.sort(list); System.out.println(list); } } /* 运行结果: [5, 222, 1, 2] [1, 2, 5, 222] */ - public static void sort(List list, Comparator super T> ) ,将集合中元素按照指定规则排序
-
Comparator比较器
public class CollectionsDemo3 { public static void main(String[] args) { ArrayList
list = new ArrayList (); list.add("cba"); list.add("aba"); list.add("sba"); list.add("nba"); //排序方法 按照第一个单词的降序 Collections.sort(list, new Comparator () { @Override public int compare(String o1, String o2) { /* 两个对象比较的结果有三种:大于,等于,小于。 如果要按照升序排序,则 o1 小于 o2 如果要按照降序排序,则 o1 小于 o2 */ return o2.charAt(0) - o1.charAt(0); } }); System.out.println(list); } } public int compare(String o1, String o2) - Comparable 强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
- Comparator 强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。
- 练习例子
//实体 public class Student{ private String name; private int age; public Student() { } public Student(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 "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } } //测试类 public class Demo { public static void main(String[] args) { // 创建四个学生对象 存储到集合中 ArrayList
list = new ArrayList (); list.add(new Student("rose",18)); list.add(new Student("jack",16)); list.add(new Student("abc",16)); list.add(new Student("ace",17)); list.add(new Student("mark",16)); /* 让学生 按照年龄排序 升序 */ // Collections.sort(list);//要求 该list中元素类型 必须实现比较器Comparable接口 for (Student student : list) { System.out.println(student); } } } /* 发现 当我们调用Collections.sort()方法的时候程序报错 原因 如果想要集合中的元素完成排序,那么必须要实现比较器Comparable接口、 */ public class Student implements Comparable { .... @Override public int compareTo(Student o) { return this.age-o.age;//升序 } } /* 运行结果 Student{name='jack', age=16} Student{name='abc', age=16} Student{name='mark', age=16} Student{name='ace', age=17} Student{name='rose', age=18} */ - 拓展,如果想在方法中独立使用定义规则可以采用Collections.sort(List list,Comparetor c)
Collections.sort(list, new Comparator
() { @Override public int compare(Student o1, Student o2) { return o2.getAge()-o1.getAge();//以学生的年龄降序 } }); /* 上列代码,效果与上边输出一样 下列代码,为多规则判定 */ Collections.sort(list, new Comparator () { @Override public int compare(Student o1, Student o2) { // 年龄降序 int result = o2.getAge()-o1.getAge(); //年龄降序 if(result==0){//第一个规则判断完了 下一个规则 姓名的首字母 升序 result = o1.getName().charAt(0)-o2.getName().charAt(0); } return result; } });
Map家族
-
Map接口,存储一组键值对对象,提供key/value的映射
序号 描述类 1 Map
|➡HashMap
该类是一个散列表,它存储的内容是键值对(key/value)映射
根据键的HashCode值存储数据,具有很快的访问速度,最多记录一条为Null的键,不支持线程同步
底层为哈希表结构(数组+链表/红黑树)2 Map
|➡HashMap
|➡LinkedHashMap
底层为 链表 + 哈希表 -
常用方法
- public V put(K key, V value),把指定的键与指定的值添加到Map集合中
- public V remove(Object key),把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的
- public V get(Object key) ,根据指定的键在Map集合中获取对应的值
- public Set keySet(),获取Map集合中所有的键,存储到Set集合中
- public Set
> entrySet(),获取到Map集合中所有的键值对对象的集合(Set集合)
public class MapDemo { public static void main(String[] args) { //创建 map对象 HashMap
map = new HashMap (); //添加元素到集合 map.put("黄晓明", "杨颖"); map.put("文章", "马伊琍"); map.put("邓超", "孙俪"); System.out.println(map); //String remove(String key) System.out.println(map.remove("邓超")); System.out.println(map); // 想要查看 黄晓明的媳妇 是谁 System.out.println(map.get("黄晓明")); System.out.println(map.get("邓超")); } } /* tips: 使用put方法时,若指定的键(key)在集合中没有,则没有这个键对应的值,返回null,并把指定的键值添加到集合中 若指定的键(key)在集合中存在,则返回值为集合中键对应的值(该值为替换前的值),并把指定键所对应的值,替换成指定的新值 */ -
Map遍历键找值
public class MapDemo01 { /* 1.获取Map中所有的键,由于键是唯一的,所以返回一个Set集合存储所有的键。方法提示: keyset() 2.遍历键的Set集合,得到每一个键。 3.根据键,获取键所对应的值。方法提示: get(K key) */ public static void main(String[] args) { //创建Map集合对象 HashMap
map = new HashMap (); //添加元素到集合 map.put("胡歌", "霍建华"); map.put("郭德纲", "于谦"); map.put("薛之谦", "大张伟"); //获取所有的键 获取键集 Set keys = map.keySet(); // 遍历键集 得到 每一个键 for (String key : keys) { //key 就是键 //获取对应值 String value = map.get(key); System.out.println(key+"的CP是:"+value); } } } -
Entry键值对对象以及遍历
/* Map中存放的是两种对象 key(键)/value(值),它们在在Map中是一一对应关系。一组K/V又称做Map中的一个Entry(项),Entry将键值对的对应关系封装成了对象,即键值对对象。这样我们在遍历Map集合时,就可以从每一个键值对(Entry)对象中获取对应的键与对应的值。 方法: public K getKey() :获取Entry对象中的键。 public V getValue() :获取Entry对象中的值。 操作步骤: 1. 获取Map集合中,所有的键值对(Entry)对象,以Set集合形式返回。方法提示: map.entrySet() 。 2. 遍历包含键值对(Entry)对象的Set集合,得到每一个键值对(Entry)对象。 */ public class MapDemo02 { public static void main(String[] args) { // 创建Map集合对象 HashMap
map = new HashMap (); // 添加元素到集合 map.put("胡歌", "霍建华"); map.put("郭德纲", "于谦"); map.put("薛之谦", "大张伟"); // 获取 所有的 entry对象 entrySet Set > entrySet = map.entrySet(); // 遍历得到每一个entry对象 for (Entry entry : entrySet) { // 解析 String key = entry.getKey(); String value = entry.getValue(); System.out.println(key+"的CP是:"+value); } } } /* tips:Map集合不能直接使用迭代器或者foreach进行遍历。但是转成Set之后就可以使用了。 */ -
HashMap存储自定义类型键值,使用map.keySet()方法
/* 每位学生(姓名,年龄)都有自己的家庭住址。则将学生对象和家庭住址存储到 map集合中。学生作为键, 家庭住址作为值 */ public class Student { private String name; private int age; public Student() { } public Student(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; } public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return age == student.age && Objects.equals(name, student.name); } @Override public int hashCode() { return Objects.hash(name, age); } } /*map.keySet()方法*/ public class HashMapTest { public static void main(String[] args) { //1,创建Hashmap集合对象。 Map
map = new HashMap (); //2,添加元素。 map.put(newStudent("lisi",28), "上海"); map.put(newStudent("wangwu",22), "北京"); map.put(newStudent("zhaoliu",24), "成都"); map.put(newStudent("zhouqi",25), "广州"); map.put(newStudent("wangwu",22), "南京"); //3,取出元素。键找值方式 Set keySet = map.keySet(); for(Student key: keySet){ Stringvalue = map.get(key); System.out.println(key.toString()+"....."+value); } } } -
Java 9 ,添加了几重集合工厂方法,更方便创建少量元素的集合
public class HelloJDK9 { public static void main(String[] args) { Set
str1=Set.of("a","b","c"); //str1.add("c");这里编译的时候不会错,但是执行的时候会报错,因为是不可变的集合 System.out.println(str1); Map str2=Map.of("a",1,"b",2); System.out.println(str2); List str3=List.of("a","b"); System.out.println(str3); } } /* 两点注意: 1.of()方法只是Map,List,Set这三个接口的静态方法,其父类接口和子类实现并没有这类方法,比如 HashSet,ArrayList等待; 2.返回的集合是不可变的; */
常见的数据结构
数据存储的常用结构有:栈、队列、数组、链表和红黑树。我们分别来了解一下:
栈
-
栈(stack) ,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。
-
简单的说,采用该结构的集合,对元素的存取有如下的特点
- 先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。
- 栈的入口、出口的都是栈的顶端位置
-
这里两个名词需要注意
- 压栈,就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
- 弹栈,就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
队列
- 队列(queue),简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行删除。
- 简单的说,采用该结构的集合,对元素的存取有如下的特点
- 先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,小火车过山洞,车头先进去,车尾后进去;车头先出来,车尾后出来。
- 队列的入口、出口各占一侧。
数组
-
数组(Array),是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一排出租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。
-
简单的说,采用该结构的集合,对元素的存取有如下的特点
- 查找元素快:通过索引,可以快速访问指定位置的元素
- 增删元素慢,指定索引位置增加元素 需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。指定索引位置删除元素,需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中
链表
-
链表(linked list),由一系列结点node组成(链表中每一个元素称为结点),结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表与双向链表,那么这里给大家介绍的是单向链表。
-
简单的说,采用该结构的集合,对元素的存取有如下的特点
- 多个结点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了。
- 查找元素慢,想查找某个元素,需要通过连接的节点,依次向后查找指定元素
- 增删元素快,增加元素,只需要修改连接下个元素的地址即可。删除元素,只需要修改连接下个元素的地址即可
红黑树
-
二叉树(binary tree) 是每个结点不超过2的有序树(tree)。简单的理解,就是一种类似于我们生活中树的结构,只不过每个结点上都最多只能有两个子结点。二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作"左子树"和"右子树"
-
我们要说的是二叉树的一种比较有意思的叫做红黑树,红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。
-
红黑树的约束
- 节点可以是红色的或者黑色的
- 根节点是黑色的
- 叶子节点(特指空节点)是黑色的
- 每个红色节点的子节点都是黑色的
- 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
-
红黑树的特点,速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍