zoukankan      html  css  js  c++  java
  • 工作之余第二篇(看源码自己实现ArrayList和LinkList)

    先看源码:

    首先看构造器,构造器有三种,一种直接给定初始长度的,如下代码

    public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) { //判断长度是否大于0 如果大于0 则直接将长度给他
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) {  //如果等于0,定义一个空数组实例
                this.elementData = EMPTY_ELEMENTDATA;
            } else { //否则就抛出异常 为负数
                throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
            }
        }

     第二种构造函数是不给定长度的,如下代码

    public ArrayList() {  //直接给定一个空数组实例
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }

     第三种构造函数,直接包含指定元素的构造器,放一个集合,如下

    public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();  //集合直接转换为数组
            if ((size = elementData.length) != 0) {  //在数组长度不为0的情况下
                // c.toArray might (incorrectly) not return Object[] (see 6260652)  在c.toArray之后返回的可能不是一个数组是需要进行下一步操作
                if (elementData.getClass() != Object[].class)
              //通过copyOf将其转换为数组 elementData
    = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array.如果为0只直接替换一个空数组 this.elementData = EMPTY_ELEMENTDATA; } }

    然后就是开始看添加了。

    public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!  标红的方式是要确定定义数组的容量是否足够,接下来有分析
            elementData[size++] = e;  //size是定义的全局变量 没进行一次赋值 size++之后会改变哟,每增加一个值的时候就加1,第一次赋值是size是0,赋值完成后加1,第二次size就是1了。
            return true;
        }
    private void ensureCapacityInternal(int minCapacity) {  //这个minCapacity代表上边标红方法中的size+1,这个需要记住哟
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); //接下来先介绍红色方法,介绍完后在介绍绿色方法。
        }
    
    
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};  //这个就是一个空数组实例
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //判断如果是一个空数组实例的话 就拿数组默认长度值(10)来和size+1的值作比较,谁大取谁
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;  //不为空则直接返回size+1的值
        }

    接下来介绍绿色方法了。

    private void ensureExplicitCapacity(int minCapacity) {
            modCount++;  //修改次数,具体用途还不清楚,下边有解释
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)  //判断size+1是否大于数组缓冲区的长度,如果大于则进行扩容了
                grow(minCapacity);  //grow这个方法就要开始扩容了
        }
    private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;  //首先把最初的数组长度暂存
            int newCapacity = oldCapacity + (oldCapacity >> 1);  //最初的数组长度正常一半的长度赋给newCapacity
            if (newCapacity - minCapacity < 0)  //再次进行判断 如果长度还是不够
                newCapacity = minCapacity;  //那么就直接将长度给size+1这个值
            if (newCapacity - MAX_ARRAY_SIZE > 0)  //数组也有默认的最大值,如果超出就要进行处理
                newCapacity = hugeCapacity(minCapacity);  //红色方法则是对于超出默认最大值的处理
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);  //在确定新数组长度之后就需要将原来已存在数组复制到新数组中,从而保证扩容后不会丢失原来的数据
        }
    
    
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;  //这个值就是数组默认定义的最大值,下边有详细解释
    private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ?  //检查是否超出数组最大值,如果超出了就将最大值给他,没有超出则给减去8之后的最大值。
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }

    以上就是添加方法了。带参数的其他添加跟这个类似,不过添加了下标验证。如下

    public void add(int index, E element) {
            rangeCheckForAdd(index);  //验证数组下标是否越界
    
            ensureCapacityInternal(size + 1);  // Increments modCount!!  //检查是否需要扩容
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
            elementData[index] = element;
            size++;
        }

    addAll同上,先判断是否扩容以及扩容的大小,然后进行arraycopy

    public boolean addAll(Collection<? extends E> c) {
            Object[] a = c.toArray();
            int numNew = a.length;
            ensureCapacityInternal(size + numNew);  // Increments modCount 先进行扩容
            System.arraycopy(a, 0, elementData, size, numNew);
            size += numNew;
            return numNew != 0;
        }

    获取元素:

    public E get(int index) {
            rangeCheck(index);  //先检查下标是否在数组下标范围内,然后就取值
    
            return elementData(index);
        }
    private void rangeCheck(int index) {  //下标超出 报异常
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }

    删除元素:

    public E remove(int index) {
            rangeCheck(index);  //检查下标
    
            modCount++;
            E oldValue = elementData(index);  //先将原来的数组保存
    
            int numMoved = size - index - 1;  //需要移动的值
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // clear to let GC do its work
    
            return oldValue;
        }

    以下是自己实现的代码,比较粗糙

    package com.interact.test.webService;
    
    import java.util.Arrays;
    
    public class ArrayListTest {
    
        Object [] arr = new Object[default_size];
        private static int size = 0;
        private static int default_size = 10;
    
        public int getSize(){
            return size;
        }
    
        //获取元素
        public Object getIndex(int index){
            //需要校验index的值,是否大于0并且小于最大长度
            if(index >= 0 && index < arr.length){
                return arr[index];
            }else{
                return "数组下标越界";
            }
        }
    
        //添加元素
        public void add(Object o){
            if(size >= default_size){  //默认长度不够的时候 进行扩容
                default_size += default_size/2;  //扩容到原来长度的1.5倍,就是就是加上原来长度的一半
            }
            arr = Arrays.copyOf(arr,default_size);  //扩容之后通过底层的复制数组的方法,将之前保存的数据放到扩容之后的数组中
            arr[size++] = o;
        }
    
        public void remove(int index){
            Object [] temp = new Object[size-1];
            if(index >= 0 && index <= size){  //先进行校验
                for (int i = 0; i < index; i++) {
                    temp[i] = arr[i];
                }
                for (int i = index + 1; i < size ; i++) {
                    temp[i-1] = arr[i];
                }
            }else{
                System.out.println("下标越界");
            }
            arr = Arrays.copyOf(temp,size-1);
            size--;
        }
    
        public static void main(String [] args){
            ArrayListTest arrayListTest = new ArrayListTest();
            for (int i = 0; i < 16; i++) {
                arrayListTest.add(i);
            }
            arrayListTest.remove(3);
            for (int i = 0; i < arrayListTest.getSize(); i++) {
                System.out.println(arrayListTest.getIndex(i));
            }
        }
    }

     native关键字的作用

    告诉编译器调用的方法是外部定义的,一般是指C写的底层方法。主要意思是java调用java以外的方法的标示。

    毫秒以下的单位以及进度

    微秒,纳秒,皮秒跟毫秒一样进度为1000,1000毫秒=1秒,1000微秒=1毫秒,1000皮秒=1微秒。

    OutOfMemoryError

    内存溢出,给定长度满足不了实际长度时就会导致内存溢出。

    为什么有时候一下异常throw出去了,但是却没有在函数头部声明

    异常分两种,Checked异常和Runtime异常,在使用checked异常时需要在函数头部声明,而Runtime异常则不需要,主要当发生非运行时异常你就需要try catch进行捕获,告诉你哪里出问题了,从而进行修改。而运行时异常,则是在运行过程中才可能出现的异常,编辑器无法帮助你定位,所以即使你声明throw,然后再由调用者try catch捕获也没有用,所以就不再需要在函数头部就行声明。

    最大数组大小定义为Integer.MAX_VALUE - 8,减去8的原因

    因为要存储2^31 = 2,147,483,648这些长度的数组需要8bytes,所以给定最大长度为Integer.MAX_VALUE - 8。

    java中的>> <<的作用

    位运算符,转换为二进制,左位移运算符,右位移运算符,三个>>>的跟两个的一样。

    java的访问修饰符

    public      同类同包不同包的子类不同包的非子类可以使用

     protectd 同类同包不同包的子类可以使用

    default   同类同包中可以使用

    private   只有同类中可以使用

    modCount

    修改次数,找了找资料,理解之后的大概意思就是,ArrayList不是线程安全的,使用迭代器时,其他线程可能会同时对其进行修改,这时候就会报异常,ConcurrentModificationException,也就是所谓fail-fast策略。而这个策略实现的方式就是,modCount每次修改就会加1,而每次初始化就将这个值暂存起来,放到expectedModCount这个变量中,在迭代过程中会进行比较,如果一样则没问题,不相同则报异常。注意到 modCount 声明为 volatile,保证线程之间修改的可见性。

    volatile

    编译器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份,并且防止拿到的数据是编译器优化之后的代码。其实就是拿到最真实实时的数据。

    泛型的实现原理

    主要是类型擦除,在编译成字节码文件时,字节码文件是不包含泛型中的类型信息的,会将类型擦除掉。

    还需要理解,参考链接:https://blog.csdn.net/aoxida/article/details/50774459

    linkList的实现(已完善)

    package com.interact.test.webService;
    
    public class LinkListTest {
    
        private static int size = 0;
        private static Node first;
        private static Node last;
        private static Node[] objects = new Node[100];  //用来存储数据
    
        //定义一个静态内部类
        private static class Node {
            private String current;
            private Node next;
            private Node prev;
    
            Node(String current, Node next, Node prev) {
                this.current = current;
                this.next = next;
                this.prev = prev;
            }
        }
    
        public void add(Object o) {
            Node temp = last;
            Node newNode = new Node(o.toString(), null, temp);
            last = newNode;
            if (temp == null) {
                first = newNode;
            } else {
                temp.next = newNode;
            }
            objects[size++] = newNode;  //用数组存数据是否有问题
        }
    
        public Object getIndex(int index) {  //可能有问题
            if(index < (size >> 1)){  //如果index小于总长度的一半 则从第一个开始找,从前往后推
                Node node = first;
                for (int i = 0; i < index; i++) {
                    node = objects[i].next;
                }
                return node.current;
            }else{ //如果index大于总长度的一半 则从最后一个开始找,从后往前推
                Node node = last;
                for (int i = size-1; i > index; i--) {  //这里需要注意 当寻找的下标大于总长度一半的时候 不再是从0开始,而且i开始逐渐减去1
                    node = objects[i].prev;
                }
                return node.current;
            }
        }
    
        public void linkFirst(String element){  //将该值作为第一个节点
            Node node = first;  //先将当前第一个值暂时保存
            Node newNode = new Node(element,node,null);  //通过Node构造器获取一个新的节点
            first = newNode; //然后将新的节点给全局变量第一个节点first
            if(node == null){  //如果node是空的,也就说明当前first是空的,说明当前linkList没有值
                last = newNode;  //此时第一个节点是刚创建的新节点,最后一个也是这个新节点
            }else{
                node.prev = newNode;  //如果不是空的,那么当前第一个节点的前一个节点应该是null,此时就需要将这个新的节点设置为之前第一个节点的前节点
            }
            size++;
        }
    
        public void remove(Object o) {
            for (int i = 0; i < size; i++) {
                if (objects[i].current.equals(o)) {
                    final Node prev = objects[i].prev;  //前一个节点
                    final Node next = objects[i].next;
    
                    prev.next = next;  //将前一个节点的下一个节点设置成当前节点的下一个节点,从而达到连接后面节点的效果
                    next.prev = prev;  //同样将要移除节点的前一个节点设置为移除节点的前一个节点。
    
                    objects[i].current = null; //进行垃圾回收
                    objects[i] = null;
                    size--;
    
                }
            }
        }
    
        public static void main(String[] args) {
            LinkListTest listTest = new LinkListTest();
            listTest.add("111");
            listTest.add("222");
            listTest.add("333");
            listTest.add("444");
            listTest.add("555");
            listTest.add("666");
            System.out.println(listTest.getIndex(1));
            System.out.println(listTest.getIndex(4));
            listTest.remove("222");
            listTest.linkFirst("999");
            System.out.println(listTest.getIndex(1));
            System.out.println(listTest.getIndex(0));
        }
    
    }

    vue组件:主要是拆分代码,减少vue实例的代码量,方便ui的重用。

         vue创建组件的方式,Vue.component(组件名称,组件构造器),跟创建Vue对象相似,同样有data、methods、watch等,但是组件的data必须是一个函数,而且没有el获取根实例。

    如下代码:

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <title></title>
            <script src="vue.js" type="text/javascript"></script>
        </head>
        <body>
            <div id="app">
                    <my-com></my-com>
            </div>
            <script type="text/javascript">
                Vue.component("myCom",{
                    //data必须是一个函数
                    data :function(){
                        return{
                            count:0    
                        }
                    },
                    template:"<button v-on:click='count++'>点击了{{count}}次</button>"
                })
                var vm = new Vue({
                    el:"#app"
                })
            </script>
        </body>
    </html>

    还有通过extend的几种方式来实现的

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <title></title>
            <script src="vue.js"></script>
        </head>
        <body>
            <div id="app">
                <my-com></my-com>
                <mycom2></mycom2>
                <mycom3></mycom3>
                <mycom4></mycom4>
            </div>
            <template id="temp">
                <div >
                    <h1>第四种组件的实现方式</h1>
                    <h2>这种写法的好处是能够提示信息</h2>
                </div>
            </template>
            <script>
                var com1 = Vue.extend({
                    template:'<h1>组件的第一种实现方式</h1>'
                })
                Vue.component('myCom',com1)
                Vue.component('mycom2',Vue.extend({
                    template:'<h2>组件的第二种实现方式</h2>'
                }))
                Vue.component('mycom3',{
                    template:'<h3>组件的第三种实现方式</h3>'
                })
                Vue.component('mycom4',{
                    template:"#temp"
                })
                var vm = new Vue({
                    el:"#app"
                })
            </script>
        </body>
    </html>

    组件是通过prop来进行数据传递的。

  • 相关阅读:
    机房收费系统重构(三)—工厂+反射+DAL
    机房收费系统重构(二)—菜鸟入门
    机房收费系统重构(—)—小试牛刀
    vb.net机房收费登录功能
    设计模式总结之结构型模式
    设计模式总结之创建型模式
    大话设计之桥接模式
    大话设计之单例模式
    大话设计之适配器模式
    大话设计之抽象工厂模式
  • 原文地址:https://www.cnblogs.com/qcq0703/p/11986084.html
Copyright © 2011-2022 走看看