当前位置:网站首页>线程池和单例模式以及文件操作
线程池和单例模式以及文件操作
2022-07-07 16:19:00 【罗汉翔】
原文链接:
坚持学习Java100天第024天 线程池和单例模式以及文件操作
线程池和单例模式以及文件操作
学习目标
生产者与消费者
JDK5特性JUC
单例模式
关键字volatile
线程池
ConcurrentHashMap
生产者与消费者安全问题产生
当存在多个生产者或者多个消费者的时候,之前的生产者和消费者模式是存在安全问题的。主要产生原因如下:
线程本身就是一个新创建的方法栈内存 (CPU进来读取数据)
线程的notify(),唤醒第一个等待的线程,但是起始等待的线程是多个的,之前陷入等待的其他线程不一定能被唤醒。解决办法是全部唤醒 notifyAll()。
被唤醒线程,已经进行过if判断,一旦醒来继续执行,但是此时资源可能是不可用的。解决方法是线程被唤醒后,不能立刻就执行,再次判断标志位,利用循环。while(标志位) 标志位是true,永远也出不去。
定义资源对象,Resource.java
package com.zhangdapeng520.thread01_producer_consumer;
/**
* 定义资源对象
* 成员 : 产生商品的计数器
* 标志位
*/
public class Resource {
private int count;
private boolean flag;
// 消费者调用
public synchronized void getCount() {
// flag是false,消费完成,等待生产
while (!flag)
//无限等待
try {
this.wait();
} catch (Exception ex) {
}
System.out.println("消费第" + count);
// 修改标志位,为消费完成
flag = false;
// 唤醒对方线程
this.notifyAll();
}
// 生产者调用
public synchronized void setCount() {
// flag是true,生产完成,等待消费
while (flag)
// 无限等待
try {
this.wait();
} catch (Exception ex) {
}
count++;
System.out.println("生产第" + count + "个");
// 修改标志位,为生产完成
flag = true;
// 唤醒对方线程
this.notifyAll();
}
}
创建生产者线程,Produce.java
package com.zhangdapeng520.thread01_producer_consumer;
/**
* 生产者线程
* 资源对象中的变量++
*/
public class Produce implements Runnable{
private Resource r ;
public Produce(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.setCount();
}
}
}
创建消费者线程,Customer.java
package com.zhangdapeng520.thread01_producer_consumer;
/**
* 消费者线程
* 资源对象中的变量输出打印
*/
public class Customer implements Runnable{
private Resource r ;
public Customer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.getCount();
}
}
}
创建测试类,ThreadTest.java
package com.zhangdapeng520.thread01_producer_consumer;
public class ThreadTest {
public static void main(String[] args) {
Resource r = new Resource();
// 接口实现类,生产的,消费的
Produce produce = new Produce(r);
Customer customer = new Customer(r);
// 创建线程
new Thread(produce).start();
new Thread(produce).start();
new Thread(produce).start();
new Thread(produce).start();
new Thread(produce).start();
new Thread(produce).start();
new Thread(customer).start();
new Thread(customer).start();
new Thread(customer).start();
new Thread(customer).start();
new Thread(customer).start();
new Thread(customer).start();
}
}
线程方法sleep和wait的区别
sleep在休眠的过程中,同步锁不会丢失 ,不释放。
wait()等待的时候,发布监视器的所属权,释放锁。唤醒后要重新获取锁,才能执行。
sleep和wait的区别就在于,sleep不会释放同步锁,wait会释放同步锁。
生产者和消费者案例性能问题
wait()方法和notify()方法,本地方法调用OS的功能,和操作系统交互,JVM找OS,把线程停止。频繁等待与唤醒,导致JVM和OS交互的次数过多。
notifyAll()唤醒全部的线程,也浪费线程资源,为了一个线程,不得以唤醒的了全部的线程。
Lock接口深入
Lock接口替换了同步synchronized,提供了更加灵活,性能更好的锁定操作。
Lock接口中方法 : newCondition() 方法的返回值是接口 : Condition。
使用Condition接口,我们也能够让一个线程停止或者唤醒,且不需要频繁让JVM和OS交互,能够有效的解决生产者和消费者案例中的性能问题。
Condition本质上是一个线程队列,我们可以给生产者创建一个生产者线程队列,给消费者创建一个消费者生成队列。当生产者生产完资源的时候,我们调用producerCondition.wait(),让生产者线程队列进入等待状态,然后调用costomerCondition.signal()通知消费者队列进行消费。反之,当消费者消费完资源的时候,我们调用consumerCondition.wait()让消费者线程队列进入等待状态,然后调用producerCondition.signal()让生产者线程队列生产资源。
同样重要的是,Condition支持和Lock嵌套使用,我们可以在一个Lock锁住的代码块里面,使用Condition。
生产者与消费者改进为Lock接口
Condition接口 (线程的阻塞队列)
进入队列的线程,释放锁
出去队列的线程,再次的获取锁
接口的方法 : await() 线程释放锁,进入队列
接口的方法 : signal() 线程出去队列,再次获取锁
线程的阻塞队列,依赖Lock接口创建
创建资源类,Resource.java
package com.zhangdapeng520.thread02_producer_consumer_condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 改进为高性能的Lock接口和线程的阻塞队列
*/
public class Resource {
private int count;
private boolean flag;
private final Lock lock = new ReentrantLock(); // Lock接口实现类对象
//Lock接口锁,创建出2个线程的阻塞队列
private final Condition producerCondition = lock.newCondition(); // 生产者线程阻塞队列
private final Condition customerCondition = lock.newCondition(); // 消费者线程阻塞队列
//消费者调用
public void getCount() {
lock.lock();//获取锁
// flag是false,消费完成,等待生产
while (!flag)
// 无限等待,消费线程等待,执行到这里的线程,释放锁,进入到消费者的阻塞队列
try {
customerCondition.await();
} catch (Exception ex) {
}
System.out.println("消费第" + count);
// 修改标志位,为消费完成
flag = false;
// 唤醒生产线程队列中的一个
producerCondition.signal();
lock.unlock(); // 释放锁
}
//生产者调用
public void setCount() {
lock.lock();//获取锁
// flag是true,生产完成,等待消费
while (flag)
// 无限等待,释放锁,进入到生产线程队列
try {
producerCondition.await();
} catch (Exception ex) {
}
count++;
System.out.println("生产第" + count + "个");
// 修改标志位,为生产完成
flag = true;
// 唤醒消费者线程阻塞队列中年的一个
customerCondition.signal();
lock.unlock(); // 释放锁
}
}
创建生产者类,Producer.java
package com.zhangdapeng520.thread02_producer_consumer_condition;
/**
* 生产者线程
* 资源对象中的变量++
*/
public class Producer implements Runnable {
private final Resource r;
public Producer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.setCount();
}
}
}
创建消费者类,Customer.java
package com.zhangdapeng520.thread02_producer_consumer_condition;
/**
* 消费者线程
* 资源对象中的变量输出打印
*/
public class Customer implements Runnable {
private Resource r;
public Customer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.getCount();
}
}
}
创建测试类,ThreadTest.java
package com.zhangdapeng520.thread02_producer_consumer_condition;
public class ThreadTest {
public static void main(String[] args) {
Resource r = new Resource();
// 接口实现类,生产的,消费的
Producer produce = new Producer(r);
Customer customer = new Customer(r);
// 创建线程
new Thread(produce).start();
new Thread(produce).start();
new Thread(produce).start();
new Thread(customer).start();
new Thread(customer).start();
new Thread(customer).start();
}
}
Lock锁的实现原理
使用技术不开源,技术的名称叫做轻量级锁。
使用的是CAS锁 (Compare And Swap) 自旋锁。
JDK限制 : 当竞争的线程大于等于10,或者单个线程自旋超过10次的时候
JDK强制CAS锁取消。升级为重量级锁 (OS锁定CPU和内存的通信总线)。
设计模式
设计模式 不是技术,是以前的开发人员,为了解决某些问题实现的写代码的经验。
所有的设计模式核心的技术,就是面向对象。
Java的设计模式有23种,分为3个类别,创建型,行为型,功能型。
单例模式
单例模式是创建对象的一种方式,主要目标是保证对象在内存中只存在一个。实现的方法有很多种,通常是不考虑多线程的。在多线程中,考虑到线程的安全性,还有多线程版的单例模式。
实现步骤
私有修饰构造方法
自己创建自己的对象
方法get,返回本类对象
示例代码:
package com.zhangdapeng520.single01;
/**
* - 私有修饰构造方法
* - 自己创建自己的对象
* - 方法get,返回本类对象
*/
public class Single {
private Single() {
}
private static Single s = new Single(); // 自己创建自己的对象
// 方法get,返回本类对象
public static Single getInstance() {
return s;
}
}
创建一段测试代码测试:
package com.zhangdapeng520.single01;
public class Test {
public static void main(String[] args) {
//静态方法,获取Single类的对象
Single instance = Single.getInstance();
System.out.println("instance = " + instance);
instance = Single.getInstance();
System.out.println("instance = " + instance);
instance = Single.getInstance();
System.out.println("instance = " + instance);
instance = Single.getInstance();
System.out.println("instance = " + instance);
instance = Single.getInstance();
System.out.println("instance = " + instance);
}
}
懒汉式的安全问题
一个线程判断完变量 s=null,还没有执行new对象,被另一个线程抢到CPU资源,同时有2个线程都进行判断变量,对象创建多次。
也就是在多线程中最常见的线程安全,多个线程操作同一个资源时,很容易出现重复。要解决线程安全问题,使用synchronized关键字即可。可以是同步方法,也可以是同步代码块。
改造单例模式:Single.java
package com.zhangdapeng520.single02;
/**
* - 私有修饰构造方法
* - 创建本类的成员变量, 不new对象
* - 方法get,返回本类对象
*/
public class Single {
private Single() {
}
private static volatile Single s = null;
public static Single getInstance() {
//再次判断变量,提高效率
if (s == null) {
synchronized (Single.class) {
//判断变量s,是null就创建
if (s == null) {
s = new Single();
}
}
}
return s;
}
}
接着,我们创建一个测试类进行测试:Test.java
package com.zhangdapeng520.single02;
public class Test {
public static void main(String[] args) {
//静态方法,获取Single类的对象
Single instance = Single.getInstance();
System.out.println("instance = " + instance);
instance = Single.getInstance();
System.out.println("instance = " + instance);
instance = Single.getInstance();
System.out.println("instance = " + instance);
instance = Single.getInstance();
System.out.println("instance = " + instance);
instance = Single.getInstance();
System.out.println("instance = " + instance);
}
}
性能问题 : 第一个线程获取锁,创建对象,返回对象. 第二个线程调用方法的时候,变量s已经有对象了,根本就不需要在进同步,不要在判断空,直接return才是最高效的.双重的if判断,提高效率 Double Check Lock
private static volatile Single s = null;
public static Single getInstance(){
//再次判断变量,提高效率
if(s == null) {
synchronized (Single.class) {
//判断变量s,是null就创建
if (s == null) {
s = new Single();
}
}
}
return s;
}
关键字volatile
成员变量修饰符,不能修饰其它内容。
关键字作用 :
保证被修饰的变量,在线程中的可见性
防止指令重排序
单例的模式,使用了关键字,不使用关键字,可能线程会拿到一个尚未初始化完成看的对象(半初始化)。
示例:MyRunnable.java
package com.zhangdapeng520.single03;
public class MyRunnable implements Runnable {
private volatile boolean flag = true; // volatile保证flag是全局唯一的
@Override
public void run() {
m();
}
private void m() {
System.out.println("开始执行");
while (flag) {
}
System.out.println("结束执行");
}
// 修改flag标志
public void setFlag(boolean flag) {
this.flag = flag;
}
}
测试类:VolatileTest.java
package com.zhangdapeng520.single03;
public class VolatileTest {
public static void main(String[] args) throws InterruptedException {
MyRunnable myRunnable = new MyRunnable();
// 线程开始执行
new Thread(myRunnable).start();
// 休息2s
Thread.sleep(2000);
// main线程修改变量
myRunnable.setFlag(false);
}
}
线程池ThreadPool
线程的缓冲池,目的就是提高效率.。new Thread().start(),线程是内存中的一个独立的方法栈区,JVM没有能力开辟内存空间,和OS交互。
JDK5开始内置线程池。
Executors类
静态方法static newFixedThreadPool(int 线程的个数),方法的返回值ExecutorService接口的实现类,管理池子里面的线程。
ExecutorService接口的方法submit (Runnable r),提交线程执行的任务。
Callable接口
实现多线程的程序,接口特点是有返回值,可以抛出异常 (Runnable没有)。抽象的方法只有一个call。启动线程,线程调用重写方法call。
ExecutorService接口的方法
submit (Callable c)提交线程执行的任务
Future submit()方法提交线程任务后,方法有个返回值 Future接口类型
Future接口,获取到线程执行后的返回值结果
Callable和Runnable都可以实现多线程程序,不同的是,Callable有返回值,Runnable没有返回值。使用Executors可以创建线程池,线程池可以执行带Callable的实现类,也可以执行Runnable的实现类。
我们写个示例感受一下。
创建一个线程类,实现Runnable接口。MyRunnable.java
package com.zhangdapeng520.thread03;
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始启动");
}
}
创建一个线程类,实现Callable接口。MyCallable.java
package com.zhangdapeng520.thread03;
import java.util.concurrent.Callable;
public class MyCall implements Callable<String> {
public String call() throws Exception{
return "返回字符串";
}
}
创建一个测试类,创建线程池,提交Callable的任务和Runable的任务进行测试。ThreadTest.java
package com.zhangdapeng520.thread03;
import java.util.concurrent.*;
public class ThreadTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建线程池,线程的个数是2个
ExecutorService es = Executors.newFixedThreadPool(2);
// 线程池管理对象service,调用方法啊submit提交线程的任务
MyRunnable my = new MyRunnable();
// 提交线程任务,使用Callable接口实现类
Future<String> future = es.submit(new MyCall()); // 返回接口类型 Future
// 接口的方法get,获取线程的返回值
String str = future.get();
System.out.println("str = " + str);
// 没有返回值的线程任务也可以提交
es.submit(my);
es.submit(my);
es.submit(my);
es.shutdown();//销毁线程池
}
}
ConcurrentHashMap
ConcurrentHashMap类本质上Map集合,键值对的集合。使用方式和HashMap没有区别。
凡是对于此Map集合的操作,不去修改里面的元素,不会锁定。
ConcurrentHashMap是一种线程安全的Map,在多线程的程序中,推荐使用ConcurrentHashMap存储数据。
线程的状态
在某一个时刻,线程只能处于其中的一种状态。这种线程的状态反应的是JVM中的线程状态,和OS无关。
线程主要有以下状态:
受阻塞状态BLOCKED 新建状态NEW 运行状态RUNABLE 时间等待TIMED_WAITING 结束状态TERMINATED无限等待WAITING
调用Thread.sleep()方法和this.wait()方法会进入等待状态。sleep不会释放锁,wait会释放锁。
File类
文件夹 Directory ,存储文件的容器,防止文件重名而设置。文件归类,文件夹本身不存储任何数据,计算专业数据称为目录。文件 File,存储数据的,同一个目录中的文件名不能相同。
一个文件夹中可以有很多个文件,而且文件夹中还可以嵌套的存放文件夹。但是文件中,只能存数据,不能存文件夹。文件是计算机中数据存储的载体,是一个整体。
路径 Path,一个目录或者文件在磁盘中的位置。
c:\jdk8\jar 是目录的路径,是个文件夹的路径
c:\jdk8\bin\javac.exe 是文件的路径
路径分为绝对路径和相对路径,路径用来定位存储在计算机中的资源,这个资源要么是文件,要么是文件夹。还有一些其他非常特殊的资源,比如说软链接,硬链接等。
一般来说,文件夹是不带任何后缀的,文件一般都带有后缀,但是也有特殊的情况。有些文件夹是带后缀的,因为这本身就被允许。有些文件也是不带后缀的,这也是被计算机允许的。比如说我们经常用的hosts配置文件,就是不带后缀的文件。
File类,描述目录文件和路径的对象,具备平台无关性。
通过文件路径创建File
package com.zhangdapeng520.file;
import java.io.File;
/**
* @author https://github.com/zhangdapeng520
* @version 1.0.0
* @className Demo01FileByPath.java
* @description do something about Demo01FileByPath
* @createTime 2022-07-01 23:42:00
*/
public class Demo01FileByPath {
public static void main(String[] args) {
// 声明路径
String path = "C:\\tmp";
// 创建对象
File file = new File(path);
System.out.println("是否存在:" + file.exists());
System.out.println("是否为目录:" + file.isDirectory());
}
}
通过父子路径创建File
可以通过File(String parent,String child)创建File对象。parent传递父路径的字符串,child传子路径的字符串。
package com.zhangdapeng520.file;
import java.io.File;
/**
* @author https://github.com/zhangdapeng520
* @version 1.0.0
* @className Demo02FileByFatherAndChild.java
* @description do something about Demo02FileByFatherAndChild
* @createTime 2022-07-01 23:47:00
*/
public class Demo02FileByFatherAndChild {
public static void main(String[] args) {
// 声明路径
String path = "C:\\tmp";
// 创建对象
File file = new File(path, "test.txt");
System.out.println("是否存在:" + file.exists());
System.out.println("是否为文件:" + file.isFile());
}
}
通过File对象创建File
可以通过File(File parent,String child)来创建File对象。parent传递File类型的父路径,child传子路径的字符串。
package com.zhangdapeng520.file;
import java.io.File;
/**
* @author https://github.com/zhangdapeng520
* @version 1.0.0
* @className Demo02FileByFatherAndChild.java
* @description do something about Demo02FileByFatherAndChild
* @createTime 2022-07-01 23:47:00
*/
public class Demo03FileByFatherAndChild {
public static void main(String[] args) {
// 声明路径
String path = "C:\\tmp";
// 创建对象
File parent = new File(path);
File file = new File(parent, "test.txt");
System.out.println("是否存在:" + file.exists());
System.out.println("是否为文件:" + file.isFile());
}
}
创建文件夹
通过boolean mkdirs()创建目录,这个目录可以是多级目录,比如a/b/c。目录的位置和名字写在File的构造方法中。
示例代码:
package com.zhangdapeng520.file;
import java.io.File;
/**
* @author https://github.com/zhangdapeng520
* @version 1.0.0
* @className Demo02FileByFatherAndChild.java
* @description do something about Demo02FileByFatherAndChild
* @createTime 2022-07-01 23:47:00
*/
public class Demo04CreateDir {
public static void main(String[] args) {
// 声明路径
String path = "C:\\tmp";
// 创建文件对象
File file = new File(path, "a/b/c");
System.out.println("是否存在:" + file.exists());
// 创建文件夹
boolean isOk = file.mkdirs();
System.out.println("是否创建成功:" + isOk);
System.out.println("是否存在:" + file.exists());
}
}
创建文件
可以通过boolean createNewFile()创建一个文件。需要注意的是,文件所在的文件夹必须要存在的,否则不成功。文件路径写在File的构造方法中。
示例代码:
package com.zhangdapeng520.file;
import java.io.File;
import java.io.IOException;
/**
* @author https://github.com/zhangdapeng520
* @version 1.0.0
* @className Demo02FileByFatherAndChild.java
* @description do something about Demo02FileByFatherAndChild
* @createTime 2022-07-01 23:47:00
*/
public class Demo04CreateNewFile {
public static void main(String[] args) throws IOException {
// 声明路径
String path = "C:\\tmp";
// 创建文件对象
File file = new File(path, "a/b/c");
System.out.println("是否存在:" + file.exists());
// 创建文件夹
boolean isOk = file.mkdirs();
System.out.println("是否创建成功:" + isOk);
System.out.println("是否存在:" + file.exists());
// 创建文件
File testFile = new File(file, "test.txt");
isOk = testFile.createNewFile();
System.out.println("文件是否创建成功:" + isOk);
System.out.println("文件是否创建存在:" + testFile.exists());
}
}
删除文件
通过boolean delete() 删除指定的目录或者文件。路径写在File类的构造方法。需要注意,删除的文件或文件夹不会进入回收站,直接从磁盘中删除了,有风险。
示例代码:
package com.zhangdapeng520.file;
import java.io.File;
import java.io.IOException;
/**
* @author https://github.com/zhangdapeng520
* @version 1.0.0
* @className Demo02FileByFatherAndChild.java
* @description do something about Demo02FileByFatherAndChild
* @createTime 2022-07-01 23:47:00
*/
public class Demo05Delete {
public static void main(String[] args) throws IOException {
// 声明路径
String path = "C:\\tmp";
// 创建文件对象
File file = new File(path, "a/b/c");
System.out.println("是否存在:" + file.exists());
// 创建文件夹
boolean isOk = file.mkdirs();
System.out.println("是否创建成功:" + isOk);
System.out.println("是否存在:" + file.exists());
// 创建文件
File testFile = new File(file, "test.txt");
isOk = testFile.createNewFile();
System.out.println("文件是否创建成功:" + isOk);
System.out.println("文件是否创建存在:" + testFile.exists());
// 删除文件
isOk = testFile.delete();
System.out.println("文件是否删除成功:" + isOk);
System.out.println("文件是否创建存在:" + testFile.exists());
// 删除文件夹
isOk = file.delete();
System.out.println("文件夹是否删除成功:" + isOk);
System.out.println("文件夹是否创建存在:" + file.exists());
}
}
File类判断方法
boolean exists() 判断构造方法中的路径是否存在
boolean isDirectory()判断构造方法中的路径是不是文件夹
boolean isFile()判断构造方法中的路径是不是文件
boolean isAbsolute() 判断构造方法中的路径是不是绝对路径
示例代码:
package com.zhangdapeng520.file;
import java.io.File;
import java.io.IOException;
/**
* @author https://github.com/zhangdapeng520
* @version 1.0.0
* @className Demo02FileByFatherAndChild.java
* @description do something about Demo02FileByFatherAndChild
* @createTime 2022-07-01 23:47:00
*/
public class Demo05Delete {
public static void main(String[] args) throws IOException {
// 声明路径
String path = "C:\\tmp";
// 创建文件对象
File file = new File(path, "a/b/c");
// 创建文件夹
boolean isOk = file.mkdirs();
System.out.println("是否创建成功:" + isOk);
System.out.println("是否存在:" + file.exists());
System.out.println("文件夹是否为文件:" + file.isFile());
System.out.println("文件夹是否为绝对路径:" + file.isAbsolute());
System.out.println("文件夹是否创建存在:" + file.exists());
System.out.println("====================");
// 创建文件
File testFile = new File(file, "test.txt");
isOk = testFile.createNewFile();
System.out.println("文件是创建除成功:" + isOk);
System.out.println("文件是否创建存在:" + testFile.exists());
System.out.println("文件是否为文件:" + testFile.isFile());
System.out.println("文件是否为绝对路径:" + testFile.isAbsolute());
}
}
文件路径
绝对路径
在磁盘中的路径具有唯一性
Windows系统中,盘符开头
C:/Java/jdk1.8.0_221/bin/javac.exe
Linux或者Unix系统, /开头,磁盘根
/usr/local
互联网路径 :
www.baidu.com
https://item.jd.com/100007300763.html
https://pro.jd.com/mall/active/3WA2zN8wkwc9fL9TxAJXHh5Nj79u/index.html
相对路径
必须有参照物
C:/Java/jdk1.8.0_221/bin/javac.exe
bin是参考点 : 父路径 C:/Java/jdk1.8.0_221
bin是参考点 : 子路径 javac.exe
bin参考点: 父路径使用 ../表示
package com.zhangdapeng520.file;
import java.io.File;
import java.io.IOException;
/**
* @author https://github.com/zhangdapeng520
* @version 1.0.0
* @className Demo02FileByFatherAndChild.java
* @description do something about Demo02FileByFatherAndChild
* @createTime 2022-07-01 23:47:00
*/
public class Demo06Absolute {
public static void main(String[] args) throws IOException {
// 声明路径
String path = "C:\\tmp";
// 绝对路径
File absoluteFile = new File(path, "a/b/c");
boolean isOk = absoluteFile.mkdirs();
System.out.println("文件夹是否为绝对路径:" + absoluteFile.isAbsolute());
// 相对路径
File relativeFile = new File("tmp/a/b/c");
isOk = relativeFile.mkdirs();
System.out.println("文件夹是否为绝对路径:" + relativeFile.isAbsolute());
}
}
边栏推荐
- zdog. JS rocket turn animation JS special effects
- Backup Alibaba cloud instance OSS browser
- Target detection 1 -- actual operation of Yolo data annotation and script for converting XML to TXT file
- List selection JS effect with animation
- Introduction de l'API commune de programmation de socket et mise en œuvre de socket, select, Poll et epoll
- SD_DATA_RECEIVE_SHIFT_REGISTER
- Chapter 1 Introduction to CRM core business
- [principles and technologies of network attack and Defense] Chapter 5: denial of service attack
- Tips for this week 131: special member functions and ` = Default`
- Face recognition attendance system based on Baidu flying plasma platform (easydl)
猜你喜欢
Tips for short-term operation of spot silver that cannot be ignored
Disk storage chain B-tree and b+ tree
Performance test process and plan
ICer知识点杂烩(后附大量题目,持续更新中)
数学分析_笔记_第11章:Fourier级数
Taffydb open source JS database
4种常见的缓存模式,你都知道吗?
The report of the state of world food security and nutrition was released: the number of hungry people in the world increased to 828million in 2021
Improve application security through nonce field of play integrity API
js拉下帷幕js特效显示层
随机推荐
[4500 word summary] a complete set of skills that a software testing engineer needs to master
[deep learning] 3 minutes introduction
同消费互联网的较为短暂的产业链不同,产业互联网的产业链是相当漫长的
Yearning-SQL审核平台
[PaddleSeg源码阅读] PaddleSeg Validation 中添加 Boundary IoU的计算(1)——val.py文件细节提示
debian10编译安装mysql
Pro2:修改div块的颜色
Test for 3 months, successful entry "byte", my interview experience summary
Ten thousand words nanny level long article -- offline installation guide for datahub of LinkedIn metadata management platform
面试官:页面很卡的原因分析及解决方案?【测试面试题分享】
Use onedns to perfectly solve the optimization problem of office network
万字保姆级长文——Linkedin元数据管理平台Datahub离线安装指南
磁盘存储链式的B树与B+树
财富证券证券怎么开户?通过链接办理股票开户安全吗
List selection JS effect with animation
Explain it in simple terms. CNN convolutional neural network
Mobile app takeout ordering personal center page
Slider plug-in for swiper left and right switching
Hash, bitmap and bloom filter for mass data De duplication
[trusted computing] Lesson 12: TPM authorization and conversation