新手小白学java四进阶
- 一、IO流(文件输入输出)(java.io)
- IO流的分类
- IO流的四大类:InputStream(字节输入流)、OutStream(字节输出流)、Reader(字符输入流)、Writer(字符输出流),这四个类都是抽象类
- 注意:在java中,只要类名以Stream的都是字节流;只要以Reader/Writer结尾的都是字符流
- 所有的流都实现了Closeable接口,因此所有流使用结束要记得调用close()方法
- 所有的输出流(OutStream、Writer)都实现了fluentable接口,都是可刷新的,因此在输出流在最终输出后,记得调用flush()方法刷新
- java.io下常用16个流
- FileInputStream
- IDEA的默认当前路径是在:工程project的根目录
- 读取read
- 注意:上述读取read方式不常用,以下是开发中读取的正确编写方式:
- available()方法:返回流中剩余的没有读到的字节数量
- skip()方法:跳过几个字节不读
- FileOutputStream
- write()方法:如果通过这种构造方法FileOutputStream(String name)的形式创建对象,然后使用write()方法写入:谨慎使用,会先把原文件内容清空然后写入
- 使用FileOutputStream的有参构造方法FileOutputStream(String name, boolean append)其中append处的布尔类型设置为true,之后的write写入就会:以追加的方式在原文件后面写入,不会清空原文件内容
- 使用FileInputStream与FileOutputStream可以实现文件的复制
- FileReader:几乎与FileInputStream用法相同,只不过将FileInputStream中的用的byte[]数组变成char[]数组即可
- FileWriter:几乎与FileOutputStream用法相同,只不过将FileOutputStream中的用的byte[]数组变成char[]数组即可
- write()方法
- BufferedReader(带有缓冲区的字符输入流):与之前的in/outStream以及Reader/Writer相比,缓冲区流自带数组,因此无需像之前一样需要我们自己手动创建char[]和byte[]数组
- 节点流与包装(处理)流:当一个流的构造方法需要传入一个流时,这个被传进来的流叫做节点流;外部负责包装的这个流,叫做包装流,又称处理流。并且关闭close时,只需将bufferedReader关闭即可,无需关闭节点流(因为BufferedReader在执行close()方法时,方法体内部也会执行节点流的close()方法)
- readLine()方法:读一行
- 转换流:将字节流转换成字符流InputStreamReader/OutputStreamWriter
- 比如当一个包装流时BufferedReader想传入一个FileInputStream节点流时,由于类型不同(一个是字节流,一个是字符流),那么可以通过InputStreamReader将字节流FileInputStream转换成字符流Reader然后传入BufferedReader进行使用。
- 数据流(也是包装流)
- DataOutputStream:数据专属的流,在写入数据(write+Byte/Short/Int/Long/Double/Float/Boolean/char)时,会把数据内容和数据类型一同写入文件中
- DataInputStream:字节输入流,DataOutputStream写入的文件,必须只能用DataInputSTream才能正确读出,并且读取时需要提前知道写入的顺序,读的顺序需要和写入的顺序一致,才可以正常取出数据。
- 标准输出流:PrintWiter/PrintStream
- PrintStream:标准的字节输出流,默认输出到控制台,无需手动close()
- 可以手动设置标准输出流的输出方向(可以选择不输出到控制台,可以手动设置输出到其他文件中)
- File类:文件和目录路径名的抽象表达形式
- File构造方法
- File类的常用方法
- exists()方法:判断文件或目录是否存在
- creatNewFile()方法:以文件的形式创建一个文件
- mkdir()方法:以目录的形式创建一个目录
- mkdirs()方法:以多重目录的形式创建一个多重目录
- getParent()方法:获取指定文件或者目录的父路径
- getAbsolutePath()方法:获取绝对路径
- getName()方法:获取文件名
- isDirectory()方法:判断是目录还是文件
- lastModified()方法:文件最后一个被修改的时间
- length()方法:获取文件大小是多少字节
- listFiles()方法:获取当前目录下的所有子文件
- ObjectOutputStream:用于实现序列化Serialize
- 序列化的实现:注意,参与序列化的对象必须实现Serializable接口
- ObjectInputStream:用于实现反序列化Deserialize
- 反序列化的对象也需要实现Serializable接口
- 序列化与反序列化集合
- 一次序列化多个对象:即将对象放到集合中,然后将集合序列化
- 反序列化集合
- transient关键字:用来不参加序列化操作
- 关于序列化版本号(用于区分同名的类)
- 注意:不建议自动生成序列化版本号,建议我们手动写出来赋值
- IO 与properties的联合使用:对于一些需要经常修改的信息,可以将其单独写到一个文件(属性配置文件)中,让程序动态读取。这样一来,就算里面的内容被修改,java代码无需改动,也无需重新编译,服务器也无需重启,就可以动态的获得新信息。
- 线程
- 进程A的内存与进程B的内存是独立,不共享的
- 而在java中,线程A和线程B是共享堆内存和方法区内存;但是栈内存是独立的,一个线程有其自己单独的一个栈内存。各自可以同时执行,互不影响,这就是多线程。
- 单核并不能真正实现多线程并发
- 实现进程的两种方式
- 第一种:编写一个类,直接继承java.long.Thead,必须重写run方法。
- 注意:run()方法与start()方法的区别!!!!
- 第二种(更常用):编写一个类,实现java.lang.Runnable接口,实现run方法
- 第二种还可以采用匿名内部类的方式
- 修改/获取线程名字的方法:setName()和getName()
- 注意:当线程没有设置名字时,默认名字是:Thread-0,Thread-1,等等
- 获取当前线程对象:currentThread()静态方法
- 线程的sleep()静态方法
- sleep面试题
- 终止睡眠的线程(即唤醒)的方法:interrupt()方法
- 同时注意:run()方法中的异常不能throws抛给上级,只能用try catch。因为run()方法在父类(Runnale接口)中没有抛出任何异常,因此由于子类不能比父类抛出更多的异常,所以run()方法中只能用try catch来解决异常,而不能通过throws抛给上级。
- 强行终止线程的执行:stop()方法——已过时,不建议使用
- 合理终止线程的执行(常用):在线程中设置一个boolean的成员变量即可
- 线程调度
- 线程优先级:获取线程优先级方法:getPriority()
- Thread.yield()静态方法:线程让位,让当前的线程暂停回到就绪态,让给其他线程
- join()方法:线程合并
- 线程安全!!!!!!
- 线程不安全的条件
- 解决线程安全:线程同步机制:线程排队执行,不能并发执行
- 同步与异步
- 线程同步机制:同步代码块 synchronized
- 注意:局部变量永远不会有线程安全问题
- synchronized还可以用在实例方法上进行修饰,用来锁this表示的对象。
- synchronized也可以用于修饰静态方法,是类锁,将此类全部锁住排队!!!!
- synchronized小总结
- 死锁:持有某个资源去请求另一个资源时容易发生,因此不建议嵌套使用synchronized
- 开发中应该如何解决安全问题
- 守护线程:又称后台线程,如垃圾回收线程,当用户线程结束后,守护进程自动结束
- 守护进程的实现:setDaemon()方法
- 定时器java.util.Timer:间隔特定的时间,执行特定的程序
- 定时器的实现:
- 定时器例子:定时备份
- 实现线程的第三种方式(JDK8以后的新特性):实现Callable接口(其中的call()方法相当于线程中的run()方法)
- wait()和notify()方法:是Object类中的方法,而不是线程的方法
- 生产者和消费者:
- 反射机制:通过反射机制可以操作j字节码文件
- 获取Class的三种方式:要操作一个类的字节码,收下你需要获取这个类的字节码即Class
- 第一种:Class.forName()静态方法
- 第二种方式:通过Object祖先级别的类中的getClass()方法
- 第三种方式:类.class
- 通过反射来实例化对象
- 反射机制灵活性
- forName()方法的发生:若只想执行静态代码块,可以使用forName()方法
- 获取类路径下(在src下的都是类路径下的)文件的绝对路径
- 直接以流的方式返回而无需获取绝对路径:getResourceAsStream()
- 资源绑定器ResourceBundle:sun公司写好的,用来获取属性配置文件中的内容,注意,仅能用于绑定properties属性文件(即文件扩展名为properties),且此文件必须在类路径下,并且扩展名properties省略不写。
- 类加载器
- 反射机制小总结
- 使用反射获取属性field
- getFields()方法是获得目标类中的public修饰的属性filed。
- getDeclaredFields()方法获取目标类中声明的所有属性
- 获取一个类的类名:getName()和getSimpleName()方法
- 获取指定类中的属性的类型:getType()方法
- 获取指定类中的属性的修饰符列表:getModifiers()方法:返回一个数字,数字对应着修饰符的代号。将数字转换成对应的修饰符,用静态方法Modifier.toString(int i)
- 通过反射机制,反编译一个类的属性field(略)
- 通过反射机制,访问对象的属性
- 注意:反射机制的一大缺点:可以打破封装(setAccessible(true)),直接在外部给私有属性赋值
- 可变长度参数:类型后面跟三个点:如int... args
- 反射机制中的Method
- 获取类中所有的method:getDeclaredMethods()方法
- 获取类中方法的返回值类型:getReturnType()方法;同理,获取方法的修饰符类型:getModifiers()方法
- 获取方法中形式参数列表的类型:getParameterTypes()方法
- 反编译 Method(了解,略)
- 通过反射机制,调用一个对象(注意不是类)的方法:获取类,new对象,获取方法,然后使用invoke()方法调用对象的方法!!!******
- 反射Constructor构造方法
- 获取构造方法:getDeclaredConstructors()方法
- 通过反射机制调用构造方法实例化对象:和之前的一样,对于无参构造方法:直接获取类,然后类名.newInstance()方法即可;对于有参构造:先获取有参构造方法(getDeclaredConstructor(形参)),然后调用构造方法new对象即可
- 通过反射机制获取父类(getSuperClass()方法)和父接口(getInterfaces()方法)
- 注解/注释类型Annotation:是一种引用数据类型
- 注解定义与使用
- JDK已经内置的注解类:java.lang包中有Deprecated过时的、Override重写、SuppressWarnings三种注解
- override注解:只能注解方法,只在编译阶段起作用,与运行期无关
- 元注解:就是修饰注解的注解,主要有Target和Tetention
- 元注解Retention:表示目标只能保存在XX中
- 元注解target:表示目标只能出现在xxx中,即只允许标注xxx
- Deprecated表示已过时的:可以用来注解类、方法、变量等等
- 注解中定义属性
- 注意:如果一个注解的属性的名字是value的话,并且只有一个的话,使用时,该属性名value可以省略不写。(有两个叫value的属性不行)
- 注解中的属性可以是哪些类型?
- 通过反射机制获取注解
- 通过反射机制获取注解对象中属性的值
- 注解在开发中的作用
一、IO流(文件输入输出)(java.io)
IO流的分类
IO流的四大类:InputStream(字节输入流)、OutStream(字节输出流)、Reader(字符输入流)、Writer(字符输出流),这四个类都是抽象类
注意:在java中,只要类名以Stream的都是字节流;只要以Reader/Writer结尾的都是字符流
所有的流都实现了Closeable接口,因此所有流使用结束要记得调用close()方法
所有的输出流(OutStream、Writer)都实现了fluentable接口,都是可刷新的,因此在输出流在最终输出后,记得调用flush()方法刷新
java.io下常用16个流
FileInputStream
IDEA的默认当前路径是在:工程project的根目录
读取read
int read(byte[] b):返回值是读取到的字节的数量,而不是值本身
以读取存储"abcdef"的txt文件为例:
如何把读取的内容依次输出:
注意:上述读取read方式不常用,以下是开发中读取的正确编写方式:
初版:
终极改进版:
catch与finally中的内容同上
available()方法:返回流中剩余的没有读到的字节数量
仍以读取“abcdef”为例:
skip()方法:跳过几个字节不读
仍以读取“abcdef”为例:
FileOutputStream
write()方法:如果通过这种构造方法FileOutputStream(String name)的形式创建对象,然后使用write()方法写入:谨慎使用,会先把原文件内容清空然后写入
使用FileOutputStream的有参构造方法FileOutputStream(String name, boolean append)其中append处的布尔类型设置为true,之后的write写入就会:以追加的方式在原文件后面写入,不会清空原文件内容
此时,会自动在原文件的末尾写入,而不会清空原文件内容再写入
使用FileInputStream与FileOutputStream可以实现文件的复制
FileReader:几乎与FileInputStream用法相同,只不过将FileInputStream中的用的byte[]数组变成char[]数组即可
整体框架一模一样:
FileWriter:几乎与FileOutputStream用法相同,只不过将FileOutputStream中的用的byte[]数组变成char[]数组即可
write()方法
BufferedReader(带有缓冲区的字符输入流):与之前的in/outStream以及Reader/Writer相比,缓冲区流自带数组,因此无需像之前一样需要我们自己手动创建char[]和byte[]数组
节点流与包装(处理)流:当一个流的构造方法需要传入一个流时,这个被传进来的流叫做节点流;外部负责包装的这个流,叫做包装流,又称处理流。并且关闭close时,只需将bufferedReader关闭即可,无需关闭节点流(因为BufferedReader在执行close()方法时,方法体内部也会执行节点流的close()方法)
readLine()方法:读一行
转换流:将字节流转换成字符流InputStreamReader/OutputStreamWriter
比如当一个包装流时BufferedReader想传入一个FileInputStream节点流时,由于类型不同(一个是字节流,一个是字符流),那么可以通过InputStreamReader将字节流FileInputStream转换成字符流Reader然后传入BufferedReader进行使用。
数据流(也是包装流)
DataOutputStream:数据专属的流,在写入数据(write+Byte/Short/Int/Long/Double/Float/Boolean/char)时,会把数据内容和数据类型一同写入文件中
DataInputStream:字节输入流,DataOutputStream写入的文件,必须只能用DataInputSTream才能正确读出,并且读取时需要提前知道写入的顺序,读的顺序需要和写入的顺序一致,才可以正常取出数据。
标准输出流:PrintWiter/PrintStream
PrintStream:标准的字节输出流,默认输出到控制台,无需手动close()
可以手动设置标准输出流的输出方向(可以选择不输出到控制台,可以手动设置输出到其他文件中)
例子:手写一个日志工具
定义一个日志类:
进行测试:
输出结果:
File类:文件和目录路径名的抽象表达形式
File构造方法
File类的常用方法
exists()方法:判断文件或目录是否存在
creatNewFile()方法:以文件的形式创建一个文件
mkdir()方法:以目录的形式创建一个目录
mkdirs()方法:以多重目录的形式创建一个多重目录
getParent()方法:获取指定文件或者目录的父路径
getAbsolutePath()方法:获取绝对路径
getName()方法:获取文件名
isDirectory()方法:判断是目录还是文件
lastModified()方法:文件最后一个被修改的时间
length()方法:获取文件大小是多少字节
listFiles()方法:获取当前目录下的所有子文件
ObjectOutputStream:用于实现序列化Serialize
用于将java对象存储到硬盘文件中,是将java对象保存下来的过程。
序列化的实现:注意,参与序列化的对象必须实现Serializable接口
ObjectInputStream:用于实现反序列化Deserialize
用于将硬盘上的数据恢复到内存中,并恢复成java对象的过程。
反序列化的对象也需要实现Serializable接口
序列化与反序列化集合
一次序列化多个对象:即将对象放到集合中,然后将集合序列化
反序列化集合
transient关键字:用来不参加序列化操作
关于序列化版本号(用于区分同名的类)
从ArrayList的源代码学习手动设置序列化版本号:
注意:不建议自动生成序列化版本号,建议我们手动写出来赋值
IO 与properties的联合使用:对于一些需要经常修改的信息,可以将其单独写到一个文件(属性配置文件)中,让程序动态读取。这样一来,就算里面的内容被修改,java代码无需改动,也无需重新编译,服务器也无需重启,就可以动态的获得新信息。
属性配置文件内部的格式:
线程
进程A的内存与进程B的内存是独立,不共享的
而在java中,线程A和线程B是共享堆内存和方法区内存;但是栈内存是独立的,一个线程有其自己单独的一个栈内存。各自可以同时执行,互不影响,这就是多线程。
单核并不能真正实现多线程并发
实现进程的两种方式
第一种:编写一个类,直接继承java.long.Thead,必须重写run方法。
以下面例子:
首先main方法会通过一个主线程执行(在一个栈区),当代码执行到myThread.start()时,此时start()方法会开辟出另一块栈内存空间,一旦开辟出来空间,start()方法就结束了,mian方法会继续往下执行。而与此同时,由于start()开启了myThread线程,因此在新开辟的myThead栈内存区会自动调用run方法(类似于main)执行。因此,main方法在start()代码执行结束之后,main的主线程与myThread的分支线程并发,同时执行。
注意:run()方法与start()方法的区别!!!!
只有执行start()才会去为新的线程开辟新的栈内存。
而如上图所示,在main方法中调用myThead.run()方法,和普通的方法调用一样,在main方法的栈区调用run()方法压栈,然后执行,而不会开辟新的线程,也不能实现并发,只有执行完run()方法体重的代码后,main方法才会继续往下执行。
第二种(更常用):编写一个类,实现java.lang.Runnable接口,实现run方法
第二种还可以采用匿名内部类的方式
修改/获取线程名字的方法:setName()和getName()
注意:当线程没有设置名字时,默认名字是:Thread-0,Thread-1,等等
获取当前线程对象:currentThread()静态方法
这是java.lang.Thread包下的方法:currentThread()
线程的sleep()静态方法
sleep面试题
注意:sleep()是Thread类的静态方法,即使用引用.sleep()的方式去调用sleep方法,但本质还是会转换成类名Thread.sleep()的方法执行,来阻塞sleep代码所在当前的线程。
终止睡眠的线程(即唤醒)的方法:interrupt()方法
同时注意:run()方法中的异常不能throws抛给上级,只能用try catch。因为run()方法在父类(Runnale接口)中没有抛出任何异常,因此由于子类不能比父类抛出更多的异常,所以run()方法中只能用try catch来解决异常,而不能通过throws抛给上级。
强行终止线程的执行:stop()方法——已过时,不建议使用
缺点:容易丢失数据,因为这种方式是直接杀死线程,线程没有保存的数据会丢失。
合理终止线程的执行(常用):在线程中设置一个boolean的成员变量即可
我们可以在else中的方法体中执行保存数据的操作,将数据保存完毕之后再return即可。
线程调度
线程优先级:获取线程优先级方法:getPriority()
最高10,默认5,最低1
Thread.yield()静态方法:线程让位,让当前的线程暂停回到就绪态,让给其他线程
join()方法:线程合并
线程安全!!!!!!
线程不安全的条件
解决线程安全:线程同步机制:线程排队执行,不能并发执行
同步与异步
线程同步机制:同步代码块 synchronized
线程同步机制的语法:
synchronized(){
}
其中小括号()中的对象是,被若干指定线程共享的对象,从而实现若干指定线程对此对象的访问需要排队,而不能同时进行,从而实现了线程的安全和同步。
注意:局部变量永远不会有线程安全问题
synchronized还可以用在实例方法上进行修饰,用来锁this表示的对象。
synchronized也可以用于修饰静态方法,是类锁,将此类全部锁住排队!!!!
synchronized小总结
以上都是排它锁
还有互斥锁(后续学习)
死锁:持有某个资源去请求另一个资源时容易发生,因此不建议嵌套使用synchronized
开发中应该如何解决安全问题
守护线程:又称后台线程,如垃圾回收线程,当用户线程结束后,守护进程自动结束
守护进程的实现:setDaemon()方法
定时器java.util.Timer:间隔特定的时间,执行特定的程序
定时器的实现:
定时器例子:定时备份
实现线程的第三种方式(JDK8以后的新特性):实现Callable接口(其中的call()方法相当于线程中的run()方法)
wait()和notify()方法:是Object类中的方法,而不是线程的方法
生产者和消费者:
反射机制:通过反射机制可以操作j字节码文件
获取Class的三种方式:要操作一个类的字节码,收下你需要获取这个类的字节码即Class
第一种:Class.forName()静态方法
第二种方式:通过Object祖先级别的类中的getClass()方法
第三种方式:类.class
通过反射来实例化对象
反射机制灵活性
forName()方法的发生:若只想执行静态代码块,可以使用forName()方法
获取类路径下(在src下的都是类路径下的)文件的绝对路径
直接以流的方式返回而无需获取绝对路径:getResourceAsStream()
资源绑定器ResourceBundle:sun公司写好的,用来获取属性配置文件中的内容,注意,仅能用于绑定properties属性文件(即文件扩展名为properties),且此文件必须在类路径下,并且扩展名properties省略不写。
类加载器
反射机制小总结
使用反射获取属性field
以自己定义的student类为例:
getFields()方法是获得目标类中的public修饰的属性filed。
getDeclaredFields()方法获取目标类中声明的所有属性
获取一个类的类名:getName()和getSimpleName()方法
获取指定类中的属性的类型:getType()方法
获取指定类中的属性的修饰符列表:getModifiers()方法:返回一个数字,数字对应着修饰符的代号。将数字转换成对应的修饰符,用静态方法Modifier.toString(int i)
通过反射机制,反编译一个类的属性field(略)
通过反射机制,访问对象的属性
注意:反射机制的一大缺点:可以打破封装(setAccessible(true)),直接在外部给私有属性赋值
可变长度参数:类型后面跟三个点:如int… args
反射机制中的Method
获取类中所有的method:getDeclaredMethods()方法
获取类中方法的返回值类型:getReturnType()方法;同理,获取方法的修饰符类型:getModifiers()方法
获取方法中形式参数列表的类型:getParameterTypes()方法
反编译 Method(了解,略)
通过反射机制,调用一个对象(注意不是类)的方法:获取类,new对象,获取方法,然后使用invoke()方法调用对象的方法!!!******
反射Constructor构造方法
获取构造方法:getDeclaredConstructors()方法
通过反射机制调用构造方法实例化对象:和之前的一样,对于无参构造方法:直接获取类,然后类名.newInstance()方法即可;对于有参构造:先获取有参构造方法(getDeclaredConstructor(形参)),然后调用构造方法new对象即可
通过反射机制获取父类(getSuperClass()方法)和父接口(getInterfaces()方法)
注解/注释类型Annotation:是一种引用数据类型
注解定义与使用
[修饰符列表] @interface 注解类型名{
}
JDK已经内置的注解类:java.lang包中有Deprecated过时的、Override重写、SuppressWarnings三种注解
override注解:只能注解方法,只在编译阶段起作用,与运行期无关
元注解:就是修饰注解的注解,主要有Target和Tetention
并且可以在运行时被反射机制所读取。
元注解Retention:表示目标只能保存在XX中
元注解target:表示目标只能出现在xxx中,即只允许标注xxx
Deprecated表示已过时的:可以用来注解类、方法、变量等等
别人调用时,会发现已过时的方法中间会有一道横杠。
注解中定义属性
注意:如果一个注解的属性的名字是value的话,并且只有一个的话,使用时,该属性名value可以省略不写。(有两个叫value的属性不行)
注解中的属性可以是哪些类型?
在使用注解的时候,如果属性数组中只有一个元素,大括号可以省略
通过反射机制获取注解
通过反射机制获取注解对象中属性的值
注解在开发中的作用