一. 集合 (一)数据存储的常用结构:栈, 队列, 数组, 链表, 红黑树
1.栈:先进后出(FILO , First In Last Out), 入口和出口在同一侧
适用场景:
栈内存:main方法先进栈调用,main方法中的其他方法都调用完毕后,main才能出栈
反转内容:车尾变车头,进去再出来就反转了
2.队列:先进先出(FIFO, First In First Out), 入口和出口在集合的两侧
适用场景:秒杀,抢购,在线售票, 高并发场景
3.数组:
查询快:数组的地址是连续的,我们通过数组的首地址可以找到数组,通过数组的索引可以快速查找某一个元素
增删慢:数组的长度是固定的,我们想要增加/删除一个元素,必须创建一个新数组,把原数组的数据复制过来
4.链表:
查询慢:链表中的地址不是连续的,每次查询元素,都必须从头开始查询
增删快:链表结构,增加/删除一个元素,对链表的整体结构没有影响,所以增删快
单向链表:链表中只有一条链子,不能保证元素的顺序(存储元素和取出元素的顺序有可能不一致)
双向链表:链表中有两条链子,有一条链子是专门记录元素的顺序,是一个有序的集合
适用场景:查询少,增删多的场景
5.红黑树:是一种 平衡 二叉 查找 树
特点:趋近于平衡树,查询的速度非常的快,查询叶子节点最大次数和最小次数不能超过2倍
添加元素到红黑树:元素自动排序 左小,右大.
查找树的遍历:
从根节点开始,每个节点做3个操作:
①先往左找有没有子节点,直到没有左子节点
②返回当前节点的数据
③往右找右子节点,右边没有则返回父节点 (二)List接口
1.接口的特点:
①有序的集合,存储元素和取出元素的顺序是一致的(存储132 取出123)
②有索引,包含了一些带索引的方法
③允许存储重复的元素
2.接口特有的方法:
[AppleScript] 纯文本查看 复制代码
①void add(int index , E element): 将指定的元素,添加到指定索引的位置上
②E remove (int index): 移除指定位置的元素,返回的是被移除的元素
③E set ( int index, E element): 用指定的元素替换集合中指定位置的元素,返回的是原来的元素
④E get( int index): 返回集合中指定位置的元素.
3.List子集合
(1).ArrayList集合
底层为数组结构,查询快,增删慢,线程不安全,效率高
(2).LinkedList集合
特点:①底层是一个链表结构:查询慢,增删快
②里边包含了大量操作首尾元素的方法
注意:使用LinkedList集合特有的方法,不能使用多态
LinkedList方法:
[AppleScript] 纯文本查看 复制代码
①void addFirst(E e):将指定元素插入此列表的开头
②void addLast(E e):将指定元素插入此列表的结尾
③void push(E e):将元素推入此列表所表示的堆栈,等效于addFirst(E e)
④E getFirst( ): 获取此列表中的第一个元素
⑤E getLast( ):获取此列表中的最后一个元素
⑥E removeFirst( ):移除并返回列表的第一个元素
⑦E removeLast( ):移除并返回此列表的最后一个元素
⑧E pop( ):从列表所表示的堆栈处弹出一个元素,等效于 removeFirst( )
(三). Set接口:元素不可重复,无索引
1.HashSet类:底层是哈希表,元素无序,元素不可重复(用hashCode()和equals()方法来判断)
获取元素时,先获取hashCode值进行比较,如果相同,则是哈希冲突,进行equals判断如果不重复,则添加
如果重复,则哈希冲突,再比较相同哈希值元素的equals()
如果equals()方法比较有相同的,则元素重复,不添加
如果equals()方法比较没有相同的,则没有重复的,添加
2.哈希值:是一个十进制的整数,由系统随机给出,一般是通过将对象的内部地址转换成一个整数来实现的
3.哈希表:
JDK 8以前 : 哈希表 = 数组 + 链表
JDK 8以后 : 哈希表 = 数组 + 链表/红黑树(提高查询的速度);
特点 : 速度快 4.LinkedHashSet类:哈希表+链表
特点:①元素存取有序(存入和取出顺序一致)
②元素不可重复
③没有索引
总结:要存储的元素可以重复的,用List集合:
增删少,用ArrayList
增删多,用LinkedList
要存储的数据要求不重复,或者相对一个集合去重,用Set集合:
无序存取,用HashSet
有序存取,用LinkedHashSet
(四), 可变参数
1.使用前提:当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数.
2.使用格式:定义方法时使用
修饰符 返回值类型 方法名( 数据类型...变量名){}
3.可变参数的原理:
可变参数底层就是一个数组,根据传递参数个数不同,会创建不同长度的数组,来存储这些参数传递的参数个 数,可以是0个,1,2...多个
4.注意事项:
①一个方法的参数列表,只能有一个可变参数
②如果方法的参数有多个,那么可变参数必须写在参数列表的末尾
(五), Collections集合工具类
[AppleScript] 纯文本查看 复制代码
1.static?<T>?boolean?addAll(Collection<T>?c,?T...?elements) :往集合中添加一些元素
2.static?void?shuffle(List<?>?list) :打乱集合顺序
3.static?<T>?void?sort(List<T>?list) :将集合中元素按照默认规则排序
需要实现Comparable<E>接口,重写 int compareTo(E e)方法
大小的比较通过compareTo( )方法的返回值确定:
如果是负数: 当前元素比被比较元素小
如果是0: 当前元素与被比较元素相等
如果是整数: 当前元素比被比较元素大
规则: this-参数: 升序 参数-this:降序
4.[AppleScript] 纯文本查看 复制代码
static?<T>?void?sort(List<T>?list,Comparator<??super?T>?) :将集合中元素按照指定规则排序
(六),Map集合: java.util.Map<k,v>集合
(1)Map集合的特点:
①.Map集合是一个双列集合,一个元素包含两个值(一个key,一个value)
②.Map集合中的元素,key和value的数据类型可以相同,也可以不同
③.key不允许重复,value可以重复
④.key和value是一一对应的,一个键只能对应一个值
Map集合适合存储一对一关系的数据
(2)Map中的子类:
1.HashMap集合的特点:
①.Hashmap集合底层是哈希表,是一个线程不安全的集合,是多线程的集合,查询的速度特别快
②.无序,存储和取出的顺序有可能不一致
2.LinkedHashMap的特点:
①.LinkedHashMao集合底层是哈希表+链表(保证迭代的顺序)
②.LinedHashMap集合是一个有序的集合,存储和取出的顺序是一致的
补充:
HashSet底层使用的就是HashMap
LinkedHashSet底层使用的就是LinkedHashMap
3.Hashtable集合底层也是哈希表,是一个线程安全的集合,是单线程的集合,查询速度慢
对比:
①Hashtable不允许存储null值和null键,而HashMap可以
②Hashtable线程安全但效率低,而HashMap线程不安全但效率高
(3)Map中的常用方法
1. V?put(K?key,?V?value): 添加/修改键值对. 如果键存在, 则用新值替换旧的值
2. V?remove(Object?key): 根据键删除整个键值对, 返回被删除元素的值
3. V?get(Object?key): 根据键获取值. 如果键不存在, 则返回null
4. boolean containsKey(Object key): 判断是否包含指定的键
5. Set<K>?keySet(): 获取Map集合中所有的键, 存储到Set集合中 6. Set<Map.Entry<K,V>>?entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)
(七), JDK9对集合添加的优化
List接口,Set接口,Map接口:里边增加了yige静态的方法of,可以给集合一次性添加多个元素
使用前提:当集合中存储的元素的个数已经确定了,不再改变时使用.
注意事项:①of方法只适用于List接口,Set接口,Map接口,不适用于接口的实现类
②.of方法的返回值是一个不能改变的集合,集合不能再使用add,put方法添加元素,会抛出异常
③.Set接口和Map接口在调用of方法的时候,不能有重复的元素,否则会抛出异常
(八), Debug
1. 断点: breakpoint, 在debug模式下, 程序运行到断点会暂停住, 便于我们在程序运行过程中查看
2. Debug调试程序: 可以让代码逐行执行,查看代码执行的过程,调试程序中出现的bug
使用方式:
在行号的右边,鼠标左键单击,添加断点(每个方法的第一行,哪里有bug添加到哪里) 右键,选择Debug执行程序, 程序就会停留在添加的第一个断点处
3. 执行程序: [AppleScript] 纯文本查看 复制代码
f8:逐行执行程序 f7:进入到方法中 shift+f8:跳出方法 f9:跳到下一个断点,如果没有下一个断点,那么就结束程序 ctrl+f2:退出debug模式,停止程序 Console:切换到控制台
二, 异常 [AppleScript] 纯文本查看 复制代码
1.异常:指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止
2.分类: ①Exception:编译期异常,写代码时出现的问题 ②RuntimeException:运行期异常,程序运行过程中出现的问题 try{ //可能会出现异常的代码 }catch(Excaption e){ //异常的处理逻辑 } Error:错误,绝症,必须将程序停下来,修改代码才能解决
3.异常关键字throw ①.throw关键字必须写在方法的内部 ②.throw关键字后边new的对象必须是Exception或者Exception的子类对象 ③.throw关键字抛出指定的异常对象,我们就必须处理这个异常对象 throw后边创建的是RuntimeException或者是RuntimeException的子类对象,我们可以不处理,默认交给JVM去处理(打印异常对象,中断程序) throw后边创建的是编译异常(写代码的时候报错),我们就必须处理
4.throws关键字:异常处理的第一种方式,交给别人处理 ①使用格式:在方法声明时使用 修饰符 返回值类型 方法名(参数裂变) throws AAAException, BBBException...{ throw new AAAException("产生原因"); throw new BBBException("产生原因"); ..... } ②注意事项:a.throws关键字必须写在方法声明处
b.throws关键字后边声明的异常必须是Exception或者是其子类 c.方法内部如果抛出了多个异常对象,那么throws后边必须也声明多个异常 如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可 d.调用了一个声明抛出异常的方法,我们就必须得处理声明的异常 要么继续使用throws声明抛出,交给方法的调用者处理,最终交给JVM 要么try...catch自己处理异常
5.try...catch (捕获异常) :异常处理的第二种方式,自己处理异常 ①格式: try{ //可能产生异常的代码 }catch(定义一个异常的变量,用来接收try中抛出的异常对象){ //产生异常对象之后,怎么处理异常对象 //一般在工作中,会把异常的信息记录到一个日志中 } . . . catch(异常类名 变量名){ } ②注意: try中可能会抛出多个异常对象,可以用多个catch来处理 如果try中出现了异常,那就会执行catch中的异常处理逻辑,然后继续执行之后的代码 如果try中没有异常,那就不会执行catch中的异常处理逻辑,而是直接继续执行后面的代码 6.throwable中的3个异常处理方法 ①String getMessage( ): 异常的信息,没有原因返回null ②String toString( ): 异常的类型和原因信息 ③void printStackTrace( ): 使用标准错误输出流打印异常信息
7.finally代码块 格式: try{ 可能产生异常的代码 }catch(定义一个异常的变量,用来接收try中抛出的异常对象){ //产生异常对象之后,怎么处理异常对象 //一般在工作中,会把异常的信息记录到一个日志中 } . . . catch(异常类名 变量名){ }finally{ 无论是否出现异常都会执行 } 注意:①:finally不能单独使用,必须和try一起使用 ②:finally一般用于资源释放(资源回收),无论此程序是否出现异常,最后都要资源释放 8.异常注意事项: ( 1 )捕获多个异常的3个方式 ①多个异常分别处理(一个try捕获一个异常,并对应一个catch) ②多个异常一次捕获,多次处理(一个try捕获多个异常,并对应多个catch) 如果catch里面定义的异常变量有子父类的关系,那么子类的异常变量必须写在上边,否则会报错 ③多个异常一次捕获一次处理(一个try捕获多个异常,并对应一个catch) ( 2 )finally中有return语句 则永远返回该return语句的值,应该避免在finally中写return语句 ( 3 )子父类继承重写方法时的异常要求(简记:父类异常什么样,子类异常也就什么样) ①.如果父类抛出多个异常,子类重写父类方法时也抛出相同的异常,或者是父类异常的子类,或者不抛出异常 ②.如果父类没有抛出异常,子类重写该方法时也不可抛出异常.此时子类产生该异常,只能捕获处理,不能抛出 9.自定义异常: 格式: public class XXXException extends Exception / RuntimeException{ 添加一个空参数的构造方法 添加一个带异常信息的构造方法 } 注意: ①.自定义异常类一般都是以Exception结尾,说明该类是一个异常类 ②.自定义异常类,必须要继承Exception或者RuntimeException 如果继承的是编译时期的异常,则必须抛出或捕获处理 如果继承的是运行时期的异常,则不用管,让JVM去处理
三, 多线程 (一)计算机基本概念 1.并发和并行
并发:指两个或多个事件在同一时间段内同时发生
并行:指两个或多个事件在同一时刻发生(同时发生)
2.进程与线程
进程:一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;
线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程至少有一个线程
多线程的好处: 效率高,多个线程之间互不影响
3.线程的调度
分时调度:所有线程轮流使用cpu的使用权,平均分配每个线程占用cpu的时间.
抢占式调度:优先级高的优先使用cpu,若优先级相同,则随机选择一个线程执行. (Java使用)
4.主线程
主要执行的(main)线程
(二)创建多线程 1.第一种方式: 继承Thread类
①定义类,继承Thread类
②重写run( )方法,run方法内部是线程要执行的任务
③创建Thread子类的对象,调用start( )方法启动线程
a.注意: 必须调用start( )方法来开启线程,不能直接调用run( )方法,否则就会变成单线程.
同一个线程对象,不能多次调用start( )方法 b.Thread中的常用方法: (1)获取线程的名称: ①使用Thread类中的方法getname( )
String getName( ) 返回该线程的名称.
②可以先获取到当前正在执行的线程,使用线程的方法getName( )获取线程的名称
static Thread currentThread 返回对当前正在执行的线程对象的引用
2.第二种方式:实现Runnable接口
①定义类,实现Runnable接口
②重写run( )方法
③创建Runnable实现类对象
④创建Thread类对象,在构造方法中传入Runnable实现类对象
⑤通过Thread对象调用statrt( )方法启动线程
3.Thread和Runnable两种方式的区别
实现Runnable接口创建多线程程序的好处:
①避免了单继承的局限性 : 一个类只能继承一个类,如果继承了Thread类就不能继承其他类了,实现Runnable接口的方式还可以继承其他的类,实现其他的接口
②增强了程序的扩展性,降低了程序的耦合性 : 线程是Thread,任务是Runnable接口,相当于将线程和任务分开了
4第三种方式: 匿名内部类创建线程 匿名内部类的三种方式:代码如下 public class Test {
[AppleScript] 纯文本查看 复制代码
public static void main(String[] args) {
// 继承Thread类, 使用匿名内部类简化
new Thread("继承Thread类") {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}.start();
// 实现Runnable接口, 使用匿名内部类简化
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
};
new Thread(r, "可以设置名字").start();
// 更简单的方式
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}, "实现Runnable接口").start();
}
}
5.线程安全问题 : 多线程访问共享数据,CPU高速切换,在操作共享数据时还未完成,其他线程又进行操作 6.解决线程安全问题方式:
①同步代码块:使用synchronized关键字修饰的代码块,并传入一个当作锁的对象
格式: synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
②同步方法: 使用synchronized关键字修饰的方法,具有默认的锁对象
非静态同步方法的锁对象:this
静态同步方法的锁对象:本利的class属性-->class本间对象反射()
格式:修饰符 synchronized 返回时类型 方法名(参数列表){
可能会出现问题的代码;
}
拓展: 获取一个类的字节码对象的3种方式:
1.类名.class属性
2.对象.getClass( )
3.Class.forName(字符串的类的全名)
③锁机制:
[AppleScript] 纯文本查看 复制代码
java.util.concurrent.locks.Lock接口: JDK 5 新增的Lock接口 // 成员方法 void lock(): 获取锁 void unlock(): 释放锁 java.util.concurrent.locks.ReentrantLock类: Lock的实现类
// 成员变量创建锁对象, 该锁对象也要所有线程共享唯一一个 Lock lock = new ReentrantLock();
@Override
public void run() { // 加锁 lock.lock(); try { // 操作共享变量的代码... } finally { // 在finally中保证释放锁 lock.unlock(); } }
7.线程的状态:
1.1线程的6种状态与生命周期
线程的生命周期中, 可以出现有6种状态:
1. NEW 新建
线程被创建, 但没有调用 start() 启动
2. RUNNABLE 可运行
调用 start()方法后已启动, 但可能正在执行run()方法的代码, 也可能正在等待CPU的调度
3. BLOCKED 阻塞
线程试图获取锁, 但此时锁被其他线程持有
4. WAITING无限等待
通过锁对象调用无参的 wait() 进入此状态.
等待其他线程通过锁对象执行 notify() 或 notifyAll() 才能结束这个状态
5. TIMED_WAITING 计时等待
如通过锁对象调用有参的 wait(long millis) 或sleep(long millis), 则进入此状态.直到时间结束之前被其他线程通过锁对象执行notify()或notifyAll()唤醒, 或时间结束自动唤醒
6. TERMINATED 终止
run()方法结束(执行结束, 或内部出现异常), 则进入此状态
1.2 Object类中关于线程的方法:
// 成员方法 (只能通过"锁对象"调用)
[AppleScript] 纯文本查看 复制代码
void notify(): 随机唤醒在同一个锁对象上的某一个处于等待状态的线程 void notifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程 void wait(): 让当前线程处于无限等待状态 void wait(long timeout): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态 void wait(longtimeout, int nanos): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态