一、数组
目录
5、请你讲讲数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?
7、为什么数组查询比链表要快?而插入删除比链表效率低?(重要)
1、数组的声明
格式1: int [] arr = new int[5];
格式2: int [] arr = new int[]{1,3,5,7}
格式3: int[]arr = {1,3,5,7};
其他类型类似注意二维数组中的第一个[]的值不能为空
2、下面是数组对应内存的分布:
其中string[] str = new String[]{"aa","bb"}
1、先去String的常量池看有没有关于aa,bb的值,有则不创建直接引用,如没有则创建aa、bb并将其放进常量池中(常量池是放在方法区中的,属于线程共享资源)。
2、凡是有new的一定是创建了新对象,此时内存创建了一个新的对象地址为ox1234,该地址指向常量池中的aa、bb
3、将该地址赋给str对象。
(该过程至少创建了1个对象、最多创建3个对象)
3、数组的一些概念及知识点
1、数组是一个对象,不同类型的数组具有不同的类; 2、数组长度不能动态调节,即数组的长度声明了就是固定的,数组是连续存储的结构,因为已经预定义了它的大小,所以可以取得它的length 3、数组是一种引用数据类型,那么他肯定是继承Object类的,所以里面有equals() 方法但是没有重写过(没重写过即是==), 因为他并不是 比较数组内的内容,而是比较地址。 4、数组不是基本类型,在java中,数据类型就分为基本数据类型(即原生类)和引用数据类型,所以数组不是原生类是引用数据类型
4、举例说明
string[] str = new String[]{"aa","bb"}
string[] str2 = new String[]{"aa","bb"}
此时str.equals(str2),结果为flase
1、str和str1是==分别new的两个地址(只要new就一定是创建对象,虽然这两个对象指向的值相同)
2、数组中的equals()是继承object的原生方法,该方法内部实现就算==,所以相当于str==str1,由于地址不同所以为flase
int array[100]定义是错的,int array[] = new int[100];才是对的
5、请你讲讲数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?
考察点:Array
参考回答:
Array和ArrayList的不同点:
Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
Array大小是固定的,ArrayList的大小是动态变化的。
ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
6、数组主要方法的底层源码
public class Array {
private int[] data;
private int size;
private int capacity;
public Array(int capacity) {
data = new int[capacity];
}
public Array() {
this(10);
}
public int[] getData() {
return data;
}
public void setData(int[] data) {
this.data = data;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public int getCapacity() {
return capacity=data.length;
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
//判断是否为空
public boolean isEmpty() {
return size==0;
}
//添加,数组头插入
public void addFirst(int e) {
add(0,e);
}
//插数组尾
public void addLast(int e) {
add(size, e);
}
//插入数组,常用
public void add(int index,int e) {
if(index<0 || index>size) {//地址超出范围
throw new IllegalArgumentException("失败");
}
if(size==data.length) { //数组存满了,则进行扩容,扩容两倍
resize(new int[data.length*2]);
}
for (int i = size-1; i >= index; i--) { //数组有没满则进行存储
data[i+1]=data[i];
}
data[index]=e; //index即当前数据的下标索引
size++; //长度++
}
//扩容
public void resize(int[] dataNew) {
// int[] dataNew = null;
// if(size==data.length) {
// dataNew = new int[data.length*2];
// }
for (int i = 0; i < data.length; i++) {
dataNew[i] = data[i];
}
data = dataNew;
}
//重写toString方法,内部是new一个StringBuffer对象进行存储。
@Override
public String toString() {
StringBuffer bf = new StringBuffer();
bf.append(‘[‘);
for (int i = 0; i < size; i++) {
bf.append(data[i]);
if(i != size-1) {
bf.append(‘,’);
}
}
bf.append(‘]’);
return bf.toString();
}
}
Array底层的toString方法是new 一个StringBuffer对象进行存储的。
声明了一个数组则其长度是固定的,如果数组满了,则会调用扩容方法resize重新分配一个两倍的内存,并将原先的数据存进该数组中。原先的数组就等着被回收。(各个Array的子类的扩容长度不同)
7、为什么数组查询比链表要快?而插入删除比链表效率低?(重要)
已知:
1、数据存储结构分为顺序存储、链接存储、索引存储、散列存储。
2、数组属于顺序存储,用一段连续的内存位置来存储。
3、链表属于链接存储,用一组任意的存储单元来存储,不要求物理上相邻。查询涉及到CPU特性
处理速度由快到慢排序:
1、CPU寄存器
2、CPUL1缓存
3、CPUL2缓存
4、内存
5、硬盘总结:
因为CPU缓存会读入一段连续的内存,顺序存储符合连续的内存,所以顺序存储可以被缓存处理,而链接存储并不是连续的,分散在堆中,所以只能内存去处理。
所以数组查询比链表要快。
而数组大小固定,插入和删除都需要移动元素,链表可以动态扩充,插入删除不需要移动元素,只需要更改元素中的指针。所以链表的插入删除比数组效率高。注意:查询比数组快,删除插入效率又高的方式就是散列存储,散列英文名即为hash
8、数组的拷贝方法
数组的拷贝方式有四种,分别是:
for循环 clone() System.arraycopy() Arrays.copyof() (clone和arraycopy都是本地方法。四种clone的效率最高)
- clone()方法:()(object的方法)
int[] array = {1,2,3,4,5,6};
int[] array2 = new int[6];
array2 = array.clone();
- System.arraycopy() 方法: (System类调用的静态方法)
int[] array = {1,2,3,4,5,6};
int[] array2 = new int[6];
System.arraycopy(array,0,array2,0,array.length);
- Arrays.copyof() (Arrays类)(该方法中调用了System.arraycopy() 方法)
int[] array = {1,2,3,4,5,6};
int[] array2 = new int[6];
array2 = Arrays.copyOf(array,array.length);
9、浅拷贝和深拷贝(重要)
java中对象的拷贝分两种:深拷贝和浅拷贝
深拷贝和浅拷贝最根本的区别在于是否是真正获取了一个对象的复制实体,而不是引用。
- 浅拷贝:只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做“浅拷贝”,换句话说,浅复制仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。
- 深拷贝:在计算机中开辟了一块新的内存地址用于存放复制的对象。实现深拷贝的两种方式是重写对象的clone方法(继承自Object)或者使用对象的序列化
上面的几个拷贝方法都属于浅拷贝
实现深拷贝方法:
1、重写对象的clone方法(重new一个object对象)
//省略构造函数和toString() private class Person implements Cloneable{ int age; String name; @Override protected Object clone() { Object o = null; try { o = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return o; } }
2、使用对象的序列化(先implements Serializable,然后将要对象的拷贝数据写入流,然后new一个对象反序列化接收该流)
把对象写到流里的过程是序列化过程(Serialization),而把对象从流中读出来的过程则叫做反序列化过程(Deserialization)。
应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。
在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象。
这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象可否设成transient,从而将其排除在复制过程之外。
//省略了构造方法和toString() private class Person implements Serializable { int age; String name; public Object deepClone() throws Exception{ // 序列化 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); // 反序列化 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } }
对于序列化可以看:https://blog.csdn.net/weixin_39723544/article/details/80527550
至此数组的内容就差不多了