数据结构与算法(java版) 第一季 - 02 动态数组

目录

1.线性表

2.接口涉及

3.简单接口的实现

4.clear

5.add

6.打印

7.remove

8.add_index

9.接口测试

10.动态扩容

11.泛型

12.对象数组

13.clear_细节

14.remove_细节

15.equals

16.null值处理

17.ArrayList源码分析


1.线性表

  • 什么是数据结构?
  •  线型表示具有n个相同类型元素的有限序列(n>=0)
  •  生活之中的线型表

2.接口涉及

  • 第一种线型表:数组这里的array是存放在相应的栈空间之中,new出来的数据是存放在相应的堆空间之中,并且new出来的内存地址是连续的。数组存在一个严重缺点,无法进行动态的申请相应的容量(改变这个方式,可以进行自己写一个动态数据)。
  • 动态数组的接口设计
  •  初始代码块的设计,这里是设计的相应的ArrayList。
    public class ArrayList {
    
        /**
         * 清除所有元素
         */
        public void clear() {
    
        }
    
        /**
         * 元素的数量
         * @return
         */
        public int size() {
            return 0;
        }
    
        /**
         * 是否为空
         * @return
         */
        public boolean isEmpty() {
            return false;
        }
    
        /**
         * 是否包含某个元素
         * @param element
         * @return
         */
        public boolean contains(int element) {
            return false;
        }
    
        /**
         * 添加元素到尾部
         * @param element
         */
        public void add(int element) {
    
        }
    
        /**
         * 获取index位置的元素
         * @param index
         * @return
         */
        public int get(int index) {
           return 0;
        }
    
        /**
         * 设置index位置的元素
         * @param index
         * @param element
         * @return 原来的元素ֵ
         */
        public int set(int index,int element) {
            return 0;
        }
    
        /**
         * 在index位置插入一个元素
         * @param index
         * @param element
         */
        public void add(int index, int element) {
    
        }
    
        /**
         * 删除index位置的元素
         * @param index
         * @return
         */
        public int remove(int index) {
            return 0;
        }
    
        /**
         * 查看元素的索引
         * @param element
         * @return
         */
        public int indexOf(int element) {
    
            return -1;
        }
    
    }
    

3.简单接口的实现

  • 动态数组的设计

public class ArrayList {
    private int size;//元素数量
    private int[] elements;//存放数组

    private static final int DEFAULT_CAPACITY = 10;//默认是相应的为10的存储空间
    private static final int ELEMENT_NOT_FOUND = -1;//这里是进行一个return的声明

    //提供容量的一个相应的数组
    public ArrayList(int capacity)
    {
        capacity = (capacity < DEFAULT_CAPACITY)? DEFAULT_CAPACITY:capacity;
        elements = new int[capacity];
    }

    /*
    //不提供容量的一个数组
    public ArrayList()
    {
        elements = new int[DEFAULT_CAPACITY];//当然这里是不推荐进行这样写入的,一般是进行外部声明,这里是使用的DEFAULT_CAPACITY
    }
    */
    //上面的相应的构造函数还可以进行下面的写法
    public ArrayList()
    {
        this(DEFAULT_CAPACITY);//直接是使用相应的无参构造函数调用相应的有参构造函数
    }
    /**
     * 清除所有元素
     */
    public void clear() {

    }

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

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

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

    /**
     * 添加元素到尾部
     * @param element
     */
    public void add(int element) {

    }

    /**
     * 获取index位置的元素
     * @param index
     * @return
     */
    public int get(int index) {
        if(index < 0 || index >= size)
        {
            throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size);//这里是直接使用抛出异常的方式进行相应的处理
            //抛出异常的情况就是相应的颜色编程红色
            /* 抛出的异常是这样的情况,直接点击相应的位置就是可以得到相应的错误的位置
            Exception in thread "main" java.lang.IndexOutOfBoundsException: Index:-10,Size:0
	        at ArrayList.get(ArrayList.java:74)
	        at Main.main(Main.java:11)
             */
        }
       return elements[index];
    }

    /**
     * 设置index位置的元素
     * @param index
     * @param element
     * @return 原来的元素ֵ
     */
    public int set(int index,int element) {
        if(index < 0 || index >= size)
        {
            throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size);//这里是直接使用抛出异常的方式进行相应的处理
            //抛出异常的情况就是相应的颜色编程红色
            /* 抛出的异常是这样的情况,直接点击相应的位置就是可以得到相应的错误的位置
            Exception in thread "main" java.lang.IndexOutOfBoundsException: Index:-10,Size:0
	        at ArrayList.get(ArrayList.java:74)
	        at Main.main(Main.java:11)
             */
        }
        int old = elements[index];
        elements[index] = element;
        return old;
    }

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

    }

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

    /**
     * 查看元素的索引
     * @param element
     * @return
     */
    public int indexOf(int element) {
        //这个含义就是进行相应的遍历的过程
        for(int i= 0; i<size;i++)
        {
            if(elements[i] == element)
                return i;
        }
        return ELEMENT_NOT_FOUND;
    }

}

4.clear

 public void clear() {
       
        size = 0;//只要控制相应的size的大小就是可以控制相应的异常的抛出
        /*
        不能够说它是进行内存的浪费,因为后面还是可能会用,不用销毁相应的内存过程,就是可以不用使用相应的清除掉内存的过程.不用进行清除就是可以不用浪费相应的内存.

         */

    }
  • 斐波那契数的另一种写法

    public static int fib3(int n)
    	{
    		if(n <= 1) return n;
    		int first = 0;
    		int second = 1;
    		for(int i= 0;i < n-1; i++)//或者这里的条件是进行相应的while(n-- > 1),这里的含义就是进行相应的n-1的一个执行过程
    		{
    			second += first;
    			first = second - first;
    		}
    		return second;
    	}
  • 斐波那契数的另一种写法
  •  一个数组new出来之后,一般而言,new出来一个对象就是进行相对空间进行一个申请空间的过程。一般而言,在后面我们是要进行一个delete的一个过程,但是在相应的java之中,是不需要这个过程的,直接存在一个垃圾回收的过程。

5.add

    /**
     * 添加元素到尾部
     * @param element
     * 一开始的时候相当于size是,依次进行添加的过程
     */
    public void add(int element) {
        elements[size++] = element;
        /*
        一开始初始化是进行相应的数组之中的元素是10个,后面才是进行相应的扩容的过程
         */
    }

6.打印

  • 这里System.out.println()方法是直接进行调用相应的toString的方式进行打印,我们希望将立面的元素进行打印出来,这里的调用方式是如下所示:
 @Override
    public String toString() {
        //size = 3,[99,88,77]
        StringBuilder string = new StringBuilder();//这里的StringBuilder是需要转换成为相应的String格式
        string.append("size=").append(size).append(",[");
        for (int i = 0; i< size;i++ )
        {


         /* string.append(elements[i]);
         if (i!=size-1)//判断是不是最后一个元素,如果要不是最后一个元素,才是可以进行相应的拼接过程使用。
            {
                string.append(",");
            }
          */
            if (i!=0)//选择这种方式的原因是他不进行相应的减法运算
            {
                string.append(",");
            }
            string.append(elements[i]);
        }

        string.append("]");

        return string.toString();
    }
  • 流程

7.remove

  • 删除操作进行的时候,需要注意最后一个元素的过程使用。

最后一个元素是不需要进行处理的,防止后面是需要进行使用的。并且上面的字节是连续的,因此,使用的过程之中只能是进行相应的元素挪动的过程。

 比如我们这里是需要进行挪动的是index为3,那么相应的一个挪动范围是index+1 - size-1

  • 具体的挪动的代码是如下所示:
    /**
         * 删除index位置的元素
         * @param index
         * @return
         */
        public int remove(int index) {
            //首先是对于相应的index进行判断
            if(index<0 || index>=size){
                throw new IndexOutOfBoundsException("Index超出了范围");
            }
            
            //记录原始的old元素是什么
            int old = elements[index];
            
            //进行相应的一个挪动过程使用
            for(int i = index+1;i<size-1;i++)
            {
                elements[i - 1] = elements[i];
    
            }
            size--;
            return old;
        }

8.add_index

  • 注意挪动的时候,首先进行挪动的是最后面的先进行挪动,注意顺序问题。
  •  这里的使用过程是如下所示:
 /**
     * 在index位置插入一个元素
     * @param index
     * @param element
     */
    public void add(int index, int element) {
        //首先是需要进行相应的index的判断.这里的索引的判断是需要进行相应的注意的,是与别的判断是不同的.
        //这里的index可以是等于相应的size的大小的.
        if(index < 0 || index > size)
        {
            throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size);//这里是直接使用抛出异常的方式进行相应的处理
            //抛出异常的情况就是相应的颜色编程红色
            /* 抛出的异常是这样的情况,直接点击相应的位置就是可以得到相应的错误的位置
            Exception in thread "main" java.lang.IndexOutOfBoundsException: Index:-10,Size:0
	        at ArrayList.get(ArrayList.java:74)
	        at Main.main(Main.java:11)
             */
        }

        //挪动的范围 从相应的index - size-1,一定要注意相应的顺序.
        for(int i = size-1;i >= index;i--)
        {
            elements[i+1] = elements[i];
        }
        elements[index] = element;
        size++;


    }
  • 接下来是进行相应的一个封装异常的过程使用,将上述异常出现的情况直接进行了相应的封装,免去了相应的处理的过程使用。

        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);
            }
        }
        
  • 将相应的add方法进行相应的修改

        /**
         * 添加元素到尾部
         * @param element
         * 一开始的时候相当于size是,依次进行添加的过程
         */
        public void add(int element) {
           // elements[size++] = element;
            /*
            一开始初始化是进行相应的数组之中的元素是10个,后面才是进行相应的扩容的过程
             */
            add(size,element);
        }

9.接口测试

  • 在开发之中,我们会遇到各种的测试,一般是采用打印的方式。另一种方式是进行抛出异常的方式进行。
  • 使用声明一个Asserts工具方式,相应的代码段是如下所示:
    public class Asserts {
    	public static void test(boolean value) {
    		try {
    			if (!value) throw new Exception("测试未通过");
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    }

    测试的主函数如下所示:

10.动态扩容

  • 进行扩容的方式,如下所示:这里是不可以new一个新的数组在后面接着使用,原因是因为new出来的内存空间是随机的吧,不能够保证内存是在它后面的,只能是新申请一个空间。原来的内存空间是没有使用的,需要将原来的内存空间自动进行垃圾回收。
  • 创建一个新的方法,是进行扩容的方法.
     /**
         * 保证容量
         */
        public void ensureCapacity(int capacity)
        {
            int oldCapacity = elements.length;
            if(oldCapacity >= capacity) return;
            
            int newCapacity = oldCapacity + (oldCapacity>>1); //这里的含义是原始的容量变成原来的1.5倍
            int[] newElements = new int[newCapacity];//新声明一个数组
            for(int i = 0 ;i<size;i++)
            {
                newElements[i] = elements[i];
            }
            elements = newElements;//原来的elements指向新的进行刚刚创建的elements
            
            System.out.println(oldCapacity + "扩容为" + newCapacity);
        }
  • 原始的函数之中需要进行改变.

11.泛型

  • 我们在使用的过程之中,希望可以放入一些别的类型的.这里我们应当注意的是,泛型只能是放入相应的一个对象类型的,不能够放入基本数据类型,比如就是说:如果要是放入相应的int类型就是需要进行更改为Integer(包装类).
  • 在使用泛型编写的过程之中,发现出现了这个问题.如下所示

    我们知道,所有类型的父类都是Object类型,但是Object类型是不可以进行继承自己的。因此,我们这里使用一个强制类型转换方式进行。如下所示:
  •  此刻的ArrayList是如下所示:
    import com.sun.java.swing.plaf.windows.WindowsDesktopIconUI;
    
    import java.net.BindException;
    import java.util.Arrays;
    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(int capaticy) {
            capaticy = (capaticy < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capaticy;
            elements = (E[]) new java.lang.Object[capaticy];
        }
    
        public ArrayList() {
            this(DEFAULT_CAPACITY);
        }
    
        /**
         * 清除所有元素
         */
        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) {  // 1
                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; // n
                }
            }
            return ELEMENT_NOT_FOUND;
        }
    
    //	public int indexOf2(E element) {
    //		for (int i = 0; i < size; i++) {
    //			if (valEquals(element, elements[i])) return i; // 2n
    //		}
    //		return ELEMENT_NOT_FOUND;
    //	}
    //
    //	private boolean valEquals(Object v1, Object v2) {
    //		return v1 == null ? v2 == null : v1.equals(v2);
    //	}
    
        /**
         * 保证要有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;
    
            System.out.println(oldCapacity + "扩容为" + newCapacity);
        }
    
        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() {
            // size=3, [99, 88, 77]
            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]);
    
    //			if (i != size - 1) {
    //				string.append(", ");
    //			}
            }
            string.append("]");
            return string.toString();
        }
    }

12.对象数组

  • 上面代码进行更改之后,出现一个内存管理.为什么要放入地址,因为如果要是放入的是相应的值的话,会产生一系列的问题,对象的大小是不确定的,放入空间会由对象的大小决定,会产生一定的问题.
  • 主函数的测试代码图如下所示:

13.clear_细节

  • 对象的销毁过程,如上图所示,将地址位置变成null,就是进行一个断线的过程,数组的内存和对象的内存是不一样的.
  • 这里的clear为什么要进行调整的过程,之前为什么不进行调整,以前的时候直接将size变成0,原来的内存是可能使用的.这里的意思就是clear使用过之后,后面还是会进行add的过程.  而 本处如果要是使用对于对象的地址值时,是将原来的地址值进行一个覆盖的过程,然后将原来的对象进行清空.拿下面的代码进行讲解:
  • clear方法是如下所示: 
   /**
     * 清除所有元素
     */
    public void clear() {
        /*
        这样的含义就是数组之中都是null,但是数组内存是存在的,不能够进行销毁,
        不能够使用element=null,相当于第一根线断了,不能够继续使用.
        能循环利用的留下,不能够循环利用的滚蛋.
         */
        for (int i = 0; i < size; i++) {
            elements[i] = null;
        }
        size = 0;
    }
  • 如何证明最终的Person对象是挂了的???在Person代码之中添加如下所示,就是当地址指针不再指向Person对象的时候,调用如下的析构函数,说明对象挂了.
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("Person对象挂了");
        }

    我们发现在使用clear之后,并没有出现垃圾回收,因此,在clear之后可以使用一个提醒机制.相应的代码是如下所示:System.gc();//这里的含义就是对于垃圾回收之后,直接调用上面所谓的"对象挂了"的函数.

14.remove_细节

  • 处理的过程中,将此处的地址值进行删除,后面的地址值依次向前移动.最后的地址值设置为null.
  • remove对应的代码是如下所示:
    /**
     * 删除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;
    }

15.equals

  • 在原始的Person之中添加重写equals方法,相应的代码是如下所示:
     @Override//使用重写的方式进行equals方式的实现,一般对于对象是不进行地址的比较的,都是使用对象之中相应的属性进行比较,如果要是元素之中的两个元素是一样的就是可以判断是一致的.
        public boolean equals(Object o) {
         Person person = (Person)o;
         return this.age == person.age;
        }

    如果要是不进行重写equals方法就是直接进行使用地址比较的方式.如果不是对象是Integer,相当于重写equals,内部直接进行比较相应的数值是否相等,并不是进行地址的比较.

  • IndexOf()方式代码是如下所示:

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

16.null值处理

  • 如果null值是可以存的,add代码是不用进行改正的.如果要是不能够存取空null值,这里在add方法之中就是需要进行一个添加操作.

        添加上面所示这一句就是可以的. 

  • 如果传入对象是null的,直接查询第一个null的位置.因此,Index函数是需要进行分开处理的.
      /**
         * 查看元素的索引
         * @param element
         * @return
         */
        public int indexOf(E element) {
            if (element == null) {  // 1
                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; // n
                }
            }
            return ELEMENT_NOT_FOUND;
        }
    

17.ArrayList源码分析

  • 直接在IDEA页面上敲击java.util.ArrayList<E>点击进入源码之中进行查看.代码之中modCount是在迭代器之中才是有用的,在一般的过程之中是没有那么大的作用.
  • == 是进行比较的是内存地址是否相等,一般我们是使用equals进行重写复用的方式进行.
  • 重写的改写
        @Override//使用重写的方式进行equals方式的实现,一般对于对象是不进行地址的比较的,都是使用对象之中相应的属性进行比较,如果要是元素之中的两个元素是一样的就是可以判断是一致的.
        public boolean equals(Object o) {
            if (o == null) return false;
            if (o instanceof Person) {
                Person person = (Person) o;
                return this.age == person.age;
            }
            return false;//这里是比较的两种类型是不同的.
    
        }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值