Java进阶篇
-
数组Array[](重点)
数组是一个简单的线性序列,元素的访问速度非常快,用来存储同一类型的元素(一维数组、二维数组…)
数组一但被创建,大小就被固定,在其生命周期中不可改变
每个数组中都有个只读成员length,length是数组的大小,而不是保存的元素个数;length-1为最后一个元素的下标,当数组下标越界时,会出现运行时异常RuntimeExpectionError
对象数组中保存的是对象的引用地址,基本数据类型数组保存的就是基本类型的值
数组中存储元素的内存地址是连续的
整个数组的内存地址=首个元素的内存地址(因为数组中元素的内存地址是连续的,那么知道了首个元素的内存地址之后,后面的元素地址都可以通过计算得出)
数组这种数据结构带来的优缺点:
优点是查找元素的效率极高;
(原因:数组的内存地址就是第1个元素的内存地址,又因为数组的内存地址是连续的,一个数组保存的都是同一类型的,所有数组每个元素占用的内存大小是一样的,通过元素的下标可以得知偏移量,这样可以计算出来第n个元素的内存地址,直接通过这个内存地址找出第n个元素,而不是通过下标一个一个找出来的。所以数组这种数据结构是查询效率极高的一种数据结构)
缺点①是增加或删除元素的时候效率较低;
(原因:为了保证数组中元素的内存地址是连续的,增加或者删除元素后会导致后面元素的内存地址要统一前移或者后移,而数组中最后一个元素的增加和删除对效率没有影响,比如ArrayList中的add方法默认从数组末尾添加的)
②是数组不适合存储数据量较大的数据
(原因:因为在很难在堆中找到一块很大的连续的内存空间)数组的初始化
//静态创建数组 int[] a ={1,2,3,4}; //动态创建数组 int[] a = new int[4]; //数组的大小为4,数组中没有元素,数值型数组会被程序自动初始化成0,
其他类型默认初始化的值:
byte----0
short----0
int----0
long----0L
float----0.0F
double----0.0
boolean----false
char----\u0000
引用数据类型----null
打印出数组中的内容 Arrays.toString()方法 在java.util.Arrays包下
int[] a = {3,8,5,65,34,27};
System.out.println(a.toString());
System.out.println(Arrays.toString(a));
//Output:
[I@1b6d3586
[3, 8, 5, 65, 34, 27]
Arrays.toString()方法源码:
public static String toString(int[] a) {
if (a == null)
return "null";
int iMax = a.length - 1;
if (iMax == -1)
return "[]";
StringBuilder b = new StringBuilder();
b.append('[');
for (int i = 0; ; i++) {
b.append(a[i]);
if (i == iMax)
return b.append(']').toString();
b.append(", ");
}
}
实际上是StringBuilder中的append拼接
-
包装类
8种基本数据类型对应着8种包装类(引用类型)
了解下Number类中的方法(Number是抽象类)
装箱与拆箱
//自动装箱: int a = 1; Integer a1 = a; //自动拆箱 int a1 = a; //手动装箱:基本数据类型装换成引用数据类型 Integer i = new Integer(a); //手动拆箱:引用数据类型转换成基本数据类型 int b = i.intValue();
-
Date
获取系统当前时间:
Date d = new Date();
(java.util包下的Date)日期格式化
使用java.text.SimpleDateFormat类(支持文本转日期,日期转文本的)
//日期转文本
Date d = new Date();
//System.out.println(d); // Mon Nov 02 08:45:03 UTC 2020
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String nowTime = sdf.format(d);
System.out.println(nowTime); // 2020-11-02 08:48:34
//文本转日期
String time = "2020-11-02 08:48:34";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse(time); //这方法是同时要抛出一个Exception异常
System.out.println(d); // Mon Nov 02 08:45:03 UTC 2020
//记录一个方法执行的毫秒数
//System.currentTimeMillis()获取自1970年1月1日00:00:00时至现在系统时间为止的毫秒数
long begin = System.currentTimeMillis();
//调用方法
long end = System.currentTimeMillis();
System.out.println("该方法耗时"+(end-begin)+"毫秒");
//毫秒数转换成日期(从自1970年1月1日00:00:00算起)
Date d = new Date(1023564561);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String nowTime = sdf.format(d);
System.out.println(nowTime); // 1970-01-12 20:19:24
//获取昨天此时的时间
Date yTime = new Date(System.currentTimeMillis() - 1000*60*60*24);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String yesTime = sdf.format(yTime);
System.out.println(yesTime); // 2020-11-01 09:13:22
-
数字格式化
java.text.DecimalFormat类专门负责数字格式化的方法//# 代表任意数字 //0 代表保留几位小数 不够的0补位 double d = 123456789; DecimalFormat df = new DecimalFormat("###,###,###.00"); String d1 = df.format(d); // 转为字符串类型 System.out.println(d1); //123,456,789.00
-
Random
//随机产生一个int类型的随机数 Random r = new Random(); int i = r.nextInt(); // 无参数时,范围在int的取值范围内 //获取有确定取值范围的随机数 int x = r.nextInt(101); // 获取[0,100)的随机数,左闭右开
-
IO与NIO有什么区别?
这篇文章非常详细:NIO 和 IO 到底有什么区别?
-
异常
Throwable是Java异常的顶级类,所有的异常类都继承这个类;
Throwable又分为Error和Exception;
Error是非程序异常,即程序不能捕获的异常,一般是编译或者系统错误,如OutOfMemorry内存溢出等。
Exception是程序异常,由程序内部产生;Exception又分为运行时异常和非运行时异常(编译时异常/检查时异常)。
运行时异常的特点是Java编译器不会检查它,是开发人员逻辑出错导致的异常,也就是说程序中可能出现这类异常,即使没有try-catch语句捕获它,也没有throws子句声明抛出它,也会编译通过,运行时异常可处理或者不处理。运行时异常一般常出来定义系统的自定义异常,业务根据自定义异常做出不同的处理。常见的运行时异常NullPointException、ArrayIndexOutOfBroundsException等。
非运行时异常是必须处理的异常,捕获或者抛出,如果不处理程序就不能编译通过。如IOException、ClassNotFoundException等。注意:所有的异常都是在运行时发生的,因为程序运行阶段才可以new异常对象,异常的发生就是new异常对象。
常见的运行时异常:
ClassCastException(类转换异常)
IndexOutOfBoundsException(数组越界)
NullPointerException(空指针)
ClassNotFoundException(指定的类不存在)
IOException(IO异常)
SQLException(SQL异常) -
Throw和Throws的区别
二者都是消极处理异常方式,
Throw是一种动作,用在方法体内,表示抛出异常
Throws是一种状态,跟在方法名后,表示该方法可能会抛出一个或多个异常void MethodA(int a) throws Exception1,Exception2 { try{ ...... }catch(Exception1 e){ throw e; }catch(Exception2 e){ System.out.println("出错了!"); } }
异常中的两个方法
取得异常描述信息getMessage()
取得异常时堆栈信息(比较适用于程序的调试阶段)printStackTrace()
注意:打印异常堆栈追踪信息的时候,Java后台采用的是异步线程的方式。异常机制使Java程序更健壮,用try catch捕捉异常然后打印出异常信息,后面的程序会照样执行,不会因此终止try {} catch() {} finally {}
finally代码块中的代码在任何情况下都会执行,一般用来关闭资源
如果catch中有return,先按顺序执行完catch中的代码,执行finally中的代码,最后return,return永远在方法的最后执行public static int method(){ int i = 100; try{ return i; }finally{ i++; } } //上面方法的执行顺序为先i++再return /* 上面代码反编译结果 int i =100; int j = i; i++; return j; */ public static void main(String[] args){ int a = method(); System.out.println(a); // 打印值为100 }
final、finally、finalize分别是什么?
final是关键字,用来修饰类(不能被继承)、方法(不能被重写)、变量(常量)。
finally也是关键字,一般跟在try catch后面使用,里面的代码一定会执行,用来关闭资源。
finalize()是Object类中的一个方法,当一个对象被虚拟机宣告死亡时,会由JVM来调用这个方法。 -
什么是序列化
序列化就是以二进制的形式把对象保存到硬盘中,一般保存在文件中。
反序列化就是再把二进制的字节序列恢复成内存中的对象
当要序列化多个对象的时候,可以把对象们放进一个List中
要序列化一个对象的时候,对象要实现Serializeable接口 -
对象拷贝
1、为什么需要拷贝对象?
当我们想要对一个对象进行处理,但是有需要保留这个对象原有状态时,需要用到对象的拷贝。2、如何实现对象拷贝?
① 实现Cloneable接口,并重写Object的clone()方法,实现浅拷贝
② 实现Serilaizable接口,通过对象的序列化和反序列化,实现深拷贝3、深拷贝与浅拷贝有什么区别?
浅拷贝:只是复制了对象的引用地址,复制之后两对象指向堆中同一个对象,所以修改一个对象的任一属性,另一个也会随之修改。
深拷贝:在堆中创建了一个新的对象,把原来对象中所有非静态属性复制到新对象中,所以修改一个,另一个不会改变。
-
集合(重点)
Collection在java.util包下 ,是一个用来存放对象的容器,集合中不能存基本类型数据,如果把一个int类型的数据存入集合中,会自动转换成Integer类型(自动装箱)。
集合本身也是个对象,也有内存地址。
集合中保存的是对象的引用地址,而对象本身存在堆内存中。
一个集合可以存放不同类型的数据,不限数量。Collection中的常用方法
boolean add():向集合中添加一个元素
int size():获取集合的大小
void clear():清空集合
boolean isEmpty():判断集合是否为空,底层判断size是否为0
boolean contains():判断集合是否包含某个元素,底层是equals()
boolean remove():移除集合中的某个元素,底层也是equals()contains()方法的使用:
Collection c = new ArrayList(); String s1 = new String("abc"); String s2 = new String("def"); c.add(s1); c.add(s2); String s3 = new String("abc"); System.out.println(c.contains(s3)); //Output: true
contains方法是判断集合中是否包含这个元素的,它在源代码中调用了equals方法,实际上就是用s3和集合c中的每一个元素去进行equals比较,equals方法返回true就说明集合中存在这个元素。
在上面的例子中s1和s3都是String类型的,String重写了equals方法,只比较内容是否相同,所以s1.equals(s3)为true,c.contains(s1)也为true。
注意:
String、Date、包装类都重写了equals方法。
contains和remove方法底层都调用了equals方法。迭代器iterator
Iterable是Collection的超级父接口,Collection通过继承这个类得到了迭代器对象
Iterator it = Collection对象.iterator(); //it是迭代器对象
//创建一个集合对象c Collection c = new ArrayList(); //Collection继承了Iterable类,可以使用iterator()方法 //获取迭代器对象it Iterator it = c.iterator(); //判断是否存在下一个对象 while(it.hasNext()){ //next()方法的返回值为Object Object it = it.next(); System.out.println(o); }
Iterator接口中的三个方法
hasNext():如果存在下一个元素,返回true
next():返回下一个元素,Object类型
remove():移除的是当前迭代器指向的元素。
注意:
迭代器最初的时候并没有指向集合中的第一个元素。
如果集合结构发生改变,必须重新获取迭代器(比如集合中新增了元素进去)
在遍历集合时,不能调用集合的remove方法, 会导致集合里的元素发生改变,要使用迭代器的remove方法
集合类的继承机构:
Collection的主要子接口:List,Set
List:有序、可重复的集合
Set:无序、不可重复的集合
Queue:队列(先进先出)—— 栈(先进后出)
注意:有序的集合是指存进去是什么顺序,取出来也是什么顺序,并且有序的集合每个元素都有下标(从0开始)List接口主要的三种实现类:ArrayList、LinkedList、Vector
List list = new ArrayList(); list.add("a"); // 默认往末端添加元素 list.add(0,"A"); // 往指定位置处(下标)添加元素,效率低,使用少(ArrayList底层是数组) list.indexOf("b"); // 返回元素b在集合中第一次出现的索引,如果没有返回-1
ArrayList
底层采用了数组这种数据结构,
非线程安全的,
默认初始化容量为10,
(在JDK8中,默认是一个空列表,当第一次添加元素的时候,容量变为10)
当添加元素满了,底层数组自动扩容到原来的1.5倍,
频繁扩容会降低效率所以ArrayList也将涉及到性能调优的问题:
ArrayList有三种构造方法:
①new ArrayList() 创建一个默认初始值为10的空列表
②new ArrayList(int initialCapacity) 创建一个指定初始容量的列表
③new ArrayList(Collection<? extends E> c) 构造一个包含指定的collection元素的列表
ArrayList底层是数组来实现的,数组的自动扩容会拉低程序的效率,要想提高效率就要减少它扩容的次数,在创建集合的时候给一个合适的初始容量,使用第②中构造方法,可以适当提高ArrayList的性能。LinkedList
底层采用了双向链表数据结构
链表长度最初为0
非线程安全单向链表的数据结构
链表中最基本的单元叫做节点node
每个node中都有两个属性叫做值域和指针域
值域:保存对象的内存地址
指针域:保存下一节点的内存地址,也就是下个对象的内存地址
末尾节点的指针域为空,没有指向。ArrayList与LinkedList的比较
ArrayList和LinkedList都是List接口的实现类,他们具有相同的特性:有序且可重复,都有下标。不同之处就是在执行增删查询时的性能不同
ArrayList:查询快,增删慢
LinkedList:查询慢,增删快
他们这样的特性主要取决于他们底层的实现原理不同。
ArrayList底层是数组,数组的数据结构是通过数学表达式计算出要查找的元素的内存地址,从而直接找到这个元素,而增删元素会导致数组中需要移动大量的内存地址,以保证数组的内存地址是连续的,所以开销很大。
LinkedList底层是双向链表,双向链表的结构是每个节点有一个指针,从任意节点开始向前向后查找,这种需要一个个去查找的效率远不如数组的查询效率。而这种链式结构的插入和删除就相对容易很多,只需要指针重新指向新的元素就可以实现增加和删除元素。Vector集合采用了数组,线程安全(所有方法都有synchronized修饰,所有方法都是线程同步的)效率低,很少使用。
Set接口的一种实现HashSet:
Set<Object> set = new HashSet<>();
Set还有一种的实现方式是通过SortedSet接口(SortedSet接口继承了Set接口)实现的,TreeSet是SortedSet接口下的方法。SortedSet<> set = new TreeSet<>();
创建HashSet和TreeSet集合实际上是new了一个HashMap,存储元素实际是存进了HashMap的key值中(源代码如下)。//HashSet底层代码的add方法 public boolean add(E e){ return map.put(e,PRESENT)==null; //实际上 是把元素e是放入了Map集合的key值中,可以说Map中的key值的集合就是Set集合 }
所以想要了解Set,先来了解一下HashMap的实现原理
https://blog.csdn.net/weixin_42540974/article/details/109529780