废话不多说,上代码

1.从类名开始(我真是太贴心了,给自己点个赞)

  1. public class Array<E>

首先数组类需要带有泛型,这个不多说。需要注意的是在java中,数组只能存放同一个类型的。

2.成员变量

  1. private int size; //数组中元素的个数
  2. private E[] data; //数组声明

插个题外话:
关于size和索引,最开始学数组时让我很伤神,首先数组的索引是从0开始,而size是指数组中元素的
的个数,假设数组中有3个元素,那么size=3,而索引则为0,1,2。它们是差一位的,这个神奇的设计让我每次在写循环的界限条件时,
总要换算一下。
比如,遍历出数组的所有元素

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

我的心路历程是这样的:
  首先,第一步想,从0开始,到最后一位元素的索引位置结束,那么最后一位元素的索引是应该进来for循环的,那么i就应该
小于最后一位元素的索引的下一位,那么最后一位元素的索引的下一位是谁呢,对哦,size比索引大一位,那么应该是size,
所以应该i<size;
如果每次写for循环的界限时,都要这么想一下,白白消耗脑力阿。是不是只有我这么笨。
最后我的办法是转化成图来记在脑海里。每次用到的时候,直接脑海里浮现出这个图。

学习的本质就是将复杂的东西简单化。

3.构造方法
一种用户指定初始数组容量
一种用户不指定初始数组容量

  1. public Array (int capacity) {
  2. data = (E[])new Object[capacity];
  3. size = 0;
  4. }
  5.  
  6. public Array () {
  7. this(10); //调用另一个构造方法,并默认初始容量为10
  8. }

4.居家必备的基本方法

  1. //获得数组元素个数
  2. public int getSize () {
  3. return size;
  4. }
  5. //获得数组长度
  6. public int getCapacity () {
  7. return data.length;
  8. }
  9. //获得数组是否为空
  10. public boolean isEmpty () {
  11. return size == 0;
  12. }

5.添加方法

  1. 数组添加的本质就是:从后往前到指定索引位置,每个元素向后移一个格,给新来的腾出个地方。
  2. 重点:从后往前
  1. //向数组指定位置添加元素,index为指定索引位置,e为添加的值
  2. public void add (int index, E e) {
  3. //索引位置不能让它瞎插,索引为负数,或者跳格子插,不可以。
  4. if (index < 0 || index > size) {
  5. throw new IllegalArgumentException("add is fail, require index < 0 || index > size");
  6. }
  7. //当数组容量满了的时候,调用扩容方法,此处给它扩当前数组长度的两倍。
  8. if (data.length == size) {
  9. this.resize(data.length * 2);
  10. }for (int i = size - 1; i >= index; i--) {
  11. data[i+1] = data[i];
  12. }
  13. //新来的进坑
  14. data[index] = e;
  15. //维护size
  16. size ++;
  17.  
  18. }
  1. //向数组第一位添加元素
  2. public void addFirst (E e) {
  3. //直接复用上一个add方法
  4. this.add(0, e);
  5. }
  6. //向数组最后一位添加元素
  7. public void addLast (E e) {
  8. //同理
  9. this.add(size, e);
  10. }

6.删除方法(我个人分为两种,一种根据索引删除,一种根据值删除)

  1. 删除的本质:和添加相反,从要删除的索引位置的下一位开始,到最后一位元素索引位置结束,依次向前占一个坑。
  2. 重点:遍历的时候从前往后
  1. //根据索引删除某个元素 返回删除的元素
  2. public E remove (int index) {
  3. if (index < 0 || index >= size) {
  4. throw new IllegalArgumentException("remove is fail,require index < 0 || index >= size");
  5. }
  6. //先把要删除的元素存起来,不然等会就给覆盖了。
  7. E value = data[index];
  8. for (int i = index + 1; i < size; i++) {
  9. data[i-1] = data[i];
  10. }
  11. //维护size
  12. size --;
  13. //此处为什么设置为null呢,因为泛型的原因,传进来的都是类对象,数组中存的是引用地址,引用不断开的话,垃圾回收器没办法回收。
  14. data[size] = null;
  15. //此处缩容,当数组元素个数等于数组长度四分之一时,进行缩容
    if (size == data.length/4 && data.length / 2 != 0) {
  16. //缩容为数组长度的二分之一
  17. this.resize(data.length /2);
  18. }
  19. return value;
  20. }

  1. 问题来了,为什么不在二分之一时就进行缩容呢?而是四分之一呢?

  2. 此处涉及到复杂度震荡问题,比较极端的一个情况是:
  3. 比如容量为10的一个数组,
  4. 此时该数组满了,此时要进来个元素,然后数组进行扩容,那么添加完元素此时数组的情况为容量为20
  5. 内部有11个元素。
  6. 此时我再对数组进行删除一个元素,删除之后,数组元素个数变为10个,恰好为数组长度的二分之一,
  7. 那么自动进行缩容,以此类推,反复操作,每次扩容缩容的时间复杂度为O(n),所以此处应用了lazy的解决方案
  8. 就是等到数组元素个数为数组长度的四分之一时,再进行缩容,就可以避免这个问题。
  1. //根据值删除某个元素
  2. public void removeByValue (E e) {
  3. //复用根据值查找元素的方法,返回索引(此方法在下面)
  4. int index = this.getByElement(e);
  5. if (index != -1) {
  6. //复用根据索引删除的方法
  7. this.remove(index);
  8. }
  9. }
  10. //删除第一个元素
  11. public E removeFirst () {
  12. return this.remove(0);
  13. }
  14. //删除最后一个元素
  15. public E removeLast () {
  16. return this.remove(size - 1);
  17. }

7.查找方法(同样分为两种,一种根据索引,一种根据值

  1. //根据索引查找数组某个元素,返回值
  2. public E getByIndex (int index) {
  3.  
  4. if (index < 0 || index >= size) {
  5. throw new IllegalArgumentException("get is fail, require index < 0 || index >= size");
  6. }
  7.  
  8. return data[index];
  9. }
  1. //根据值查找数组某个元素,返回索引
  2. public int getByElement (E e) {
  3. //本质:遍历数组进行比对
  4. for (int i = 0; i < size; i++) {
  5. if (data[i].equals(e) ) {
  6. return i;
  7. }
  8. }
  9. return -1;
  10. }
  1. //是否包含该元素
  2. public boolean contains (E e) {
  3. //本质:遍历数组进行比对
  4. for (int i = 0; i < size; i++) {
  5. if (data[i].equals(e)) {
  6. return true;
  7. }
  8. }
  9. return false;
  10. }

8.修改方法

  1. //修改数组某个元素
  2. public void set (int index, E e) {
  3.  
  4. if (index < 0 || index >= size) {
  5. throw new IllegalArgumentException("set is fail, require index < 0 || index >= size");
  6. }
  7.  
  8. data[index] = e;
  9. }

9.扩容方法
扩容的本质:就是开辟个新数组,把旧数组的内容复制过去

  1. private void resize (int newCatacity) {
  2. E[] newData = (E[])new Object[newCatacity];
  3. for (int i = 0; i < size; i++) {
  4. newData[i] = data[i];
  5. }
  6. //给成员变量data重新赋值新引用(后面有内存图介绍)
  7. data = newData;
  8. }

画个扩容引用转换的内存图

测试代码

  1. public static void main(String[] args) {
  2. Array<Integer> array = new Array(5);
  3. array.addLast(1);
  4. array.addLast(2);
  5. array.addLast(3);
  6. array.addLast(4);
  7. array.addLast(5);
  8. }

10.toString方法
本质就是:创建一个StringBuilder对象,然后通过append方法,将数组的内容遍历,添加进StringBuilder对象。

  1. @Override
  2. public String toString () {
  3. StringBuilder stringBuilder = new StringBuilder();
  4. //Format(String, Object, Object) 将指定的 String 中的格式项替换为两个指定的 Object 实例的值的文本等效项。
  5. stringBuilder.append(String.format("size =%d ,capacity =%d\n ", size, data.length));
  6. stringBuilder.append('[');
  7. for (int i = 0; i < size; i++) {
  8. stringBuilder.append(data[i]);
  9. if (i != size -1) {
  10. stringBuilder.append(',');
  11. }
  12. }
  13. stringBuilder.append(']');
  14.  
  15. return stringBuilder.toString();
  16. }

最后,

浮于表面看千遍,

不如自己思一遍,

希望这篇文章能够对你起到帮助。

使用java语言实现一个动态数组(详解)(数据结构)的更多相关文章

  1. “全栈2019”Java第三十章:数组详解(下篇)

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  2. “全栈2019”Java第二十八章:数组详解(上篇)

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  3. C语言基础 - 实现动态数组并增加内存管理

    用C语言实现一个动态数组,并对外暴露出对数组的增.删.改.查函数 (可以存储任意类型的元素并实现内存管理) 这里我的编译器就是xcode 分析: 模拟存放 一个 People类 有2个属性 字符串类型 ...

  4. “全栈2019”Java第二十九章:数组详解(中篇)

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  5. Java基础之 数组详解

    前言:Java内功心法之数组详解,看完这篇你向Java大神的路上又迈出了一步(有什么问题或者需要资料可以联系我的扣扣:734999078) 数组概念 同一种类型数据的集合.其实数组就是一个容器. 数组 ...

  6. 纯数据结构Java实现(1/11)(动态数组)

    我怕说这部分内容太简单后,突然蹦出来一个大佬把我虐到哭,还是悠着点,踏实写 大致内容有: 增删改查,泛型支持,扩容支持,复杂度分析.(铺垫: Java语言中的数组) 基础铺垫 其实没啥好介绍的,顺序存 ...

  7. (待续)C#语言中的动态数组(ArrayList)模拟常用页面置换算法(FIFO、LRU、Optimal)

    目录 00 简介 01 算法概述 02 公用方法与变量解释 03 先进先出置换算法(FIFO) 04 最近最久未使用(LRU)算法 05 最佳置换算法(OPT) 00 简介 页面置换算法主要是记录内存 ...

  8. Java基础之数组详解

    数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同. Java 语言中提供的数组是用来存储固定大小的同类型元素. 你可以声明一个数组变量,如 numbers[1 ...

  9. JAVA数组详解

    package com.keke.demo; import java.text.ParseException;import java.text.SimpleDateFormat;import java ...

随机推荐

  1. 小白学习VUE第一篇文章---如何看懂网上搜索到的VUE代码或文章---使用VUE的三种模式:

    小白学习VUE第一篇文章---如何看懂网上搜索到的VUE代码或文章---使用VUE的三种模式: 直接引用VUE; 将vue.js下载到本地后本目录下使用; 安装Node环境下使用; ant-desig ...

  2. Istio 1.3 发布,HTTP 遥测不再需要 Mixer

    原文链接:Istio 1.3 发布,HTTP 遥测不再需要 Mixer Istio 是 Google.IBM 和 Lyft 联合开源的服务网格(Service Mesh)框架,旨在解决大量微服务的发现 ...

  3. CSS——段落处理

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  4. 上手Dubbo之 环境搭建

    和传统ssm整合--写XML配置文件 搭建服务的提供者和服务的消费者,实现服务消费者跨应用远程调用服务提供者 公共模块抽取 公共模块的抽取 服务的消费者远程调用服务的提供者, 最起码他自己要得到在服务 ...

  5. nginx主配置参数详解

    ########Nginx的main(全局配置)文件 #指定nginx运行的用户及用户组,默认为nobody #user nobody; #开启的线程数,一般跟逻辑CPU核数一致 worker_pro ...

  6. iOS 开发中一些 tips

    tableView 的 tableHeaderView 高度不正确的问题: func forceRefreshHeader() { let size = headerView.systemLayout ...

  7. Python学习笔记整理总结【Django】【MVC/MTV/路由分配系统(URL)/视图函数 (views)/表单交互】

     一.Web框架概述  Web框架本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. #!/usr/bin/env python # -*- coding:utf-8 ...

  8. android实现emoji输入

    学android也有一段时间, 一直都是自己摸索, 各种上网查资料, 也明白了不能一味去索取有时间也要分享一些自己的心得 . 最近几天都在写关于android emoji输入的小例子,网上有不少源码还 ...

  9. java8 Stream使用总结

    [前言] java8新特性 java8 函数接口 java8 Optional使用总结 Java 8 时间日期使用 java8 lambda表达式 1.流的介绍 Java8 中的 Stream 是对集 ...

  10. Ubuntu下安装并使用sublime text 3(建议:先安装Package controls 后在看本教程,否则可能会安装不了)

    首先从Sublime Text官网下载合适的包 然后使用 tar -xvvf sublime_text_3_build_3207_x64.tar.bz2 解压: 再使用 mv sublime_text ...