_04_java核心类库2

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 自定义异常

  1. 定义要求

    1. 继承Exception类或其子类。
    2. 定 义一个无参构造和一个有参构造。
    public class MyException extends Exception{
        public MyException(){}
        public MyException(String massage){
            super(message);
        }
    }
    
  2. 产生并抛出异常

    ... 
    public void func() throws MyException{		// 注意
        ...
        throw new MyException("***异常发生了。"); 
        ...
    }
    

1.7 异常机制的意义

​ 将异常处理的程序代码集中在一起,与正常的代码分开,使得程序简洁、优雅并易于维护。

​ 目前使用过的异常机制,应该都是被迫使用,它的好处当真是还没有体会到,不过也不急,毕竟现在自己的代码量还是太少啊 ! 没有经历过真实的应用场景,就不要谈意义。

2. 文件操作

2.1 File类

  1. 作用:描述文件及目录的信息

  2. 常用方法:

    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 则保留。
        }
    });
    
  3. 注意:

    文件与文件夹也不能重名,这是系统的限制。创建重名对象时返回false。

  4. 案例:

    1. 如何通过File获取当前路径?
    String path = new File("").getAbsolutePath();    // 获取的应该是启动启动程序时的路径
    A.class.getResource("/").getPath();				 // 类路径的 根目录
    A.class.getResource("").getPath();				 // A类的class文件所在路径
    

2.2 IO流简介

  1. 基本分类

    • 字节流、字符流

    • 输入流、输出流

    • 节点流、处理流

      节点流:直接与输入输出 源 对接的流。

      处理流:建立在节点流基础之上的流。

  2. 结构框架

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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

  1. 注意:

    ​ 读写对象的类型必须实现 java.io.Serializable 接口来开启类的序列化功能。所谓序列化就是将一个对象 需要存储的相关信息 有效组织成 字节序列 的过程。逆向过程就称之为反序列化。

  2. 常用方法:

    ObjectOutputStream(OutputStream out);
    ObjectInputStream(InputStream in);
    
    void writeObject(Object obj);
    Object readObject();
    
    void close();
    
  3. 序列化版本号

    序列化版本号的作用是什么?

    ​ 标识这个类是否发生改变,序列化时 版本号会一起写入文件,反序列化时,会判断文件中的版本号与类中的版本号是否一致,如果不一致就认为类的结构发生了变化,这时候就会报 InvalidClassException 。所以serialVersionUID应该与类结构对应,当对类进行了修改后(如加了一个字段),就应该换一个新的版本号。

    ​ 版本号一致就可以转换,类中如果有新增的字段会以默认方式初始化,文件比类中多字符段也不会报错。一般写版本号的目的就是保证类修改后依然可以反序列化。

    如何生成序列化版本号?

    ​ 因为版本号只是当前类结构的一种标识,所以随便写一个就好,只要保证类修改后换一个新的版本号行。但是每次修改类都要自己编一个版本号也比较麻烦,所以方式二就是通过IDE生成,IDE会自动根据当前类的内容生成一个版本号,每次修改类后只要重新生成一次就好,不用担心会不会和以前的版本号重复。

    为什么不写也可以?

    ​ 可以发现实现serializable接口后,不显示书写serialVersionUID也能行啊?如果不写,编译器在编译时自动根据类的内容计算并添加一个版本号。这时只要修改类的结构,增删字段或方法就会导致版本号不同(方法的内容修改不会导致版本号不同)。但是多数情况下我们并不希望它如此敏感。所以一般都会写serialVersionUID,来自行控制版本问题。

  4. transient关键字

    用于标识不需要进行序列化的字段。

    private transient String name;
    

    这时name字段序列化时就不会写入文件,反序列化时会以默认方式初始化,也就是 null 。

  5. 经验

    同一个文件中可以写入多个对象,但读取时无法判断是否读到文件末尾,如果读到了末尾还进行读取,会报EOFException。所以一个文件中保存多个对象时,可以将多个对象放入一个ArrayList对象中,一起保存。

2.4.10 RandomAccessFile

  1. 简介

    实现文件的随机读写。

  2. 常用方法

    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 问题

  1. 为什么使用字符流复制图片会失败?

    • 问题描述:字符流就是在字节流的基础上添加了字符的编解码功能,那么 解码、编码 两相对应不应该内容不变吗?

    • 实验:

    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 线程的两种创建方式

  1. 继承 java.lang.Thread 类,并重写run方法。
  2. 以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 相关概念

  1. 同步机制:

    ​ 保证多个线程有条不紊的同时(宏观)进行的机制,它的作用在于保证多个线程同时进行不出错,所以它的主要任务是对多个线程在执行次序上进行协调,使并发执行的多个线程之间能按照一定的规则共享临界资源。

  2. **临界资源:**一次仅允许一个线程使用的资源。

  3. **临界区:**访问临界资源的代码区域。

3.5.2 实现方式:

​ 对临界区加锁,不允许两个线程同时进入临界区。synchronized加锁或lock加锁。

3.5.3 synchronized

  1. 部分代码锁定

    synchronized(var){   // 注意这里的var可以是任意引用类型对象的引用,一个对象就是一把锁。
        // 临界区代码
    }
    
  2. 方法锁定

    public synchronized void func(){
        
    } 
    public static synchronized void func(){
        
    }
    

    相当于:

    synchronized(this){ 整个方法体 }
    synchronized(A.class){ 整个方法体 } // A 表示方法所在的类
    

    也就是说能不能锁住要看,调用方法时两个this是不是同一个对象。

  3. 注意事项

    • 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();

注意:

  1. wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。
  2. 调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)
  3. 调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;
  4. 调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;

3.6.2 生产者消费者问题

  1. 问题描述

    有两个进程:一组生产者进程和一组消费者进程共享一个初始为空、固定大小为n的缓存(缓冲区)。生产者的工作是制造一段数据,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待,如此反复; 同时,只有缓冲区不空时,消费者才能从中取出消息,一次消费一段数据(即将其从缓存中移出),否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或者一个消费者从中取出消息。

3.8 Callable接口

  1. 定义如下
public interface Callable<V> {
    V call() throws Exception;
}
  1. 特点

    作用同Runnable差不多,特点是它支持泛型,可以有返回值。

    默认抛出异常

3.9 Future 接口

  1. 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。

作用:定义了获取返回值,取消任务的等功能。

  1. 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 线程池

  1. 相关概念

    ​ 线程池:首先创建一些线程,把它们的集合称为线程池,当需要时从线程池中取出一个空闲的线程为之服务,用完后不关闭该线程,而是将该线程换回到线程池中。

    ​ 在线程池编程模式下,任务是提交给线程池的,而不是直接交给某个Thread。任务提交后再由线程池分配线程处理。一个线程只能执行一个任务,但可以同时向一个线程池提交多个任务。

    ​ 线程池从Java1.5开始支持。

  2. 相关类和方法

    ExecutorService接口 定义了线程池的功能,定义了和后台任务执行相关的方法

    ThreadPoolExecutor类 是ExecutorService的主要实现类。然而他的实例创建起来很是麻烦,有多个参数需要制定。

    Executor 是线程池的工厂类,一般用它来帮我们创建线程池,它创建的实例也一般为ThreadPoolExecutor类的实例。

  3. Executors的常用方法

    static ExecutorService newCachedThreadPool();   // 可以根据需要新创建线程
    static ExecutorService newFixedThreadPool(int n);  // 固定线程数的
    static ExecutorService newSingleThreadExecutor();  // 只有一个线程的线程池
    
  4. ExecutorService接口的常用方法

    void execute(Runnable runner);
    <T> Future<T> submit(Callable<T> | Runnable task);  
    // 它返回的就是 FutureTask 对象;它是一个泛型方法
    // 传入Runnable的实例时,返回值为Future<?> ,故get只能得到Object。
    void shutdown();
    void shutdownNow();
    
  5. 举例

    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 相关协议

  1. TCP协议

    TCP协议的三次握手

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UglxS9aK-1606327865386)(img/image-20201126010515332.png)]

    四次挥手

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZbcdTFEI-1606327865387)(img/image-20201126010606878.png)]

  2. 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类

  1. 基本概念

    • java.lang.Class类的实例可以描述Java的类和接口,也就是一种数据类型。
    • 它没有公共构造方法,其实例由 JVM 和 ClassLoader 自动创建。
  2. 获取方式

    // 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("完全限定名")
    
  3. 常用方法

    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类

  1. 获取实例

    通过Class对象的getConstructor方法获取

  2. 常用方法

    T newInstance(Object... initargs);  // 创建实例 **
    
    int getModifiers();                 // 获取方法的访问修饰符,【因为不只可以获取到共有构造方法】
    String getName();					// 获取方法名,不知有何用?
    Class<?>[] getParameterTypes();     // 获取方法的所有参数类型
    
  3. 示例

    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(); // 获取泛型信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值