手写ArrayList核心源码

ArrayList是Java中常用的数据结构,不光有ArrayList,还有LinkedList,HashMap,LinkedHashMap,HashSet,Queue,PriorityQueue等等,我们将手写这些常用的数据结构的核心源码,用尽量少的代码来揭示核心原理。

下面我们来手写ArrayList的核心源码

首先我们定义一个QArrayList,不要问为什么叫QArrayList,因为我之前写过Qt,仅此而已。源码 public class<T> QArrayList,Java中的ArrayList的底层就是用一个Object[] 结构来保存数据的。我们也要定义一个Object[] 属性。

而且我们还要定义一个默认的数据的大小,以便在调用默认构造函数的情况下使用。

private final int DEFAULT_LIST_SIZE = 8;

还要定义一个 int mSize 变量,mSize 默认为0代表下一个可以存放数据的数组的索引代表下一个可以存放数据的数组的索引代表下一个可以存放数据的数组的索引

重要的事情说三遍

到现在为止我们的类如下:

public class QList<T> {
//默认的数组的大小
private final int DEFAULT_LIST_SIZE = 8; //存放数据的地方
private Object[] mData; //下一个可以存放数据的当前数组的索引
private int mSize; ......
}

好了,存放数据的数组也有了,下一个可以存放数据的当前的数组的索引也有了

ArrayList 底层是用数组存放数据,那么会有一个问题,如果此时数组满了我们再往里面存放数据的时候,怎么办呢?ArrayList是再新建一个数组,新数组的大小是原来数组大小的2倍,那么我们也这样做。

此时,我们实现 add,get,remove,resize等这几个核心方法,QArrayList完整的代码如下 :

public class QArrayList<T> {
//默认的数组的大小
private final int DEFAULT_LIST_SIZE = 8; //存放数据的地方
private Object[] mData; //下一个可以存放数据的当前数组的索引
private int mSize; public QArrayList() {
//new 一个数组,用来存放
mData = new Object[DEFAULT_LIST_SIZE]; //下一个可以存放数据的当前数组的索引为0
mSize = 0;
} public QArrayList(int capacity){
if(capacity <= 0 || capacity > Integer.MAX_VALUE){
throw new RuntimeException("invalid capacity");
} mData = new Object[capacity];
mSize = 0;
} //返回当时数组的已经存放了多少个元素
public int size() {
return mSize;
} //返回数组的总大小,其实这个接口没有必要对外提供,这里我们只是为了演示用
public int capacity() {
return mData.length;
} //添加一个元素
public void add(T e) {
//规定不允许添加一个空元素
if(e == null){
return;
} //如果当前数组已经满了,扩容为原来数组的2倍
if (mSize >= mData.length) { //扩容
resize();
} //将添加的元素添加到数组中
mData[mSize] = e; //同时 mSize++ 指向下一个可以存放数据的位置
mSize++;
} //获取指定位置的元素,如果position不合法,直接抛出异常
//这样做是有必要的,我们提供的是一个库
// 直接抛出异常让使用知道用错了,没有必要 return null
// 因为这是个库,不是业务,就算return null,也是业务层的事
public T get(int position) {
if (position < 0 || position >= mData.length) {
throw new RuntimeException("position is invalid");
} // position 大于 mSize 也没有关系,因为也是返回null,证明没有获取到
return (T) mData[position];
} //删除指定位置的元素
public T remove(int position) {
//和上面一样,位置不合法直接抛出异常
if (position < 0 || position >= mData.length) {
throw new RuntimeException("position is invalid");
} //把当前要删除的元素保存下来,最后返回要删除的元素
T e = (T) mData[position]; //删除后,把后面的所有元素都往前移位
for (int i = position + 1; i < mData.length; i++) {
mData[i - 1] = mData[i];
} //别忘了 mSize 要 --
mSize--; //返回删除的元素
return e;
} //删除指定的元素
public boolean remove(T e) {
//因为数组可能没有满,如果删除的是null,没有必要,我们不允许
if (e == null) {
return false;
} //找到删除元素的位置
int position = -1;
for (int i = 0; i < mData.length; i++) {
if (e == mData[i] || e.equals(mData[i])) {
position = i;
break;
}
} //没有找到就返回
if (position == -1) {
return false;
} //删除
return remove(position) != null;
} //扩容,我们都以2倍的容量扩容
private void resize() {
Object[] old = mData;
mData = new Object[mData.length * 2];
for (int i = 0; i < old.length; i++) {
mData[i] = old[i];
} old = null;
}
}

注释都有相关的解释

我们来测试,测试代码如下:

    public static void main(String[] args) {
QArrayList<String> list = new QArrayList<>();
list.add("tom");
list.add("jim");
list.add("lilei");
list.add("hanmeimei"); System.out.println("list.get(2)=" + list.get(2));
System.out.println("list.size()=" + list.size());
for (int i = 0; i < list.size(); i++) {
System.out.println("list.get(" + i + ") = " + list.get(i));
} System.out.println("=======================");
System.out.println("演示删除操作");
list.remove("jim"); for (int i = 0; i < list.size(); i++) {
System.out.println("list.get(" + i + ") = " + list.get(i));
}
}

输出如下:

list.get(2)=lilei

list.size()=4

list.get(0) = tom

list.get(1) = jim

list.get(2) = lilei

list.get(3) = hanmeimei

============

演示删除操作

list.get(0) = tom

list.get(1) = lilei

list.get(2) = hanmeimei

但是最重要的扩容功能还没有演示,下面是扩容演示的测试代码:

 public static void main(String[] args) {
//新建一个只有2个元素的数组
QArrayList<String> list = new QArrayList<>(2); //打印出扩容后的容量
System.out.println("扩容前 : list.capacity()=" + list.capacity()); //我们添加了4个元素
list.add("tom");
list.add("jim");
list.add("lilei");
list.add("hanmeimei"); //打印出扩容后的容量
System.out.println("扩容后 : list.capacity()=" + list.capacity()); //打印
for (int i = 0; i < list.size(); i++) {
System.out.println("list.get(" + i + ") = " + list.get(i));
}
}

输出如下:

扩容前 : list.capacity()=2

扩容后 : list.capacity()=4

list.get(0) = tom

list.get(1) = jim

list.get(2) = lilei

list.get(3) = hanmeimei

可以看到,我们新建了一个底层只有2个元素的数组,但是我们添加了4个元素,我们打印出扩容后的数组的容量是 4 ,可见我们的扩容机制是没有问题的。

以上就是QArrayList的核心原理,我们下节手写LinkedList的核心原理

1 手写Java ArrayList核心源码的更多相关文章

  1. 3 手写Java HashMap核心源码

    手写Java HashMap核心源码 上一章手写LinkedList核心源码,本章我们来手写Java HashMap的核心源码. 我们来先了解一下HashMap的原理.HashMap 字面意思 has ...

  2. 6 手写Java LinkedHashMap 核心源码

    概述 LinkedHashMap是Java中常用的数据结构之一,安卓中的LruCache缓存,底层使用的就是LinkedHashMap,LRU(Least Recently Used)算法,即最近最少 ...

  3. 2 手写Java LinkedList核心源码

    上一章我们手写了ArrayList的核心源码,ArrayList底层是用了一个数组来保存数据,数组保存数据的优点就是查找效率高,但是删除效率特别低,最坏的情况下需要移动所有的元素.在查找需求比较重要的 ...

  4. 5 手写Java Stack 核心源码

    Stack是Java中常用的数据结构之一,Stack具有"后进先出(LIFO)"的性质. 只能在一端进行插入或者删除,即压栈与出栈 栈的实现比较简单,性质也简单.可以用一个数组来实 ...

  5. 4.1 手写Java PriorityQueue 核心源码 - 原理篇

    本章先讲解优先级队列和二叉堆的结构.下一篇代码实现 从一个需求开始 假设有这样一个需求:在一个子线程中,不停的从一个队列中取出一个任务,执行这个任务,直到这个任务处理完毕,再取出下一个任务,再执行. ...

  6. 4.2 手写Java PriorityQueue 核心源码 - 实现篇

    上一节介绍了PriorityQueue的原理,先来简单的回顾一下 PriorityQueue 的原理 以最大堆为例来介绍 PriorityQueue是用一棵完全二叉树实现的. 不但是棵完全二叉树,而且 ...

  7. Java HashMap 核心源码解读

    本篇对HashMap实现的源码进行简单的分析. 所使用的HashMap源码的版本信息如下: /* * @(#)HashMap.java 1.73 07/03/13 * * Copyright 2006 ...

  8. Java——ArrayList底层源码分析

    1.简介 ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问.数组的缺点是每个元素之间不能有间隔, 当数组大小不满足时需要增加存储能力,就要将已经有数 ...

  9. 手撕spring核心源码,彻底搞懂spring流程

    引子 十几年前,刚工作不久的程序员还能过着很轻松的日子.记得那时候公司里有些开发和测试的女孩子,经常有问题解决不了的,不管什么领域的问题找到我,我都能帮她们解决.但是那时候我没有主动学习技术的意识,只 ...

随机推荐

  1. undefined reference to libiconv_open ext/iconv/.libs/iconv.o by install phpsource

    错误信息:ext/iconv/.libs/iconv.o(.text+0x30e2): In function `php_iconv_stream_filter_factory_create':/ho ...

  2. Spring Data MongoDB 四:基本文档改动(update)(一)

    Spring Data MongoDB 三:基本文档查询(Query.BasicQuery)(一) 学习MongoDB 二:MongoDB加入.删除.改动 一.简单介绍 Spring Data  Mo ...

  3. ecmall时间的问题

    $time1 = date("Y-m-d H:i:s", gmtime());   $time = date("Y-m-d H:i:s", time()); / ...

  4. accessor mothod mutator mothod 更改器方法 访问器方法 类的方法可以访问类的任何一个对象的私有域!

    LocalDate.plusDate String.toUpperCase GregorianCalendar.add import java.time.*; public class Calenda ...

  5. 后台管理微服务(二)——docker的使用

    1. docker概述 1.1 Docker是什么 Docker 是软件工业的集装箱技术 Docker 是一个容器引擎,docker提供了一套完整的容器解决方案. Docker 是一个能将开发的程序自 ...

  6. vsftp登录时间太长的解决办法

    与ssh一样,vsftp的配置文件默认开启了DNS反向解析,这可能会造成用户在登陆到FTP服务器的时候奇慢无比,只要在配置文件中禁用DNS反向解析即可解决文件. 编辑/etc/vsftpd/vsftp ...

  7. [bzoj1002] [FJOI2007]轮状病毒轮状病毒(基尔霍夫矩阵)

    Description 轮状病毒有很多变种,所有轮状病毒的变种都是从一个轮状基产生的.一个N轮状基由圆环上N个不同的基原子 和圆心处一个核原子构成的,2个原子之间的边表示这2个原子之间的信息通道.如下 ...

  8. 安装MySQLdb出现的问题

    枫竹梦的环境是自己编译安装的MySQL,安装目录在/usr/local/mysql. 下载MySQLdb,由于网络上大多数的链接都是指向比较老的sourceforge上,而我们安装最新的1.2.5,h ...

  9. platform_set_drvdata 和 platform_get_drvdata

    ndev是我们在probe函数中定义的局部变量,如果我想在其他地方使用它怎么办呢? 这就需要把它保存起来.内核提供了这个方法,使用函数platform_set_drvdata()可以将ndev保存成平 ...

  10. 找到bashrc

    (1)直接sudo gedit ~/.bashrc就可以了,编辑完后关闭就行 (2)主文件夹下ctrl+h就能找到.bashrc文件 之所以要找到bashrc文件,是为了把命令 source /opt ...