标签:final 自己 下标 lca flow 有一个 情况 turn inter
来,进来的小伙伴们,我们认识一下。
我是俗世游子,在外流浪多年的Java程序猿
在Java中,存在两种存储数据的容器:
我们首先来了解下数组
首先,我们要明白:数组是相同类型数据的有序集合。
我猜一定有人会说,Object的数组可以存字符串,数字等等,你说的不对
Object: 在我面前,你们都是弟弟
其中,我们将存储在数组中的数据称之为:元素,
元素在数组中存储的位置称之为下标。
我们可以通过下标来得到所对应的元素,反过来也一样
我们都知道,声明对象就是在内存中开辟一块空间,而声明一个数组就是在内存中开辟一串连续的空间:
在Java中,任何的数据类型都可以定义对象,比如:
String[] arrs = new String[8];
arrs[0] = "元素1";
System.out.println(arrs[0]); // 元素1
int[] intArrs = {1,2,3,4,5,6,7};
System.out.println(intArrs[0]); // 元素1
内存结构如上图所示
简单一点说,使用数组可以分为3步:
我们来看下下面这段代码:
String[] arrs = new String[8];
arrs[0] = "元素1";
arrs[8] = "元素8";
System.out.println(arrs[0]);
/**
* Java.lang.ArrayIndexOutOfBoundsException: 8
*/
需要注意的一点是:在任何的编程语言中,如果需要通过下标来得到指定元素,那么这个下标的值一定是从0开始的,且可取值的最大范围为:指定长度 - 1。
上面代码中:我们最大可以操作的范围为 7
而且,数组在声明的时候有分配内存空间的操作,那么如果我们指定下标超过了数组指定的长度,那么我们的程序就会抛出异常:
上面介绍了数组的简单使用,下面我们再来重点看一个操作:数组拷贝。比如:
String[] arrs = {"元素1","元素2","元素3","元素4","元素5","元素6","元素7"};
String[] newArrs = new String[7];
// 如何把arrs数据赋值给newArrs
arrs 是另一种定义方式:
数组拷贝操作:
在Java.lang.System
中为我们提供了一个静态方法,可以专门用来做数组拷贝:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
首先来看如何使用:
String[] arrs = {"元素1","元素2","元素3","元素4","元素5","元素6","元素7"};
String[] newArrs = new String[7];
System.arraycopy(arrs, 0, newArrs, 0, 7);
Arrays.stream(newArrs).forEach(System.out::println);
/**
元素1
元素2
元素3
元素4
元素5
元素6
元素7
*/
newArrs = new String[3];
System.arraycopy(arrs, 2, newArrs, 0, 3);
Arrays.stream(newArrs).forEach(System.out::println);
/**
元素3
元素4
元素5
*/
Arrays.stream(newArrs).forEach(System.out::println);
这是jdk8之后推出的lambda的写法,看不懂的可以忽略,就当for循环输出就好
从上面的方法和输出我们多少都能看出来这里的参数的意义,用一句话总结下来就是:
将源数组的第srcPos个位置到指定长度的元素copy到目标数组从第destPos个位置开始到指定长度位置
老外很有趣,以后再看到
src
,source
这些都是代表的是源;dest
,target
都是代表目标而且一般参数的顺序都是:先源后目标
切记:在copy的时候,不能超过目标数组限定长度
而且:该方法执行效率略低,涉及到数组整体数据的copy
数组还有一个二维数组,这里就不介绍了。在实际工作中很少会用到。
关于数组就介绍到这里,我们还没有结束,下面我们重点要来聊一聊Java中的集合
可以说,集合在Java中也是属于一种容器,上面我们介绍的数组也是一种容器。上面我们也介绍过了数组,我们先考虑一个问题:
我们在这里来总结一下数组的特点,上面说到:
数组是一个相同类型数据的集合,在数组中只能存储一种类型的数据
可以说,这两个特点让我们在实际的开发中存在过多的限制。
所以出现了我们接下来要聊的集合
集合在Java中存在于java.util
包下,为我们提供了一套性能优良,使用方便的接口和类。
在集合框架中,我们可以感受到另外一种编程思想:面向接口编程
根据存储数据格式进行划分,可以分为两大类
两者都是接口,通过接口来规范操作方式
其中,Collection
存储的数据是单一元素,而Map
是已 <Key, Value>的格式存储起来的。下面我们通过一张图来看看这一系列的主角们:
我们直接来聊聊ArrayList
ArrayList属于List的子类,其特点:
很多时候不知道无从下手的时候,就先从该类的构造方法看起:
// 无参
List<String> list1 = new ArrayList<>();
// 指定初始化长度
List<String> list2 = new ArrayList<>(8);
// 初始化内容
List<String> list3 = new ArrayList<String>(Arrays.asList(arrs));
下面我们通过这三个构造方法来具体看下ArrayList是如何实现的:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
通过查看构造方法,我们可以看出:
private static final int DEFAULT_CAPACITY = 10;
我们记住这个属性,很快我们就会说到
背景: 我们后续的方式都以无参构造方法创建的ArrayList对象进行说明
默认构造方法在初始化的时候,底层默认是空的Object数据,上面数组说到,我们在存储数据的时候数组必须要定义数组的长度,那么无参构造方法在添加元素的时候又是怎么做到添加成功而且不会出错呢?
下面我们来详细了解一下
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
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++;
}
两者都是添加元素的方法,唯一的区别在于:
add(index, element)方法可以指定元素的添加位置
关于该方法的执行效率,是分情况的
而且添加方式可以分为两步:
下面我们来研究一下扩容的方法:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
针对底层初始化的时候出现空Object数组的情况,这里对该情况进行了验证:
然后得到上面通过验证得到的长度进行扩容判断,如果容量不足,才会进行扩容的操作:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
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);
}
我们具体查看grow()方法,可以得到以下结果:
elementData
是空数组的情况下,会扩容到10个长度。因为如果新扩容后的长度小于calculateCapacity()得到的值的话,calculateCapacity()得到的值就是最终要扩容的值
calculateCapacity()得到的值:使我们当前List的size() + 我们需要添加List中元素的数量
ArrayList底层是采用数据结构来实现的,所以我们随机访问数据的方式和直接通过数组来访问是一样的,不过在ArrayList中专门提供了一个方法:
List<String> list = new ArrayList<>(5);
list.add("11");
list.add("12");
list.add("13");
list.add("14");
list.add("15");
System.out.println(list.get(0)); // 11
下来可以自己看下实现源码,肯定是数组根据下标获取元素的方式
这块主要的内容是 Iterator,是Java对设计模式中迭代器模式的实现,下面我们来看操作代码
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
System.out.println(next);
}
List提供了iterator()
,通过该方法我们就可以拿到Iterator进行迭代操作,我们先来看一看它的执行流程是什么样,其中最重要的两个属性:
public boolean hasNext() {
return cursor != size;
}
next()
得到当前元素,同时改变cursor和lastRet的值,可以进行下一次的操作public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
hasNext()
为false看图更详细
ListIterator<String> listIterator = list.listIterator();
准确来讲,ListIterator是Iterator的子类,拥有和Iterator同样的功能,但是又有不同:
add()
和set()
,支持在迭代过程中向集合中添加和修改元素hasPrevious()
和previous()
,支持逆向迭代其实,ArrayList如果只是局部变量的话,是不牵扯线程安全不安全的,只有作为共享资源存在的时候,才会出现线程不安全的问题:
volidate
修饰这部分设计到多线程的知识,后面会对多线程的基础知识进行介绍
同时还会专门开一个专栏,深入了解多线程的内容。大家在这里记住就可以了
那么, 这种情况下,我们如何操作来保证线程安全呢?
自己查看源码,其实就是对方法进行加锁操作
java.util.concurrent
下,也就是我们所说的JUC更多关于ArrayList使用方法推荐查看其文档:
标签:final 自己 下标 lca flow 有一个 情况 turn inter
原文地址:https://blog.51cto.com/14948012/2542376