一、java中异常与错误
1.1 、java的异常和错误概述
-
异常在java中以类和对象的形式存在,类之间是互相继承的
-
最上层的父类object下面有一个throwable(可抛出的)
-
Throwable的两个分支:
Error(不可处理,直接退出jvm)
Exception(可处理的)
-
error的两个分支
virtualmachineerror(已损坏或已耗尽继续运行所需的资源,内存不足)
Ioerror(io错误,磁盘交互的错误)
-
Exception的两个分支:
ExceptionSubClass:编译时异常,表示编写程序是预先处理的异常
RuntimeException:运行时异常,运行的时候产生的异常
1.2、常见的运行时异常有
-
算术异常类:ArithmeticExecption
-
空指针异常类:NullPointerException
-
类型强制转换异常:ClassCastException
-
数组负下标异常:NegativeArrayException
-
数组下标越界异常:ArrayIndexOutOfBoundsException
-
违背安全原则异常:SecturityException
-
文件已结束异常:EOFException
-
文件未找到异常:FileNotFoundException
-
字符串转换为数字异常:NumberFormatException
-
操作数据库异常:SQLException
-
输入输出异常:IOException
-
方法未找到异常:NoSuchMethodException
1.3、异常结构图
二、异常的处理方法
2.1、在方法上抛出异常
2.2、使用try....catch
使用规范:
1.catch后面小括号中的类型可以是具体的异常类型,也可以是该异常的父类
2.catch可以写多个,写的时候必须准守从上到下,从小到大的原则
异常对象的方法:
-
e.getMessage()打印异常的简单描述信息
-
e.printStackTrace()打印异常堆栈追踪信息,使用异步的方式打印(不和主程序 一起执行)
另外,finally方法一定会执行,return是一个函数的出口。但是finally函数如果写在return的下面,那么会先执行finally方法,后执行return。有时候可以使用添加一个变量的方式来满足java的执行规范:从上到下执行和return为方法出口最后执行。
2.3、数字和时间格式用法
数字的格式
使用DecimalFormat类创建数字的格式对象,然后用该对象调用format方法来修改格式
时间的格式
时间的获取:使用date类可以创建一个事件对象
format转换时间格式:构造方法,输出的时间格式不符合日常使用,所以需要和string字符串转换
使用方式:
Date():根据当前系统时间创建日期对象
Date(long time):根据传入的毫秒值时间创建日期对象
使用SimpleDateFormat对象创建一个固定的时间格式,然后调用format方法来转换格式,返回一个字符串类型
在构造方法中传入一个参数,代表是1970年1月1日过参数个时间
System.currentTimeMillis()方法是获取1970到系统现在时间的毫秒数,所以获取昨天时间只需要减去一天的毫秒数就行了
同理可以用该方法来记录程序运行的时间
三、Collection集合
3.1、collection集合系列概述
最上层的Collection接口泛化:list和set
-
List接口:List集合存储元素特点是有序可重复,存储的元素有下标。有序的意思是存进去之后顺序未改变。
-
ArrayList:集合低层采用数组这种数据结构,是非线程安全的。
-
LinkedList:集合低层采用双向链表的数据结构
-
Vector:集合低层采用数组,但是是线程安全的。所有的方法都包含Synchronized关键字,效率低下,所以使用较少。
-
-
Set接口:无需不可重复,无下标,顺序会改变
-
Hashset:实际上创建该集合的时候低层new出一个Hashmap的集合。实际上数据存储到hashMap里了。hashSet就是Hashmap的键。
-
SortedSet接口:直接泛化与set接口。由TreeSet继承
-
TreeSet:底层实际上是TreeMap
-
3.2、迭代器Iterator
迭代器就是集合的一个快造,用于集合的遍历。使用Iterator<String> it = list.iterator();创建一个list集合的迭代器(相当于对这个集合进行照相)
在定义该方法的时候使用String泛型,这样就可以使用String类型直接接受迭代器的数据。这样可以直接对数据输出。
ArrayList<String> sites = new ArrayList<String>();
sites.add("Google");
sites.add("Runoob");
sites.add("Taobao");
sites.add("Zhihu");
// 获取迭代器
Iterator<String> it = sites.iterator();
// 输出集合中的所有元素
while(it.hasNext()) {
System.out.println(it.next());
}
3.3、Collection常用函数
-
使用泛型的话就执行存放指定的元素
-
没有使用泛型,可以存放任意object类
-
不能存放基本类型还有java对象,只是存放java对象的内存地址
-
collection中的常用方法
-
add方法向集合中添加元素
-
size()获取集合中的个数
-
clear()清空集合
-
contains(object o)判断当前集合中是否返回元素o。此时调用的是equal方法,如果对该方法重写了就返回true,反之不行
-
isEmpty()判断集合中是否为空
3.4、采用迭代器删除集合中元素
在删除集合中元素的时候必须使用迭代器来调用remove方法,否者会报错
因为使用集合本身的方法删除必须创建迭代器,但是删除之后没法通知迭代器。导致迭代器和结合不符合,会产生异常
while(it.hashnext()){
Object o=it.next();
it.remove();
System.out.print(o);
}
3.5、链表和顺序表
链表的优缺点:
由于链表上的结构是使用地址来寻找下一个参数的。所以随机增删元素的时候不会有大量的元素位移。效率较高
由于是使用地址查找的,所以在查找元素的时候都是从首元素开始遍历,效率较低。
顺序表的优缺点:
顺序表是使用数组来存储数据的,可以通过数学表达式的方式快速计算数据的位置,查找比较快。
但是在增加和删除的时候需要移动大量的元素,效率较低。而且需要在内存中找到一块较大的内存。
3.6、list和set集合底层分支
-
ArrayList:非线程安全,采用数组的数据结构,初始长度10,默认加载因子0.75,扩容1.5倍
-
LinkList:采用双向链表,没有这些特点
-
Vector:线程安全,有syndronized关键字,初始10,默认0.75,倍数2,效率低。
-
HashSet:由HashMap实现,无序集合,初始容量16,默认加载因子0.75,扩容为当前容量的2倍。
-
TreeSet:由TreeMap实现,有序集合,根据元素的自然顺序或者指定的Comparator进行排序,没有初始容量和加载因子的概念。
四、Map容器
4.1、map集合系列概述
Map集合是一个接口,和Collection集合没有关系
Map集合以key和value方式存储元素,存放的都是内存地址,特点就是无序不可重复
-
HashMap:底层是哈希表,非线程安全的
-
HashTable:底层也是哈希表数据结构,是线程安全的,带有Synchronized关键字,效率较低。
-
继承Hashtable的Properities类:该类线程安全的,效率较高。
-
-
SortedMap集合的key存储无需不可重复,集合key部分元素可以自动排序,可排序的集合
-
其中TreeMap底层数据结构是一个二叉树
-
4.2、Map接口中的常用方法
-
V put(K key,V value) 向Nap集合中添加键值对
-
V get(Object key)通过key获取value
-
void clear() 清空Map集合
-
boolean containsKey(Object key) ¥判断Map中是否包含某key boolean containsValue(Object value)判断Map中是否包含某个value boolean isEmpty()判断Map集合中元素个数是否为e
-
Set<K>keySet()获取Map集合所有的key(所有的键是一个set集合)
-
V remove(Object key) 通过key删除键值对
-
int size()获助Nap集合中键值对的个数。
-
Collection<V> values()获取Iap集合中所有的value,返回-个Collection
-
Set<Map.Entry<K,V>>entrySet()特Map集合转换成Set集合
public static void main(String[] args) {
Map<Integer,String> map=new HashMap<Integer, String>();
map.put(1,"aaa");
map.put(2,"bbb");
map.put(3,"vvvv");
map.put(4,"dfda");
System.out.println(map.get(2));//bbb
System.out.println("键值对的数量:"+map.size());//4
map.remove(3);
System.out.println("键值对的数量:"+map.size());//3
System.out.println(map.containsKey(1));//true
System.out.println(map.containsValue("bbb"));//true
map.clear();
System.out.println(map.size());//0
System.out.println(map.isEmpty());//true
//遍历的两种方式
Set<Integer> Key=map.keySet();
for (Integer a:Key){
System.out.println(a+":"+map.get(a));
}
Iterator<Integer> it=Key.iterator();
while (it.hasNext()){
System.out.println(it.next()+":"+map.get(it.next()));
}
Set<Map.Entry<Integer,String>> set=map.entrySet();
Iterator< Map.Entry<Integer,String>> it2=set.iterator();
while (it2.hasNext()){
Map.Entry<Integer,String> entry=it2.next();
Integer key=entry.getKey();
String value=entry.getValue();
System.out.println(key+" "+value);
}
}
4.3、HashMap集合
hashmap集合底层是哈希表/散列表的数据结构
哈希表是一个数组和单向链表的结合体,兼顾了两者的有点
底层源代码:
public class HashMap{
Node<k,v> table;
static class Node<k,v>{
final k key;
final int hash;
v value;
node<k,v> next;
}
}
底层就是一个自定义内部类的数组,类中包含hash值key,value还有下一个hash地址
4.4、哈希表的性能问题
当散列分部不均匀的时候就会导致性能变差
哈希表的运算方法是先使用哈希值通过特定的哈希算法确定数组的下标,然后将数据存放到某个下标中的单链表中。
所以如果固定hashcode的返回值将会使所有的数据存放到同一下标的链表下,哈希表就成了单向链表
如果哈希值的返回值都不一样,就会每个下标存放一个,这样就变成了数组。
在查询的时候先使用hashcode计算下标的值在一个个查找,就会比链表块很多
4.5、增加和删除的实现原理
map.put(k,v)实现原理:
-
先将kv封装到Node对象当中。
-
底层会调用k的hashCode0方法得出hash 值
-
然后通过哈希函数/哈希算法,将hash值转换成数组的下标
-
如果下标位置上如果没有任何元素,就把Node添加到这个位置上了。
-
如果说下标对应的位置上有链表,此时会拿着k和链表上每一个节点中的k进行equals
-
如果所有的equals方法返回都是false,那么这个新节点将被添加到链表的末尾。
-
如果其中有—个equals返回了true,那么这个节点的value将会被覆盖。
-
-
map.get(k)实现原理:
-
先调用k的hashCode0方法得出哈希值,
-
通过哈希算法转换成数组下标,
-
通过数组下标快速定位到某个位置上,
-
如果这个位置上什么也没有,返回null。
-
如果这个位置上有单向链表,那么会拿着参数k和单向链表上的每个节点中的k进行equals,
-
如果所有equals方法返回false,那么get方法返回null,
-
如果其中有一个节点的k和参数k equals的时候返回true,那么此时这个节点的value就是我们要找的value,get方法最终返回这个要找的value。
-
-
4.6、Hashmap和Hashset区别
HashMap集合底层是哈希表数据结构,是非线程安全的。key和value容许为null
在JDK8之后,如果哈希表单向链表中元素超过8个,单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量小于6时,会重新把红黑树变成单向链表数据结构。这种方式也是为了提高检索效率,二叉树的检索会再次缩小扫描范围。提高效率。
初始化容量16.默认加载因子75(当内容达到百分之75的时候自动扩容)扩容是:扩容之后的容量是原容重的2倍。
Hashtable集合底层也是哈希表数据结构,是线程安全的,其中所有的方法都带有synchronized关键字,效率较低,现在使用较少了,因为控制线程安全有其它更好的方案。
Hashtable的key和value不允许null
Hashtable集合初始化容里11,默认加载因子是75,Hashtable集合扩容是:原容量*2+1
4.7、properties集合
是一个线程安全的双字段都是String类型的map集合形式
public static void main(String[] args) {
Properties properties=new Properties();
properties.setProperty("aaa","bbb");
System.out.println(properties.getProperty("aaa"));
}
4.8、TreeSet集合
使用自定义的类排序的时候需要对类进行操作才能完成该功能
有两种实现方式:类实现Comparable接口,然后重写方法;或者构造Treeset的时候传递一个比较器(均在下面有所演示)
比较规则不发生改变的时候,建议实现接口
当比较规则有多个的时候建议使用内部类的方式传递比较器
无序不可重复,但是可以按照元素大小自动排序,称为可排序集合
public static void main(String[] args) {
TreeSet<String> tree=new TreeSet<>();
tree.add("dsa");
tree.add("wd");
tree.add("ccc");
tree.add("aaa");
for (String a:tree){
System.out.println(a);
// aaa
// ccc
// dsa
// wd
}
}
TreeSet可以自动排序各种基本类型的数据。因为低层调用了compareTo方法比较,如果在泛型中使用自定义的类来比较就会直接报错。
因此想实现TreeSet集合对自定义类排序,必须实现Comparable接口,并重写CompareTo()方法
Customer c1=new Customer(1);
Customer c2=new Customer(4);
Customer c3=new Customer(2);
Customer c4=new Customer(5);
TreeSet<Customer> c=new TreeSet<>();
c.add(c1);
c.add(c2);
c.add(c3);
c.add(c4);
for (Customer a:c){
System.out.println(a);
}
class Customer implements Comparable<Customer>{
int age;
public Customer(int age){
this.age=age;
}
@Override
public int compareTo(Customer o) {
return this.age-o.age;
}
@Override
public String toString(){
return "Customer"+"age:"+age;
}
}
在TreeSet的自动排序中调用了CompareTo方法,所以使用自己定义的类必须继承接口之后重写方法,在方法中定义排序规则
编写比较规则
@Override
public int compareTo(Customer o) {
if (this.age==o.age){
return this.name.compareTo(o.name);
}else
return this.age-o.age;
}
输出结果
同时可以使用匿名内部类在定义的时候就重写比较方法
TreeSet<Customer> c=new TreeSet<>(new Comparator<Customer>() {
@Override
public int compare(Customer o1, Customer o2) {
if (o1.age==o2.age){
return o1.name.compareTo(o2.name);
}else
return o1.age-o2.age;
}
});
Conllections工具类
可以使用该类对list进行排序,直接对list排序
List<String> list=new ArrayList<>();
//将list转化为线程安全的
Collections.synchronizedList(list);
list.add("dad");
list.add("dsa");
list.add("fds");
list.add("fas");
Collections.sort(list);
for (String s:list){
System.out.println(s);
}
对set排序
Set<String> set=new HashSet<>();
set.add("dsa");
set.add("ewad");
set.add("dsfaa");
set.add("dsaf");
List<String> mylist=new ArrayList<>(set);
Collections.sort(mylist);
for (String a:mylist){
System.out.println(a);
}
五、二叉树
TreeSet/TreeMap是自平衡二叉树,遵循从小到大的原则存放。
遍历二叉树的时候有三种方式:
前序遍历:根左右
中序遍历:左根右
后续遍历:左右根
TreeSet/TreeMap采用的遍历方式是中序遍历
Iterator迭代器采用的也是中序遍历