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

ArrayList为我们提供了哪些功能

我们来看一个简单的代码,随机生成100个随机数,查看生成随机数当中是否存在50这个数。

public class MyArrayList {

  public static void main(String[] args) {
    Random random = new Random();
    ArrayList<Integer> list = new ArrayList<>();
    for (int i = 0; i < 100; i  ) {
      list.add(random.nextInt(5000));
    }
    for (int i = 0; i < 100; i  ) {
      if (list.get(i) == 50) {
        System.out.println("包含数据 50");
      }
    }
    list.set(5, 1000);// 设置下标为5的数据为100
    list.remove(5);// 删除下标为5的数据
    list.remove(new Integer(888));// 删除容器当中的第一个值为5的数据
  }
}

上述代码包含了ArrayList最基本的一个功能,一个是add方法,向数组容器当中加入数据,另外一个方法是get从容器当中拿出数据,set方法改变容器里的数据,remove方法删除容器当中的数据。ArrayList的很多其他的方法都是围绕这四个最基本的方法展开的,因此我们在这里不仔细介绍其他的方法了,后面我们自己实现的时候遇到问题的时候自然会需要设计相应的方法,然后我们进行解决即可。

现在我们就需要去设计一个数组容器实现“增删改查”这四个基本功能。

设计原理分析

首先明白一点我们需要使用什么工具去实现这样一个容器。我们手里有的工具就是Java提供给我们的最基本的功能——数组(这个好像是废话,我们的标题就是数组容器🤣)。

当我们在Java当中使用数组去存储数据时,数据在Java当中的内存布局大致如下图所示。

我们在设计数组容器这样一个数据结构的时候主要会遇到两个问题:

  • 我们申请数组的长度是多少。
  • 当数组满了之后怎么办,也就是我们的扩容机制。

对于这两个问题,首先我们数组的初始大小可以有默认值,在我们自己实现的MyArrayList当中设置为10,我们在使用类时也可以传递一个参数指定初始大小。第二个问题当我们的数组满的时候我们需要对数组进行扩容,在我们实现的MyArrayList当中我们采取的方式是,新数组的长度是原数组的两倍(这个跟JDKArrayList方式不一样,ArrayList扩容为原来的1.5倍)。

代码实现

为了让我们的类实现的更加简单我们在代码当中就不做很多非必要的逻辑判断并且抛出异常,我们的代码只要能表现出我们的思想即可。

首先定义一个接口MyCollection,表示我们要实现哪些方法!

public interface MyCollection<E> {

  /**
   * 往链表尾部加入一个数据
   * @param o 加入到链表当中的数据
   * @return
   */
  boolean add(E o);

  /**
   * 表示在第 index 位置插入数据 o
   * @param index
   * @param o
   * @return
   */
  boolean add(int index, E o);

  /**
   * 从链表当中删除数据 o
   * @param o
   * @return
   */
  boolean remove(E o);

  /**
   * 从链表当中删除第 index 个数据
   * @param index
   * @return
   */
  boolean remove(int index);

  /**
   * 往链表尾部加入一个数据,功能和 add 一样
   * @param o
   * @return
   */
  boolean append(E o);

  /**
   * 返回链表当中数据的个数
   * @return
   */
  int size();

  /**
   * 表示链表是否为空
   * @return
   */
  boolean isEmpty();

  /**
   * 表示链表当中是否包含数据 o
   * @param o
   * @return
   */
  boolean contain(E o);

  /**
   * 设置下标为 index 的数据为 o
   * @param index
   * @param o
   * @return
   */
  boolean set(int index, E o);
}

我们的构造函数,初始化过程。

  public MyArrayList(int initialCapacity) {
    this();
    // 增长数组的空间为 initialCapacity,即申请一个数组
    // 且数组的长度为 initialCapacity
    grow(initialCapacity); 
  }

  public MyArrayList() {
    this.size = 0; // 容器当中的数据个数在开始时为 0
    this.elementData = EMPTY_INSTANCE; // 将数组设置为空数组
  }

我们需要实现的最复杂的方法就是add了,这个方法是四个方法当中最复杂的,其余的方法都相对比较简单。

  • 进入add方法之后,我们需要找到符合要求的最小数组长度,这个值通常是容器当中元素的个数size 1 ,也就是图中的minCapacity首先先比较这个值和现在数组的长度,如果长度不够的话则需要进行扩容,将数组的长度扩大到原来的两倍。
  • 如果不需要扩容,则直接讲元素放入到数组当中即可。

  @Override
  public boolean add(E o) {
    // 这个函数的主要作用就是确保数组的长度至少为 size   1
    ensureCapacity(size   1);
    // 新增加了一个数据,容器的大小需要   1
    elementData[  size] = o;
    return true;
  }

  /**
   * 这个函数的主要作用就是确保数组的长度至少为 capacity
   * @param capacity
   */
  public void ensureCapacity(int capacity) {
    int candidateCapacity = findCapacity(capacity);
    if (elementData.length < candidateCapacity)
      grow(candidateCapacity);
  }

  /**
   * 这个函数的主要目的就是找到最终数组长度需求的容量
   * @param minCapacity
   * @return
   */
  private int findCapacity(int minCapacity) {
    /**
     * 如果 if 条件为 true 即 elementData 还是初始化时设置的空数组
     * 那么返回默认大小和需要大小的最大值 
     * 否则直接返回 minCapacity
     */
    if (elementData == EMPTY_INSTANCE){
      return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
  }

我们为什么需要将ensureCapacity的访问限制权限设置为public?因为我们想让用户尽量去使用这个函数,因为如果我们如果写出下面这样的代码我们会一直申请内存空间,然后也需要将前面的数组释放掉,会给垃圾回收器造成更大的压力。

    ArrayList<Integer> list = new ArrayList<>();
    for (int i = 0; i < 1000000; i  ) {
      list.add(i);
    }

下面我们对ArrayList的方法进行测试:

import java.util.ArrayList;

class Person {

  String name;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  @Override
  public String toString() {
    return "Person{"  
        "name='"   name   '\''  
        '}';
  }
}


public class ArrayListTest {

  public static void main(String[] args) {
    ArrayList<Person> o1 = new ArrayList<>();
    o1.ensureCapacity(10000000);
    long start = System.currentTimeMillis();
    for (int i = 0; i < 10000000; i  ) {
      o1.add(new Person());
    }
    long end = System.currentTimeMillis();
    System.out.println("end - start: "   (end - start));
    ArrayList<Person> o2 = new ArrayList<>();
    start = System.currentTimeMillis();
    for (int i = 0; i < 10000000; i  ) {
      o2.add(new Person());
    }
    end = System.currentTimeMillis();
    System.out.println("end - start: "   (end - start));
  }
}
// 输出结果
end - start: 1345
end - start: 4730

从上面的测试结果我们可以看出提前使用ensureCapacity方法之后,程序执行的时间更加短。

插入数据的add方法。

  @Override
  public boolean add(E o) {
    // 这个函数的主要作用就是确保数组的长度至少为 size   1
    ensureCapacity(size   1);
    // 新增加了一个数据,容器的大小需要   1
    elementData[size] = o;
    size  ;
    return true;
  }

add在指定下标插入数据。

  • 首先将插入下标后的数据往后移动一个位置
  • 然后在将数据放在指定下标的位置。

  /**
   * 在下标 index 位置插入数据 o
   * 首先先将 index 位置之后的数据往后移动一个位置
   * 然后将 index 赋值为 o
   * @param index
   * @param o
   * @return
   */
  @Override
  public boolean add(int index, E o) {
    // 确保容器当中的数组长度至少为 size   1
    ensureCapacity(size   1);
    // 将 elementData index位置之后的数据往后移动一个位置
    // 做一个原地拷贝
    System.arraycopy(elementData, index, elementData, index   1,
        size - index); // 移动的数据个数为 size - index
    elementData[index] = o;
    size  ;
    return true;
  }

删除数据的方法remove

  • 首先先删除指定下标的数据。
  • 然后将指定下标后的数据往前移动一个位置
  • 在实际的操作过程中我们可以不删除,直接移动,这样也覆盖被插入位置的数据了。

  /**
   * 移除下标为 index 的数据
   * @param index
   * @return
   */
  @Override
  public boolean remove(int index) {
    // 需要被移动的数据个数
    int numMoved = size - index - 1;
    if (numMoved > 0)
      System.arraycopy(elementData, index 1, elementData, index,
          numMoved);
    elementData[--size] = null;

    return true;
  }

移除容器当中具体的某个对象。

  /**
   * 这个方法主要是用于溢出容器当中具体的某个数据
   * 首先先通过 for 循环遍历容器当中的每个数据,
   * 比较找到相同的数据对应的下标,然后通过下标移除方法
   * @param o
   * @return
   */
  @Override
  public boolean remove(E o) {
    if (o == null) {
      for (int index = 0; index < size; index  )
        if (elementData[index] == null) {
          remove(index);
          return true;
        }
    } else {
      for (int index = 0; index < size; index  )
        if (o.equals(elementData[index])) {
          remove(index);
          return true;
        }
    }
    return false;
  }

set方法,这个方法就很简单了。

  @Override
  public boolean set(int index, E o) {
    elementData[index] = o;
    return true;
  }

重写toString方法。

  @Override
  public String toString() {

    if (size <= 0)
      return "[]";

    StringBuilder builder = new StringBuilder();
    builder.append("[");
    for (int index = 0; index < size; index  ) {
      builder.append(elementData[index].toString()   ", ");
    }
    builder.delete(builder.length() - 2, builder.length());
    builder.append("]");
    return builder.toString();
  }

测试代码

public static void main(String[] args) {
    MyArrayList<Integer> list = new MyArrayList<>();
    for (int i = 0; i < 15; i  ) {
      list.add(-i);
    }
    System.out.println(list.contain(5));
    System.out.println(list);
    list.remove(new Integer(-6));
    System.out.println(list);
    System.out.println(list.elementData.length); // 容器会扩容两倍,而默认容器长度为10,因此这里是 20 
    list.add(5, 99999);
    System.out.println(list);
    System.out.println(list.contain(99999));
  }
// 代码输出
false
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13, -14]
[0, -1, -2, -3, -4, -5, -7, -8, -9, -10, -11, -12, -13, -14]
20
[0, -1, -2, -3, -4, 99999, -5, -7, -8, -9, -10, -11, -12, -13, -14]
true

完整代码

import java.util.ArrayList;
import java.util.Arrays;


public class MyArrayList<E> implements MyCollection<E> {

  /**
   * 容器当中存储数据的个数
   */
  private int size;

  /**
   * 容器中数组的默认长度
   */
  private static final int DEFAULT_CAPACITY = 10;

  /**
   * 存放具体数据的数组,也就是我们容器当中真正存储数据的地方
   */
  Object[] elementData;

  /**
   * 当容器当中没有数据将 elementData 设置为这个值,这个值是所有实例一起共享的
   */
  private static final Object[] EMPTY_INSTANCE = {};


  public MyArrayList(int initialCapacity) {
    this();
    // 增长数组的空间为 initialCapacity,即申请一个数组
    // 且数组的长度为 initialCapacity
    grow(initialCapacity);
  }

  public MyArrayList() {
    this.size = 0; // 容器当中的数据个数在开始时为 0
    this.elementData = EMPTY_INSTANCE; // 将数组设置为空数组
  }

  /**
   * 这个函数的主要作用就是确保数组的长度至少为 capacity
   * @param capacity
   */
  public void ensureCapacity(int capacity) {
    int candidateCapacity = findCapacity(capacity);
    if (elementData.length < candidateCapacity)
      grow(candidateCapacity);
  }

  /**
   * 这个函数的主要目的就是找到最终数组长度需求的容量
   * @param minCapacity
   * @return
   */
  private int findCapacity(int minCapacity) {
    /**
     * 如果 if 条件为 true 即 elementData 还是初始化时设置的空数组
     * 那么返回默认大小和需要大小的最大值
     * 否则直接返回 minCapacity
     */
    if (elementData == EMPTY_INSTANCE){
      return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
  }

  /**
   * 该函数主要保证 elementData 的长度至少为 minCapacity
   * 如果数组的长度小于 minCapacity 则需要进行扩容,反之
   * @param minCapacity 数组的最短长度
   */
  private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // 新的数组长度为原来数组长度的两倍
    int newCapacity = oldCapacity << 1;

    // 如果数组新数组的长度 newCapacity 小于所需要的长度 minCapacity
    // 新申请的长度应该为 minCapacity
    if (newCapacity < minCapacity) {
      newCapacity = minCapacity;
    }
    // 申请一个长度为 newCapacity 的数组,在将原来数组
    // elementData 的数据拷贝到新数组当中
    elementData = Arrays.copyOf(elementData, newCapacity);
  }

  @Override
  public boolean add(E o) {
    // 这个函数的主要作用就是确保数组的长度至少为 size   1
    ensureCapacity(size   1);
    // 新增加了一个数据,容器的大小需要   1
    elementData[size] = o;
    size  ;
    return true;
  }

  /**
   * 在下标 index 位置插入数据 o
   * 首先先将 index 位置之后的数据往后移动一个位置
   * 然后将 index 赋值为 o
   * @param index
   * @param o
   * @return
   */
  @Override
  public boolean add(int index, E o) {
    // 确保容器当中的数组长度至少为 size   1
    ensureCapacity(size   1);
    // 将 elementData index位置之后的数据往后移动一个位置
    // 做一个原地拷贝
    System.arraycopy(elementData, index, elementData, index   1,
        size - index); // 移动的数据个数为 size - index
    elementData[index] = o;
    size  ;
    return true;
  }

  /**
   * 这个方法主要是用于溢出容器当中具体的某个数据
   * 首先先通过 for 循环遍历容器当中的每个数据,
   * 比较找到相同的数据对应的下标,然后通过下标移除方法
   * @param o
   * @return
   */
  @Override
  public boolean remove(E o) {
    if (o == null) {
      for (int index = 0; index < size; index  )
        if (elementData[index] == null) {
          remove(index);
          return true;
        }
    } else {
      for (int index = 0; index < size; index  )
        if (o.equals(elementData[index])) {
          remove(index);
          return true;
        }
    }
    return false;
  }

  /**
   * 移除下标为 index 的数据
   * @param index
   * @return
   */
  @Override
  public boolean remove(int index) {
    // 需要被移动的数据个数
    int numMoved = size - index - 1;
    if (numMoved > 0)
      System.arraycopy(elementData, index 1, elementData, index,
          numMoved);
    elementData[--size] = null;

    return true;
  }

  @Override
  public boolean append(E o) {
    return add(o);
  }

  @Override
  public int size() {
    return size;
  }

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

  @Override
  public boolean contain(E o) {
    if (o == null) {
      for (int index = 0; index < size; index  )
        if (elementData[index] == null) {
          return true;
        }
    } else {
      for (int index = 0; index < size; index  )
        if (o.equals(elementData[index])) {
          return true;
        }
    }
    return false;
  }

  @Override
  public String toString() {

    if (size <= 0)
      return "[]";

    StringBuilder builder = new StringBuilder();
    builder.append("[");
    for (int index = 0; index < size; index  ) {
      builder.append(elementData[index].toString()   ", ");
    }
    builder.delete(builder.length() - 2, builder.length());
    builder.append("]");
    return builder.toString();
  }
    
  @Override
  public boolean set(int index, E o) {
    elementData[index] = o;
    return true;
  }


  public static void main(String[] args) {
    MyArrayList<Integer> list = new MyArrayList<>();
    for (int i = 0; i < 15; i  ) {
      list.add(-i);
    }
    System.out.println(list.contain(5));
    System.out.println(list);
    list.remove(new Integer(-6));
    System.out.println(list);
    System.out.println(list.elementData.length);
    list.add(5, 99999);
    System.out.println(list);
    System.out.println(list.contain(99999));
  }
}

以上就是Java中数组容器(ArrayList)设的计与实现的详细内容,更多关于Java数组容器的资料请关注Devmax其它相关文章!

Java中数组容器(ArrayList)设的计与实现的更多相关文章

  1. html5使用canvas实现弹幕功能示例

    这篇文章主要介绍了html5使用canvas实现弹幕功能示例的相关资料,需要的朋友可以参考下

  2. 前端实现弹幕效果的方法总结(包含css3和canvas的实现方式)

    这篇文章主要介绍了前端实现弹幕效果的方法总结(包含css3和canvas的实现方式)的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  3. H5 canvas实现贪吃蛇小游戏

    本篇文章主要介绍了H5 canvas实现贪吃蛇小游戏,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  4. HTML5页面无缝闪开的问题及解决方案

    这篇文章主要介绍了HTML5页面无缝闪开方案,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. ios – parse.com用于键,预期字符串的无效类型,但是得到了数组

    我尝试将我的数据保存到parse.com.我已经预先在parse.com上创建了一个名为’SomeClass’的类.它有一个名为’mySpecialColumn’的列,其数据类型为String.这是我尝试使用以下代码保存数据的代码:如果我运行这个我得到:错误:密钥mySpecialColumn的无效类型,预期字符串,但得到数组这就是我在parse.com上的核心外观:有谁知道我为什么会收到这个错误?

  6. ios – 上下文类型’NSFastEnumeration’不能与数组文字一起使用

    斯威夫特3,你会这样做吗?解决方法正如您所发现的,您不能使用as-casting将数组文字的类型指定为NSFastEnumeration.您需要找到一个符合NSFastEnumeration的正确类,在您的情况下它是NSArray.通常写这样的东西:

  7. ios – 将容器带到视图前方

    我怎样才能解决这个问题?

  8. ios – 如何使用XCode 6.4下载和替换AppGroup容器

    我知道如何使用XCode6的Devices窗口下载和替换特定iOS应用程序的文件系统容器.但是对于我正在开发的应用程序,我需要能够下载和替换共享的AppGroup容器以进行调试.这将使我能够模拟AppGroup文件夹内容中的情况以进行测试.任何人都可以告诉我如何做到这一点?

  9. ios – 获取资产目录文件夹中所有图像的数组

    在iOS中,是否可以获取资产目录文件夹中的图像数组?我不确定为什么会对此进行投票.我真的不知道从哪里开始.我的另一种方法是创建文件夹中所有文件的plist,但它似乎是多余的.我无法添加任何代码,因为我会添加什么?

  10. ios – 在UITableView上移动UIView – 触摸顶部UIView仍然选择表行

    =======用一些代码编辑:这是我在容器B中所做的代码.这是B帧的一个非常直接的动画.self.view是ContainerB的UIView.所有视图都在屏幕上通过故事板.其他容器是B的子视图.请让我知道你想看到的其他代码.解决方法嗯……不确定这是否适用于您的情况,但尝试在容纳所有其他容器的更大容器中管理您的动画.我的意思是,创建一个包含A,B,O及其子视图的ContainerZ,并尝试从Z中设置B的位置动画,检查B是否在A前面.

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部