集合基础认识

集合和数组的区别对比

对象数组

数组的定义格式:
	数据类型[] 数组名 = new 数据类型[数组长度];
	数据类型是对象的数据类型,就是对象数组
存储元素:	
	数组名[索引] = 对象的地址(使用对象名保存)
取出元素:
	对象的数据类型 变量名 = 数组名[索引];
    此时变量名保存的是当前对象的地址
    变量名.成员方法(set/get...)

集合和数组的区别

数组: 长度不可变
集合: 长度可变

ArrayList集合底层是数组,有索引
int[] arr = {1,2,3};

//存储数据类型
数组: 基本数据类型 + 引用数据类型
集合: 引用数据类型 + 基本数据类型的包装类


数组扩容

数组扩容:
数组在内存中是占用一段连续的存储空间,当数组初始化后,数组的长度就会固定不变,
需要增加数组的长度时,由于数组的存储空间附近可能被其他数据存储的空间占用,
所以只能创建一片新的存储空间用来存储数组。
//数组的长度是固定的,如果需要扩充,必须创建新的数组,原数组的元素要复制到新的数组中。

1 定义方法,形参是一个数组,返回值也是一个数组(输入一个数组,返回一个新数组)
	public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        int[] b=new int[arr.length*2];
        for(int i=0;i<arr.length;i++){
            b[i]=arr[i];
        }
        System.out.println(Arrays.toString(b));
    }
    返回结果:
    	[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]

2 使用方法System.arraycopy( 数组1 , 起始下标 , 数组2 , 目标位置 , 要拷贝的数据长度)
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        int[] b=new int[arr.length*2];
        System.arraycopy(arr,0,b,2,arr.length);
        System.out.println(Arrays.toString(b));
    }
    返回结果:
    	[0, 0, 1, 2, 3, 4, 5, 0, 0, 0]

3 使用方法Arrays.copyof( 数组1 , 新数组长度)
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        int[] b = Arrays.copyOf(arr, arr.length * 2);
        System.out.println(Arrays.toString(b));
    }

数组的高级操作

二分查找法

前提条件:
	数组元素要有顺序

思路分析:
1.查找范围索引[min,max],如果要是查询整个数组,范围[0,数组长度-1]
2.查找的结束条件
循环时,当min>max 结束循环,返回-1,表明不存在该元素
      当min<=max,继续循环
3.可以查找的情况下(min<=max)
   1.计算中间索引位置 mid = (min + max)/2 -- 注意:mid表示的是索引位置不是元素
   2.使用要查找的元素和mid索引位置的元素进行比较
          1.查找的元素 > mid位置元素 在mid位置的右边下次查找范围[mid+1,max]
          2.查找的元素 < mid位置元素 在mid位置的左边下次查找范围[min,mid-1]
          3.相等 找到了 返回当前mid值

代码实现:
public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        int a = 10;
        int index = binarySearch(arr, a);
        System.out.println("index = " + index);
}

private static int binarySearch(int[] arr, int a) {
        int min = 0;
        int max = arr.length - 1;
        while (min <= max) {
            int mid = (min + max) / 2;
            if (arr[mid] > a) {
                max = mid - 1;
            } else if (arr[mid] < a) {
                min = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
}

冒泡排序

效果:
	数值从小到大进行排序

思路分析:
1.相邻元素两两进行比较,大的放在右边,小的放在左边进行交换
2.每一轮确定当前轮次的最大值,放在最右边
3.下一轮比较会少一个元素,少的是上一轮得到的最大值
4.最后一次自己和自己比较,不需要进行的

结论:
      n 个数据参与比较,比较 n-1 次
      每一轮会得到一个最大值,不参与下一轮的比较

代码实现:
	1.外层循环作用:控制交换的轮次,长度为n的数组交换n-12.内层循环作用:获取元素进行交换操作
		-1:为了防止索引越界
		-i:每一轮少一个最大值参与下一次比较

public static void main(String[] args) {
        int[] arr = {3, 2, 1, 6, 5};
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    int temp = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
        pringArr(arr);
}

private static void pringArr(int[] arr) {
    for (int i = 0; i < arr.length; i++) {
         System.out.print(arr[i] + " ");
    }
}

递归***

定义:
	递归在程序中体现为方法调用自身

作用:
	解决复杂问题,复杂问题可以拆解为解决思路相同的小问题,以极少的代码实现需求

注意事项:
	必须要有出口,否则栈内存溢出(参考死循环)
	小问题解决思路要与大问题相同

代码实现:
//1.求1-100的和
public static void main(String[] args) {
        int sum = getSum(100);
        System.out.println("sum = " + sum);
    }

    private static int getSum(int i) {
        if (i == 1) {
            return 1;
        } else {
            return i + getSum(i - 1);
        }
}

//2.求5的阶乘
public static void main(String[] args) {
        int num = getJc(5);
        System.out.println("num = " + num);
}

private static int getJc(int i) {
        if (i == 1) {
            return 1;
        } else {
            return i * getJc(i - 1);
        }
}

快速排序(快排)****

思路分析:
基准数归位的过程
1.基准数的选取
    一般使用数组最左侧数字作为基准数
    注意:选取基准数之后,该数字不动,从另外一侧开始查找
2.数据的交换
    从右侧找比基准数小的,从左侧找比基准数大的
    交换
3.基准数归位
    效果:
        基准数左侧都是比自己小的
        右侧都是比自己大的
代码实现:
1.核心代码实现:递归方法第一次执行,第一个基准数归位操作
2.完整代码实现:添加递归及递归出口
public static void main(String[] args) {
        int[] arr = {6, 1, 7, 4, 2, 3, 5, 9, 10, 8};
        quiteSort(arr, 0, arr.length - 1);
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
    }

    private static void quiteSort(int[] arr, int left, int right) {
        System.out.println("基准数归为前的顺序为:" + Arrays.toString(arr));
        if (left > right) {
            return;
        }
        int left0 = left;
        int right0 = right;
        int baseNumber = arr[0];
        while (left != right) {
            while (arr[right] >= baseNumber && right > left) {
                right--;
            }
            while (arr[left] <= baseNumber && right > left) {
                left++;
            }
            int temp = arr[left];
            arr[left] = arr[right];
            arr[right] = temp;
        }
        //基准数归位
        int temp = arr[left];
        arr[left] = arr[left0];
        arr[left0] = temp;
        System.out.println("基准数归为后的顺序为:" + Arrays.toString(arr));
        System.out.println("------------------------------");
        quiteSort(arr, left0, left - 1);

        quiteSort(arr, left + 1, right0);
    }

Arrays数组工具类

1.toString(数组名):
思考:如果存储的元素是自定义类对象,想要打印的数组格式中是元素内容,怎么做?
     1.数组中存储的是基本数据类型
          打印出来的是[数据值, 数据值, ...]
     2.数组中存储的是引用数据类型
         如果重写ObjecttoString(),打印[属性值, 属性值, ...]
         没有重写,[地址值, 地址值, ...]
2.sort(数组名):
	对数组中元素进行快速排序
    目前为止只能排序基本数据类型,以及String字符串

3.int binarySearch(数组名,查找的元素):
	先sort,再使用
    二分查找法
    使用前提:数组元素有顺序
    返回值:元素存在,返回索引位置
          元素不存在,返回[-插入点-1]
          插入点:元素如果存在,应该在数组中的哪个索引位置上
          -1:如果元素应该出现在0索引位置,返回-0证明出现在0索引,返回的数据是错误的,此时-1变成-1,避免该问题产生

4.copyOf(原始数组名,新数组长度)
	int[] arr = {1, 2, 3, 4, 5};
	int[] ints = Arrays.copyOf(arr,10);
	//底层根据反射原理,利用参数1的数据类型,结合传递的长度,创建新数组,并将元素进行存储
	System.out.println(Arrays.toString(ints));
//打印结果:[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
	int[] ints1 = Arrays.copyOf(arr, 2);
	System.out.println(Arrays.toString(ints1));
//打印结果:[1, 2]

对比System.arrayCopy()
System.arrayCopy()
    //必须先指定目标数组,必须要求目标数组长度 >= 原始数组
	//目标数组必须手动指定,长度过小,会报索引越界异常

总结:
1. Arrays.copyOf( arr , newArr.length )会根据newArr.length从左到右拷贝数据,不会有索引越界异常报告
2. System.arrayCopy()必须手动指定新数组长度,且新数组长度必须 >= 原始数组长度,否则控制台打印索引越界异常

集合

单列集合

集合与数组的区别:
	1. 	数组长度不可变,集合长度可变。
	2. 	数组可以存储基本数据类型和引用数据类型。
		集合只能存储引用数据类型,若要存储基本数据类型,需要存储对应的包装类。

集合体系结构:
	--- Collection 单列集合接口(: 张三,李四,王五)
			-- List 可重复存储接口
					-- ArrayList 
					-- LinkedList
			-- Set 不可重复集合接口
					-- HashSet
					-- TreeSet
	--- Map 双列集合接口(: 张三,上海; 李四,北京; 王五,南京)
			-- HashMap
			-- TreeMap

Collection

概述: 
	1.单列集合的顶层接口,表示一组对象,这些对象也成为Collection的元素。
	2.JDK不提供此接口的任何直接实现,提供更具体的子接口(SetList)实现。

创建Collection集合对象:
	1.多态的方式进行创建
	2.具体的实现类ArrayList

常用方法:
	boolean add(E e)  添加元素,返回布尔类型,可查看是否添加成功
	boolean remove(Object o)  从集合中移除指定的元素
	/*
	底层:
		1.传递参数是元素本身,在底层数组中查找元素第一次出现的索引位置
		2.使用System.arraycopy方法进行删除
	*/
	boolean removeif(Object o)  根据条件进行删除
	/*
	底层:
		 removeIf方法内部会遍历集合(迭代器),获取每一个元素元素传递给Predicate<? super E> filter重写的test方法 (可以改进为lambda表达式)	重写的test方法的功能:根据传递的元素,进行是否删除的判断方法返回值true:可以删除false:不能删除
	*/
	void clear()  清空集合
	boolean contains(Object o) 判断集合中是否存在指定的元素
	boolean isEmpty()  判断集合是否为空
	int size() 集合的长度,也就是集合中元素的个数
	/*
	底层使用size成员变量
	底层:
		1.返回集合中存储的有效数据个数
			例如:集合扩容长度为10,但是只存储2个元素,返回值就是2
	*/

代码实现:
public static void main(String[] args) {
        Collection<String> collection = new ArrayList<>();
//        boolean add(E e)  添加元素,返回布尔类型,可查看是否添加成功
        collection.add("aaa");
        collection.add("bbb");
        collection.add("ccc");
        System.out.println(collection);  //[aaa, bbb, ccc]

//        boolean remove(Object o)  从集合中移除指定的元素
		//删除成功,返回true
    	//删除失败,返回false
        boolean result1 = collection.remove("aaa");
        System.out.println(result1);  //true
        boolean result2 = collection.remove("ddd");
        System.out.println(result2);  //false
        System.out.println(collection);  //[bbb, ccc]

//        boolean removeif(Object o)  根据条件进行删除
    /*
    	removeif底层会遍历集合,得到集合中的每一个元素。
		s依次表示集合中的每一个元素
		返回true,则删除,false则不删
    */
        collection.removeIf(
                (String s) -> {
                    return s.length() == 3;
					//删除 长度为 3 的 字符串
                }
        );
        System.out.println(collection);  //[]

//        void clear()  清空集合
        collection.clear();
        System.out.println(collection);  //[]

//        boolean contains(Object o) 判断集合中是否存在指定的元素
        boolean result1 = collection.contains("a");
        System.out.println(result1);  //false
        boolean result2 = collection.contains("aaa");
        System.out.println(result2);  //true

//        boolean isEmpty()  判断集合是否为空
		//为空返回true, 不为空返回false
        boolean empty = collection.isEmpty();
        System.out.println(empty);  //false

//        int size() 集合的长度,也就是集合中元素的个数
        int size = collection.size();
        System.out.println(size);  //3

}

迭代器

Iterator : 迭代器,集合的专用遍历方式

Iterator<E> iterator() : 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引。

常用方法:
	boolean hasNext() : 判断当前位置是否有元素可以被取出
	E next() : 获取当前位置的元素 且 将迭代器对象移向下一个索引位置
//如果没有元素还进行取出操作,会出现异常NoSuchElementException

操作步骤:
	1.获取迭代器对象
	2.循环判断是否有元素,使用hasNext()
	3.循环内取出元素,使用next()

原理分析:
	1.Iterator<E> iterator():迭代器创建完成,默认指向0索引位置
	2.boolean hasNext():判断当前位置是否有元素
	3.E next():1.取出元素 2.向后移动索引位置

注意事项: //并发修改异常ConcurrentModificationException
	产生原因:
		使用迭代器遍历集合的同时使用集合自己的增删方法
    解决方案:
         1.直接使用普通for循环
         2.使用迭代器自己的方法(删除)

代码练习:
    public static void main(String[] args) {
        Collection<String> c = new ArrayList<>();
        c.add("a");
        c.add("d");
        c.add("c");
        c.add("d");
        c.add("b");
        //集合中有特有计数器值进行对集合变化次数进行记录

        Iterator<String> iterator = c.iterator();
        //迭代器创建时根据原有集合添加元素初始化迭代器中的计数器值
        //此计数器值会作为iterator.next()输出依据
        //后续更改集合元素会导致集合中计数器数值改变
        //集合中计数器值和迭代器中计数器值不相等,会返回ConcurrentModificationException(并发修改异常)异常报错

        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }

总结:
	iterator() : 创建迭代器对象,默认指向集合0索引
	hasNext() : 判断当前指向索引有没有元素
	next() : 打印当前索引元素,并将迭代器移动至下一位

/*
	在循环遍历过程中,不能添加或删除元素,导致计数器值变化,控制台会打印ConcurrentModificationException 异常
*/

//迭代器删除方法
普通for循环:
public static void main(String[] args) {
        ArrayList<String> c = new ArrayList();
        c.add("a");
        c.add("d");
        c.add("c");
        c.add("d");
        c.add("b");
        //集合中有特有计数器值进行对集合变化次数进行记录
        for (int i = 0; i < c.size(); i++) {
            String s = c.get(i);
            if ("d".equals(s)){
                c.remove(i);
                i --;
            }
        }
		System.out.println(c);  //[a, c, b]
}

迭代器删除:
public static void main(String[] args) {
        ArrayList<String> c = new ArrayList();
        c.add("a");
        c.add("d");
        c.add("c");
        c.add("d");
        c.add("b");
        Iterator<String> iterator = c.iterator();
        while (iterator.hasNext()) {
            String s = iterator.next();
            if ("d".equals(s)){
                iterator.remove();
            }
        }
        System.out.println(c); //[a, c, b]
    }

增强for循环

简化数组和Collection集合的遍历

使用前提:
	容器的类/接口需要和Iterable有关(继承/实现关系)

特点:
	1.JDK5之后出现,内部原理是一个Iterator迭代器
	2.实现Iterable接口的类才可以使用迭代器和增强for
	3.单列集合可以直接使用迭代器和增强for,双列集合不可以直接使用

格式:
	for(元素数据类型 变量名 : 数组或者Collection集合){
        //使用元素
    }

快捷键:
	容器对象名.for

注意事项:
	1.格式中'变量名' 依次表示集合/数组中的每一个元素
			//元素是通过增强for底层的迭代器获取的
	2.增强for底层是迭代器,要注意并发修改异常

三种遍历方式的使用场景
	1.普通for:
        使用方法需要操作索引
	2.迭代器:
        遍历的同时进行删除操作 -- 使用removeIf方法
	3.增强for:
        单纯遍历容器
	4.forEach
        集合容器名.forEach(new Consumer(){
        	...
        })
        使用forEach遍历集合同时,不能使用集合自己的增删方法,否则会抛出并发修改异常

范例:
public static void main(String[] args) {
        ArrayList<String> c = new ArrayList();
        c.add("a");
        c.add("b");
        c.add("c");
        c.add("d");
        c.add("e");
        for (String s : c) {
            System.out.println(s);
        }
}

总结:
	1.数据类型一定是集合或者数组中元素的类型
	2.s 仅仅是变量名,在循环的过程中,依次表示集合或者数组中的每一个元素
	3.c 就是便利的集合或者数组

代码展示:
public static void main(String[] args) {
        ArrayList<String> c = new ArrayList();
        c.add("a");
        c.add("b");
        c.add("c");
		//底层通过迭代器选择索引,默认 0 索引
        for (String s : c) {
            s = "q";
            System.out.println(s);  //q q q
        }
        System.out.println(c);  //[a, b, c]
}
注意事项:
	1.修改第三方变量值,不会影响原集合中元素

使用场景:
	1.需要操作索引,使用普通 for 循环
	2.在遍历的过程中需要删除元素,使用迭代器
	3.仅仅需要遍历,使用增强 for

List集合

概述:
	1.有序集合,有序指的是存取顺序
	2.用户可以精确控制列表中每个元素的插入位置,用户可以通过整数索引访问元素,并搜索列表中的元素
	3.允许重复的元素

list使用方法:
void add(int index,E element) : 在指定位置添加元素
E remove(int index) : 删除指定索引处的元素,返回被删除的元素
E set(int index,E element) : 修改指定索引处的元素,返回被修改的元素
E get(int index) : 返回指定索引处的元素

补充方法:
int lastIndexOf (Object o):获取指定元素在集合中最后一次出现的索引位置,存在返回索引,不存在返回-1
static <E> List<E> of (E... elements):使用同种数据类型元素构建List集合
List<E> subList (int fromIndex, int toIndex):截取[fromIndex,toIndex)的元素,返回新集合
<T> T[] toArray (T[] a):将集合转换为存储相同数据类型的数组

代码展示:
public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");

        //在bbb之前添加ddd元素
        list.add(1, "ddd");
        System.out.println(list);

        //删除最后一个元素
        String remove = list.remove(2);
        System.out.println(remove);
        System.out.println(list);

        //删除多个元素
        ArrayList<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("bbb");
        list.add("bbb");
        list.add("ccc");
        list.removeIf(s -> "bbb".equals(s));
        System.out.println(list);

        //更改bbb为ddd
        String s = list.set(1, "ddd");
        System.out.println(s);
        System.out.println(list);

        //获取第一个位置的元素
        System.out.println(list.get(0));

        //获取集合的前两个元素(括号中索引左闭右开) (起始元素索引,目标元素索引+1)
        List<String> s = list.subList(0, 2);
        System.out.println(s);
        System.out.println(list);
    }

特有方法***(实用)
List list1 = new ArrayList<>(List.of(1,2,3,4,5,6,7,8));
	//List.of : 批量添加元素(JDK12可用,8不可用)
System.out.println(list1);
	//[1, 2, 3, 4, 5, 6, 7, 8]

//将集合乱序
Collections.shuffle(list1);
System.out.println(list1);
	//[5, 4, 3, 8, 6, 7, 2, 1]

 //集合排序
Collections.sort(list1);
System.out.println(list1);
	//[1, 2, 3, 4, 5, 6, 7, 8]

LinkedList

ArrayList区别:
	1. ArrayList : 底层数据结构是数组,查询快,增删慢
	2. LinkedList : 底层数据结构是链表,查询慢,增删快

底层:
	是双向链表 -- 火车车厢
	是List接口的实现类,可以使用List接口中的方法(LinkedList内部重写的)
	所有操作索引相关的方法都可以进行使用
	但是:索引只是表示当前节点对象在链表中位置,查找还是要从头/尾进行查找

LinkedList 核心成员变量:
	Node<E> first:用来记录链表第一个Node对象的地址
	Node<E> last:用来记录链表最后一个Node对象的地址
内部类Node<E>: -- 真正存储数据的节点对象
	封装三个属性:
		1.前一个节点地址
		2.元素值
		3.后一个节点地址

代码展示:
public static void main(String[] args) {
        LinkedList<String> list = new LinkedList<>();

        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        System.out.println("--------------------------");

        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }
        System.out.println("--------------------------");

        for (String s : list) {
            System.out.println(s);
        }
}

使用方法:
头结点记录下一个节点地址
尾结点记录上一个节点地址
void addFirst(E e) : 在该列表开头插入指定的元素
void addLast(E e) : 将指定的元素追加到此列表的末尾
E getFirst() : 返回此列表中的第一个元素
E getLast() : 返回此列表中的最后一个元素
E removeFirst() : 从此列表中删除并返回第一个元素
E removeLast() : 从此列表中删除并返回最后一个元素

代码展示:
public static void main(String[] args) {
        LinkedList<String> list = new LinkedList<>();

        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        list.addFirst("ddd");
        System.out.println(list);
			//[ddd, aaa, bbb, ccc, ddd]

        list.addLast("ccc");
        System.out.println(list);
			//[ddd, aaa, bbb, ccc, ddd, ccc]

        System.out.println(list.getFirst() + ":" + list.getLast());
			//ddd:ccc

        list.removeFirst();
        list.removeLast();
        System.out.println(list);
			//[aaa, bbb, ccc, ddd]
}

ArrayList集合

ArrayList构造方法和添加方法

构造方法
	ArrayList()
集合的泛型
	<引用数据类型> 表示限定集合存储的数据类型
	<>中只能是对象类型或者基本数据类型的包装类

基本数据类型的包装类:
	例如 int ---> Integer

添加元素方法

方法1: 对象名.add(元素)
方法2: 对象名.add(索引,元素)
    
    方法2是指插入索引位置,若不存在,索引越界异常(比如,一个数据都没有,直接插入1索引,会报错)
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(10);
        list.add(121);
        list.add(0,122);
        System.out.println(list);
    }

直接打印集合对象名,有自己的打印格式[元素1,元素2,......]

ArrayList常用方法

容器基本操作: 增删改查

删除方法:
	1.remove(元素) 存在返回true 不存在返回false
	2.remove(索引) 索引必须存在,不存在报错索引越界异常
	
修改方法:
	set(索引,元素) 使用该元素替换指定索引位置的元素

获取方法:
	get(索引)
	size() 获取集合元素个数
	
数组长度:数组名.length  是属性
字符串长度:对象名.length() 是方法
集合元素个数:对象名.size() 是方法

集合存储字符串并遍历

1.创建集合对象
	ArrayList<String> list = new ArrayList<>()
2.添加元素
    list.add(元素)
3.遍历
    list.fori
4.获取元素
    get(索引)

举例:
    public static void main(String[] args) {
        Random random = new Random();
        ArrayList<Integer> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            int a = random.nextInt(11);
            list.add(a);
        }
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }
    }

集合删除元素

1.正向遍历会存在缺少或漏掉等情况
public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("a");
        list.add("b");
        list.add("a");
        list.add("c");
        list.add("a");
        list.add("a");
        for (int i = 0; i < list.size(); i++) {
            list.remove("a"); //[b, c, a]
            list.remove(i); //[a, a, a]
        }
        System.out.println(list);
    }
    
2.反向遍历
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("a");
        list.add("b");
        list.add("a");
        list.add("c");
        list.add("a");
        list.add("a");
        for (int i = list.size() - 1; i >= 0; i--) {
            list.remove("a"); // [b, c]
        }
        System.out.println(list);
    }

3.使用list.contains方法  //如果此列表中包含指定的元素,则返回 true。(将此作为while循环判断条件,从而判定,只要我的list中存在"a",那就一直返回true,循环一直进行)
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("a");
        list.add("b");
        list.add("a");
        list.add("c");
        list.add("a");
        list.add("a");
        while (list.contains("a")){
            list.remove("a"); //[b, c]
        }
        System.out.println(list);
    }

Set集合

特点:
	不可重复(去重)
	存取顺序不一致
	没有带索引的方法,不能通过索引获取元素

代码展示:
public static void main(String[] args) {
        Set<String> set = new TreeSet<>();
        set.add("aaa");
        set.add("bbb");
        set.add("ccc");

        Iterator<String> it = set.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
        System.out.println("------------------------");
        for (String s : set) {
            System.out.println(s);
        }
}

TreeSet

不用于Set特点 : 可以将内部元素按照/*规则*/进行排序

底层:
	红黑树

代码展示:
public static void main(String[] args) {
        TreeSet<Integer> ts = new TreeSet<>();
        ts.add(5);
        ts.add(2);
        ts.add(3);
        ts.add(1);
        ts.add(2);s
        System.out.println(ts);
}

基本使用:
	根据[指定的排序规则]完成去重和排序
        Integer类和String类内部带有自己的排序规则,所以可以直接进行排序操作
        Integer:按照自然排序规则 -- 数字从小到大排序
        String:自然排序规则 -- 按照字符的字典顺序(比较每一个字符值,从小到大)
        自定义类对象:自己制定排序规则

两种比较方式:
1.自然排序 : 
	1.1自定义类实现 Comparable<T> 接口,T就是自定义对象的数据类型
	1.2重写int compareTo(Object o)
	1.3根据返回值进行排序
		返回值三种情况:
			1.3.1 负数:即将存入的数值小,存左边
			1.3.2  0 : 重复,不存(去重)
			1.3.3 正数:即将存入的数值大,存右边
	
	//注意:实现Comparable<T>接口的类,如何更改排序规则?
		(1)重新书写compareTo();改写内部的排序规则(只针对于自定义类有效),不能改写 Integer , String 这些 Java 提供好的类(底层源代码是不可改变的)
		(2)使用比较器排序


2.比较器排序 : 
	创建 TreeSet 对象的时候传递 Comparator 的实现类对象,重写 compare 方法,根据返回值进行排序

使用步骤:
	1.使用带参数构造方法创建TreeSet集合对象
	2.在构造方法内传递Comparator<要排序的数据类型>
	3.重写compare方法,方法内有两个参数
		o1和o2
			o1表示即将存入的数据
			o2表示集合中存在的
	4.比较元素属性值
		返回值三种情况:
			负数:即将存入的小,存左边
			0:重复,不存
			正数:即将存入的大,存右边

//使用中,默认使用自然排序,当自然排序不满足现状时,使用比较器排序

关于返回值规则:
	1.如果返回值为负数,表示当前存入的元素是较小值,存左边
	2.如果返回值为0,表示当前存入的元素跟 集合中元素重复,不存
	3.如果返回值为正数,表示当前存入的元素是较大值,存右边

3.两种排序方式对比及使用建议
 	1.自然排序:
		1.1使用空参构造创建TreeSet集合对象
		1.2类实现Comparable<T>接口,重写compareTo方法
	2.比较器排序:
		2.1使用带参构造创建TreeSet集合对象
		2.2在构造方法内传递Comparator<T>比较器,重写compare方法
	3.compareTo()compare()方法的返回值
		负数:即将存入的小,存左边
		0:重复,不存
		正数:即将存入的大,存右边
	4.使用建议
		4.1如果自然排序的规则不满足条件,使用比较器排序去覆盖自然排序规则
		4.2在书写的时候,先按照一个方向熟悉一套排序规则,如果不满足,再进行修改

进阶展示:

//Student: 
public  class Student implements Comparable<Student> {
    /*  
     2022/01/10
     14:44
     I am Iron man
    */
    private String name;
    private Integer age;

    public Student() {
    }

    public Student(String name, Integer 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(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        int result = this.getAge() - o.getAge();
        			//使用Age元素进行排序
        result = result == 0 ? this.getName().compareTo(o.getName()) : result;
				//Age元素相等时,使用Name元素进行先后顺序排序
        return result;
    }


}

//StudentTest:
public class StudentTest {
    /*  
     2022/01/10
     14:45
     I am Iron man
    */
    public static void main(String[] args) {

        TreeSet<Student> ts = new TreeSet<>();

        ts.add(new Student("zhangsan", 25));
        ts.add(new Student("lisi", 25));
        ts.add(new Student("wangwu", 25));
        ts.add(new Student("zhangsan", 27));

        for (Student t : ts) {
            System.out.println(t);
        }
    }
}
/*
Student{name='lisi', age=25}
Student{name='wangwu', age=25}
Student{name='zhangsan', age=25}
Student{name='zhangsan', age=27}
*/

HashSet*******

特点:
	1.底层数据结构是哈希表
	2.不能保证存储和取出的顺序完全一致
	3.没有带索引的方法,所以不能使用普通for循环遍历
	4.由于是Set集合,所以元素唯一

代码展示:
public static void main(String[] args) {
        HashSet<String> hs = new HashSet<>();
        hs.add("hello");
        hs.add("world");
        hs.add("java");
        hs.add("java");
        hs.add("java");
        Iterator<String> it = hs.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
				//world java hello

        }
}

哈希值 : 是JDK根据对象的地址或者属性值,运算得出的int类型的整数
public int hashCode() : 
	1.没有重写ObjecthashCode(),根据对象的地址值计算出哈希值
		不同对象调用hashCode()得到的哈希值不同
	2.重写后,根据对象的属性值计算哈希值,和地址值无关,不同对象的属性值相同,计算的哈希值是相同的

//哈希冲突问题
	不同对象属性值不同,但是计算得到的哈希值可能是相同的/或者根据哈希值计算出的存入索引位置相同
	此时需要使用重写的equals方法比较对象的属性值进行区分是否为同一个对象(看具体内容)

JDK7与JDK8底层比较

//JDK7底层原理分析
	1.数据结构:数组+链表(组成哈希表)
	2.创建对象:默认长度为16,加载因子(扩容因子)0.75,当元素个数达到16*0.75--12个时扩容为原来的23.首次添加元素:
		根据对象的哈希值和数组长度计算存入的索引位置,直接添加
		索引位置: 哈希值%数组长度,导致存储不是连续的
	4.再次添加:
		根据哈希值计算存入的索引位置,判断当前位置是否为nullnull,直接存储
		不为null:
			1.哈希冲突 -- 不同对象属性值也不同,此时计算得到的哈希值相同的,存储的索引位置相同的
			2.计算的索引位置相同 -- 不同对象属性值不同,计算得到的哈希值不同,计算得到的索引位置相同
			数组长度为16%16  17 -- 余数为 1 -- 索引位置相同
			数组长度为32%32  321 -- 余数为 1 -- 索引位置相同
			证明有元素,需要通过equals方法比较属性值
				1.属性值相同,不存
				2.属性值不同,新元素添加到数组,老元素挂在下面,形成链表

//JDK8底层优化
	优化:
		哈希表:数组 + 链表 + 红黑树
		链表长度足够长,查询效率变低
		当链表长度达到8转换为红黑树,将红黑树的根节点存储在数组中
		如果转换为红黑树,此时再次添加元素时,需要先和根节点进行比较大小
		小的存左,大的存右
		此时还是使用 equals() 比较对象的内容

Map集合

1.概述
	interface Map<K,V> : 
		K : 键的数据类型
		V : 值的数据类型
	//键是不能重复的,值可以重复,并且一个键只能对应一个值
	//在Map的内部有一个类,将K和V封装成Entry<K,V>的对象(键值对数据对象)

2.常用方法
	V put(K key,V value):添加元素,返回(被添加)的元素
		1.如果键不存在,直接添加键值对,返回的是默认初始化值null
		2.如果键存在,此时覆盖原来的值(修改),返回是修改之前的值

	V remove(Object key):删除元素,返回被删除的值
		删除键(删除Entry对象)
		clear():清空集合

	boolean containsKey(Object key):判断是否包含键
		理解:精确查询(键不重复的)
	boolean containsValue(Object value):判断是否包含值
		理解:模糊查询(值可以是重复的)
	boolean isEmpty():判断集合是否为空
	int size():获取集合键值对(Entry对象)的个数
	
3.Map集合的遍历
	1.Map接口没有实现Iterable,不能使用迭代器/增强for
	2.没有索引,也不能使用普通for
	
	需要进行转换 Map - Collection:
		第一种:
			获取所有的键的集合 map集合对象名.keySet(),返回Set<K>集合,使用迭代器/增强for遍历Set集合获取每一个键,map集合对象名.get(K k),返回的是键对应的值

		第二种:
			获取所有的键值对对象Entry对象的Set集合,Set<Map.Entry<K,V>> entrySet(),遍历集合,获取每一个Entry对象,通过对象调用getKey()/getValue()获取键/值

		第三种 forEach():
	map.forEach(new BiConsumer<String, String>() {
		@Override
		public void accept(String key, String value) {
			System.out.println(key+"---"+value);
		}
	}); 
	//可转为Lambda表达式: 
/*
map.forEach(key,value -> System.out.println(key+"---"+value));
*/

HashMap集合

HashSet底层使用的是HashMapHashSet的元素当做HashMap的键进行添加并实现去重
依赖重写hashCode和equals方法

原理解析
	将数据封装为Entry对象(键和值)
	根据键计算哈希值,结合数组长度计算索引位置
		为null,直接添加
		不为null
	equals方法比较[]的属性值
		相同:覆盖旧值
		不同:新的Entry对象添加到数组,老的挂在下面形成链表
			 JDK8版本开始,链表长度达到8,转换为红黑树

使用建议:
	如果有必要使用自定义类对象作为键,一定要重写hashCode和equals方法
	建议一般使用Integer/String作为键,去重性能更好

TreeMap集合

TreeSet集合底层就是TreeMap
TreeSet集合添加元素的时候,使用的本质上是TreeMap的put方法
将元素作为Map集合的键进行添加
底层也是红黑树,排序要制定排序规则

原理解析
	底层是红黑树
	对键进行排序和去重,依赖自然排序规则/比较器排序规则

注意:
	如果有必要使用自定义类对象作为键,要给类制定自然排序规则或者是创建TreeMap时传递比较器规则

泛型

<E> 称为 泛型, E 表示 数据类型 , 也可写为 T V K Q
	提供了编译时类型安全检测机制

优点:
	1.将运行时期的问题提前到编译时期
	2.避免强制类型转换

使用范围:
	1.类名后	---	泛型类	如: ArrayList<E>
		-- 定义格式 : 修饰符 class 类名<类型>{}
	2.方法声明	---	泛型方法	
		-- 定义格式 : 修饰符 <类型> 返回值类型 方法名(类型 变量名){}
	3.接口名后	---	泛型接口	
		-- 定义格式 : 修饰符 interface 接口名 <类型> {}
		-- 使用方式 : 1.实现类不给泛型,:ArrayList<E>只实现List<E>
					2.实现类确定具体的数据类型,:普通类
					3.接口继承接口,指定父接口泛型的具体类型
					4.接口继承接口,不指定父接口泛型具体类型,此时子接口需要定义父接口泛型类型

代码展示:
public class Demo05 {
    /*  
     2022/01/10
     10:44
     I am Iron man
    */
    public static void main(String[] args) {
        GenericImpl1<String> genericImpl1 = new GenericImpl1<>();
        genericImpl1.method("I Love You");

        GenericImpl2 genericImpl2 = new GenericImpl2();
        genericImpl2.method(555);
    }
}
/**
 * 创建一个泛型接口 Generic
 * 泛型方法 void method(T t)
 * @param <T>
 */
interface Generic<T>{
    void method(T t);
}
/**
 * 泛型实现类1 GenericImpl1 继续使用泛型
 */
class GenericImpl1<T> implements Generic<T> {

    @Override
    public void method(T t) {
        System.out.println(t);
    }
}

/**
 * 泛型实现类2 GenericImpl2 是定类型Integer
 */
class GenericImpl2 implements Generic<Integer>{

    @Override
    public void method(Integer integer) {
        System.out.println(integer);
    }
}

类型通配符

类型通配符: <?>
--	ArrayList<?> : 表示元素类型位置的ArrayLis,它的元素可以匹配任何的类型
--	不能把元素添加至ArrayList,获取出来的也是父类类型

好处: 简化泛型使用
弊端: 例如集合使用<?>
		---不能添加,只能获取,并且获取得到的还是Object

类型通配符上限: <? extends 类型>
--	ArrayList<? extends Number> : 
			上限限定: 当前类型及其子类类型
			表示: Number 或者其子类

类型通配符下限: <? super 类型> : 
			下限限定: 当前类型及其父类类型
			表示: 当前类型及其父类类型

代码实现:
public class Demo06 {
    /*  
     2022/01/10
     11:13
     I am Iron man
    */
    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<>();
        List<Number> list2 = new ArrayList<>();
        List<Object> list3 = new ArrayList<>();

        method1(list1);
        method1(list2);
        method1(list3);

        method2(list1);
        method2(list2);
        //method2(list3);

        //method3(list1);
        method3(list2);
        method3(list3);

    }

    public static void method1(List<?> list) {
        System.out.println(list);
    }

    public static void method2(List<? extends Number> list) {
        System.out.println(list);
    }

    public static void method3(List<? super Number> list) {
        System.out.println(list);
    }
}

数据结构

数据结构是计算机存储、组织数据的方式.是指相互之前存在一种或多种特定关系的数据元素的集合。
通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率

常见的数据结构:
	1.2.队列
	3.数组
	4.链表

树结构

树结构是由节点构成
	每一个节点中维系变量:
		1.父节点地址
		2.3.左子节点地址
		4.右子节点地址

1.二叉树
	1.1特点:
		任意节点的子节点数量()不超过2
		根节点:树结构最顶层的节点
		树高:从根节点开始到最远的子节点的层高

	注意:此时元素没有顺序(没有排序规则),只要满足二叉树的规则即可,此时元素查询效率就很低

2.二叉查找树
	2.1特点:
		2.1.1是二叉树
		2.1.2添加规则:
			任意节点左子节点值比自己小,右子节点的值比自己大

	2.2添加节点:
		2.2.1首次添加的元素作为根节点
		2.2.2再次添加的元素会先和根节点比较大小
			小,存在左面
			大,存在右面
			相同,不存
		2.2.3存在多个节点的情况下,依次使用添加的元素和多个节点进行比较
			小,存在左面
			大,存在右面
			相同,不存

	2.3注意:
		如果元素有从小到大的顺序
			1 2 3 4 5 6 7 8 9
		得到而二叉查找树数据都在一侧排列,树是畸形树(不平衡),影响查询效率

3.平衡二叉树
	3.1特点:
		3.1.1是二叉查找树(任意节点左侧都比自己小,右侧都比自己大)
		3.1.2新增规则:
			任意节点左右子树高度差<=1

	3.2左旋
		3.2.1触发机制
			根节点的右子树添加节点打破平衡(右边比左边多)
		3.2.2如何左旋
			(1)原根节点降级成为左子节点
			(2)原根节点的右子节点提升为新的根节点
			(3)原右子节点的左子节点出让给降级的根节点当做右子节点
			(根节点调节完毕之后,多出来的部分放在合适的位置上)

	3.3右旋
		3.3.1触发机制
			根节点的左子树添加节点打破平衡(左边比右边多)
		3.3.2如何右旋
			(1)原根节点降级成为右子节点
			(2)原根节点的左子节点提升为新的根节点
			(3)原左子节点的右子节点出让给降级的根节点当做左子节点

	3.4小结
		整体旋转:
			添加节点打破平衡
			左侧多向右旋转,右侧多向左旋转

4.左左和左右
	4.1触发机制
		左左:
			左子树的左子树添加节点打破平衡
		左右:
			左子树的右子树添加节点打破平衡
	4.2如何旋转
		左左:
			直接右旋
		左右:
			先局部左旋(恢复到左左的情况),再整体右旋

5.右右和右左
	5.1触发机制
		右右:
			右子树的右子树添加节点打破平衡
		右左:
			右子树的左子树添加节点打破平衡
	5.2如何旋转
		右右:
			直接左旋
		右左:
			先局部右旋(恢复到右右的情况),再整体左旋

红黑树**

1.红黑规则
	1.1任意节点的颜色是红色/黑色
	1.2根节点是黑色
	1.3一个节点没有左子节点/右子节点/父节点,记录为Nil(叶子节点),颜色为黑色
        //没有记录地址就记录黑色叶子Nil
	1.4两个红色节点不能相连
	1.5任意节点到其后代的Nil节点的简单路径(一条路走到头,不能反复)上均包含相同数目的黑色节点

2.红黑树添加节点
	2.1 默认颜色
		默认添加节点的颜色是红色

	2.2 添加节点后如何保证红黑规则
		添加位置:
			根节点 -- 变黑
			非根节点 :
				看父节点颜色:
					黑色:  不操作
					红色:
						看叔叔节点:
							叔叔节点是红色:
								1.将父节点和叔叔节点都变成黑色
								2.将祖父节点变成红色
								3.如果祖父节点是根节点变成黑色
							叔叔节点是黑色:
								1.将父节点变成黑色
								2.将祖父节点变成红色
								3.以祖父节点为支点进行旋转

一端开口 : 栈顶
一端闭合 : 栈底

数据进入栈模型的过程称为 :/进栈
数据退出栈模型的过程称为 :/出栈
//先入栈的元素为栈底元素,后入栈的元素为栈顶元素

特点 : 先进后出

队列

一端开口 : 后端
一端开头 : 前端

数据从后端进入队列的过程为 : 入队列
数据从前端离开队列的过程为 : 出队列

特点 : 先进先出

数组

相当于一个容器,用来存储数据

查询数据通过地址值和索引定位,查询任意数据耗时相同,查询速度快
删除数据时,要将原始数据删除,同时后面每个数据前移,删除效率低
添加数据时,添加位置后的每个数据后移,在添加元素,添加效率极低

特点 : 查询快,增删慢

链表

每个元素称之为 结点 , 每个结点都是独立的对象

存储内容:
	1.结点的地址值
	2.存储的具体数据
	3.下一个结点的地址

特点(对比数组) : 增删快,查询慢

从前往后 : 单向链表
双向查询 : 双向链表 (查询某个地址元素,首先判断离头近或尾近,从最近结点进行查找)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值