目录
一、集合框架
有关LinkedList的集合的,它是一个链表结构的集合
1、链表结构
1.1 单链表的结构
所谓单链表(Linked)在内存中不连续的一端内存空间, 链表的每一个元素是一个节点,每一个结点由数据元素和下一个结点的存储位置组成,链表结构与数组结构最大区别是链接结构的存储内存是不连续的,而数组结构的内存是连续的,链表结构不能与数组结构一样快速查找,
链表结构操作特点是 添加,删除元素效率高,查询效率低;
数组结构操作特点: 添加,删除效率低,查询效率高
前驱: 该节点的上一个元素的地址
后继: 该节点的下一个元素的地址
链表结构中最后一个元素的”后继“为null
1.2 单链表的实现
/**
* 添加到最后元素
* @param obj
*/
public void addLast(Object obj){
//将节点添加到最后
//add(obj , this.size);
// 创建节点
// Node node = new Node(obj);
// // 找到最后一个元素的地址
// Node lastNode = this.header;
// for(int i = 0;i<this.size-1 ;i++){
// lastNode = lastNode.next;
// }
//
// lastNode.next=node;
// 找最后一个结点 (由于最后一个结点的next是null)
Node node = new Node(obj);
Node lastNode = this.header;
while(lastNode.next!=null){
lastNode = lastNode.next;
}
lastNode.next = node;
this.size++;
}
/**
* 删除第一个节点
* @param index
* @return
*/
public void removeFirst(){
//删除第一个节点
if(this.size==0){
throw new IllegalArgumentException("没有需要删除的原始");
}
// 获取当前连接的“后继”
Node node = this.header.next;
// 并让后继作为头
this.header = node;
this.size--;
}
/**
* 删除最后节点
*/
public void removeLast(){
//删除是否存在数据
if(this.size==0){
throw new IllegalArgumentException("没有需要删除的原始");
}
// 找最后一个元素的前一个 地址 ,并将该地址的next 改为null
Node cur = this.header;
Node pre = this.header;
while(cur.next!=null){
pre = cur;
// 下一个变为当前
cur = cur.next;
}
// 最后一个元素 就是 当前
pre.next = null;
size--;
}
2、队列结构
队列结构(Queue): 在基于链表结构的基础上 ,实现的一种“先进先出”的结构, 常用操作 入队(put),出队(pop) ,设置队列的头结点 和 尾结点
package com.j2008.dataStruct;
/**
* ClassName: MyQueue
* Description:
* date: 2020/10/26 16:41
*
* @author wuyafeng
* @version 1.0 softeem.com
*/
public class MyQueue<T> {
// 头结点
private Node front;
// 尾结点
private Node tail;
// 大小
private int size;
public MyQueue(){
// 头,尾为空
this.front= this.tail=null;
}
class Node{
private T obj;
private Node next;
public Node(T obj){
this.obj = obj;
}
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
/**
* 入队 : 将元素添加到队列的尾部
*/
public void put(T obj){
// 创建节点
Node node = new Node(obj);
// 如果元素为空 则头就尾,尾就是头
if(isEmpty()){
this.front = this.tail = node;
return ;
}
// 将新元素的地址 作为尾的next
this.tail.next=node;
//将新元素的结点 作为尾节点
this.tail = node;
this.size++;
}
/**
* 出队: 将元素从队列的头部移除 (保持与队列脱离关系)
* @return
*/
public T pop(){
if(isEmpty()){
throw new IllegalArgumentException("没有弹出的原始");
}
// 移除头部元素
Node popNode = this.front;
// 设置现在的头元素是下一个
this.front = popNode.next;
// 将弹出的元素next 设置null,与队列脱离关系
popNode.next=null;
this.size--;
// 如果没有元素了 则需要 设置头尾都是null
if(this.size<0){
this.front=this.tail=null;
}
return popNode.getObj();
}
/**
* 判断元素是否为空
* @return
*/
public boolean isEmpty(){
if(this.front==null && this.tail==null){
return true;
}
return false;
}
}
3、栈结构
栈(Stack)结构也是常用数据结构之一,它具有 “后进先出” 的特点
public class MyStack<T> {
// 定义一个数组 ,用于存储元素
private Object[] obj;
private int size;
public MyStack(){
obj = new Object[10];
size=0;
}
/**
* 入栈: 压入栈顶元素
* @param t
*/
public void push(T t){
expandCapacity(size+1);
obj[size]=t;
size++;
}
/**
* 返回栈顶元素:peek
*/
public T peek(){
if(size>0) {
return (T) obj[size - 1];
}
return null;
}
/**
* 出栈: 返回栈顶的元素,并删除该元素
* @return
*/
public T pop(){
T t = peek();
if(size>0) {
// 将最后一个元素 删除
obj[size - 1] = null;
size--;
}
return t;
}
/**
* 是否为空元素
* @return
*/
public boolean isEmpty(){
return size==0;
}
/**
* 扩容数组大小 : 扩容1.5倍
*/
public void expandCapacity(int size){
if(obj.length< size){
// 需要扩容
int length = size*3/2 + 1;
this.obj = Arrays.copyOf(this.obj,length);
}
}
}
二、LinkedList集合
java.util.LinkedList集合是java.util.List的实现类,实现List接口的所有方法(添加,删除,查找,判断是空等) ,它添加,删除元素较快,查询相对慢,但是查询头尾元素较快
LinkedList集合实现双向链表接口,实现从头元素到尾元素的链表和从尾到头元素的链表,目标为了增加元素的检索效率
关于LinkedList实现大量操作头元素和尾元素的方法。 其中必须通过LinkedList的引用创建该对象
public void addFirst(E e) :将指定元素插入此列表的开头。
public void addLast(E e) :将指定元素添加到此列表的结尾。
public E getFirst() :返回此列表的第一个元素。
public E getLast() :返回此列表的后一个元素。
public E removeFirst() :移除并返回此列表的第一个元素。
public E removeLast() :移除并返回此列表的后一个元素。
public E pop() :从此列表所表示的堆栈处弹出一个元素。
public void push(E e) :将元素推入此列表所表示的堆栈。
public boolean isEmpty() :如果列表不包含元素,则返回true
三、 Set集合
java.util.Set 接口 继承自Collection接口,实现对元素的基本操作 ,与java.util.List区别于 Set集合存储无序,且唯一的元素,List存储有序,且可重复的元素
Set接口的实现类 HashSet 、 LinekedHashSet 、TreeSet
1、HashSet
HashSet集合依据元素的哈希值确定在内存中的存储位置, 所谓Hash值是内存中哈希表的唯一标志,通过哈希值可快速检索到元素所在的位置 , 所以它查询效率高 ,与HashSet类似结构的包括HashMap 等
创建一个HashSet时,就是创建一个HasMap( 关于HashMap结构后面讲)
什么是哈希表?
在Java1.8以前,哈希表的底层实现采用数组+链表结构,但是这样对于“Hash冲突” (两个对象生成的哈希值一样),即多个元素存储在一个“数据桶”中, 这样查找该元素时,依然效率低下, 为了解决由于哈希冲突导致的数据查询效率低下,JDK8以后将哈希表实现采用 数组+链表+红黑树结构
1.2.HashSet存储自定义对象类型
HashSet对于对象是否相同的依据,判断对象的hashCode值和equals是否相等,如果它们相等则判断元素一致,不能重复添加
public class People {
private int pid;
private String pname;
private int age;
public People(int pid, String pname, int age) {
this.pid = pid;
this.pname = pname;
this.age = age;
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
return this.pid;
}
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;
}
if(obj instanceof People){
People p = (People) obj;
if(p.pid == this.pid && p.getPname().equals(p.getPname())){
return true;
}
}
return false;
}
}
//存储 对象类型
Set<People> sets = new HashSet<>();
People p = new People(1001,"关羽",100);
People p2 = new People(1002,"张飞",100);
People p3 = p2;
System.out.println("p的hashcode:"+p.hashCode());
sets.add(p);
// 检查是否为同一个地址
sets.add(p);
sets.add(p2);
sets.add(p3);
// 插入一个重新new的张飞对象 HashSet以 equals和hashcode的结果作为是否重复对象的依据
People p4 = new People(1002,"张飞",90);
sets.add(p4); // 会当做是重复的对象 ,不能添加成功。
System.out.println("sets的长度:"+sets.size());
for(People obj : sets){
System.out.println(obj.getPid()+"---"+obj.getPname()+"---"+obj.getAge());
}
1.3 LinkedHashSet
在HashSet中存储的数据是唯一且无序,如何保证数据的有序型,可通过扩展HashSet的子类完成
java.util.LinkedHashSet ,它实现有序的Hash结构, 它的底层实现使用链表+哈希结构
创建LinkedHashSet时,就是创建一个LinkedHashMap结构 ,linkeHashSet中如何保证顺序一致性
accessOrder = false; 按照插入的顺序存储 accessOrder = true: 按照访问的顺序存储。
// 创建LinkedHashSet对象
LinkedHashSet<String> set = new LinkedHashSet();
set.add("aaa");
set.add("bbb");
set.add("ccc");
set.add("ddd");
//遍历元素
for(String s : set){
System.out.println(s);
}
1.4 TreeSet
TreeSet实现对Set元素的排序功能, 也包含基础的Set集合功能。 存放在TreeSet中的元素时有序的,默认升序,也可以自定义排序规则。
两种方式实现自定义排序规则
1、对元素(自定义类)实现 java.lang.Comparable 接口,重写 compareTo方法
public class Fruit implements Comparable<Fruit>{
private int id;
private String name;
public int compareTo(Fruit o) {
// return this.id-o.id; 升序
return o.id - this.id;
// 正数: 前一个大于后一个
// 负数: 前一个小于后一个
}
}
// 实现自定义排序规则的方式一 : 对象实现Comparable接口 (java.lang)
// 重写compareTo 方法。
TreeSet<Fruit> fruitSet = new TreeSet<>();
Fruit f1 = new Fruit(100,"苹果");
Fruit f2 = new Fruit(101,"香蕉");
fruitSet.add(f1);
fruitSet.add(f2);
System.out.println(fruitSet.size());
for(Fruit f : fruitSet){
System.out.println(f.getId()+"---"+f.getName());
}
2、通过匿名内部类的方式 在创建TreeSet时,创建自定义排序规则 ,new Comparator的接口
// 自定义排序规则的方式二: 对treeSet实现匿名内部类 new Comparator(java.util)
TreeSet<Integer> scores = new TreeSet (new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1; //降序
}
});
// 添加元素
scores.add(80);
scores.add(87);
scores.add(90);
scores.add(78);
for(Integer score : scores){
System.out.println(score);
}
// 按照对象的某一属性降序
TreeSet<People> peopleSet = new TreeSet<>(new Comparator<People>() {
@Override
public int compare(People o1, People o2) {
return o2.getAge()- o1.getAge(); // 根据age降序排列
}
});
peopleSet.add(new People(1001,"张飞",100));
peopleSet.add(new People(1002,"刘备",102));
peopleSet.add(new People(1003,"关羽",108));
for(People p : peopleSet){
System.out.println(p.getPid()+"--"+p.getAge());
}
四、Map集合
java.util.Map集合用于存储key-value的数据结构 ,一个键对应一个值,其中键在集合中是唯一的, Value可以重复, 例如 学号与学生的关系,省份编号对应省份信息, 对于Map集合的常用实现类包括 HashMap 、LinkedHashMap、HashTable 、TreeMap 等 。
1、HashMap(重点)
java.util.HashMap 存储无序的,键值对数据,HashMap的实现原理在JDK1.8以前使用 链表+数组结构,1.8以后使用链表+数组+红黑树结构, 使用Hash表的存储方式其检索效果高
特点:
a、HashMap的key唯一,且无序,value不唯一
b、HashMap的key和value 都可以为null
c、对于相同key 元素,它的value会覆盖原始value
HashMap的常用方法
HashMap() 构造一个空的 HashMap ,默认初始容量(16)和默认负载系数(0.75)。 HashMap(int initialCapacity) 构造一个空的 HashMap具有指定的初始容量和默认负载因子(0.75)。 HashMap(int initialCapacity, float loadFactor) 构造一个空的 HashMap具有指定的初始容量和负载因子。 HashMap(Map<? extends K,? extends V> m) 构造一个新的 HashMap与指定的相同的映射 Map 。
a、put(K key,V value) : 存储key-value 到容器中
b、V get(K key): 根据key 获取对应的value
c、Set keySet(): 返回所有的key,Set集合
d、boolean containsKey(K key): 判断key是否存在
e、clear():清空容器的原始
f、boolean containsValue(V value):判断value是否存在
g、Collection values() : 返回所有的value集合
h、isEmpty(): 判断是否为空集合
i、remove(Object key) : 根据key删除这个key-value
j、size():返回元素的大小
k、Set<Map.Entry<Key,Value>> entrySet(): 返回容器的key-value的实体类的集合,方便遍历元素
put插入的流程
步骤一: 先判断容器是否为空,为空需要扩容,
步骤二: 根据key 生成hash值,根据hash值找到对应的数组的位置,如果数组位置为空,说明没有hash冲突,直接插入,并长度+1 。
步骤三:如果数组位置的内容不为空,说明产生hash冲突 ,继续通过key查找元素,如果第一个元素存在(说明还没有生产链表)直接返回value 并覆盖value的值。
步骤四: 如果key对应的第一个元素不存在,则此时可能出现链表或红黑树, 如果是红黑树采用树结构的插入法(省略分析过程)。 否则一定是链表结构
步骤五: 如果是链表,将该元素插入到末尾,之后验证整个链表的长度是否大于8 ,如果大于8,将链表转成红黑树结构。
步骤六:插入成功之后,判断整个容器的元素个数是否超出 扩容的临界值(threshold = capacity* 增长因子)
关于JDK7.0和JDK8.0的HashMap有什么区别
1、结构不同
JDK7采用数组+链表 JDK8采用数组+链表+红黑树
2、hash值的计算方式不同
JDK7 table在创建hashmap时分配空间
JDK8 通过key的hashcode计算,在put时分配空间
3、发生hash冲突插入方式不同
JDK7采用头插法,JDK8采用尾插法
4、resize操作方式不同
JDK7重写计算index 的值,JDK8通过判断相应的位是0还是1,要么依旧是原index,要么是oldCap + 原index
3、LinkedHashMap
由于HashMap存储的key是无序,如果需要存储有序的key可使用LinkedHashMap 它依然满足HashMap的所有特点 ,并在此基础上有序
4、TreeMap
TreeMap实现一个可排序的Map集合 ,默认对key升序排列,也可以降序排序,
如果添加元素的key为自定义类,需要实现Comparable接口或Comparator接口。 TreeMap的底层实现是二叉树结构 (有关二叉树的特点 ) ,实现有序的key的分布
// TreeMap 用于存储可排序的Key -Value集合 ,
// 其中key必须实现了排序规则的对象 (包装类,String,自定类)
public static void main(String[] args) {
// 存储学号和分数 默认对key 进行升序
// TreeMap<Integer ,Integer> maps = new TreeMap<>();
TreeMap<Integer,Integer> maps = new TreeMap<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1; // 降序
}
});
maps.put(1003,88);
maps.put(1002,90);
maps.put(1004,80);
maps.put(1001,85);
//输出
Set<Integer> keys = maps.keySet();
for(Integer k : keys){
System.out.println(k+"----"+maps.get(k));
}
// 自定义规则
// 注意 ,如果key不是包装类而是自定义,必须要求该类实现Comparable或Comparator接口
TreeMap<Student ,Integer> stuMap = new TreeMap<>();
stuMap.put(new Student(1001,"张飞"),90);
stuMap.put(new Student(1003,"刘备"),87);
stuMap.put(new Student(1002,"关羽"),96);
// 这里降序排列
for(Map.Entry<Student,Integer> en : stuMap.entrySet()){
System.out.println(en.getKey().getSid() + "---"+ en.getValue());
}
}
class Student implements Comparable<Student>{
private int sid;
private String sname;
public Student(int sid, String sname){
this.sid = sid;
this.sname = sname;
}
public int getSid() {
return sid;
}
public void setSid(int sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
@Override
public int compareTo(Student o) {
return o.sid-this.sid; // this表示前一个对象, o表示后一个对象
}
}
5、HashTable
HashTable 实现 hash结构的key-value集合, 与HashMap很相似, HashTable 是线程安全(它的很多方法是同步操作),它不需要存储null的 key 和value
扩展自 Dictionary类 和 实现Map接口
常用方法 :
put ()
get()
clear()
containsKey()
containsValue()
它 有一个子类 是 Properties类,用于存储属性文件的 key- value
public static void main(String[] args) {
//创建HashTable 无序
Hashtable<String,String> tables = new Hashtable<>();
// 存储
tables.put("王宝强","马蓉");
tables.put("贾乃亮","李小璐");
tables.put("文章","马伊琍");
//获取 使用所有key遍历 返回枚举类型
Enumeration<String> keys = tables.keys();
while(keys.hasMoreElements()){
String s = keys.nextElement();
System.out.println(s + "---"+ tables.get(s));
}
// 有一个HashTable的子类 Properties
Properties prop = new Properties();
prop.setProperty("username","张三");
prop.setProperty("password","123456");
//获取对应属性名的值
System.out.println("根据属性名获取值:"+prop.getProperty("username"));
System.out.println("根据属性名获取值:"+prop.getProperty("password"));
}
五、集合常见面试题
1、Collection 和 Collections 的区别? Array 和Arrays的区别?
Collection是集合的顶级接口 ,Collections是一个集合工具类 ,它提供大量的操作集合方法,例如排序, 打乱顺序,添加元素等。
Array 表示一个数组对象 , Arrays是一个数组工具类 ,提供大量的数组操作方法。
2、List 和 Set 的区别? ArrayList 和 LinkedList的区别
相同点:List、Set 都继承Collection接口
不同点: List 存储不唯一,有序集合元素 , Set 存储唯一,无序的集合元素
ArrayList 实现数组结构集合,查询比较快,添加,删除效率低
LinkedList实现双向链表结构集合,添加,删除效率高, 查询效率低
3、HashSet 和 TreeSet的区别?
它们都存储唯一集合,都实现Set接口
HashSet无序,底层实现Hash结构的存储, TreeSet有序,可实现自定义排序,存储树形结构
4、HashMap 和 HashSet的区别?
它们都属于Hash结构的集合,存储效率较高 , HashSet是存储单个元素 ,HashMap存储键值对元素
HashMap实现Map接口,HashSet实现Set接口 ,
5、HashMap 和HashTable的区别
它们都来自Map 的实现类, HashTable 还继承一个父类 Dictionary
-
HashMap 的key和value 可以为空,HashTable 的key value 不能为空;
-
HashTable 的子类包含key value 的方法
-
HashMap线程不安全效率高 ,HashTable线程安全效率低
6、HashMap 和 TreeMap的区别?
它们都实现Map接口 ,TreeMap 有序,HashMap无序, HashMap实现哈希结构 集合,TreeMap实现二叉树集合
五、JDK8的特性
在JDK8中新增一些特殊功能,一般开发时方便使用, 其中最主要的功能如下
1、接口的默认函数
public interface MyInterface {
public default void defaultMethods(){
System.out.println("这是一个接口的默认方法。");
// 静态方法可以在default方法中调用
staticMethods();
}
public void service();
public static void staticMethods(){
System.out.println("这是接口的静态方法");
}
}
public static void main(String[] args) {
// 创建匿名内部类的方式
MyInterface my = new MyInterface() {
@Override
public void service() {
System.out.println("service方法.....");
}
};
my.defaultMethods();
//通过接口名 调用静态方法
MyInterface.staticMethods();
}
2、Lambda表达式
JDK8中支持一种对方法调用的 简写方式 ,也是一种特殊写法
语法: ([形参名]) ->{ 方法的实现}
这个接口中有且只有一个方法,并对方法实现
原始代码
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
// 降序
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1); //降序
}
})
由于JDK能识别sort的参数二是Comparator , 并Comparator是函数式接口(一个接口中有且只有一个方法。),实现的一定是唯一的方法名, 所有这里方法名和返回值也省略, 它的简写方式
// 使用lambda表达式 ([形参])->{方法体}
Collections.sort(list ,(String o1,String o2)->{
return o2.compareTo(o1);
});
由于形参的数据类与集合的元素类型一致,这里的形参类型也省略 、return也省略
// 最精简的Lambda
Collections.sort(list ,(o1,o2)->o2.compareTo(o1));
3、函数式接口
函数式接口主要用于满足前面Lambda表达式的语法的使用。 在一个接口中 有且只有一个方法的接口称为 “函数式接口” ,
如何将一个接口定义为函数式接口呢? 在接口上增加注解 “@FunctionalInterface”
package com.j2008.functionalFun;
@FunctionalInterface // 该注解的意义在于 约束接口只能由一个方法
public interface ConverterInterface<T> {
public String convertStr(T t);
}
//传统写法
ConverterInterface<Integer> con = new ConverterInterface<Integer>() {
@Override
public String convertStr(Integer o) {
return o.toString();
}
};
String ss = con.convertStr(100);
System.out.println(ss);
//使用 lambda表达式写法
ConverterInterface<Date> cons = (o)->o.toLocaleString();
String s = cons.convertStr(new Date());
System.out.println(s);
4、方法和构造器的引用
4.1 方法的引用
在以上接口函数中 这个convert方法的实现 对于java.lang包中是存在相同的方法的,所以 convert的实现可以直接引用已有静态方法 Integer.valueOf 或者 Integer.parseInt
以上代码的实现可以改成这样 :
语法: 类名 :: 静态方法名
@FunctionalInterface
public interface Convert<F,T> {
//将F转成T
public T convertType (F f);
}
public static void main(String[] args) {
// 原始使用lambda表达式 可以这样写
// 把字符串转成 Integer
Convert<String ,Integer> con = (f)->{
return Integer.parseInt(f);
};
Integer n = con.convertType("888");
// 在lambda基础上,如果实现的方法 已存在,则可直接调用 类名::方法名
Convert<String ,Integer> con2 = Integer::valueOf;
Convert<String ,Integer> con3= Integer::parseInt;
//调用方法实现
Integer n2 = con2.convertType("123");
int n3 = con3.convertType("555");
}
4.2 构造器的引用
当方法的实现 是构造器时,可直接引用构造器
语法: 类名::new
@FunctionalInterface
public interface StudentFactory<T> {
// 参数是创建对象时 的 属性
public T create(int id ,String name);
}
public class Student {
private int sid;
private String sname;
public Student(int sid, String sname) {
this.sid = sid;
this.sname = sname;
}
}
public static void main(String[] args) {
//使用Lambda实现 函数接口的方法
StudentFactory<Student> factory = (id,name)->{
return new Student(id ,name);
};
Student stu1 = factory.create(1001,"张三丰");
System.out.println(stu1);
// 以上写法可以直接换成 引用构造器方式
StudentFactory<Student> factory1 = Student::new;
//创建
Student stu2 = factory1.create(1002,"张无忌");
System.out.println(stu2);
}