day07【排序算法、异常、多线程基础】
今日目标:
(独立完成)a.排序算法(冒泡,选择) 二分查找
(理解)b.异常(理论很多,实际应用很少)
(重点掌握)c.多线程基础
一. 冒泡排序
什么是排序:
将一个乱序的数组,按照从大小到或者从小到大进行排序
冒泡排序:
核心思想:依次比较相邻两个元素
- 过程图解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-trSdDDPv-1577451009462)(img/image-20191225085720810.png)]
-
代码实现
public class BubbleSortDemo { public static void main(String[] args) { //冒泡排序 int[] nums = {1, 4, 6, 7, 8, 3, 5, 2, 9}; //排序 //外层循环:控制轮数 for (int i = 0;i < nums.length - 1;i++) { //内存循环:控制次数 for(int j = 0;j < nums.length-1-i;j++) { //比较 nums[j] nums[j+1] if (nums[j] > nums[j + 1]) { //交换 int temp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = temp; } } } //打印 System.out.println(Arrays.toString(nums));//"[元素1,元素2,元素3,....]" } }
二. 选择排序
核心思想:
选定一个元素不动,依次和该元素后面的元素进行比较
- 过程图解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WG9BTrqL-1577451009463)(img/image-20191225092428344.png)]
-
代码实现
public class SelectorSort { public static void main(String[] args) { //选择排序 int[] nums = {1, 4, 6, 7, 8, 3, 5, 2, 9}; //排序 //外层循环: 控制选中的元素 for (int i = 0;i < nums.length-1;i++) { //内存循环: 控制用于比较的元素 for (int j = i + 1; j < nums.length;j++) { //比较 if (nums[i] > nums[j]) { //交换 int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } } } //打印 System.out.println(Arrays.toString(nums)); } }
三. 二分查找
二分查找:
前提: 必须是排好序的数组或者集合才能使用二分查找
需求: 从一个已经排好序的数组中查找某个元素的索引,如果没有找到返回-1
- 过程图解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZAjRFxCL-1577451009464)(img/image-20191225095906044.png)]
-
代码实现
public class BinarySearch { public static void main(String[] args) { //排好序的数组 int[] arr = {1, 4, 5, 6, 8, 11, 22, 34, 56, 78, 100, 122}; //查找的元素(键入) int key = 122; //调用方法,查找key在arr中索引 int index = binarySearch(arr, key); System.out.println(index); } public static int binarySearch(int[] arr, int key) { //1.两个下标(索引,角标,index) int min = 0; int max = arr.length-1; while(min <= max) { //2.取中间值 int mid = (min + max) / 2; //3.比较 if (arr[mid] > key) { //中间值 大于 目标值 max = mid - 1; } else if (arr[mid] < key) { //中间值 小于 目标值 min = mid + 1; } else { //相等 return mid; } } return -1; } }
四.异常
1.什么是异常
程序出现的问题
2.异常的继承体系
Throwable -- 异常的根类
|- Error 错误类
错误是非常严重的问题(硬件或者系统问题),通常我们程序员解决不了
|- Exception 异常类
异常是由程序员代码编写不当引起的,可以解决的
|- 编译时异常: 在代码编译阶段出现的异常,称为编译时异常
Exception类,以及他的子类(RuntimeException除外)
|- 运行时异常: 在代码编译阶段不报异常(就算有也不报异常),运行时期才报出异常
RuntimeException类,以及他的子类
3.异常类中常用的三个方法
"public void printStackTrace(); 高亮打印某个异常内部详细(异常的类型,异常的原因,异常的位置)信息
public String getMessage(); 获取异常的原因
public String toString(); 重写了Object中toString方法
4.异常的分类
-
编译时异常
Exception类以及其子类(RuntimeException除外) 编译时就会报错,必须处理,否则无法继续编译
-
运行时异常
RuntimeException类以及其子类 编译时不会报错(就算有,也不会报错!!),运行时再报出异常
5.JVM异常产生过程(画图演示)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TD1TbSc5-1577451009464)(img/image-20191225104408816.png)]
五.异常处理
1.模拟JVM抛出异常(关键字throw)
public class ThrowDemo {
public static void main(String[] args) {
int[] arr = {1};
//调用
printArr(arr);
}
public static void printArr(int[] arr) {
//1.判断是否为null
if (arr == null) {
//模拟JVM抛出异常
throw new NullPointerException("哥们,您的数组是null");
}
//2.判断数组是否越界
if (arr.length <= 1) {
//模拟JVM抛出异常
throw new ArrayIndexOutOfBoundsException("哥们,您的数组长度似乎不够~~");
}
//取出数组的第二个元素
int number = arr[1];
System.out.println(number);
}
}
模拟JVM抛出异常:
throw new XxxException("异常的原因");
2.Objects类中提供的非空判断方法
public static T requireNonNull(T obj){
if(obj == null){
throw new NullPointerException();
}
return obj;
}
3.遇到异常的2种处理方式***************
①throws声明抛出异常
throws的作用: 给方法用的,给方法做声明,声明该方法内部可能会出现某种异常,
同时要求该方法的调用者必须处理该异常
throws的格式:
public 返回值类型 方法名(参数) throws XxxException,OooException,...{
方法体;
return 返回值;
}
表示该方法内部可能抛出XxxException,OooException
调用该方法:
方法名(实际参数); 调用该方法时必须处理这些异常
遇到异常的第一种处理方式:
再次声明抛出(不处理!!)
②try…catch捕获异常
try..catch的作用: 将方法可能抛出的异常捕获到,程序员自己处理,程序不会停止
try..catch的格式:
try{
可能有异常的代码
}catch(异常的类型 变量名){
变量名.printStackTrace();
}
System.out.println("程序继续执行...");
代码实现:
public class ThrowsDemo {
public static void main(String[] args) {
try {
readFile("a.txt"); //第二种处理方式,称为捕获处理
}catch (FileNotFoundException fnfe){
//处理异常
fnfe.printStackTrace();
}
System.out.println("程序结束了...");
}
//定义一个方法:读取文件
public static void readFile(String filename) throws FileNotFoundException {
//假设:现在只有一个文件 1.txt
if ("1.txt".equals(filename)) {
System.out.println("读取文件成功....");
} else {
//抛出异常
throw new FileNotFoundException("文件" + filename + "没有找到...");
}
}
}
4.[扩展1]catch代码块中三种常见的处理方式
a.在开发阶段,直接调用异常的对象printStackTrace方法,打印出来
b.在测试阶段,把异常的信息保存到异常日志或者数据库中,以便查看
c.在上线阶段,把异常的信息保存到异常日志或者数据库,当适当时候自动上传服务器
5.[扩展2]多个异常如何获取处理
我们写了三句代码,每句代码都会抛出一个异常:
method1(); // throws OneException;
method2(); // throws TwoException;
method3(); // throws ThreeException;
我们该如何捕获异常:
a.每个异常,单独try和catch
try{
method1(); // throws OneException;
}catch(OneException oe){
oe.printStackTrace();
}
try{
method2(); // throws TwoException;
}catch(TwoException te){
te.printStackTrace();
}
try{
method3(); // throws ThreeException;
}catch(ThreeException tte){
tte.printStackTrace();
}
b.所有异常一起try,分开catch
try{
method1(); // throws OneException;
method2(); // throws TwoException;
method3(); // throws ThreeException;
}catch(OneException oe){
oe.printStackTrace();
}catch(TwoException te){
te.printStackTrace();
}catch(ThreeException tte){
tte.printStackTrace();
}
注意:如果异常中有子父类关系,那么子类异常必须写在前,父类异常写在后
c.所有异常一个try,一个catch[最常用]
try{
method1(); // throws OneException;
method2(); // throws TwoException;
method3(); // throws ThreeException;
}catch(Exception oe){
oe.printStackTrace();
}
6.finally代码块
finally: 最终的,最后的,代表必须要执行的代码块
格式:
try{
可能有异常的代码
}catch(XxxException ee){
ee.printStackTrace();
}finally{
写必须要执行的代码;
写释放资源有关的代码
}
7.异常的注意事项
a.编译时异常,编译阶段必须throws或者trycatch,运行时异常,编译阶段即不需要throws也不需要trycatch
b.如果父类方法抛出了100个异常, 子类重写该方法时,最多也只能抛出这100个异常,或者其中的一部分异常
c.如果父类的方法没有抛出异常, 子类重写该方法时,也不能抛出异常
d.当一个try多个catch处理异常时,必须保证前面的类不能是后面的类的父类(子在前,父在后)
e.如果有多个异常,有三种处理方式,见"[扩展2]多个异常如何获取处理"
f.如果有资源需要释放,那么写在finally代码中
六.自定义异常
1.为什么要定义异常
因为java中定义异常,不可能描述我们开发中所有可能出现的情况
2.自定义异常的步骤
a.创建一个异常类,该类必须叫XxxException
b.继承Exception或者RuntimeException
c.必须提供两个构造(无参构造+带有String类型异常信息的构造方法)
/**
* 自定义异常:a.类名必须以Exception结尾
* b.继承Exception,或者 RuntimeException
*/
public class MyException extends /*Exception*/RuntimeException {
//c.两个构造
public MyException(){
}
public MyException(String message){
super(message);
}
}
3.自定义异常的练习(代码演示)
/**
* 注册异常:描述注册时用户名相同的情况
*/
public class RegisterException extends Exception{
public RegisterException(){}
public RegisterException(String message){
super(message);
}
}
自定义异常的练习:
public class RegisterDemo {
public static void main(String[] args) {
//开始注册
System.out.println("请输入用户名:");
String username = new Scanner(System.in).nextLine();
//调用方法
try {
register(username);
} catch (RegisterException e) {
e.printStackTrace();
}
}
/**
* 注册方法
*/
public static void register(String username) throws RegisterException {
//假设jack已经被注册过了
if ("jack".equals(username)) {
throw new RegisterException("亲,该用户名已经被注册");
}else{
System.out.println("恭喜您"+username+"注册功能");
}
}
}
七.多线程************
1.并行和并发
并行:
两个事件,在同一时刻都在执行
并发:
两个事件,在同一时间段内都在执行
2.进程和线程
进程:
正在内存中运行的程序
线程:
进程中用于完成某个特定功能的执行单元
-
进程和线程的一些区别(了解):
进程之间是独立的,包括独立的栈和独立的堆 线程之间栈(从进程中申请)是独立的,堆(进程的堆)是共享的
-
线程调度
线程调度: CPU在多个线程之间进行快速切换 线程并发的原理:就是线程调度 线程调度分类: 分时调度: 每个线程拼接分配CPU的执行时间 抢占式调度: 每个线程随机分配CPU的执行时间, 具体的方案:根据线程的优先级,优先级越高的线程抢到CPU的概率越高 我们Java采用抢占式调度
3.Thread类**************
一个Java进程中默认至少有三个线程:
主线程(main方法所在的线程)
GC线程(回收堆没用的对象)
异常线程(打印异常信息)
我们是否可以自己创建一个新的线程呢??????????????????????
有!! Java中提供一个代表线程的类:Thread
Thread的构造方法:
public Thread(); 创建一个线程对象,线程会具有默认的名字 Thread-0
public Thread(String name); 创建一个线程对象,指定该线程的名字
public Thread(Runnable r);
public Thread(Runnable r,String name);
Thread的成员方法:
public String getName(); -- 获取线程名字
public void setName(String name); -- 修改线程名字
public void run();-- 这个方法是用来写线程的任务代码
public void start(); -- 启动线程
Thread的静态方法:
public static void sleep(long 毫秒值); -- 当前线程"暂停"x毫秒的时间
这里的当前线程是指: Thread.sleep(毫秒)方法在哪个线程中写的
public static Thread currentThread(); -- 获取当前线程对象
这里的当前线程是指: Thread.curentThread();方法在哪个线程中写的
需求:
编写两个循环:
一个循环叫做听歌
一个循环叫做吃饭
要求:两个循环交替执行
public class ThreadDemo {
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
System.out.println("听歌"+i);
}
for (int i = 0; i < 20; i++) {
System.out.println("吃饭"+i);
}
}
}
以上程序无法做到吃饭和听歌一起执行,因为他们在同一个线程中,只能从上到下执行
怎么解决?? 创建一个新的线程,把其中一个循环放到新的线程中即可
4.创建新的线程方式一_继承方式******
API:一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例
分析步骤:
a.继承Thread
b.子类重写run方法(方法中写该线程需要执行的任务代码)
c.创建子类对象(创建了一个线程)
d.启动线程(调用start方法)
/**
* 1.继承Thread
*/
public class SubThread extends Thread {
//2.重写run方法,写该线程需要执行的任务
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("吃饭"+i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
//3.创建线程对象
SubThread st = new SubThread();
//4.启动线程
st.start();
for (int i = 0; i < 20; i++) {
System.out.println("听歌"+i);
}
}
}
扩展:
a.获取线程的名字:
i.如果是子线程,在子线程中直接getName即可(只能在子线程中使用)
ii.如果是主线程,在主线程中Thread.currentThread().getName()(通用的方式)
b.设置线程名字:
通过子类的构造方法(自己添加)可以做到
public class SubThread extends Thread {
public SubThread(){}
public SubThread(String name){
super(name);
}
}
通过对象的setName也可以
5.创建新的线程方式二_实现方式**************
API:另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动
分析步骤:
a.创建实现类,实现Runnable接口
b.实现类重写run方法(写线程的任务代码)
c.创建实现类对象
d.在创建Thread对象时,传入实现类对象
e.启动线程
/**
* a.创建实现类,实现Runnable接口
*/
public class MyRunnable implements Runnable{
// b.实现类重写run方法(写线程的任务代码)
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("吃饭"+i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// c.创建实现类对象
MyRunnable mr = new MyRunnable(); //此对象不是线程对象,而是一个任务对象
// d.在创建Thread对象时,传入实现类对象
Thread t = new Thread(mr);
// e.启动线程
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("听歌"+i);
}
}
}
扩展:
a.获取线程名字
只能使用通用方式:Thread.currentThread().getName()
b.设置线程名字
通过构造方法
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr,"来福");
也可以setName方法
6.匿名内部类简化线程创建方式***************
匿名内部类:
快速创建一个类的子类对象或者一个接口的实现类对象
public class NIMingDemo {
public static void main(String[] args) {
//1.使用继承方法
new Thread(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"吃饭.."+i);
}
}
}.start();
//2.使用实现方式
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"听歌.."+i);
}
}
}).start();
//3.主线程的任务
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"跳舞.."+i);
}
}
}
重点:在开发中我们一般都使用匿名内部类方式创建线程对象
总结
能够理解冒泡排序的执行原理
能够理解选择排序的执行原理
能够理解二分查找的执行原理
=================在理解的过程的基础上,把代码背下来========================
能够辨别程序中异常和错误的区别
异常:程序员编写代码不当造成的问题,程序员可以修改
错误:使用系统或者硬件引起的问题,程序员没办法
说出异常的分类
Throwable
|- Error
|- Exception
|- 编译时异常: Exception以及其子类(RuntimeException)
|- 运行时异常: RuntimeException以及其子类
列举出常见的三个运行期异常
ArrayIndexOutOfBoundsException
NullPointerException
ClassCastException
ConcurrentModificationException 并发修改异常
NoSuchElementException
"能够使用try...catch关键字处理异常
遇到编译时异常,我们可以使用try..catch捕获异常
"能够使用throws关键字处理异常
遇到编译时异常,我们可以在方法上throws继续向上抛出
能够自定义并使用异常类
a.创建一个类,XxxException
b.继承Exception或者RuntimeException
c.两个构造(无参+String参数)
"说出进程和线程的概念
进程:正在内存中运行的程序
线程:进程中完成某个功能的执行单元
"能够理解并发与并行的区别
并发: 交替执行
并行: 真的一起执行
能够描述Java中多线程运行原理
线程的调度:CPU在多个线程之间进行快速切换
"能够使用继承类的方式创建多线程
public class 子类 继承 Thread{
重写run方法
}
创建子类对象
子类对象开启线程
"能够使用实现接口的方式创建多线程
public class 实现类 实现 Runnable{
重写run方法
}
创建实现类对象(任务对象)
创建线程对象,同时传入任务对象
开启线程
实现方式比继承方式好!!!
a.实现方式 弥补 继承方式的不足(因为Java只能单继承)
b.实现方式更加灵活(耦合性更低)
c.线程池中只能接收任务对象
总之:尽可能使用实现方式
"能够使用匿名内部类创建线程
继承和实现方式必须会用匿名内部类编写