异常
程序在运行过程中发生的意外情况,称为异常。如:除数为0,访问下标不存在的数组元素等
异常是一种信号,用于向调用者传递信息,表示程序发生了意外情况。
程序运行时一旦出现了异常,将会导致程序立即终止,异常之后的代码将无法继续执行,所以需要对异常进行处理。
常见异常
不同的异常携带了不同的信息,表示发生了不同的意外情况
检查异常
所有继承自Exception类的异常,称为检查异常Checked Exception该类异常是可预期的,很有可能发生的编译器要求必须显式处理该异常,即编写代码时就强制要处理运行时异常所有继承自RuntimeException类的异常,称为运行时异常该类异常并不一定可预测发生如果代码没有逻辑性错误,是不会出现运行时异常编译器不要求必须处理该异常,即编写代码时可以不处理异常的产生和处理
产生
每种异常都是使用一个Java类来表示。
当程序发生异常时,会自动生成一个对应异常类的对象,然后将该异常对象提交给JRE,这个过程称为抛出异常throw
当JRE接收到异常对象时,会寻找能处理此异常的代码并把当前异常对象交给其处理,这个过程称为捕获异常catch
如果JRE找不到可以捕获异常的代码,则运行时系统将终止,程序将退出
(PS:所以需要对异常进行处理,否则程序将立即终止,无法继续执行。)
处理
异常处理的两种方式:
使用try…catch
使用try…catch…finally捕获并处理异常
try{//可能出现的异常代码}catch(异常类型 异常对象){//捕获异常//对异常进行处理的代码}finally{//无论是否出现异常都必须要执行的代码}
try是必须的,catch和finally至少要有一个catch可以有多个,用来捕获多个不同类型的异常
使用throws
如果一个方法可能会产生某种异常,但并不知道如何处理这种异常,此时可以声明该方法会抛出异常,表明该方法将不对这种异常进行处理,而由该方法的调用者来处理
使用throws和throw关键字:
throws用来声明方法中会抛出异常throw用来在方法内手动抛出异常
自定义异常
自定义异常时,需要继承Exception类或其子类
一般多继承自Exception或RuntimeException
如果继承Exception,则为检查异常,必须处理如果继承RuntimeException,则为运行时异常,可以不处理
方法重写的异常问题
方法重写时的异常问题
若父类不抛出异常,则子类不能抛出检查异常
,但可以抛出运行时异常
或在方法内部使用try…catch捕获处理异常若父类抛出异常,子类可以不抛出异常重写方法不能抛出比被重写方法范围更大的异常类型
异常的定位和解决
查找异常的出现的位置并解决:
首先查看没有Caused by,如果有则从Caused by开始找,如果没有则从头开始找然后找到第一行自己写的代码,问题就在这里最后根据Caused by或第一行的,所以行的异常类型和异常消息,确定产生异常的原因
File类
java.io.File
表示磁盘上的文件或目录
无论是文件还是目录谁都通过File类来表示(目录是一种特殊的文件)提供了对文件和目录的基本操作,如查看文件名,文件大小,新建或删除等File类不能访问文件的内容,如果要访问文件内容,需要使用输入/输出流
构造方法
路径分类:
绝对路径——以根开始的路径
Windows:盘符,如:D:\JAVA\Filetext\a.txt
Linux/MacOs:/正斜杠,如:/home/soft01/JAVA/Filetext/a.txt
关于路径分隔符——Windows使用\反斜杠来表示分隔符。Linux/MacOS使用/正斜杠来表示路径分隔符
(由于在Java中\表示转义符,所以在Java中表示Windows路径时需要使用\\
或使用/
来表示路径分隔符)
相对路径——不是以根开始的路径,相对于某个路径的路径
例子:…/Filetext/a.txt (.
表示当前目录,..
表示上一级目录)
//创建一个File对象,有4种方法//1.指定文件的全路径File file=new File("D:\\JAVA\\Filetext\\a.txt");File file=new File("D/JAVA/Filetext/a.txt");File file=new File("/home/soft01/JAVA/Filetext/a.txt");File file=new File("a.txt");//2.指定父目录的路径和文件名File file=new File("D:/JAVA/Filetext","code/a.txt");// 3.指定父目录的File对象和文件名File file=new File(new File("D:/JAVA/Filetext"),"a.txt");//4.指定URL统一资源标识符File file=new File(FileTest.class//获取类加载器.getClassLoader().//加载类路径下的文件,返回URL(Uniform Resource Locator统一资源定位符)getResource("data.properties")//转换为URL.toURI());//判断指定路径文件是否存在,存在则输出if(file.exists()){System.out.println(file);}
常用方法:
IO流
Input Output输入和输出流
通过IO流实现文件的输入和输出功能用于对文件进行读写的操作
流stream:可以理解为一组有顺序的,有起点和终点的动态数据集合
文件是数据在硬盘上的静态存储流是数据在传输时的动态形态
文件的分类
文本文件:基于字符编码的文件:.txt
.java
.xml
二进制文件:除了文本文件,其他所有文件都是二进制文件
建议参考链接:/u012501054/article/details/91543773
流的分类
按流的方向(站在Java的角度)
输入流:用于读取数据,比如从文件读取数据到程序中,由InputStream和Reader作为父类输出流:用于写出数据,比如将程序中的数据写出到文件中,有OutputStream和Writer作为父类
按流中的数据的单位
字节流byte:所操作的最小数据单元为字节,有InpitStream和OutputStream作为父类
字符流char:所操作的最小数据单元为字符,由Reader和Writer作为父类
(一个英文字符占1个字节,一个汉字占2个字节(GBK)或3个字节(UTF-8))
按数据的来源
节点流:直接对数据源进行操作,如操作文件包装流:对一个节点流进行操作(包装)
字节流
InputStream是字节输入流的顶层父类,常用子类有:FileInputStream
,ByteArrayInputStream
,ObjectInputStream
OutputStream是字节输出流的顶层父类,常用子类有:FileOutputStream
,ByteArrayOutputStream
,ObjectOutputStream
文件输入流输出流
FileInputStream
文件字节输入流:以字节为单位,从文件中读取数据
FileInputStream fis=null;try{fis=new FileInputStream(new File("a.txt"));//处于阻塞状态,读取一个字节,返回int类型的字节值,如果读取到末尾,这返回-1int data=-1;while((data=fis.read())!=-1){System.out.println((char)data);}} catch (IOException e) {e.printStackTrace();}finally {//需要判断是否为null,防止出现NullPointerExceptionif(fis!=null){try {//关闭输入流,只要是打开了外部的资源(文件,数据库连接,网络连接),在使用后都需要关闭,释放资源fis.close();}catch (IOException e){e.printStackTrace();}}}
FileOutputStream
文件字节输出流:以字节为单位,将数据写出到文件中
字节数组输入输出流
流(数据)的来源或目的地并不一定是文件,也可以是内存中的一块空间,例如一个字节数组
ByteArrayInputStream
字节数组输入流:从字节数组中读取数据,即将字节数组当作流输入的来源
ByteArrayOutputStream
字节数组输出流:将数据写出到内置的字节数组中,即将字节数组当作输出流的目的地
对象输入输出流
如果希望将Java对象写入到IO流中,或从IO流中读取Java对象,则要使用对象输入输出流,称为对象的序列化和反序列化
序列化和反序列化
序列化:将Java对象写入到IO流中,实现将对象保存在磁盘上或在网络中传递对象
反序列化:从IO流中读取Java对象,实现从磁盘上或网络中恢复对象
要求:
对象必须实现Serializable
接口,才能被序列化,转换为二进制流,通过网络进行传输
通过serialVersionUID
判断对象的序列化版本的一致性:
在反序列时,会将流中的serialVersionUID
与本地相应实体对象/类的serialVersionUID
进行比较
如果相同就认为版本一致,则可以进行反序列化
如果不相同,则会出现序列化版本不一致的异常InvalidClassException
ObjectInputStream
对象输入流:用来读取对象,即反序列化
ObjectInputStream
和ObjectOutputSream
属于包装流
用于对节点流进行功能扩展/包装在创建包装流,需要传入要操作的节点流对象当关闭流时,只需要关闭包装流,节点流也会被关闭
ObjectOutputStream
对象输出流:用来写入对象,即序列化
字符流
Reader是字符输入流的顶层父类,常用子类(FileReader
,BufferedReader
,InputStreamReader
)
Writer是字符输出流的顶层父类,常用子类(FileWriter
,BufferedWriter/PrintWriter,``OutputStreamWriter
)
文件输入输出流
FileReader
文件字符输入流:以字符为单位,从文件读取数据
FileWriter
文件字符输出流:以字符为单位,将数据写出到文件中
缓冲输入输出流
缓冲输入输出流属于包装流,为字符流添加缓冲的功能
当读取或写出数据时,先从缓冲区读取,减少对磁盘IO操作,提高效率
BufferedReader
缓冲字符输入流:为字符输入流添加缓冲
BufferedWriter
缓冲字符输出流:为字符输出流添加缓冲
PrintWriter
打印流,功能更强,操作更简单
转换流
用于将字节流转换为字符流,同时可以实现编码的转换
在转换时需要指定使用的字符集,如果不指定默认使用JVM的字符集
在Java中没有提供将字符流转换为字节流的方法,不支持该操作
InputStreamReader
将字节输入流转换为字符输入流
OutputStreamWriter
将字节输出流转换为字符输出流
RandomAccessFile
随机读写流,是一个字节流,可以对文件进行随机读写
随机:可以定位到文件的任意位置进行读写,通过移动指针(Pointer)来实现读写:使用该流既能读取文件,也能写入文件
用法
/* 文件模式:* r以只读的方式打开文本,也就意味着不能用write来操作文件(如果文件不存在,会报FileNotFoundException)* rw读操作和写操作都是允许的(如果文件不存在,会自动创建文件)* rws每当进行写操作,同步的刷新到磁盘,刷新内容和元数据* rwd每当进行写操作,同步的刷新到磁盘,刷新内容*/try(RandomAccessFile raf=new RandomAccessFile("x.txt", "rw");){//获取当前指针的位置,从0开始System.out.println(raf.getFilePointer());//当前使用utf-8,一个汉字占3个字节,一个字母占1个字节raf.write("张三".getBytes());raf.write("hello".getBytes());//11System.out.println(raf.getFilePointer());System.out.println("写入成功");//将指针移动到8位置raf.seek(8);raf.write("李四".getBytes());//14System.out.println(raf.getFilePointer());raf.seek(6);byte[] buffer=new byte[2];raf.read(buffer);//读取2个字节,指针对应后移,即到8的位置System.out.println(new String(buffer));System.out.println(raf.getFilePointer());//将指针向后跳过指定的字节,只能往前,不能倒退raf.skipBytes(3);buffer=new byte[1024*1024];int num=-1;while ((num=raf.read(buffer))!=-1){//四(在写入李四时将llo给覆盖掉了,使用slipBytes跳过3个字节即跳过了李)System.out.println(new String(buffer,0,num));}//修改数据raf.seek(8);raf.write("赵".getBytes());System.out.println("修改成功");} catch (IOException e) {e.printStackTrace();}
线程
进程
进程:在操作系统中独立运行的程序,每运行一个应用程序就对应着一个进程process
多进程:在操作系统中可以同时运行多个程序
线程
线程:线程是进程内部的一个执行单元,用来执行应用程序中的一个功能thread
多线程:在一个应用程序中可以同时执行多个功能。
特性:
一个进程中可以包含多个线程,且至少要有一个线程一个线程必须属于某个进程,进程是线程的容器一个进程中的多个线程共享该进程的所有资源
CPU时间片
对于单核CPU,在某个时间点只能处理一个程序
CPU分配给各个程序的时间,称为时间片,即该进程允许运行时间(时间很短)
从表面上看各个程序是同时运行的,实际上CPU在同一时间只能执行一个程序只是CPU在很短的时间类,在不同程序间切换,轮流执行每个程序,执行速度很快,所以感觉上像是同时在运行
创建线程
继承Thread类
定义一个类,继承自Thread类,重写run*()方法创建该类的实例,即创建一个线程调用start()方法,启动线程(不能直接调用run()方法)实现Runnable接口
定义一个类,实现Runnable接口,实现run()方法创建实现类的实例创建Thread类的一个实例,将上一步的实现类的实例传入调用start()方法,启动线程两种方式的对比
继承Thread类
线程执行的代码放在Thread类的子类run方法中无法再继承其他类
实现Runnable接口
线程执行的代码放在Runnable接口的实现类的run()方法中可以继承其他类,避免单继承的局限性适合多个相同程序代码的线程去处理同一个资源增强程序的健壮性
线程的生命周期
参考链接:/p/468c660d02da
线程安全问题
多个线程同时访问共享数据时可能会出现问题,称为线程安全问题(当多线程访问共享数据时,由于CPU的切换,导致一个线程只执行了关键代码的一部分,还没执行完此时另一个线程参与进来,导致共享数据发生异常)
**解决:**线程同步机制synchronized+锁
被synchronized包围的代码块,称为同步代码块被synchronized修饰的方法,称为同步方法锁,也称为对象锁,每个对象都自带一个锁(标识),且不同对象的锁是不一样的
执行过程:
当线程执行同步代码或同步方法时,必须获取特定对象的锁才行且一旦对象的锁被获取,则该对象就不再拥有说,直到线程执行完同步代码块或同步方法时,才会释放对象的锁如果线程无法获得特定对象上的锁,则线程会进入该对象的锁池中等待,直到锁被归还对象,此时需要改锁的线程会进行竞争
线程同步的优缺点
优点:解决了线程安全的问题,使代码块在某一时间只能被一个线程访问缺点:由于需要进行锁的判断,消耗资源,效率变低
解决:
两种方式:同步代码块,同步方法
synchronized(this){if(num>0){try{Thread.sleep(10);}catch (InterruptedException e){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"-----出售车票"+num--);}}}public synchronized void sellTicket(){if(num>0){try{Thread.sleep(10);}catch (InterruptedException e){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"-----出售车票"+num--);}}
线程间的通信
锁池和等待池
每个对象都自带锁池和等待池
锁池
当线程执行synchronized块时如果无法获取特定对象上的锁,此时会进入该对象的锁池当锁被归还给该对象时,锁池中的多个线程会竞争获取该对象的锁获取对象锁的线程将执行synchronized块,执行完毕后会释放锁等待池:
当线程获取对象的锁后,可以调用wait()
方法放弃锁,此时会进入该对象的等待池当其他对象调用该对象的notify()
或notifyAll()
方法时,等待池中的线程会被唤醒,会进入该对象的锁池当线程获取对象的锁后,将从它上次调用wait()
方法的位置开始继续运行相关方法
注意:
这三个方法都只能在synchronized块中使用,即只有获取了锁的线程才能调用等待和唤醒必须使用的是同一个对象
生产者-消费者问题
生产者-消费者问题是多线程同步的一个经典问题,即并发协作的问题。
所谓生产者-消费者问题,实际上主要是包含了两种线程:生产者线程,消费者线程
生产者线程:
生产商品并放入缓冲区
当缓冲区满时,生产者不可再生产商品
消费者线程:
从缓冲区中取出商品当缓冲区为空时,消费者不可再取出商品(注:生产者和消费者使用的是同一个缓冲区)
实现:
@Overridepublic void run() {while(true){synchronized (pool){if(pool.isFull()){try{pool.wait();}catch (InterruptedException e){e.printStackTrace();}}else {pool.put();System.out.println(this.name+"生产了一个商品,现在商品数量:"+pool.getNum());pool.notifyAll();}}try{Thread.sleep(3000);}catch (InterruptedException e){e.printStackTrace();}}}
@Overridepublic void run() {while(true){synchronized (pool){if(pool.isEmpty()){try{pool.wait();}catch (InterruptedException e){e.printStackTrace();}}else {pool.get();System.out.println(this.name+"消费了一个商品,现在商品数量:"+pool.getNum());pool.notifyAll();}}try{Thread.sleep(2000);}catch (InterruptedException e){e.printStackTrace();}}}
线程单例
为每个线程提供一个实例
同一个线程获取的是一个实例不同线程获取的是不同的实例
Java中提供了一个ThreadLocal,直接提供了线程单例的解决方案
用于管理变量,提供了线程局部变量它为变量在每个线程中都存储了一个本地的副本
实现:
public class MyThreadLocal<T> {private Map<Thread,T> map=new HashMap<>();public void set(T t){//将当前线程作为Keymap.put(Thread.currentThread(), t);}public T get(){return map.get(Thread.currentThread());}}