Java基础篇:数组

Java基础篇:数组

一、数组基础知识:

1、什么是数组:

数组,就是一个容器,存放着同一种类型的数据的集合。数组的好处就是可以自动给数组中的元素从0开始编号,方便操作这些元素。数组属于引用变量,并且数组的长度是固定的,数组的使用有四个步骤,声明数组,分配空间,赋值,处理。

2、一维数组的声明与创建:

元素类型[] 数组名 = new 元素类型[元素个数或数组长度];

元素类型[] 数组名 = new 元素类型[]{元素,元素,……};

元素类型[] 数组名 = {元素,元素,……};

示例:int[] arr = new int[5];

int[] arr = new int[]{3,5,1,7};

int[] arr = {3,5,1,7};

注意:给数组分配空间时,必须指定数组能够存储的元素个数来确定数组大小。创建数组之后不能修改数组的大小。可以使用length 属性获取数组的大小。

3、数组的初始化:

int[] arr = new int[5];//创建数组第一种方式,此时默认值都是为0
arr[0] = 1;//数组的初始化
arr[1] = 2;//数组的初始化

int[] arr = new int[]{3,5,1,7};//第二种方式:创建并初始化数组

int[] arr = {3,5,1,7};//第三种方式:创建并初始化数组

int[] arr;
arr = {1,2,3,4,5};//这种方式是错误的

4、数组的常见异常:

(1)ArrayIndexOutOfBoundsException 索引值越界。

原因:访问了不存在的索引值:

public static void main(String[] args) {

    int[] x = { 1, 2, 3 };
    System.out.println(x[3]);//数组角标从0开始
}

(2)NullPointerException 空指针异常:

原因: 引用类型变量没有指向任何对象,而访问了对象的属性或者是调用了对象的方法。

public static void main(String[] args) {
    
    int[] x = { 1, 2, 3 };
    x = null;
    System.out.println(x[1]);
}

Java基础篇:数组

5、数组内存分析:

Java基础篇:数组

Java基础篇:数组

6、Arrays的使用:

遍历: toString()    将数组的元素以字符串的形式返回

排序: sort()        将数组按照升序排列

查找: binarySearch()在指定数组中查找指定元素,返回元素的索引,如果没有找到返回(-插入点-1) 注意:使用查找的功能的时候,数组一定要先排序。

public static void main(String[] args) {
	int[] array = new int[]{10,30,50,40,60};
	System.out.println(Arrays.toString(array));
		
	Arrays.sort(array);
	System.out.println(Arrays.toString(array));
		
	System.out.println("最小值:"+array[0]+";最大值:"+array[array.length-1]);
		
	int result = Arrays.binarySearch(array, 40);
	System.out.println("目标值的角标:"+result);
}

7、二维数组:

(1)二维数组定义:数组类型[][] 数组名 = new 数组类型[一维数组的个数][每一个一维数组中元素的个数];

public static void main(String[] args) {	
		int[][] a = new int[3][4];
		System.out.println(a);//[[I@15db9742
		System.out.println(a.length);//获取二维数组中存储的一维数组的个数3
		System.out.println(a[0]);//获取的是二维数组中第一个一维数组:[I@6d06d69c
		System.out.println(a[0].length);//第一个一维数组的长度:4
		System.out.println(Arrays.toString(a[0]));//默认值都是0:[0, 0, 0, 0]	
}

疑问: 为什么a.length = 3, a[0].length = 4?

Java基础篇:数组

(2)二维数组的初始化:

//静态初始化:
int[][] b = new int[][]{
  {11,12,13,14},{21,22,23,24},{31,32,33,34}};
//动态初始化:
int[][] c = new int[3][4];
int value = 0;
for(int i = 0;i<c.length;i++){
    for(int j = 0;j<c[i].length;j++){
		c[i][j] = ++value;
	}
}

 

二、数组提高:

1、数组的特点:

在java中有很多方式来存储一列数据,而且在操作上面比数组方便的多?但为什么我们还需要使用数组,而不是替代它呢?

数组与其他种类的容器之间的区别有三个方面呢:效率、类型和保存基本类型的能力。在Java中,数组是一种效率最高的存储和随机访问对象引用序列的方式。

数组确实是没有List、Set这些集合使用方便,但是在某些方面数组还是存在一些优势的,例如:速度,而且集合类的底层也都是通过数组来实现的。

--------这是ArrayList的add()------
    public boolean add(E e) {
    ensureCapacity(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
    }

(1)例子:数组和list求和操作的比较:

Long time1 = System.currentTimeMillis();
        for(int i = 0 ; i < 100000000 ;i++){
            sum += arrays[i%10];
        }
        Long time2 = System.currentTimeMillis();
        System.out.println("数组求和所花费时间:" + (time2 - time1) + "毫秒");
        Long time3 = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            sum  += list.get(i%10);
        }
        Long time4 = System.currentTimeMillis();
        System.out.println("List求和所花费时间:" + (time4 - time3) + "毫秒");
--------------Output:
数组求和所花费时间:696毫秒
List求和所花费时间:3498毫秒

从上面的时间消耗上面来说数组对于基本类型的求和计算的速度是集合的5倍左右。其实在list集合中,求和当中有一个致命的动作:list.get(i)。这个动作是进行拆箱动作,Integer对象通过intValue方法自动转换成一个int基本类型,在这里就产生了不必要的性能消耗。

所以在性能要求较高的场景中请优先考虑数组。

2、变长数组:

数组是定长的,一旦初始化声明后是不可以改变长度的。一旦初始化声明后是不可改变长度的。这对我们在实际开发中是非常不方便的,聪明的我们肯定是可以找到方法来实现的。

那么如何来实现变长数组呢?我们可以利用List集合add方法里面的扩容思路来模拟实现。下面是ArrayList的扩容方法:

public void ensureCapacity(int minCapacity) {
        modCount++;  
        /**
         * 若当前需要的长度超过数组长度时进行扩容处理
         */
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        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);
    }

它的思路是将原始数组拷贝到新数组中,新数组是原始数组长度的1.5倍。所以模拟的数组扩容代码如下:

public class ArrayUtils {
    /**
     * @desc 对数组进行扩容
     * @param <T>
     * @param datas 原始数组
     * @param newLen 扩容大小
     * @return T[]
     */
    public static <T> T[] expandCapacity(T[] datas,int newLen){
        newLen = newLen < 0 ? datas.length :datas.length + newLen;   
        //生成一个新的数组
        return Arrays.copyOf(datas, newLen);
    }
    
    /**
     * @desc 对数组进行扩容处理,1.5倍
     * @param <T>
     * @param datas  原始数组
     * @return T[]
     */
    public static <T> T[] expandCapacity(T[] datas){
        int newLen = (datas.length * 3) / 2;      //扩容原始数组的1.5倍
        //生成一个新的数组
        return Arrays.copyOf(datas, newLen);
    }
    
    /**
     * @desc 对数组进行扩容处理,
     * @param <T>
     * @param datas 原始数组
     * @param mulitiple 扩容的倍数
     * @return T[]
     */
    public static <T> T[] expandCapacityMul(T[] datas,int mulitiple){
        mulitiple = mulitiple < 0 ? 1 : mulitiple;
        int newLen = datas.length * mulitiple;
        return Arrays.copyOf(datas,newLen );
    }
}

通过这种迂回的方式我们可以实现数组的扩容。因此在项目中如果确实需要变长的数据集,数组也是在考虑范围之内的,我们不能因为他是固定长度而排斥他!

3、数组复制问题:

前在做集合拷贝的时候由于集合没有拷贝的方法,所以一个一个的复制是非常麻烦的,所以我就干脆使用List.toArray()方法转换成数组然后再通过Arrays.copyOf拷贝,在转换成集合,个人觉得非常方便,殊不知我已经陷入了其中的陷进!我们知道若数组元素为对象,则数组里面数据是对象引用。

public class Test {
    public static void main(String[] args) {
        Person person_01 = new Person("chenssy_01");
        
        Person[] persons1 = new Person[]{person_01};
        Person[] persons2 = Arrays.copyOf(persons1,persons1.length);
        
        System.out.println("数组persons1:");
        display(persons1);
        System.out.println("---------------------");
        System.out.println("数组persons2:");
        display(persons2);
        //改变其值
        persons2[0].setName("chessy_02");
        System.out.println("------------改变其值后------------");
        System.out.println("数组persons1:");
        display(persons1);
        System.out.println("---------------------");
        System.out.println("数组persons2:");
        display(persons2);
    }
    public static void display(Person[] persons){
        for(Person person : persons){
            System.out.println(person.toString());
        }
    }
}
-------------Output:
数组persons1:
姓名是:chenssy_01
---------------------
数组persons2:
姓名是:chenssy_01
------------改变其值后------------
数组persons1:
姓名是:chessy_02
---------------------
数组persons2:
姓名是:chessy_02

从结果中发现,persons1中的值也发生了改变,这是典型的浅拷贝问题。所以通过Arrays.copyOf()方法产生的数组是一个浅拷贝。同时数组的clone()方法也是,集合的clone()方法也是,所以我们在使用拷贝方法的同时一定要注意浅拷贝这问题。

4、数组转换为List注意的地方:

(1)我们经常需要使用到Arrays这个工具的asList()方法将其转换成列表。方便是方便,但是有时候会出现莫名其妙的问题。如下:

public static void main(String[] args) {
        int[] datas = new int[]{1,2,3,4,5};
        List list = Arrays.asList(datas);
        System.out.println(list.size());
    }
------------Output:
1

 结果是1,是的你没有看错, 结果就是1。但是为什么会是1而不是5呢?先看asList()的源码:

public static <T> List<T> asList(T... a) {
        return new ArrayList<T>(a);
    }

注意这个参数:T…a,这个参数是一个泛型的变长参数,我们知道基本数据类型是不可能泛型化的,也是就说8个基本数据类型是不可作为泛型参数的,但是为什么编译器没有报错呢?这是因为在java中,数组会当做一个对象来处理,它是可以泛型的,所以我们的程序是把一个int型的数组作为了T的类型,所以在转换之后List中就只会存在一个类型为int数组的元素了。所以我们这样的程序System.out.println(datas.equals(list.get(0)));输出结果肯定是true。当然如果将int改为Integer,则长度就会变成5了。

(2)我们再看下面程序:

enum Week{Sum,Mon,Tue,Web,Thu,Fri,Sat}
    public static void main(String[] args) {
        Week[] weeks = {Week.Sum,Week.Mon,Week.Tue,Week.Web,Week.Thu,Week.Fri};
        List<Week> list = Arrays.asList(weeks);
        list.add(Week.Sat);
    }

这个程序非常简单,就是讲一个数组转换成list,然后改变集合中值,但是运行呢?

Exception in thread "main" java.lang.UnsupportedOperationException
    at java.util.AbstractList.add(AbstractList.java:131)
    at java.util.AbstractList.add(AbstractList.java:91)
    at com.array.Test.main(Test.java:18)

编译没错,但是运行竟然出现了异常错误!UnsupportedOperationException ,当不支持请求的操作时,就会抛出该异常。从某种程度上来说就是不支持add方法,我们知道这是不可能的!什么原因引起这个异常呢?先看asList()的源代码:

public static <T> List<T> asList(T... a) {
        return new ArrayList<T>(a);
    }

这里是直接返回一个ArrayList对象返回,但是注意这个ArrayList并不是java.util.ArrayList,而是Arrays工具类的一个内之类:

private static class ArrayList<E> extends AbstractList<E>
    implements RandomAccess, java.io.Serializable{
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;
        ArrayList(E[] array) {
            if (array==null)
                throw new NullPointerException();
        a = array;
    }
       /** 省略方法 **/
    }

但是这个内部类并没有提供add()方法,那么查看父类:

public boolean add(E e) {
    add(size(), e);
    return true;
    }
    public void add(int index, E element) {
    throw new UnsupportedOperationException();
    }

这里父类仅仅只是提供了方法,方法的具体实现却没有,所以具体的实现需要子类自己来提供,但是非常遗憾这个内部类ArrayList并没有提高add的实现方法。在ArrayList中,它主要提供了如下几个方法:

       1、size:元素数量

       2、toArray:转换为数组,实现了数组的浅拷贝。

       3、get:获得指定元素。

       4、contains:是否包含某元素。

       所以综上所述,asList返回的是一个长度不可变的列表。数组是多长,转换成的列表是多长,我们是无法通过add、remove来增加或者减少其长度的。

 

参考文章:

https://blog.csdn.net/oguro/article/details/52971487

https://blog.csdn.net/chenssy/article/details/17732815

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/114671.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号