目录
1 多线程基础
1.1 进程、线程相关概念
1):进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程
2):线程是由进程创建的,是进程的一个实体
3):一个进程可以有多个线程
4):单线程:同一个时刻,只允许执行一个线程
5):多线程:同一个时刻,可以执行多个线程,比如一个qq可以打开多个聊天窗口,多个app可以同时下载
6):并发:同一个时间段,多个任务交替执行,造成一种“貌似同时”的错觉,单核CPU实现的多任务就是并发
7):并行:同一个时刻,多个任务同时执行,多核CPU可以实现并行,并行也可能出现并发
1.2 创建线程的两种方方式
1):继承Thread类,重写run方法
2):实现Runnable接口,重写run方法,建议使用
3):当main线程启动一个子线程后,主线程不会阻塞,会继续执行
4):启动线程调用start()方法最终会执行run()(如果直接调用run()就不会启用新的线程,就相当于调用了一个普通的方法)
5):实现Runnable接口方式更加适合多个线程共享一个资源的情况(多个线程对象需要做同一个动作,就可以把继承了接口的对象放入多个Thread里面,就可以开启多个线程),并且避免了单继承的限制
public class Thread_ {
public static void main(String[] args) {
//不能通过hi直接调用start方法(没有,接口只有run())
//把创建的对象放入到Thread的对象
Hi hi = new Hi();
Thread thread = new Thread(hi);
thread.start();
Runtime runtime = Runtime.getRuntime();
int cpuNums=runtime.availableProcessors();
System.out.println(cpuNums);
}
}
//实现接口的方式
class Hi implements Runnable{
int times =0;
@Override
public void run() {
while (true){
times++;
System.out.println("hi!"+times);
try {
//sleep休眠的时间以毫秒为单位,进制为1000(=1秒)
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times==10){
break;
}
}
}
}
1.3 线程退出
1):如果希望main线程去控制线程t1的终止,必须可以修改loop(run()中while(loop))
2):让线程t1退出run()方法,从而终止t1线程(通知方式),只要可以控制变量就可以
1.4 线程的常用方法
1):setName(),设置线程的名称,使之与参数name相同
2):getName(),返回该线程的名称
3):start(),使该线程开始执行,Java虚拟机底层调用该线程的start0()方法开启新线程
4):run(),调用线程对象的run()方法
5):setPriority(),设置线程的优先级(min=1,norm=5,max=10)
6):getPriority(),获取线程的优先级
7):sleep(),在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
8):interrupt(),中断线程,没有结束线程
9):yield(),线程的礼让,让出CPU,让其他线程执行,但礼让的时间不确定,所以不一定礼让成功
10):join(),线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程的所有任务
1.5 守护线程
1):用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
2):守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程也结束,比如垃圾回收机制
3):setDeamon(),设置该线程为守护线程,主线程结束后该线程自动结束
1.6 线程的7大状态
new,Runnable(Ready、Running),TimedWaiting、Waiting、Bloked
1.7 线程同步机制
1):线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
2):关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时表明该对象在任一时刻只能由一个线程访问
3):同步代码块:synchronized(对象){//只有这个对象的线程受到互斥锁限制
//得到对象锁(互斥锁,不公平),才能操作同步代码
//需要被同步的代码}
4):synchronized还可以放在方法声明中,表示整个方法为同步方法
public synchronized void print(){
//需要被同步的代码}
5):同步静态方法的锁为当前类本身 默认锁:当前类名.class
6):同步非静态方法的锁为当前类本身的对象,默认锁为this
7):释放锁
- 当前线程的同步方法、同步代码块执行结束案例:上厕所,完事出来
- 当前线程在同步代码块、同步方法中遇到break、return.案例:没有正常的完事,经理叫他修改bug,不得已出来
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束案例:没有正常的完事,发现忘带纸,不得已出来
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去
8):sleep(),yield(),不会释放锁,站着茅坑不拉屎
9):练习
public class Exercisse02_ {
// 有2个用户分别从同一个卡上取钱((总额:10000)
// 每次都取1000,当余额不足时,就不能取款了
// 不能出现超取现象=》线程同步问题.
public static void main(String[] args) {
Card card = new Card();
Thread thread01 = new Thread(card);
Thread thread02 = new Thread(card);
thread01.setName("线程1");
thread02.setName("线程2");
thread01.start();
thread02.start();
}
}
class Card implements Runnable {
int money = 10000;
@Override
public void run() {
//synchronized不可以直接加在这里,有while,会导致一个线程一直结束不了,直到取完钱
while (true) {
synchronized (this) {
if (money < 1000) {
System.out.println("余额不足1000" + Thread.currentThread().getName());
break;
}
money -= 1000;
System.out.println("卡中剩余" + money + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
2 IO流
2.1 文件基础知识
1):文件是保存数据的地方,比如大家经常使用的word文档,txt文件,excel文件...都是文件。它既可以保存一张图片,也可以保持视频,声音...文件在程序中是以流的形式来操作的
2):流:数据在数据源(文件)和程序(内存)之间经历的路径
3):输入流:数据从数据源(文件)到程序(内存)的路径
4):输出流:数据从程序(内存)到数据源(文件)的路径
2.2 常用的文件操作
创建文件
1):new File(String pathname)//根据路径构建一个File对象
2):new File(File parent,String child)//根据父目录文件+子路径构建(如果就在父目录文件下,子路径其实就是文件名)
3):new File(String parent,String child)//根据父目录路径+子路径构建
4):createNewFile (),创建新文件,返回T/F反映是否创建成功,需要处理异常
5):先new获得文件对象(内存),再调用方法创建新文件(硬盘)
获取文件信息
6):getName(),得到文件名字
7):getAbsolutePath(),获取绝对路径
8):getParent(),获取文件父级目录
9):length(),获得文件大小字节,utf-8汉字三个字节,英语1个字节
10):exists(),文件是否存在
11):isFile(),是不是一个文件
12):isDirectory(),是不是一个目录
目录操作和文件删除
13):mkdir(),创建文件一级目录的文件D:\\de,返回T/F反映是否创建成功
14):mkdirs(),创建多级目录的文件D:\\de\\b\\c,返回T/F反映是否创建成功
15):delete(),删除空目录或空文件,返回T/F反映是否删除成功
2.3 IO流的分类
1):流的分类
- 按操作数据单位不同分为:字节流(8 bit,二进制文件),字符流(按字符,文本文件)
- 按数据流的流向不同分为:输入流,输出流
- 按流的角色的不同分为:节点流,处理流/包装流
2.4 字节流
1):InputStream常用的子类
- FileInputStream:文件输入流
- BufferedInputStream:缓冲字节输入流,直接父类是FileInputStream
- ObjectInputStream:对象字节输入流
FileInputStream
2):read(),从该输入流一次读取一个字节的数据,如果返回-1表示读取完毕
3):read(byte[] b),一次读取b.length长度,读取的内容放入到数组中,下次读取会覆盖数组中上次的内容,读取正常返回实际读取的字节数,-1读取完毕
FileOutStream
1):FileOutputStream fileOutStream = new FileOutputStream("D:\\IO测试.txt");文件不存在则创建,这种创建形式是覆盖;
2):FileOutputStream fileOutStream = new FileOutputStream("D:\\IO测试.txt",true);这种创建方式是在文件末尾追加
3): write(int):写入单个字符
4): write(char):写入指定数组
5): write(char,off,len):写入指定数组的指定部分
相关APl: String类:toCharArray:将String转换成char
2.5 字符流
FileReader和 FileWriter是字符流,即按照字符来操作io
FileReader
1):new FileReader(File/String)
2):read:每次读取单个字符,返回该字符,如果到文件末尾返回-1
3):read(char[]):批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1相关APl:
1.new String(char):将char[]转换成String
2. new String(char[,off,len):将char[的指定部分转换成String
FileWriter
1):new FileWriter(File/String):覆盖棒式,相当于流的指针在首端
2):new FileWriter(File/String,true):些加模式,相当于流的指针在尾端
3): write(int):写入单个字符
4): write(char):写入指定数组
5): write(char,off,len):写入指定数组的指定部分
6):write (string):写入整个字符串
7): write(string,off,len):写入字符串的指定部分
相关APl: String类:toCharArray:将String转换成char
注意:
FileWriter使用后,必须要关闭(close)或刷新(flush),否则写入不到指定的文件!
2.6 节点流和处理流
1):节点流可以从一个特定的数据源读写数据,如FileReader、FileWriter
2):处理流(也叫包装流)是“连接”在已存在的流(节点流或处理流)之上,里面有一个属性Reader/Writer (可以封装一个相应的任意节点流)。为程序提供更为强大的读写功能,如BufferedReader、BufferedWriter;性能更高,操作更便捷
3):创建缓冲流对象,需要套在指定的节点流基础上,ReaderWriter是按字符读取,所以不能操作二进制文件(图片、音乐....)
处理流字符流-BufferedReader/BufferedWriter
//需要关闭,包装的FileReader不需要关闭会自动关闭
4):readLine(),按行读取,返回null时表示文件读取完毕
class Buffer_{
@Test
public void m() throws Exception{
//覆盖方式
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("D:\\IO测试2"));
//追加的方式
// BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("D:\\IO测试2",true));
bufferedWriter.write("hello,测试\n测试");
bufferedWriter.write("hello,测试\n测试");
bufferedWriter.close();
}
}
5):练习,将源文件的内容复制到另一个文件
public class Exercise01_ {
public static void main(String[] args) throws Exception{
String srcPath="D:\\IO测试2";
String desPath="D:\\IO测试.txt";
BufferedReader bufferedReader = new BufferedReader(new FileReader(srcPath));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(desPath));
String line;
while ((line=bufferedReader.readLine())!=null){
bufferedWriter.write(line);
}
bufferedWriter.close();
}
}
处理流字节流-BufferedInputStream/BufferedoutputStream
6):BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("InputStream的实现类"));
7):BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("OutputStream的实现类"));
8):注意:关闭外层处理流即可,底层会关闭节点流
2.7 处理流-对象流
就是能够将基本数据类型或者对象进行序列化和反序列化
为了让某个类可序列化要实现Serializable接口
1):ObjectOnputStream提供序列化就是在保存数据时,保存数据的值和数据类型
2):ObjectInputStream提供反序列化就是在恢复数据时,恢复数据的值和数据类型
//序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存
String filePath = "e:\\data.dat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
//序列化数据到 e:\data.dat
oos.writeInt(100);// int -> Integer (实现了 Serializable)
oos.writeBoolean(true);// boolean -> Boolean (实现了 Serializable)
oos.writeChar('a');// char -> Character (实现了 Serializable)
oos.writeDouble(9.5);// double -> Double (实现了 Serializable)
oos.writeUTF("韩顺平教育");//String
//保存一个 dog 对象,Dog类实现序列化接口
oos.writeObject(new Dog("旺财", 10, "日本", "白色"));
oos.close()
//反序列化
// 1.创建流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src\\data.dat"));
// 2.读取, 注意顺序
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
System.out.println(ois.readObject());
// 3.关闭
ois.close();
3):序列化注意事项
- 读写顺序要一致
- 要求序列化或反序列化对象,需要实现Serializable
- 序列化的类中建议添加SerialVersionUID,为了提高版本的兼容性
- 序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员
- 序列化对象时,要求里面属性的类型也需要实现序列化接口
- 序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化
2.8 处理流-标准输入输出流
1):System.in(编译类型是InputStream运行类型是BufferedInputStream)标准输入,InputStream,键盘(默认设备)
2):System.out(PrintStream)标准输出,PrintStream,显示器
2.9 转换流
InputStreamReader\OutputStreamWriter
1):InputStreamReader:Reader的子类,可以将InputStream(字节流)包装成Reader(字符流)
2):OutputStreamWriter:Writer的子类,实现将OutputStream(字节流)包装成Writer(字符流)
3):当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
4):可以在使用时指定编码格式(比如utf-8, gbk , gb2312, ISO8859-1等)
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "gbk"));
/*将字节流 FileOutputStream包装成(转换成)字符流OutputStreamWriter,对文件进行写入(按照gbk格式,可以指定其他,比如utf-8)*/
// 1.创建流对象
OutputStreamWriter osw =new OutputStreamWriter(new FileOutputStream("d:\\a.txt"), "gbk");
// 2.写入
osw.write("hello,韩顺平教育~");
// 3.关闭
osw.close();
System.out.println("保存成功~");
2.10 打印流
1):打印流只有输出流,没有输入流
字节打印流
2):默认情况下打印流PrintSream输出数据的位置是标准输出即显示器(底层是Write)
3):System.setOut(“路径”/文件对象),修改打印流输出 的位置设备
字符打印流
4):Writer的子类
PrintWriter printWriter = new PrintWriter(System.out);
PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\f2.txt"));
PrintStream printStream = new PrintStream(new PrintStream(""));
PrintStream printStream = new PrintStream(new File(""));
PrintStream out = System.out;
//在默认情况下,PrintStream 输出
out.print("john, hello");
System.setOut(new PrintStream("e:\\f1.txt"));
2.11 Properties类
父类是Hashtable
1):properties后缀的是配置文件
2):Properties类专门用于读写配置文件的集合类,格式:键=值
3):键值对不需要有空格,需要用引号,默认是String
4):常用方法:
- load: 加载配置文件的键值对到Properties对象
- list:将数据显示到指定设备/流对象
- getProperty(key):根据键获取值
- setProperty(key,value):设置键值对到Properties对象,没有相同的key就是创建,有的话覆盖原来的
- store:将Properties中的键值对存储到配置文件,在idea中,保存信息到配置文件,如果含有中文,会存储为unicode码
//1. 创建 Properties 对象
Properties properties = new Properties();
//2. 加载指定配置文件
properties.load(new FileReader("src\\mysql.properties"));
//3. 把 k-v 显示控制台
properties.list(System.out);
//4. 根据 key 获取对应的值
String user = properties.getProperty("user");
String pwd = properties.getProperty("pwd");
System.out.println("用户名=" + user);
System.out.println("密码是=" +pwd);
Properties properties = new Properties();
properties.setProperty("charset", "utf8");
properties.setProperty("user", "汤姆");//注意保存时,是中文的 unicode 码值
properties.setProperty("pwd", "888888");
//将 k-v 存储文件中即可
properties.store(new FileOutputStream("src\\mysql2.properties"), null);
//null:是注释的位置
System.out.println("保存配置文件成功~");