当前位置:网站首页>线程池和单例模式以及文件操作

线程池和单例模式以及文件操作

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

原网站

版权声明
本文为[罗汉翔]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_44885775/article/details/125632591