Java使用数组实现ArrayList的动态扩容的方法

提到数组大家肯定不会陌生,但我们也知道数组有个缺点就是在创建时就确定了长度,之后就不能更改长度。所以Java官方向我们提供了ArrayList这个可变长的容器。其实ArrayList底层也是用数组进行实现的,今天我们就自己使用数组实现ArrayList的功能。

一、整体框架

废话不多说,我们以存放int类型元素为例,看一下ArrayList需要的成员变量和需要实现的方法。

public class ArrayList
 private int size;//用来记录实际存储元素个数
  private int[] elements;//用来存储元素的数组

  //构造方法:在创建ArrayList对象时,初始化elements数组
  public ArrayList(int capacity){
    elements = new int[capacity];
  }

 /**
 * 元素的数量
 * @return
 */
 public int size() {}
 /**
 * 是否为空
 * @return
 */
 public boolean isEmpty() {}
 /**
 * 查看元素的索引
 * @param element
 * @return
 */
 public int indexOf(int element) {}
 /**
 * 是否包含元素
 * @param element
 * @return
 */
 public boolean contains(int element) {}
 /**
 * 获取index位置的元素
 * @param index
 * @return
 */
 public int get(int index) {}
 /**
 * 设置index位置的元素
 * @param index
 * @param element
 * @return 原来的元素
 */
 public int set(int index, int element) {}
 /**
 * 在index索引位置插入元素
 * @param index
 * @param element
 */
 public void add(int index, int element) {}
 /**
 * 添加元素到尾部
 * @param element
 */
 public void add(int element) {}
 /**
 * 删除index位置的元素
 * @param index
 * @return
 */
 public int remove(int index) {}
 /**
 * 清除所有元素
 */
 public void clear() {}
 /**
 * 用来打印列表
 */
 public String toString() {}

}

二、方法实现

框架我们已经有了,接下来我们一步步实现方法就行。

size()方法:

这个方法很简单,因为我们有size属性,直接将其返回就行了。

public int size() {
 return size;
}

isEmpty()方法:

这个方法也很简单,判断是否为空只需要判断size是否为0即可。

public boolean isEmpty() {
 return size == 0;
}

indexOf(int element)方法:

这个方法是用来查询元素的所在索引位置,并返回。我们通过遍历列表查找即可,找到就将元素返回,没有找到返回-1。

public int indexOf(int element) {

 for (int i = 0; i < size; i++) {
 if (element.equals(elements[i])) {
  return i;
 }
 return -1; 

}

contains(int element)方法:

这个方法是用来查看所传元素是否在数组中,我们可以直接通过indexOf()方法查看返回值是否不等于-1,不等于-1返回true,等于-1返回false。

public boolean contains(int element) {
 return indexOf(element) != -1;
}

get(int index)方法:

这个方法用来获取指定索引位置的元素,我们直接返回数组的指定索引位置元素即可。

public int get(int index) {;
 return elements[index];
}

set(int index, int element)方法:

这个方法用来将指定索引位置元素改为指定元素,并将原来元素返回,我们通过数组索引获取到该位置元素,并将指定元素赋值给该位置索引并将原来元素返回。

public int set(int index, int element) {
 int old = elements[index];
 elements[index] = element;
 return old;
}

add(int index, int element)方法:

个方法是在指定索引位置插入指定元素,我们首先将指定索引位置的元素以及之后所有的元素向后移动一位,然后再将指定元素赋值给数组的指定索引位置,最后并将size加1。

public void add(int index, int element) {

 for (int i = size; i > index; i--) {
 elements[i] = elements[i - 1];
 }
 elements[index] = element;
 size++;

}

add(int element)方法:

这个方法是在数组尾部添加元素,我们直接调用add(int index, int element)方法,将size作为index的参数传入即可。

public void add(int element) {
 add(size, element);
}

remove(int index)方法:

这个方法用来删除指定索引位置的元素,并将要删除元素返回,我们首先通过指定索引获取原来元素,当删除该元素后需要将index索引后面的所有元素向前移动一位,最后将size减1。

public int remove(int index) {
 int old = elements[index];
 for (int i = index + 1; i < size; i++) {
 elements[i - 1] = elements[i];
 }
 size--; 

 return old;
}

clear()方法:

这个方法,用于清空数组中所有元素,我们只需要将size赋值为0即可。

public void clear() {

 size = 0;
}

toString()方法:

这个方法用于将所有元素打印出来,通过StringBuilder对象进行拼接,最后转成字符串返回即可。

public String toString() {

 StringBuilder string = new StringBuilder();
 string.append("size=").append(size).append(", [");

 for (int i = 0; i < size; i++) {

 if (i != 0) {
  string.append(", ");
 }
 string.append(elements[i]);
 }

 string.append("]");
 return string.toString();
}

以上代码基本实现了对数组的进行数据的增删改查,但最后想达到我们的目的还要进一步优化。

三、优化

1.实现动态扩容

当我们向数组中添加元素时,如果数组已经满了我们就需要就数组进行动态扩容。扩容的原理并不是真的对原数组进行增加内存操作,而是重新创建一个更大的数组,并将原数组的元素赋给新的数组。我们声明一个私有方法确保添加元素前数组的容量足够。

private void ensureCapacity(int capacity) {
 int oldCapacity = elements.length;
 if (oldCapacity >= capacity) return;

 // 新容量为旧容量的1.5倍
 int newCapacity = oldCapacity + (oldCapacity >> 1);
 int[] newElements = new int[newCapacity];
 for (int i = 0; i < size; i++) {
 newElements[i] = elements[i];
 }
 elements = newElements;

}

我们在add()方法中首先调用ensureCapacity(int capacity)方法进行判断数组容量是否足够,不够进行扩容。

public void add(int index, E element) {

 ensureCapacity(size + 1);

 for (int i = size; i > index; i--) {
 elements[i] = elements[i - 1];
 }
 elements[index] = element;
 size++;
}

2. 确保索引不越界

当我们在调用传入索引的方法时,首先要保证传入索引在可用范围内,不然会出现角标越界的异常。
定义outOfBound()方法,用于抛出角标越界异常信息。

private void outOfBounds(int index) {
 throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}

定义rangeCheck()方法用于检查索引是否越界,如果越界,调用outOfBound()抛出异常。

private void rangeCheck(int index) {
 if (index < 0 || index >= size) {
 outOfBounds(index);
 }
}

而对于调用add()方法时所传入的索引范围可以取到size位置,我们在定义一个rangeCheckForAdd()方法,用于检查调用add()方法时索引是否越界。

private void rangeCheckForAdd(int index) {
 if (index < 0 || index > size) {
 outOfBounds(index);
 }
}

在get()方法,set()方法和remove()方法中,先调用rangeCheck()方法,检查传入索引是否越界。

public int get(int index) {
 rangeCheck(index);
 return elements[index];
}

public int set(int index, E element) {
 rangeCheck(index);

 int old = elements[index];
 elements[index] = element;
 return old;
}

public int remove(int index) {
 rangeCheck(index);

 int old = elements[index];
 for (int i = index + 1; i < size; i++) {
 elements[i - 1] = elements[i];
 }
 size--;
 return old;
}

3. 设置常量以及重写构造方法

对于类中的常数我们更希望封装成常量,并且声明一个默认容量,希望在创建对象时未传入容量参数或传入容量参数过小直接创建一个相对大小合适的数组。

private static final int DEFAULT_CAPACITY = 10;//我们将默认容量设为10
private static final int ELEMENT_NOT_FOUND = -1;//我们将获取指定元素的索引时,未找到的返回值-1封装为常量

//声明无参构造方法,在调用无参构造方法是直接创建默认容量的数组
public ArrayList(){
  elements = new int[DEFAULT_CAPACITY];
}

//声明有参构造方法,在传入参数小于默认容量时,我们仍然创建默认容量数组
public ArrayList(int capaticy) {
 capaticy = (capaticy < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capaticy;
 elements = new int[capaticy];
}

四、使用泛型

以上步骤已经使用数组完全实现了ArrayList的全部功能,但只能存放int类型的数据,如果我们希望储存其他类型的数据我们只需要使用Java中的泛型就能够完成。

思路与上述完全一样,整体代码如下:

public class ArrayList<E> {
 /**
 * 元素的数量
 */
 private int size;
 /**
 * 所有的元素
 */
 private E[] elements;

 private static final int DEFAULT_CAPACITY = 10;
 private static final int ELEMENT_NOT_FOUND = -1;

 public ArrayList() {
 elements = (E[]) new Object[DEFAULT_CAPACITY];
 }

 public ArrayList(int capaticy) {
 capaticy = (capaticy < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capaticy;
 elements = (E[]) new Object[capaticy];
 }

 /**
 * 清除所有元素
 */
 public void clear() {
 for (int i = 0; i < size; i++) {
  elements[i] = null;
 }
 size = 0;
 }

 /**
 * 元素的数量
 * @return
 */
 public int size() {
 return size;
 }

 /**
 * 是否为空
 * @return
 */
 public boolean isEmpty() {
  return size == 0;
 }

 /**
 * 是否包含某个元素
 * @param element
 * @return
 */
 public boolean contains(E element) {
 return indexOf(element) != ELEMENT_NOT_FOUND;
 }

 /**
 * 添加元素到尾部
 * @param element
 */
 public void add(E element) {
 add(size, element);
 }

 /**
 * 获取index位置的元素
 * @param index
 * @return
 */
 public E get(int index) {
 rangeCheck(index);
 return elements[index];
 }

 /**
 * 设置index位置的元素
 * @param index
 * @param element
 * @return 原来的元素ֵ
 */
 public E set(int index, E element) {
 rangeCheck(index);

 E old = elements[index];
 elements[index] = element;
 return old;
 }

 /**
 * 在index位置插入一个元素
 * @param index
 * @param element
 */
 public void add(int index, E element) {
 rangeCheckForAdd(index);

 ensureCapacity(size + 1);

 for (int i = size; i > index; i--) {
  elements[i] = elements[i - 1];
 }
 elements[index] = element;
 size++;
 }

 /**
 * 删除index位置的元素
 * @param index
 * @return
 */
 public E remove(int index) {
 rangeCheck(index);

 E old = elements[index];
 for (int i = index + 1; i < size; i++) {
  elements[i - 1] = elements[i];
 }
 elements[--size] = null;
 return old;
 }

 /**
 * 查看元素的索引
 * @param element
 * @return
 */
 public int indexOf(E element) {
 if (element == null) {
  for (int i = 0; i < size; i++) {
  if (elements[i] == null) return i;
  }
 } else {
  for (int i = 0; i < size; i++) {
  if (element.equals(elements[i])) return i;
  }
 }
 return ELEMENT_NOT_FOUND;
 }

 /**
 * 保证要有capacity的容量
 * @param capacity
 */
 private void ensureCapacity(int capacity) {
 int oldCapacity = elements.length;
 if (oldCapacity >= capacity) return;

 // 新容量为旧容量的1.5倍
 int newCapacity = oldCapacity + (oldCapacity >> 1);
 E[] newElements = (E[]) new Object[newCapacity];
 for (int i = 0; i < size; i++) {
  newElements[i] = elements[i];
 }
 elements = newElements;

 }

 private void outOfBounds(int index) {
 throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
 }

 private void rangeCheck(int index) {
 if (index < 0 || index >= size) {
  outOfBounds(index);
 }
 }

 private void rangeCheckForAdd(int index) {
 if (index < 0 || index > size) {
  outOfBounds(index);
 }
 }

 @Override
 public String toString() {
 StringBuilder string = new StringBuilder();
 string.append("size=").append(size).append(", [");
 for (int i = 0; i < size; i++) {
  if (i != 0) {
  string.append(", ");
  }

  string.append(elements[i]);
 }
 string.append("]");
 return string.toString();
 }
}

到此这篇关于Java使用数组实现ArrayList的动态扩容的方法的文章就介绍到这了,更多相关Java ArrayList动态扩容内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java中Arraylist动态扩容方法详解

    前言 本文主要给大家介绍了关于Java中Arraylist动态扩容的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. ArrayList 概述 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长.ArrayList不是线程安全的,只能用在单线程环境下.实现了Serializable接口,因此它支持序列化,能够通过序列化传输:实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问:实现了Cloneable接口,能被克隆.

  • Java使用数组实现ArrayList的动态扩容的方法

    提到数组大家肯定不会陌生,但我们也知道数组有个缺点就是在创建时就确定了长度,之后就不能更改长度.所以Java官方向我们提供了ArrayList这个可变长的容器.其实ArrayList底层也是用数组进行实现的,今天我们就自己使用数组实现ArrayList的功能. 一.整体框架 废话不多说,我们以存放int类型元素为例,看一下ArrayList需要的成员变量和需要实现的方法. public class ArrayList private int size;//用来记录实际存储元素个数 private

  • 关于ArrayList的动态扩容机制解读

    目录 1. 前言 2. ArrayList 的动态扩容机制 2.1. ArrayList 的主要属性 2.2. ArrayList 的构造器 2.3. ArrayList 的动态扩容 3. 小结 3.1. 初始容量 3.2. 动态扩容大小 3.3. 动态扩容大小测试 1. 前言 对于 ArrayList 的动态扩容机制想必大家都听说过,之前的文章中也谈到过,不过由于时间久远,早已忘却. 所以利用这篇文章做做笔记,加深理解记忆 2. ArrayList 的动态扩容机制 要了解其动态扩容机制就必须先

  • java删除数组中的某一个元素的方法

    实例如下: package org.company.project.test; import java.util.Arrays; import java.util.Scanner; public class ArraysDelete { public static void main(String[] args) { //删除数组中的某一个元素的方法: //把最后一个元素替代指定的元素,然后数组缩容 Scanner sc =new Scanner(System.in); int[] arr =

  • Java中数组容器(ArrayList)设的计与实现

    目录 ArrayList为我们提供了哪些功能 设计原理分析 代码实现 完整代码 本篇文章主要跟大家介绍我们最常使用的一种容器ArrayList.Vector的原理,并且自己使用Java实现自己的数组容器MyArrayList,让自己写的容器能像ArrayList那样工作.在本篇文章当中首先介绍ArrayList的一些基本功能,然后去分析我们自己的容器MyArrayList应该如何进行设计,同时分析我们自己的具体实现方法,最后进行代码介绍!!! ArrayList为我们提供了哪些功能 我们来看一个

  • Centos7 利用LVM实现动态扩容的方法

    摘要:最近项目组里来了很多新人,对linux分区及各种应用使用的分区不了解,导致测试数据库时突然发现某一个分区被写满了,不得不重装OS.实在看不下去了,特此分享我的一些利用LVM实现动态扩容的心得,希望对大家有帮助. 知识储备: LVM是逻辑盘卷管理(Logical VolumeManager)的简称,它是Linux环境下对磁盘分区进行管理的一种机制,LVM是建立在硬盘和分区之上的一个逻辑层,来提高磁盘分区管理的灵活性.通过LVM系统管理员可以轻松管理磁盘分区,如:将若干个磁盘分区连接为一个整块

  • 对Java ArrayList的自动扩容机制示例讲解

    注意: 不同的JDK版本的扩容机制可能有差异 实验环境:JDK1.8 扩容机制: 当向ArrayList中添加元素的时候,ArrayList如果要满足新元素的存储超过ArrayList存储新元素前的存储能力,ArrayList会增强自身的存储能力,已达到存储新元素的要求 ArrayList:本质通过内部维护的数组对象进行数据存储 ①:分析ArrayList的add(E)方法 public boolean add(E e) { ensureCapacityInternal(size + 1); /

  • Java封装数组之动态数组实现方法详解

    本文实例讲述了Java封装数组之动态数组实现方法.分享给大家供大家参考,具体如下: 前言:在此之前,我们封装的数组属于静态数组,也即数组空间固定长度,对于固定长度的数组当元素超过容量时会报数组空间不足.为了能更好的使用数组,我们来实现一个可以自动扩充容量的数组. 实现思路: 1.当数组容量达到事先定义值时创建一个空间是data数组两倍的newData数组(扩容): 2.把data数组中的元素全部赋值到newData数组中: 3.把data数组重新执行newData数组. 一.定义核心扩容方法 /

  • Java中的ArrayList容量及扩容方式

    目录 查看JDK1.8 ArrayList的源代码 1.默认初始容量为10 2.最大容量为 Integer.MAX_VALUE - 8 3.扩容方式: Java ArrayList() 扩容原理 先看下 ArrayList 的属性以及构造方法,这个比较重要 上看说的是初始化场景,下面看一下其他场景,也是相当简单 结论 查看JDK1.8 ArrayList的源代码 1.默认初始容量为10 /** * Default initial capacity. */ private static final

  • Java数组扩容实现方法解析

    这篇文章主要介绍了Java数组扩容实现方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 第一种 int[] arr2=new int[arr1.length*2] //新数组的长度 第二种 int[] arr2=java.util.Arrays.copyOf(原数组名,新数组的长度); 第三种 int[] arr2=new int[arr1.length*2] System.arraycopy(原数组名,起始下标,新数组名,起始下标,复制

随机推荐