从本博客起,我将带各人学习Java当中的数据结构。
那让我们从顺序表开始吧,本博客内容包括但不限于实现一个本身的ArrayList、干系的API先容、干系API的实现以及使用。
一.顺序表概述
1.ArrayList的位置
顺序表(ArrayList)是线性表(List)的一种,继承自AbstractList类,并实现了List接口,以下是它在Java继承体系中的位置:
2.ArrayList的结构
逻辑结构:元素的有序序列
物理结构:顺序存储
ArrayList的本质是基于数组的一连存储结构,最大的优势就是对于确定位置的元素访问及其方便。
如下是它的存储示意图:
只要知道数据下标,就可以快速拿到元素,时间复杂度为O(1)。
二.ArrayList的实现
ArrayList在jdk当中的实现有足足1732行,我们挑选了部分核心方法来实现:
ps:想要查看源码,按住Ctrl+Alt选中ArrayList并点击,弹出选项框点击要查找的类即可:
想要深入相识建议查看源码,不过不建议初学者看源码,因为涉及一些Java高级特性。
核心方法
我们要实现的核心方法如下(为了学习,简便,先不考虑泛型):
- // 新增元素,默认在数组最后新增
- public void add(int data) {}
- //判断是否已满
- public boolean isFull() {}
- // 在 pos 位置新增元素
- public void add(int pos, int data) {}
- // 判定是否包含某个元素
- public boolean contains(int toFind) {}
- // 查找某个元素对应的位置
- public int indexOf(int toFind) {}
- // 获取 pos 位置的元素
- public int get(int pos) {}
- // 给 pos 位置的元素设为 value [更新]
- public void set(int pos, int value) {}
- //检查pos位置是否合法
- private void checkPos(int pos) { }
- //删除第一次出现的关键字key
- public void remove(int toRemove) {}
- // 获取顺序表长度
- public int size() {}
- // 清空顺序表
- public void clear() {}
复制代码
1.属性
Java当中顺序表的存储结构如下:
嗯......这可能有些复杂,为了方便学习,我们简化一下:
先不考虑泛型实现,也不考虑io流,这些以后再说!我们假设存储的元素都是整形,那我们本身的ArrayList就有了3个属性:
用来存储的结构,数组
用来表示有效元素的个数的数,usedSize
用来初始化数组大小的自界说常量,DEFAULT_SIZE
ok!这下我们的ArrayList就初步构建完成。
2.构造方法
- //默认初始化
- public MyArrayList() {
- this.elem = new int[DEFAULT_SIZE];
- }
- //指定容量初始化
- public MyArrayList(int initCapacity) {
- this.elem = new int[initCapacity];
- }
复制代码 初始化之后,我们就可以操作顺序表了,即增、删、查、改。
3.增
- //新增元素
- public void add(int data) {
- if (isFull()) {
- this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
- }
- this.elem[this.usedSize] = data;
- this.usedSize++;
- }
-
- //在 pos 位置新增元素
- public void add(int pos, int data) {
- if (isFull()) {
- this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
- }
- checkPos(int pos)
- for (int i =usedSize-1; i > pos; i--) {
- this.elem[i+1] = this.elem[i];
- }
- this.elem[pos] = data;
- this.usedSize++;
- }
- //判断非满
- public boolean isFull() {
- if (this.usedSize == this.elem.length) {
- return true;
- }
- return false;
- }
- //检查位置是否合法
- private void checkPos(int pos) {
- if (pos < 0 || pos >this.elem.length) {
- throw new PosOutOfBoundsException("pos out of bounds!");
- }
- }
复制代码 我们提供2种新增元素方式,即默认新增和指定位置新增,它们构成了方法的重载。
在新增元素之前,我们必须检查数组是否已满,否则就可能发生StackOutOfBounds异常。判断是否已满的方式很简朴,只要比较有效元素个数是否等于数组长度即可(.length为数组自带属性)。
如果数组已满,我们就要扩容,扩容方式是调佣Arrays方法库中的复制数组,并指定大小为2倍,再赋值给原数组elme。随后就可以新增元素了。
默认新增:在数组的末尾添加元素,直接this.elem[this.usedSize] = data即可。
指定位置(下标)新增:首先我们要判断这个“位置”是否合法,使用私有方法checkPos(),即是否在原数组边界外(由于是线性表,元素必须是一连的,不能空一个元素),不合法抛出我们自界说异常PosOutOfBoundsException,合法则把所有pos后元素后移一位,然后插入即可。
4.查
- //判定是否包含某个元素
- 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 位置的元素
- public int get(int pos) {
- checkPos(pos);
- return this.elem[pos];
- }
复制代码 5.删
删除一个元素,我们有2件事要做,即找到谁人元素位置、把之后的元素前移1格覆盖要删除的元素。
- //删除第一次出现的关键字key
- public void remove(int toRemove) {
- int index = this.indexOf(toRemove);
- if (index == -1) {
- System.out.println(toRemove + " is not found!");
- return;
- }
- for (int i = index+1; i < this.elem.length; i++) {
- this.elem[i] = this.elem[i-1];
- }
- this.usedSize--;
- }
复制代码 indexOf()方法实现了查找某元素位置,remove()则实现删除,即前移后置元素,这里的实现很简朴,就不赘述了。
6.改
- // 给 pos 位置的元素设为 value [更新]
- public void set(int pos, int value) {
- checkPos(pos); //任然要检查位置是否合法
- this.elem[pos] = value;
- }
复制代码 7.其他
一些其他方法的实现:
- //展示元素
- public void display() {
- for (int i = 0; i <= usedSize; i++) {
- System.out.print(elem[i] + " ");
- }
- }
- // 获取顺序表长度
- public int size() {
- return this.usedSize;
- }
- // 清空顺序表
- public void clear() {
- this.usedSize = 0;
- }
复制代码 三.ArrayList常用方法使用示例
以下代码给出了常用方法的使用方法,供各人学习参考:
- import java.util.ArrayList;
- import java.util.Arrays;
- public class ArrayListDemo {
- public static void main(String[] args) {
- // 1. 初始化
- List<String> fruits = new ArrayList<>();
- //ArrayList<String> fruits = new ArrayList<>()也可
- System.out.println("初始化列表: " + fruits); // []
- // 2. 添加元素
- fruits.add("Apple"); // 末尾添加
- fruits.add(0, "Banana");// 指定位置插入
- fruits.addAll(Arrays.asList("Orange", "Grape"));
- System.out.println("添加元素后: " + fruits); // [Banana, Apple, Orange, Grape]
- // 3. 访问元素
- System.out.println("\n访问元素演示:");
- System.out.println("索引1的元素: " + fruits.get(1)); // Apple
- System.out.println("列表大小: " + fruits.size()); // 4
- System.out.println("包含Orange? " + fruits.contains("Orange")); // true
- System.out.println("Mango的位置: " + fruits.indexOf("Mango")); // -1
- // 4. 修改元素
- System.out.println("\n修改元素演示:");
- fruits.set(2, "Lemon");
- System.out.println("修改后: " + fruits); // [Banana, Apple, Lemon, Grape]
- // 5. 删除元素
- System.out.println("\n删除元素演示:");
- fruits.remove("Banana"); // 按对象删除
- fruits.remove(0); // 按索引删除
- System.out.println("删除后: " + fruits); // [Lemon, Grape]
- // 6. 其他操作
- System.out.println("\n其他操作演示:");
- System.out.println("是否为空? " + fruits.isEmpty()); // false
-
- // 遍历方式1:for循环
- System.out.print("for循环遍历: ");
- for(int i=0; i<fruits.size(); i++){
- System.out.print(fruits.get(i) + " ");
- }
-
- // 遍历方式2:增强for循环
- System.out.print("\n增强for循环: ");
- for(String fruit : fruits){
- System.out.print(fruit + " ");
- }
-
- // 遍历方式3:forEach方法,fruit会在集合内遍历,结合Lambda表达式可以减少代码量
- System.out.print("\nforEach方法: ");
- fruits.forEach(fruit -> System.out.print(fruit + " "));
- // 清空列表
- fruits.clear();
- System.out.println("\n清空后: " + fruits); // []
- }
- }
复制代码 说明1:初始化时,有2种选择:
- List<T> list = new ArrayList<>()
- ArrayList<T> list = new ArrayList<>()
复制代码 2种方式区别不大,但大部分情况下选择第一种,由于多态特性,虽然不能用ArrayList的特定方法,但是不指定实际绑定类型,可以让list有更灵活的变化可能,如转换成链表(虽然大部分情况下不使用):
- List<String> list = new ArrayList<>();
- list.add("Java");
- list = new LinkedList<>(); // 允许替换实现
复制代码 合理选择初始化方式,可以让代码在灵活性和功能性之间到达最佳平衡。这也是 Java 集合框架设计的英华地点。
当然,实际情况实际选择,如需要调用ArrayList特有方法,请用ArrayList类型初始化。
说明2:第三种方法结合了Lambda表达式,使循环更为简洁高效,而且这种方式支持链式编程,可以大大节流代码量,是当代编程首选。
另外,此处还等价于:
- fruits.forEach(System.out::println);
复制代码 详情请参考Lambda表达式的使用及其化简。
结语
阅读过源码的朋友应该知道Java内部的实现比这要复杂一些,涉及了很多高级特性,但是其核心的方法内涵就是这么简朴,相识了这些后,使用ArrayList就能更加得心应手。
以为有帮助不妨点个赞,下一篇我们将先容链表,链表的一些方法和特性比顺序表复杂很多,我们会更加详细地讲解。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |