既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
✨个人主页:bit me
✨当前专栏:数据结构
✨每日一语:上海就是商海,北京就是背景,誓言就是失言,彩礼就是财力,理想就是离乡,而平民就要拼命
ArrayList与顺序表
💦一.线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表:
链表:
💞二.顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表底层是一个数组,为什么不直接操作数组就好了,还需要单独写个类?
例如我们在数组中放置一部分元素
问在这个数组里面,有几个有效数据?
肯定有人会说3个,在Java里数组没有元素默认为0,判断的时候遇到0就停止,然后总数就是元素个数,那如果这三个元素中加了一个0呢?我们又该如何去判断呢?
这里正确的做法是用计数器,先创建一个对象,我们定义一个usedSize来记录有效的数据个数,然后添加一些方法,最后进行增删查改(CURD)。
💕2.1 接口的实现
在数组中是否可以隔着空的数组位插入元素?答案是不可以,在数据结构当中,每次储存元素的时候,一定要有一个前驱信息的,可以在两个元素中间插入
例如我们写的顺序表的相关操作:
我们还需要对他们进行完善和改进
步骤:
- 定义一个类:
public class MyArraylist {
public int[] elem;
public int usedSize;//0
private static final int DEFAULT\_SIZE = 4;//定义数组长度的
public MyArraylist(){
this.elem = new int[DEFAULT\_SIZE];
}
...
...
}
成员变量包含数组,数组里的元素个数,数组长度(可以使用final修饰让数据不被改变)
- 顺序表的打印:
public void display() {
//usedSize = 0
for (int i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i]+" ");
}
System.out.println();
}
用循环把每个元素都遍历一次,中间用空格分开
- 新增元素:
// 新增元素,默认在数组最后新增
public void add(int data) {
//1.判断是否是满的,如果满的,那么进行扩容
if(isFull()){
//扩容2倍
this.elem = Arrays.copyOf(this.elem,2\*this.elem.length);
}
//2.不满进行插入
this.elem[this.usedSize] = data;
this.usedSize++;
}
默认在数组最后新增
①需要判断数组是否是满的,满的就需要扩容,
②不满的话就进行元素插入,判断数组满不满状态的函数实现
isFull判断函数实现:
//判断当前数组是不是满的 true:满 false:空
public boolean isFull(){
if(this.usedSize == this.elem.length){
return true;
}
return false;
//在这里可以直接优化为一行代码
//return this.usedSize == this.elem.length;
}
- 在pos位置新增元素:
// 在 pos 位置新增元素
public void add(int pos, int data) {
//1.判断pos位置合法性
if(!checkPosInAdd(pos)){
throw new MyArraylistIndexOutofException("添加方法的Pos不合理!");
}
//2.判断是否是满的,如果满的,那么进行扩容
if (isFull()){
this.elem = Arrays.copyOf(this.elem,2\*this.elem.length);
}
//挪数据
for (int i = this.usedSize-1; i >= pos ; i--) {
this.elem[i+1] = this.elem[i];
}
//挪完了数据
this.elem[pos] = data;
this.usedSize++;
}
①先判断pos位置合法性,既不能是负数,又必须要有前驱信息的支持
②判断组数元素是否满了,继续调用isFull()函数
③我们插入数据的时候,需要先把插入元素后面的元素都往后挪一位,挪数据实现
从数组的最后一个元素开始往后挪,一次挪到当pos位置空出,没有元素的时候即可
④挪完数据之后,我们把pos位置赋值为data,并且把数组大小扩容一位,方便再进行新增元素
判断pos位置合法性函数checkPosInAdd实现:
private boolean checkPosInAdd(int pos){
//1.判断pos位置合法性
if(pos < 0 || pos > this.usedSize){
System.out.println("pos位置不合法");
return false;
}
return true;//合法
}
- 判定是否包含了某个元素
public boolean contains(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if(this.elem[i] == toFind){
return true;
}
}
return false;
}
- 查找某个元素对应的位置
// 查找某个元素对应的位置
public int indexOf(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if(this.elem[i] == toFind){
return i;
}
}
return -1;
}
- 获取pos下标的元素
// 获取 pos 位置的元素
public int get(int pos) {
if(!checkPosInGet(pos)){
throw new MyArraylistIndexOutofException("获取pos下标时,位置不合法!");
}
//判断是否为空(可有可无)
if(isEmpty()){
throw new MyArrayListEmptyException("获取元素的时候,顺序表为空!");
}
return this.elem[pos];
}
①先判断pos位置合法性
②判断数组是否为空(可有可无)
判断pos位置合法性函数checkPosInGet实现:
private boolean checkPosInGet(int pos){
//1.判断pos位置合法性
if(pos < 0 || pos >= this.usedSize){
System.out.println("pos位置不合法");
return false;
}
return true;//合法
}
判断数组是否为空isEmpty函数实现:
private boolean isEmpty(){
return this.usedSize == 0;
}
- 给pos位置的元素替换成value
①先要进行合法性判断再替换
// 给 pos 位置的元素设为 value
public void set(int pos, int value) {
if(!checkPosInGet(pos)){
throw new MyArraylistIndexOutofException("更新pos下标的元素,位置不合法!");
}
//如果合法,那么其实不用判断顺序表为空的状态了
if(isEmpty()){
throw new MyArrayListEmptyException("顺序表为空!");
}
//顺序表为满的情况也可以更新
this.elem[pos] = value;
}
- 删除第一次出现的关键字key
//删除第一次出现的关键字key
//判断条件:1.顺序表不为空 2.顺序表当中有我们要删除的元素 3.找到它的下标 4.把i+1的值赋给i,i还要小于usedSize-1
public void remove(int key) {
if(isEmpty()){
throw new MyArrayListEmptyException("顺序表为空,不能删除!");
}
int index = indexOf(key);
if(index == -1){
System.out.println("不存在你要找的数据!");
return;
}
for (int i = index; i < this.usedSize-1; i++) {
this.elem[i] = this.elem[i+1];
}
//删除完成
this.usedSize--;
this.elem[usedSize] = 0;//此处不能置为null是因为elem是int类型 如果是引用类型则置为null
}
①顺序表不为空
②顺序表当中有我们要删除的元素
③找到它的下标
④把i+1的值赋给i,i还要小于usedSize-1
(只要涉及到删除数据,如果是引用数据类型,那么就要把elem[i] = null;否则就会发生内存泄漏)
- 获取顺序表长度
// 获取顺序表长度
public int size() {
return this.usedSize;
}
- 清空顺序表
// 清空顺序表
public void clear() {
//因为是基本类型,所以置为0即可
this.usedSize = 0;
/\*当它是引用类型时
for (int i = 0; i < this.usedSize; i++) {
this.elem[i] = null;
}
this.usedSize = 0;
\*/
}
①基本类型置为0即可,若是引用类型则循环打印置为null,再置为0
注意:
此处可以把elem置为null可以吗?可以,但是很暴力,数组直接被回收了,顺序表只执行了一次就没了,再次使用的时候还需开辟新的数组,相当于我们每次使用的时候还需new一次,很麻烦也没必要
在这里面添加,获取pos下标不合法的时候我们也可以写我们需要的异常类来更好的实现我们需要的异常,实现异常的抛出是我们赋值命名的异常名:
public class MyArrayListEmptyException extends RuntimeException{
public MyArrayListEmptyException(){
}
public MyArrayListEmptyException(String message){
super(message);
}
}
public class MyArraylistIndexOutofException extends RuntimeException{
public MyArraylistIndexOutofException(){
}
public MyArraylistIndexOutofException(String message){
super(message);
}
}
在主函数里对顺序表的操作:
public static void main(String[] args) {
MyArraylist myArraylist = new MyArraylist();
myArraylist.add(0,1);//给0下标赋值为1
myArraylist.add(1,2);
myArraylist.add(2,3);
myArraylist.add(3,4);
myArraylist.add(4,5);
myArraylist.display();//输出顺序表
myArraylist.set(4,199);//把下角标为4的元素替换成199
myArraylist.display();
System.out.println("=============");
myArraylist.clear();//清空顺序表
myArraylist.display();
}
⚡️三. ArrayList简介
在集合框架中,ArrayList是一个普通的类,实现了List接口,具体框架图如下:
ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问
ArrayList实现了Cloneable接口,表明ArrayList是可以clone的
ArrayList实现了Serializable接口,表明ArrayList是支持序列化的
和Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者CopyOnWriteArrayList
ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表
🎶四. ArrayList使用
🎵4.1 ArrayList的构造
方法一ArrayList()不带参数的构造方法的使用:
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);//往数组最后的一个位置存元素
arrayList.add(2);
arrayList.add(3);
arrayList.add(4);
System.out.println(arrayList);//用字符串的形式打印出来所有的元素
System.out.println(arrayList.size());//获取当前有效数据的个数
System.out.println(arrayList.get(1));//获取指定下标的元素
方法二的使用:
ArrayList<Integer> arrayList2 = new ArrayList<>(arrayList);
arrayList2.add(99);
arrayList2.add(199);
System.out.println(arrayList2);
arrayList2承接了arrayList1的数据(使用其他的集合 来构造当前的List,底层源码实现是数组的拷贝)
方法三的使用:
ArrayList<Integer> arrayList3 = new ArrayList<>(15);
指定初始化数组容量大小
在源码的实现里:
①:第一次add的时候,我们底层的数组才变成了10,如果只是调用了不带参数的构造方法,默认还是0
②:grow函数就是扩容函数,扩容的方式是1.5倍的扩容
例如整体的举例使用:
public static void main(String[] args) {
// ArrayList创建,推荐写法
// 构造一个空的列表
List<Integer> list1 = new ArrayList<>();
// 构造一个具有10个容量的列表
List<Integer> list2 = new ArrayList<>(10);
list2.add(1);
list2.add(2);
list2.add(3);
// list2.add("hello"); // 编译失败,List<Integer>已经限定了,list2中只能存储整形元素
// list3构造好之后,与list中的元素一致
ArrayList<Integer> list3 = new ArrayList<>(list2);
// 避免省略类型,否则:任意类型的元素都可以存放,使用时将是一场灾难
List list4 = new ArrayList();
list4.add("111");
list4.add(100);
}
🎵4.2 ArrayList常见操作
| 方法 | 解释 |
|---|---|
| boolean add(E e) | 尾插 e |
| void add(int index, E element) | 将 e 插入到 index 位置 |
| boolean addAll(Collection<? extends E> c) | 尾插 c 中的元素 |
| E remove(int index) | 删除 index 位置元素 |
| boolean remove(Object o) | 删除遇到的第一个 o |
| E get(int index) | 获取下标 index 位置元素 |
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!