1. 异常机制
1.1 简介
- Throwable是所有错误和异常的超类。
- Error 是严重的JVM无法解决的问题,无法通过编程解决
- Exception 类是指一些轻微的可以编程解决的问题
1.2 分类
-
Throwable 是 Error 和 Exception 的父类。
-
Error:
目前唯一见过的Error,NoClassDefFoundError 继承关系:Error --> LinkageError --> NoClassDefFoundError
-
Exception:
-
运行时异常,RuntimeException 的子类
包括:ArithmeticException、NullPointerException、ClassCastException等
编译时检查不出来,编码时不会要求必须进行处理,在运行时可能出现,
-
检测异常,除RuntimeException之外的其他异常。
如 IOException
指编译时可以检测出来的异常,编写代码时,如果存在这些异常就会标红,必须处理了才能编译。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PKHQu5RC-1606327865373)(C:/Users/%E9%BB%98%E5%B0%98/Desktop/image-20201120133225305.png)]
-
1.3 异常捕获,执行流程
-
构造方法 中 throws 抛出了异常,那么对象是不会被创建出来的,也就是说,对象在构造完成后才能产生(或者是这时候我们才能拿到????)
-
考点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d0DyD71p-1606327865376)(C:/Users/%E9%BB%98%E5%B0%98/Desktop/image-20201120140238103.png)]
1.4 继承中的异常抛出
子类throws的异常 不能 比父类throws的更大。
也就是为了保证按照父类抛出的异常进行处理,可以解决子类抛出的异常。
1.5 throws 和 try…catch 的使用场景
- 父类没有写throws时,子类只能 try…catch 处理
- 当有连续的多层函数调用时,推荐使用throws并在 顶层 统一处理。
1.6 自定义异常
-
定义要求
- 继承Exception类或其子类。
- 定 义一个无参构造和一个有参构造。
public class MyException extends Exception{ public MyException(){} public MyException(String massage){ super(message); } }
-
产生并抛出异常
... public void func() throws MyException{ // 注意 ... throw new MyException("***异常发生了。"); ... }
1.7 异常机制的意义
将异常处理的程序代码集中在一起,与正常的代码分开,使得程序简洁、优雅并易于维护。
目前使用过的异常机制,应该都是被迫使用,它的好处当真是还没有体会到,不过也不急,毕竟现在自己的代码量还是太少啊 ! 没有经历过真实的应用场景,就不要谈意义。
2. 文件操作
2.1 File类
-
作用:描述文件及目录的信息
-
常用方法:
File(String path); File(String parent, String child); File(File parent, String child); boolean exists(); String getName(); long length(); // 注意直接就是 length long lastModifed(); String getAbsolutePath(); File getAbsoluteFile(); String getParent(); File getParentFile(); // 若是相对路径,无法获取到给定路径之前的路径 boolean delete(); // 删除文件夹必须为空,否则返回false, 文件不存在也是返回false,不会报异常 boolean createNewFile(); // 文件路径 不存在 就会 报IOException异常。 !!! boolean mkdir(); // 父路径不存在时返回 false boolean mkdirs(); File[] listFiles([FileFilter filter]); public String list([FileFilter filter]); // 注意只是子文件名,不是完整路径 boolean isFile(); boolean ifDirectory();
实例:
file.listFiles(new FileFilter(){ @Override public boolean accept(File file){ return file.isFile(); // 返回 true 则保留。 } });
-
注意:
文件与文件夹也不能重名,这是系统的限制。创建重名对象时返回false。
-
案例:
- 如何通过File获取当前路径?
String path = new File("").getAbsolutePath(); // 获取的应该是启动启动程序时的路径 A.class.getResource("/").getPath(); // 类路径的 根目录 A.class.getResource("").getPath(); // A类的class文件所在路径
2.2 IO流简介
-
基本分类
-
字节流、字符流
-
输入流、输出流
-
节点流、处理流
节点流:直接与输入输出 源 对接的流。
处理流:建立在节点流基础之上的流。
-
-
结构框架
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MRlYw7Wf-1606327865377)(C:/Users/%E9%BB%98%E5%B0%98/Desktop/image-20201120165628842.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i6AEFALK-1606327865380)(C:/Users/%E9%BB%98%E5%B0%98/Desktop/image-20201120170048991.png)]
2.3 字节流
字节输入流:InputStream 【抽象】
字节输出流:OutputStream 【抽象】
这两个是所有字节流的超类。直接子类:FileOutputStream 、ByteArrayOutputStream 。。。
2.3.1 FileOutputStream
-
构造方法
public FileOutputStream(String path); public FileOutputStream(File path); // 以上两个方法都可以再跟一个参数 Boolean append,默认false,表示新建文件以写入数据,设置为true时,表示向已有文件追加 // 如果文件不存在都会先创建,如果父目录不存在,报FileNotFoundException
-
write方法
void write(int b); // 注意 int只能写入底八位其他的会被舍弃 void write(byte[] b); void write(byte[] b,int offset,int len);
注意:默认换行符:window: \r\n linux: \n mac: \r
-
其他
void close(); void flush();
2.3.2 FileInputStream
-
构造方法
public FileInputStream(File path); public FileInputStream(String path);
-
read方法
int read(); // 注意它的返回值也是int类型,虽然只有底八位 int read(byte[] buff); // 如果读完了 返回值 都是 -1 int available(); // 获取文件的大小
-
其他方法
long skip(long n); // 跳过 n 个字节,据测试返回的都是n,不知道返回值有什么用。
2.3.3BufferedInputStream & BufferedOutputStream
-
字节缓冲流是对其他IO流的封装(包括但不限于文件IO流),使其具备缓冲区的功能。
-
优点
- 在他们的内部自动维护一个缓冲数组,默认大小8192B,以此来减少对底层的读写次数,可以提高效率。
- 增加了Mark的功能【暂时还未用到】。
-
构造方法:
public BufferedInputStream(InputStream in,[int size]); public BufferedOutputStream(OutputStream out,[int size]); // 是对其他IO流的封装,size 为缓冲区的大小,默认8192,也可自行指定
-
读写
// 读写同 FileInputStream FileOutputStream
-
如下是复制一个视频的用时
// 基本字节流 1 // 245390 // 基本字节流 1024 // 437 [8192 140] [8192+1024 124] // 缓冲字节流 1 // 1828 // 缓冲字节流 1024 // 125
可见使用缓冲流确实可以提高效率【底层调用减少了】
但是先要提高效率,好像直接加大缓冲数组就好了呀,为啥要有缓冲流呢?他到底有什么优势。-------或许是我没有数据操作的原因。
2.4 字符流
2.4.1 字符编码
-
常用字符集
ASCII、Unicode、GB系列
注意:Unicode是一个字符编码解决方案,它给每一个字符编了一个号,也就是一个十进制的数字。那怎么将这个号存入计算机呢?为此,就有了utf-8,utf-16,utf-32三种编码,它们通过自己的规则将字符的Unicode号转换为二进制存入内存。
GB系列包括:GBK和GB2312,GBK完全兼容GB2312。
Unicode这个号,到底存不存在,Java中字符串是不是用的这个号。如果是字符不可能用两个字节就能完全存下,那么java中可不可能出现不能表示的字符。??????
java 中的编码问题:java文件编码、JDK默认编码(编译时可指定encoding)、JVM默认编码(getBytes是使用的默认编码,目前不会自行指定)
这是一个复杂的问题,要慢慢解决,先要逐渐明确各种编码区别联系,还要了解java的编解码都在什么时候发生。
-
java字符串编解码方法
"".getBytes(String charset); // 编码 new String(byte[] data,String charset); // 解码 // charset可以省略,使用平台默认字符集, // 平台的默认字符集,也就是JVM默认编码,不同平台不一样,也是可以认为指定的。
2.4.2 Reader、Writer
-
为什么需要字符流:如上所述字符的编码是很复杂的,如果让我们在InputStream中拿到数据后,自己转化为字符串,这时自己面对各种各样的编码,着实不太美妙。
-
字符流不是什么新东西,他就像BufferedInputStream一样,是对字节流的封装,只不过是提供了一些特殊功能而已。
也就是说,java的IO流就两种InputStream、OutputStream,其他的要么是它们的子类,也就是提供这俩东西的实现;要么就是对这两个东西的封装,即实现特殊功能,如缓冲,字符编解码。
-
抽象基类:Reader、Write,这俩东西也就是一个规范,即说明字符流应该具有哪些功能。
2.4.3 InputStreamReader & OutputStreamWriter
-
构造方法
public InputStreamReader(InputStream in, String charset); public OutputStreamWriter(OutputStream out, String charset); // 注意:charset指定编解码字符集,可有可无默认应该是JVM的默认编码,应该是平台相关,但是没试过。
-
读写方法
// 写 void write(int c); // 给定字符的Unicode编码,也就是\u后边的东西,可以有字符强转为int得到,用的应该不多。 void write(char[] cbuff); void write(char[] cbuff,int offset,int len); void write(String s); void write(String s,int offset,int len); void flush(); // 它是由缓冲的。 // 读 int read(); // 读一个字符,虽然返回值是int但强转为char就行。 也就是说,输入字符流,已经把字符转化为Java字符串默认的Unicode编码。 int read(char[] cbuff); int read(char[] cbuff,int offset,int len); // offset 表示char[]中的便宜,len表示最多读几个字符 public String(char[] date,int offset,int len); // 将字符数组转换为字符串
2.4.4 FileReader & FileWriter
-
构造方法
public FileReader(String file); public FileReader(File file); public FileWriter(String file, boolean append); public FileWruter(File file, boolean append); // append 可省,默认false。 // FileReader和FileWriter使用平台默认字符集,无法指定编码类型。 // FileReader和FileWriter都是对 对应 InputStreamReader和OutputStreamWriter的分装,且只不过是改了一下构造方法而已,其他的两者完全一样。
-
作用:
只是为了简化创建代码,且命名格式上与FileInputStream和FileOutputStream对应而已。
2.4.5 BufferedReader & BufferedWriter
-
与BufferedInputStream 对应,添加了缓冲功能。
-
作用:当然可以提高读写效率,更重要的是他提供了一些针对字符IO的方便方法,如读一行。
-
构造方法
public BufferedReader(Reader in,int size); public BufferedWriter(Writer our,int size); // size 可省,默认8192 // 只有这4种构造方法。
-
读写方法
// 写方法 // 同OutputStreamWriter,5种方法,int,char[] 2种,String 2种。 // 特有写方法 public void newLine(); // 输出 系统项关 的换行符 public void flush(); // 读方法 // 继承自Reader,可以使用read()和read(char[]) // 特有读方法 public String readLine(); // 读一行,不包括换行符,读完后返回null void newLine();
2.4.6 PrintStream
PrintStream(OutputStream out);
void print(String s);
void println(String s);
void flush();
void close();
作用:System.out就是 PrintStream 的实例。将各种数据进行格式化输出,使用上就跟向控制台输出一样。
2.4.7 PrintWriter
PrintWriter(Writer out);
// 用法上与PrintStream相似。
2.4.8 DataInputStream & DataOutputStream
// 构造方法
DataInputStream(InputStream is);
DataOutputStream(OutputStream os);
void writeInt(int v);
int readInt();
void close();
作用:
提供了将各种基本数据类型的数据,直接写到流中的方法,然后可以用对应的read方法读出。
优点就在于读写时 不用进行数据类型和数据格式的转化。
重要的文件不会使用这种随意的格式进行读写,快捷的保存临时数据也有Object-io可以选择。所以它的用处就不是很大。
注意:
writeInt(10) 与 write(10) 的区别,前者写四个字节,后者写一个字节。
2.4.9 ObjectInputStream & ObjectOutputStream
-
注意:
读写对象的类型必须实现 java.io.Serializable 接口来开启类的序列化功能。所谓序列化就是将一个对象 需要存储的相关信息 有效组织成 字节序列 的过程。逆向过程就称之为反序列化。
-
常用方法:
ObjectOutputStream(OutputStream out); ObjectInputStream(InputStream in); void writeObject(Object obj); Object readObject(); void close();
-
序列化版本号
序列化版本号的作用是什么?
标识这个类是否发生改变,序列化时 版本号会一起写入文件,反序列化时,会判断文件中的版本号与类中的版本号是否一致,如果不一致就认为类的结构发生了变化,这时候就会报 InvalidClassException 。所以serialVersionUID应该与类结构对应,当对类进行了修改后(如加了一个字段),就应该换一个新的版本号。
版本号一致就可以转换,类中如果有新增的字段会以默认方式初始化,文件比类中多字符段也不会报错。一般写版本号的目的就是保证类修改后依然可以反序列化。
如何生成序列化版本号?
因为版本号只是当前类结构的一种标识,所以随便写一个就好,只要保证类修改后换一个新的版本号行。但是每次修改类都要自己编一个版本号也比较麻烦,所以方式二就是通过IDE生成,IDE会自动根据当前类的内容生成一个版本号,每次修改类后只要重新生成一次就好,不用担心会不会和以前的版本号重复。
为什么不写也可以?
可以发现实现serializable接口后,不显示书写serialVersionUID也能行啊?如果不写,编译器在编译时自动根据类的内容计算并添加一个版本号。这时只要修改类的结构,增删字段或方法就会导致版本号不同(方法的内容修改不会导致版本号不同)。但是多数情况下我们并不希望它如此敏感。所以一般都会写serialVersionUID,来自行控制版本问题。
-
transient关键字
用于标识不需要进行序列化的字段。
private transient String name;
这时name字段序列化时就不会写入文件,反序列化时会以默认方式初始化,也就是 null 。
-
经验
同一个文件中可以写入多个对象,但读取时无法判断是否读到文件末尾,如果读到了末尾还进行读取,会报EOFException。所以一个文件中保存多个对象时,可以将多个对象放入一个ArrayList对象中,一起保存。
2.4.10 RandomAccessFile
-
简介
实现文件的随机读写。
-
常用方法
RandomAccessFile(String path, String mode); // mode 取值:r 只读,rw:读写,rwd:读写,同步文件内容更新,rws:读写,同步文件内容和原数据的更新 int read(); void seek(long pos); // 从开头向后偏移pos个字节 void write(int b); // 输出一个字符,并覆盖原有字符 close();
此类由于在文件的指定位置进行操作,目前还用不明白,等需要了再说吧。
2.4.11 问题
-
为什么使用字符流复制图片会失败?
-
问题描述:字符流就是在字节流的基础上添加了字符的编解码功能,那么 解码、编码 两相对应不应该内容不变吗?
-
实验:
public static void main(String[] args) throws IOException { // 自己输出一个二进制文件, FileOutputStream out = new FileOutputStream("C:\\Users\\默尘\\Desktop\\test01.png"); byte[] data = {-128,-128,-128,-128,-128,-128,-128,-128,-128,-128,-128}; out.write(data); out.close(); // 使用字符流拷贝这个 字节二进制文件 FileReader fr = new FileReader("C:\\Users\\默尘\\Desktop\\test01.png"); FileWriter fw = new FileWriter("C:\\Users\\默尘\\Desktop\\test02.bmp"); int buff = 0; while(-1!=(buff=fr.read())){ fw.write(buff); } fw.close(); fr.close(); // 使用InputStream读取拷贝的文件,看看是否还是原数据。 FileInputStream in = new FileInputStream("C:\\Users\\默尘\\Desktop\\test02.bmp"); byte[] b2 = new byte[data.length]; in.read(b2); in.close(); // 打印 System.out.println("原数据:"+Arrays.toString(data)); System.out.println("拷贝后:"+Arrays.toString(b2)); }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0b7NnwZy-1606327865383)(C:/Users/%E9%BB%98%E5%B0%98/Desktop/image-20201120230222831.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wgMDV83y-1606327865383)(C:/Users/%E9%BB%98%E5%B0%98/Desktop/image-20201120230309329.png)]
-
分析
复制二进制文件有成功的,也有失败的。故猜测,是因为不是所有的二进制数据都有与之对应的字符,像上例中 -128 二进制位全1组合,就没有与之对应的字符,解码时肯定有它的处理机制,但这样编码回去后就肯定不是原来的值了。这也就是问题的根源。
-
结论
因为我们无法确定图片等二进制文件中是否存在这样 没有字符对应的二进制组合,所以也就无法保证复制成功,所以不能用字符流复制二进制文件。
-
2.5 总结
-
文件项关的IO流只有两个FileInputStream & FileOutputStream
-
对IO流的读写【包括但不限于文件IO流】,又分为两类,即字节和字符读写。
对于字节读写:多使用Buffered… 只是声明时多一个壳儿,用法一样,效率还高。
对于字符读写:也多使用Buffered… 加 FileReader声明,效率高。还好用写,readLine和newLine更便于书写。
所以,就是将Buffered用成习惯。
-
对于性能问题:
一次测试:
我用字节流复制五个视频文件到一个文件,总大小100M
使用5M的数组空间,不论用户用buffered用时都是200ms的样子。
但使用1024B的数组空间,不用Buffered耗时约700ms,但用了Buffered时间还是200ms。
而且后来我给出了10M的数组空间,速度依然只有200ms,可能达到了物理上限。
按我想的Buffered的空间只有8K,按道理我的数组空间只要比8K大,那不用Buffered就会的到更优的效果,但并非如此,至于原因,等你把这些用成条件反射的时候再看源码分析吧。原因可能是多方面的,可能不止java的实现方法。
3. 多线程
3.1 进程和线程的概念
通俗来讲就是进程就是程序的一次执行过程,是系统进行资源分配和调度的一个单位。线程就是轻量级的进程,它的创建、调度、销毁 所花费的开销要比进程更小,是系统资源调度的更小的单位。一个进程可以包含多个线程。
从编程角度讲,线程就是一个可以独立进行的任务流程。我们可以通过线程实现多个操作的并发执行。
3.2 线程的两种创建方式
- 继承 java.lang.Thread 类,并重写run方法。
- 以Runnable的实例为实参构建Thread对象。
3.3 Thread类
// 构造方法
Thread([Runnable trget][, String threadName]);
void start(); // 启动线程,,是由JVM去掉run方法,而非在start中调用run方法
long getId(); // 获取线程的唯一标识,
String getName(); // 获取线程名称
void setName(String name); // 设置线程的名称
static Thread currentThread(); // 获取当前正在执行的线程的引用,也就是获取代码所在的线程。
static void yield(); // 让步,执行状态的线程退出到就绪状态排队。
static void sleep(long ms);
int getPriority();
void setPriority(int p);
// 优先级 [1,10],默认为5;优先级越高也不一定先执行,只是获取时间片的机会更大而已。
// 常量:Thread.MAX_PRIORITY, Thread.MIN_PRIORITY
void join([long ms]); // 当前线程等待,调用者线程结束后,再继续执行,若有ms,表示最多等待的时间。
boolean isDaemon(); // 守护线程在主线程结束后停止(但也不是立即停止),非守护线程继续执行,默认为非守护线程。
void setDaemon(true); // 将线程设置为守护线程,也就是设置它在main结束后停止。
// 注意:setDaemon必须在start之前。
3.4 线程的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rgLKwcw6-1606327865384)(img/image-20201122114102128.png)]
- 新建状态:使用new关键字创建之后的状态,并未开始执行。
- 就绪状态:调用start()之后进入的状态,依然没有开始执行。
- 运行状态:线程调度器调用该程后进入的状态,线程开始执行,当时间片耗尽且人物未执行完时,就返回就绪状态。
- 消亡状态:线程任务完成后进入的状态,此时线程已终止。
- 阻塞状态:执行过程中发生阻塞事件进入的状态。阻塞解除后进入的是就绪状态,而非运行状态。
3.5 线程同步机制
3.5.1 相关概念
-
同步机制:
保证多个线程有条不紊的同时(宏观)进行的机制,它的作用在于保证多个线程同时进行不出错,所以它的主要任务是对多个线程在执行次序上进行协调,使并发执行的多个线程之间能按照一定的规则共享临界资源。
-
**临界资源:**一次仅允许一个线程使用的资源。
-
**临界区:**访问临界资源的代码区域。
3.5.2 实现方式:
对临界区加锁,不允许两个线程同时进入临界区。synchronized加锁或lock加锁。
3.5.3 synchronized
-
部分代码锁定
synchronized(var){ // 注意这里的var可以是任意引用类型对象的引用,一个对象就是一把锁。 // 临界区代码 }
-
方法锁定
public synchronized void func(){ } public static synchronized void func(){ }
相当于:
synchronized(this){ 整个方法体 } synchronized(A.class){ 整个方法体 } // A 表示方法所在的类
也就是说能不能锁住要看,调用方法时两个this是不是同一个对象。
-
注意事项
- synchronized括号中的量必须是同一个对象才能锁的住。
- 尽量减少同步代码块的范围以提高效率。
3.5.4 Lock接口
常用方法:
ReentrantLock(); // Lock的实现类
void lock(); // 获取锁
void unlock(); // 释放锁
使用方法:
ReentrantLock lock = new ReentrantLock();
lock.lock();
// 同步代码块
lock.unlock();
3.6 线程协作
3.6.1 相关方法
Object中的三个方法用于线程间的协作:
void wait([long timeout]); // 释放对象的锁,进入等待,timeout表示最多等待的毫秒数,没有给出就一直等
void notify();
void notifyAll();
注意:
- wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。
- 调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)
- 调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;
- 调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;
3.6.2 生产者消费者问题
-
问题描述
有两个进程:一组生产者进程和一组消费者进程共享一个初始为空、固定大小为n的缓存(缓冲区)。生产者的工作是制造一段数据,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待,如此反复; 同时,只有缓冲区不空时,消费者才能从中取出消息,一次消费一段数据(即将其从缓存中移出),否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或者一个消费者从中取出消息。
3.8 Callable接口
- 定义如下
public interface Callable<V> {
V call() throws Exception;
}
-
特点
作用同Runnable差不多,特点是它支持泛型,可以有返回值。
默认抛出异常
3.9 Future 接口
- Future接口
public interface Future<V> {
boolean cancel(boolean var1);
boolean isCancelled();
boolean isDone();
V get();
V get(long var1, TimeUnit var3);
}
说明:
- cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
- isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
- isDone方法表示任务是否已经完成,若任务完成,则返回true;
- get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
- get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
作用:定义了获取返回值,取消任务的等功能。
-
FutureTask类
FutureTask类继承自RunnableFuture接口,RunnableFuture又继承自Runnable接口和Future接口。
故FutureTask接口可作为Runnable的实现类有线程执行,又可以使用Future声明的功能获取执行结果。
使用方法:
public static void main(String[] args) { FutureTask<String> ft = new FutureTask<>(new Callable<String>() { @Override public String call() throws Exception { System.out.println("我是子线程的执行过程。等3秒后才能看到我的返回值。"); Thread.sleep(3000); return "我是子线程的返回值"; } }); new Thread(ft).start(); try { System.out.println("----"+ft.get()); // 主线程会被阻塞在这里知道子线程执行完 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("----主线程的输出"); }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mV356lCY-1606327865385)(img/image-20201123152302250.png)]
大致原理:
Thread调用FutureTask的run方法,run方法中又调用了Callable中的call方法,并将其返回值存储outcome字段中,当调用get方法时,线程执行完了就将outcome返回。run方法中还将当前线程的引用存放了成员变量runner中,这样就可以cancel方法中中断线程了。
3.10 线程池
-
相关概念
线程池:首先创建一些线程,把它们的集合称为线程池,当需要时从线程池中取出一个空闲的线程为之服务,用完后不关闭该线程,而是将该线程换回到线程池中。
在线程池编程模式下,任务是提交给线程池的,而不是直接交给某个Thread。任务提交后再由线程池分配线程处理。一个线程只能执行一个任务,但可以同时向一个线程池提交多个任务。
线程池从Java1.5开始支持。
-
相关类和方法
ExecutorService接口 定义了线程池的功能,定义了和后台任务执行相关的方法
ThreadPoolExecutor类 是ExecutorService的主要实现类。然而他的实例创建起来很是麻烦,有多个参数需要制定。
Executor 是线程池的工厂类,一般用它来帮我们创建线程池,它创建的实例也一般为ThreadPoolExecutor类的实例。
-
Executors的常用方法
static ExecutorService newCachedThreadPool(); // 可以根据需要新创建线程 static ExecutorService newFixedThreadPool(int n); // 固定线程数的 static ExecutorService newSingleThreadExecutor(); // 只有一个线程的线程池
-
ExecutorService接口的常用方法
void execute(Runnable runner); <T> Future<T> submit(Callable<T> | Runnable task); // 它返回的就是 FutureTask 对象;它是一个泛型方法 // 传入Runnable的实例时,返回值为Future<?> ,故get只能得到Object。 void shutdown(); void shutdownNow();
-
举例
public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newFixedThreadPool(5); Future<String> task = executorService.submit(new Callable<String>() { @Override public String call() throws Exception { return "123456"; } }); System.out.println(task.get()); executorService.shutdown(); // 否则main不会退出 }
多个线程使用相同的所锁定不同的代码块,还可以执行吗?
只要是用同一个对象为锁,不论是否在同一个run函数中,不论加锁的内容是否相同,都能锁住。
4. 网络编程
4.1 相关协议
-
TCP协议
TCP协议的三次握手
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UglxS9aK-1606327865386)(img/image-20201126010515332.png)]
四次挥手
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZbcdTFEI-1606327865387)(img/image-20201126010606878.png)]
-
UDP协议
1、UDP是无连接的,即发送数据之前不需要建立连接;
2、UDP使用尽最大努力交付,即不保证可靠交付;
3、UDP是面向报文的;
4、UDP支持一对一、一对多、多对一和多对多的交互通信等。
4.2 TCP编程模板
public static void main(String[] args) throws IOException {
//建立服务端对象,绑定4574端口。
ServerSocket serv =new ServerSocket(4574);
System.out.println("服务器启动成功,等待用户接入");
//accept()等待用户接入,如果没有用户连接,会一直等待。
//有客户连接后,accept()方法会返回一个Socket对象,代表客户端
Socket sc=serv.accept();
System.out.println("有客户端接入,客户端ip:"+sc.getInetAddress());
//从Socket中得到网络输入流,接收来自网络的数据
InputStream in=sc.getInputStream();
//从Socket中得到网络输出流,将数据发送到网络上
OutputStream out=sc.getOutputStream();
//接收客户端发来的数据
byte[] bs=new byte[1024];
//将数据存入bs数组中,返回值为数组的长度
int len=in.read(bs);
String str=new String(bs,0,len);
System.out.println("来自客户端的消息: "+str);
//向客户端写数据,注意客户端代码中别忘记写read()方法,否则会抛异常
out.write("欢迎访问,你好,我是服务端".getBytes());
System.out.println("服务端正常结束");
//关闭流!
sc.close();
}
public static void main(String[] args) throws UnknownHostException, IOException {
//建立Socket连接,new Socket(服务端ip,端口port);
Socket so=new Socket("192.168.0.104",4574);
System.out.println("连接服务器成功");
//从Socket中得到网络输入流,接收来自网络的数据
InputStream in=so.getInputStream();
//从Socket中得到网络输出流,将数据发送到网络上
OutputStream out=so.getOutputStream();
//write方法中只能为byte[],或者int。
//若服务端没有read(),则客户端会一直等。即停留在write方法中。
out.write("你好,我是客户端".getBytes());
//接收服务端发来的数据
byte[] bs=new byte[1024];
//将数据存入bs数组中,返回值为数组的长度
int len=in.read(bs);
String str=new String(bs,0,len);
System.out.println("来自服务端的消息: "+str);
//关闭流
so.close();
}
注意事项:
文件的IO流结束一定要关闭
Tcp的IO流不能关闭,但结束一定要flush
4.3 UDP编程模板
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7J3f2QQa-1606327865387)(img/image-20201126011006621.png)]
5. 反射
反射是指:在运行阶段决定创建什么对象,调用什么方法的编程机制
5.1 Class类
-
基本概念
- java.lang.Class类的实例可以描述Java的类和接口,也就是一种数据类型。
- 它没有公共构造方法,其实例由 JVM 和 ClassLoader 自动创建。
-
获取方式
// 1. 数据类型 * String.class(); // class java.lang.String int.class(); // int void.class(); // void // 2. 对象.getClass() obj.getClass(); // 3. 包装类.type() // 获取其基本类型的 Class 对象 Integer.type; // int Integer.class; // java.lang.Integer // 4. forName方法 * Class.forName("完全限定名"); // 5. 类加载器 ClassLoader cl = A.class.getClassLoader(); cl.loadClass("完全限定名")
-
常用方法
newInstance(); // 调用无参构造,创建对象,已过时 Constructor<T> getConstructor(Class<?>... parameterTypes); // 获取Class对象代表的类型中对应参数的公共构造方法。 // 注意这里参数 必须是Class的对象 Constructor<T>[] getConstructors(); // 获取 所代表的 类型的所有公 共构造器 Field getDeclaredField(String name); // 获取 字段对象,Declared表示获取所有的,不加只能获取public的。 Field[] getDeclareFields(); // 获取所有字段 Method getMethod(String name,Class<?>... parameterTypes); // 用于获取该Class对象表示类中名字为name参数为parameterTypes的指定公共成员方法 Method[] getMethods(); // 用于获取该Class对象表示类中所有公共成员方法
5.2 Constructor类
-
获取实例
通过Class对象的getConstructor方法获取
-
常用方法
T newInstance(Object... initargs); // 创建实例 ** int getModifiers(); // 获取方法的访问修饰符,【因为不只可以获取到共有构造方法】 String getName(); // 获取方法名,不知有何用? Class<?>[] getParameterTypes(); // 获取方法的所有参数类型
-
示例
public static void main(String[] args) throws Exception{ Class pclass = Class.forName("mc.PC.Person"); Constructor constructor = pclass.getConstructor(String.class,int.class); Object o = constructor.newInstance("Jsumy",15); System.out.println(o); }
5.3 Feild类
常用方法:
Object get(Object obj);
void set(Object obj,);
void setAccessible(boolean flag); //当实参传递true时,则反射对象在使用时应该取消 Java 语言访问检查
int getModifiers(); // 获取成员变量的访问修饰符
Class<?> getType(); // 获取成员变量的数据类型
String getName(); // 获取成员变量的名称
5.4 Method类
Object invoke(Object obj,Object... args); // 使用对象obj来调用此Method对象所表示的成员方法,实参传递args
int getModifiers(); // 获取方法的访问修饰符
Class<?> getReturnType(); // 获取方法的返回值类型
String getName(); // 获取方法的名称
Class<?>[] getParameterTypes(); // 获取方法所有参数的类型
Class<?>[] getExceptionTypes(); // 获取方法的异常信息
5.5 其他信息
Package getPackage(); // 获取所在的包信息
Class<? super T> getSuperclass(); // 获取继承的父类信息
Class<?>[] getInterfaces(); // 获取实现的所有接口
Annotation[] getAnnotations(); // 获取注解信息
Type[] getGenericInterfaces(); // 获取泛型信息