Java基础(5) 泛型 日期和时间 线程 File-输入流

泛型

java的泛型有点像ts的泛型

public class ArrayList<T> {private T[] array;private int size;public void add(T e) {...}public void remove(int index) {...}public T get(int index) {...}
}
// 创建可以存储String的ArrayList:
ArrayList<String> strList = new ArrayList<String>();
// 创建可以存储Float的ArrayList:
ArrayList<Float> floatList = new ArrayList<Float>();
// 创建可以存储Person的ArrayList:
ArrayList<Person> personList = new ArrayList<Person>();
  • 泛型就是编写模板代码来适应任意类型;
  • 泛型的好处是使用时不必对类型进行强制转换,它通过编译器对类型进行检查;
  • 注意泛型的继承关系:可以把ArrayList向上转型为List(T不能变!),但不能把ArrayList向上转型为ArrayList(T不能变成父类)。
使用泛型

使用ArrayList的时候,不指定类型,默认就是object;
泛型接口

public interface Comparable<T> {/*** 返回负数: 当前实例比参数o小* 返回0: 当前实例与参数o相等* 返回正数: 当前实例比参数o大*/int compareTo(T o);
}

想要使用Array.sorts,放入里面的元素就需要实现comapreTo方法。

class Person implements Comparable<Person> {String name;int score;Person(String name, int score) {this.name = name;this.score = score;}public int compareTo(Person other) {return this.name.compareTo(other.name);}public String toString() {return this.name + "," + this.score;}
}

自己编写

class Pari<T> {private T first;private T last;public Pari(T first, T last){this.first = first;this.last = last;}public T getFirst(){return this.first;}// 编译报错public static Pari<T> create (T first, T last){return new Pari<T>(first, last);}}

不能在静态方法上面添加泛型,静态泛型方法应该使用其他类型区分

 // 编译报错public static <K>Pari<K> create(K first, K last) {return new Pari<K>(first, last);}
擦拭法

泛型是一种类似”模板代码“的技术,不同语言的泛型实现方式不一定相同。
Java语言的泛型实现方式是擦拭法(Type Erasure)。
就是,虚拟机对泛型其实一无所知,所有的工作都是编译器做的。
当我们编写的泛型,实际上到虚拟机执行的时候

public class Pair {private Object first;private Object last;public Pair(Object first, Object last) {this.first = first;this.last = last;}public Object getFirst() {return first;}public Object getLast() {return last;}
}
  • 编译器把类型视为Object;
  • 编译器根据实现安全的强制转型。

Java的泛型是由编译器在编译时实行的,编译器内部永远把所有类型T视为Object处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型。

了解java泛型的实现,才能知道它的局限性,

  • T不能是基本类型,比如int,因为实际编译是object,object无法持有基本类型。
  • 无法取得带泛型的Class,因为T是Object,我们对Pair< String>和Pair< Integer>类型获取Class时,获取到的是同一个Class,也就是Pair类的Class。也就是说,无论T是啥,getClass都会返回一个class实力,因为编译后它们都属于Pair< Object>
  • 无法判断带泛型的类型:并不存在Pair< String>.class,而是只有唯一的Pair.class
Pair<Integer> p = new Pair<>(123, 456);
// Compile error:
if (p instanceof Pair<String>) {
}

所以无法通过实例p来获取泛型T

  • 局限四:不能实例化T类型:
public class Pair<T> {private T first;private T last;public Pair() {// Compile error:first = new T(); //  本意是想创建String,但编译后 first = new Object();last = new T(); //last = new Object();}
}

创建new Pair< String>()和创建new Pair< Integer>()就全部成了Object
可以通过反射

public class Pair<T> {private T first;private T last;public Pair(Class<T> clazz) {this.first = clazz.newInstance();this.last = clazz.newInstance()}
}
Pair<String> pair = new Pair<>(String.class);

通过String的反射获取到构造函数,然后调用创建一个新的String实例。

  • 泛型方法要防止重复定义方法,例如:public boolean equals(T obj);
  • 虽然,实例p无法获取创建时候传入的T,但是子类可以获取父类的泛型类型。
java的泛型继承
 static int add(Pari<? extends Number> p){Number first = p.getFirst();Number second = 2;return first.intValue()  + second.intValue();}

在java中,使用< ? extends xxx > 来限定传入的值 (上界通配符,泛型类型T的上界限定在Number了),
使用< T extends xxx>来限定T的范围
如上

add(Pari<Integer>)
add(Pari<Number>)

都可以

class Pari<T extends Number> {private T first;private T last;public Pari(T first, T last) {this.first = first;this.last = last;}public T getFirst() {return this.first;}public void setFirst(T val) {this.first = val;}// 编译报错public static <K extends Number> Pari<K> create(K first, K last) {return new Pari<K>(first, last);}static int add(Pari<? extends Number> p) {Number first = p.getFirst();Number second = 2;return first.intValue() + second.intValue();}}

这里要注意,当我们使用? extends Number获取到对应的实例的时候,是不能调用对应的set方法的
在这里插入图片描述

原因还是在于擦拭法,当我们传入的p是Pari< Double>时,他满足 ? extends Number,但是他的setFirst显然无法接受Ingeter类型。这就是? extends Number的一个重要限制。
方法参数签名setFirst(? extends Number)无法传递任何Number的子类型给setFirst(? extends Number)。
唯一的例外是可以给方法参数传入null。

作用

当我们需要实现要一个只读接口的时候,就可以通过这种方式。

public interface List<T> {int size(); // 获取个数T get(int index); // 根据索引获取指定元素void add(T t); // 添加一个新元素void remove(T t); // 删除一个已有元素
}int sumOfList(List<? extends Integer> list) {int sum = 0;for (int i=0; i<list.size(); i++) {Integer n = list.get(i);sum = sum + n;}return sum;
}

这里定义的? extends Integer跟直接定义Integer是一样的,但是? extends Integer有个好处,

  • 允许调用get()方法获取Integer的引用;
  • 不允许调用add(? extends Integer)方法并传入任何Integer的引用(null除外)。

所以

  • 使用类似<? extends Number>通配符作为方法参数时表示:
    • 方法内部可以调用获取Number引用的方法,例如:Number n = obj.getFirst();;
    • 方法内部无法调用传入Number引用的方法(null除外),例如:obj.setFirst(Number n);。
    • 使用extends通配符表示可以读,不能写。
super

上面的extends限制了T的上限,比如 ? extends Number,那么只能传入Number和Number的子类。
那如果想限制下限呢,比如 限制只能传入Integet和 integer的父类,可以用super

  static void setFirst1(Pari<? super Integer> p, Integer val1) {// void Pari.setFirst(Object val)p.setFirst(Integer.valueOf(123));// Object Pari.getFirst()p.getFirst();}

? super Integer表示,方法参数接受,所有泛型类型为Integer或Integer父类的 Pari类型。
此时setFirst也接受Integer及以上的类型的值。

考察Pair< ? super Integer>的setFirst方法,我们传入了? super Integer给 T,那么setFirst,getFirst就是

void setFirst(? super Integer)
? super Integer getFirst();

所以setFirst可以接受Integer以上的值,但是不能用Integer去接受getFirst的值,因为如果传入了Number,那么无法将Number转为Integer(Number即使是抽象类,这里也不能通过编译)
唯一可以接收getFirst()方法返回值的是Object类型

Object obj = p.getFirst();

这些点恰好和extends相反。
所以,? super Integer表示

  • 允许调用set(? super Integer)方法传入Integer的引用;
  • 不允许调用get()方法获得Integer的引用

换句话说,使用<? super Integer>通配符作为方法参数,表示方法内部代码对于参数只能写,不能读。

extends 和 super的区别

? extends T 允许 调用读方法 T get()获取T的引用,但不允许set(T)传入T的引用。
? super T 允许调用set(T)传入T的引用,不允许调用 T get()获取T的引用(获取object除外)

案例 collections的copy,复制集合

public class Collections {// 把src的每个元素复制到dest中:public static <T> void copy(List<? super T> dest, List<? extends T> src) {for (int i=0; i<src.size(); i++) {T t = src.get(i);dest.add(t);}}
}

这个copy()方法的定义就完美地展示了extends和super的意图:

  • copy()方法内部不会读取dest,因为不能调用dest.get()来获取T的引用;
  • copy()方法内部也不会修改src,因为不能调用src.add(T)。

这个copy()方法的另一个好处是可以安全地把一个List< Integer>添加到List< Number>,但是无法反过来添加

  • 因为? super T 表示 ? super Integer, 表示 dest可以写入Integer以上的值,比如Number
  • ? extends T 表示 ? extends Number,表示src可以读取Number一下的值,比如Integer
  • 然后将读取的Integer写入到 add( ? super Integer)中。
  • 但如果反过来则不行了, ? super Number只允许写入Number以上的,Integer显然不行。
PECS原则

Producer Extends Consumer Super
如果需要返回T,它是生产者(Producer),要使用extends通配符;
如果需要写入T,它是消费者(Consumer),要使用super通配符。
如上述的src的生产者,dest的消费者。
需要返回T的src是生产者,因此声明为List<? extends T>,需要写入T的dest是消费者,因此声明为List<? super T>。

无限定通配符
void sample(Pair<?> p) {
}

既不是extends也不是super,所以她不能读,也不能写,只能做一些null判断

static boolean isNull(Pair<?> p) {return p.getFirst() == null || p.getLast() == null;
}

Pair<?>是所有Pair的超类

  Pair<Integer> p = new Pair<>(123, 456);Pair<?> p2 = p; // 安全地向上转型System.out.println(p2.getFirst() + ", " + p2.getLast());

日期和时间

System.currentTimeMillis(),这是Java程序获取时间戳最常用的方法。

标准库API

我们再来看一下Java标准库提供的API。Java标准库有两套处理日期和时间的API:

  • 一套定义在java.util这个包里面,主要包括Date、Calendar和TimeZone这几个类;
  • 一套新的API是在Java 8引入的,定义在java.time这个包里面,主要包括LocalDateTime、ZonedDateTime、ZoneId等。
// 获取当前时间:Date date = new Date();System.out.println(date.getYear() + 1900); // 必须加上1900System.out.println(date.getMonth() + 1); // 0~11,必须加上1System.out.println(date.getDate()); // 1~31,不能加1// 转换为String:System.out.println(date.toString());// 转换为GMT时区:System.out.println(date.toGMTString());// 转换为本地时区:System.out.println(date.toLocaleString());// 想要针对用户的偏好精确地控制日期和时间的格式var sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println(sdf.format(date)); //2024-03-02 11:32:47

Date对象有几个严重的问题:它不能转换时区,除了toGMTString()可以按GMT+0:00输出外,Date总是以当前计算机系统的默认时区为基础进行输出。此外,我们也很难对日期和时间进行加减,计算两个日期相差多少天,计算某个月第一个星期一的日期等。

Calendar

Calendar可以用于获取并设置年、月、日、时、分、秒,它和Date比,主要多了一个可以做简单的日期和时间运算的功能。

TimeZone

Calendar和Date相比,它提供了时区转换的功能

新的一套api
  • 本地日期和时间:LocalDateTime,LocalDate,LocalTime;
  • 带时区的日期和时间:ZonedDateTime;
  • 时刻:Instant;
  • 时区:ZoneId,ZoneOffset;
  • 时间间隔:Duration。
  • 以及一套新的用于取代SimpleDateFormat的格式化类型DateTimeFormatter。
   LocalDate d = LocalDate.now(); // 2024-03-02LocalTime t = LocalTime.now(); // 11:36:39.612LocalDateTime dt = LocalDateTime.now(); //2024-03-02T11:36:39.612

由于执行时机的相差,上述的时间可能对不是,毫秒数。

LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间
LocalDate d = dt.toLocalDate(); // 转换到当前日期
LocalTime t = dt.toLocalTime(); // 转换到当前时间// 指定日期和时间:
LocalDate d2 = LocalDate.of(2019, 11, 30); // 2019-11-30, 注意11=11月
LocalTime t2 = LocalTime.of(15, 16, 17); // 15:16:17
LocalDateTime dt2 = LocalDateTime.of(2019, 11, 30, 15, 16, 17);
LocalDateTime dt3 = LocalDateTime.of(d2, t2);LocalDateTime dt = LocalDateTime.parse("2019-11-19T15:16:17");
LocalDate d = LocalDate.parse("2019-11-19");
LocalTime t = LocalTime.parse("15:16:17");

ISO 8601规定的日期和时间分隔符是T。标准格式如下:
日期:yyyy-MM-dd
时间:HH:mm:ss
带毫秒的时间:HH:mm:ss.SSS
日期和时间:yyyy-MM-dd’T’HH:mm:ss
带毫秒的日期和时间:yyyy-MM-dd’T’HH:mm:ss.SSS

DateTimeFormatter
      // 自定义格式化:DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");System.out.println(dtf.format(LocalDateTime.now()));// 用自定义格式解析:LocalDateTime dt2 = LocalDateTime.parse("2019/11/30 15:16:17", dtf);System.out.println(dt2);

此外localDateTime还提供了简单的加减法。

   LocalDateTime dt = LocalDateTime.of(2019, 10, 26, 20, 30, 59);System.out.println(dt);// 加5天减3小时:LocalDateTime dt2 = dt.plusDays(5).minusHours(3);System.out.println(dt2); // 2019-10-31T17:30:59// 减1月:LocalDateTime dt3 = dt2.minusMonths(1);System.out.println(dt3); // 2019-09-30T17:30:59

注意到月份加减会自动调整日期,例如从2019-10-31减去1个月得到的结果是2019-09-30,因为9月没有31日。
对日期和时间进行调整则使用withXxx()方法,例如:withHour(15)会把10:11:12变为15:11:12:

调整年:withYear()
调整月:withMonth()
调整日:withDayOfMonth()
调整时:withHour()
调整分:withMinute()
调整秒:withSecond()LocalDateTime dt = LocalDateTime.of(2019, 10, 26, 20, 30, 59);System.out.println(dt);// 日期变为31日:LocalDateTime dt2 = dt.withDayOfMonth(31);System.out.println(dt2); // 2019-10-31T20:30:59// 月份变为9:LocalDateTime dt3 = dt2.withMonth(9);System.out.println(dt3); // 2019-09-30T20:30:59

线程

进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个线程。

和多线程相比,多进程的缺点在于:
  • 创建进程比创建线程开销大,尤其是在Windows系统上;
    进程间通信比线程间通信要慢,因为线程间通信就是读写同一个变量,速度很快。
    而多进程的优点在于:

  • 多进程稳定性比多线程高,因为在多进程的情况下,一个进程崩溃不会影响其他进程,而在多线程的情况下,任何一个线程崩溃会直接导致整个进程崩溃。

Java多线程编程的特点又在于:

多线程模型是Java程序最基本的并发模型;
后续读写网络、数据库、Web开发等都依赖Java多线程模型。

创建一个线程

java通过new Thread
或者一个Thread的子类
或者实现Runnable接口的类来创建一个线程,
通过start启动一个线程

class MyThread extends Thread {@Overridepublic void run() {System.out.println("start new thread1");}
}class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("start new thread2");}
}public class HelloWorld {public static void main(String[] args) {System.out.println("main start");Thread t = new MyThread("test"); //参数是线程名称// start()方法会在内部自动调用实例的run()方法t.start(); // 开始并发执行Thread t2 = new Thread(new MyRunnable());t2.start();Thread t3 = new Thread() {public void run() {System.out.println("start new thread3");}};t3.start();System.out.println("main end");}}

上述执行结果

main start
start new thread1
main end
start new thread2
start new thread3

main主线程在执行start之后,其他线程就跟主线程开始同时运行了,并且由操作系统调度,程序本身无法确定线程的调度顺序。
Thread.sleep()可以把当前线程暂停一段时间。

Thread类常用的方法

public void run() 线程相关的代码都写在里面执行
public void start()启动线程
public void join(long ms) t.join(),会等待t线程之行结束再执行其他线程,参数是等待最多的时间,超过这个时间就不等待了
public static void sleep(long m) 调用Thread.sleep可以让当前线程暂停m毫秒。

Runnable接口
  • 只有一个方法
  • Runnable是Java实现线程的接口。
  • 任何实现线程功能的类,都必须实现该接口。
线程状态

一个线程对象只能调用一次start启动新线程,并在新线程中执行run方法,一旦run方法执行完毕,线程就结束了,因此,java线程的状态有以下几种。

  • New:新创建的线程,尚未执行;
  • Runnable:线程调用了start()方法后进入就绪状态,并且当线程调度器为其分配CPU资源时,它会转为运行状态。在Java中,这个状态包括了“就绪”和“运行”两种情况,也就是说,线程可能正在执行,也可能准备好了随时可以执行,只是当前没有获得CPU时间片。
  • Blocked: 阻塞状态 运行中的线程,因为某些操作被阻塞而挂起;
  • Waiting:运行中的线程,因为某些操作在等待中;
  • Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待;
  • Terminated:线程已终止,因为run()方法执行完毕。

在这里插入图片描述
t.join()可以等到t线程结束后再执行主线程。

线程优先级
  • java提供了十个优先级,1-10,主线程优先级默认为5,
  • 优先级常量 MAX_PRIORITY 最高优先级10,MIN_PRIORITY最低优先级1,NORMOR_PRIORITY默认优先级5
  • public int getPriority()获取优先级 public void setPriority(int newPriority)设置优先级
  • JVM自动把1(低)~10(高)的优先级映射到操作系统实际优先级上(不同操作系统有不同的优先级数量)。优先级高的线程被操作系统调度的优先级较高,操作系统对高优先级线程可能调度更频繁,但我们决不能通过设置优先级来确保高优先级的线程一定会先执行。
中断线程

假设从网络下载一个100M的文件,如果网速很慢,用户等得不耐烦,就可能在下载过程中点“取消”,这时,程序就需要中断下载线程的执行。
中断一个线程非常简单,只需要在其他线程中对目标线程调用interrupt()方法,目标线程需要反复检测自身状态是否是interrupted状态,如果是,就立刻结束运行。

class MyThread extends Thread {@Overridepublic void run() {int n = 0;while (!isInterrupted()) {n++;System.out.println(n + " hello!");}}
}public static void main(String[] args) throws InterruptedException {System.out.println("main start");Thread t = new MyThread();// start()方法会在内部自动调用实例的run()方法t.start(); // 开始并发执行Thread.sleep(1); // 暂停1毫秒t.interrupt(); // 中断t线程t.join(); // 等待t线程结束System.out.println("main end");}

另一个常用的中断线程的方法是设置标志位。我们通常会用一个running标志位来标识线程是否应该继续运行,在外部线程中,通过把HelloThread.running置为false,就可以让线程结束:

public class Main {public static void main(String[] args)  throws InterruptedException {HelloThread t = new HelloThread();t.start();Thread.sleep(1);t.running = false; // 标志位置为false}
}class HelloThread extends Thread {public volatile boolean running = true;public void run() {int n = 0;while (running) {n ++;System.out.println(n + " hello!");}System.out.println("end!");}
}

注意到HelloThread的标志位boolean running是一个线程间共享的变量。线程间共享变量需要使用volatile关键字标记,确保每个线程都能读取到更新后的变量值。

volatile关键字的目的是告诉虚拟机:

  • 每次访问变量时,总是获取主内存的最新值;
  • 每次修改变量后,立刻回写到主内存。
小结
  • 对目标线程调用interrupt()方法可以请求中断一个线程,目标线程通过检测isInterrupted()标志获取自身是否已中断。如果目标线程处于等待状态,该线程会捕获到InterruptedException;
  • 目标线程检测到isInterrupted()为true或者捕获了InterruptedException都应该立刻结束自身线程;
  • 通过标志位判断需要正确使用volatile关键字;
  • volatile关键字解决了共享变量在线程间的可见性问题。
守护线程

守护线程是指为其他线程服务的线程。在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出。

因此,JVM退出时,不必关心守护线程是否已结束。
setDaemon 标记为守护线程

Thread t = new MyThread();
t.setDaemon(true);
t.start();
多线程运行问题
  • 各个线程是通过竞争CPU时间获得运行机会的。
  • 各个线程在什么时候得到CPU时间,占用多久,是不可预测的。
  • 一个正在运行着的线程在什么地方被暂停是不确定的。
线程同步

如果有两个线程,比如银行系统,他有一个Bank对象,一个线程用来存,一个线程用来取,因为线程的操作时机是不确定的,就会导致存取时候得到的银行余额并不是最新的,导致出错。
为了解决这个问题,就需要将Bank对象锁住

使用synchronized关键字。

public synchornized void set(){}
public static synchornized void set(){}
synchornized(obj){setxx}

多线程模型下,要保证逻辑正确,对共享变量进行读写时,必须保证一组指令以原子方式执行:即某一个线程执行时,其他线程必须等待:
在这里插入图片描述
通过加锁解锁的操作,就能保证3条指令总是在一个线程执行期间,不会有其他线程会进入此指令区间。
这种加锁和解锁之间的代码块我们称之为临界区(Critical Section),任何时候临界区最多只有一个线程能执行。
保证一段代码的原子性就是通过加锁和解锁实现的。Java程序使用synchronized关键字对一个对象进行加锁:

synchronized(lock) {n = n + 1;
}public class Main {public static void main(String[] args) throws Exception {var add = new AddThread();var dec = new DecThread();add.start();dec.start();add.join();dec.join();System.out.println(Counter.count);}
}class Counter {public static final Object lock = new Object();public static int count = 0;
}class AddThread extends Thread {public void run() {for (int i=0; i<10000; i++) {synchronized(Counter.lock) {Counter.count += 1;}}}
}class DecThread extends Thread {public void run() {for (int i=0; i<10000; i++) {synchronized(Counter.lock) {Counter.count -= 1;}}}
}

注意·

synchronized(Counter.lock) { // 获取锁...
} // 释放锁
不需要synchronized的操作
  • 原子操作不需要synchronized操作,比如基本类型赋值(long,double除外)int n = m; 引用类型赋值List< string> list = anthoerList;
  • 单条原子操作语句不需要赋值
  • 多行赋值语句,就必须保证是同步操作
  • 如果多线程读写的是一个不可变对象,那么无需同步,因为不会修改对象的状态
小结
  • 多线程同时读写共享变量时,可能会造成逻辑错误,因此需要通过synchronized同步;
  • 同步的本质就是给指定对象加锁,加锁后才能继续执行后续代码;
  • 注意加锁对象必须是同一个实例;
  • 对JVM定义的单个原子操作不需要同步。
同步方法

自己写synchronized,还得注意需要锁住同一个对象,更好的方法是把synchronized封装起来

public class Counter {private int count = 0;public void add(int n) {synchronized(this) {count += n;}}// 等同于public synchronized void add(int n) { // 锁住thiscount += n;} // 解锁public void dec(int n) {synchronized(this) {count -= n;}}public int get() {return count;}
}

用synchronized修饰的方法就是同步方法,他会将当前的this实例锁起来。

线程调用add()、dec()方法时,它不必关心同步逻辑,因为synchronized代码块在add()、dec()方法内部。并且,我们注意到,synchronized锁住的对象是this,即当前实例,这又使得创建多个Counter实例的时候,它们之间互不影响,可以并发执行

如果一个类被设计为允许多线程正确访问,我们就说这个类就是“线程安全”的(thread-safe),上面的Counter类就是线程安全的。Java标准库的java.lang.StringBuffer也是线程安全的。

如果用synchronized修饰static,static没有this,但是JVM会给每个类创建一个Class 实例,所以用synchronized修饰static方法,锁住的是Class实例

public class Counter {public static void test(int n) {synchronized(Counter.class) {...}}
}
线程通信
  • wait() 中断方法的执行,使线程等待
  • notfify方法,唤醒处于等待的某一个线程,使其结束等待
  • notifyall方法,唤醒所有处于等待的线程,使他们结束等待。

输入输出流

输出流:

流就是指一串流动的字符,以先进先出的方式发送信息的通道。
System.out.println("test")
输出流就是程序进行写操作,将字符串"test" 以 t. e. s. t的形式一个一个通过通道塞到目的地,而这个通道就是流的形式。
更多的操作比如 打印文件也是通过流的方式。

输入流

程序通过流的形式读取数据,比如键盘输入数据,程序通过流的形式一个一个读取数据,比如读取文件,也是通过流的形式。
输入对应读取
输出对应写入

File类

Java的标准库java.io提供了File对象来操作文件和目录。

文件或文件目录

构造一个File对象,即使传入的文件或目录不存在,代码也不会出错,因为构造一个File对象,并不会导致任何磁盘操作。只有当我们调用File对象的某些方法的时候,才真正进行磁盘操作。

 // file// File file = new File("./test.txt");// File file = new File("/Users/test/Desktop/java/test.txt");File file1 = new File("/Users/test");File file = new File(file1, "Desktop/java/test.txt");System.out.println("是否文件" + file.isFile());System.out.println("是否目录" + file.isDirectory());System.out.println(file.getPath()); // 传入的路径System.out.println(file.getAbsolutePath()); // 绝对路径try {System.out.println(file.getCanonicalPath()); // 规范路径} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}// 创建目录File file2 = new File("./test/test2");System.out.println("test2存在?" + file2.exists());if (!file2.exists()) {// 不存在则创建目录file2.mkdirs();// 多层级用mkdirs,单层用mkdir}// 创建文件 createNewFileFile file3 = new File("./test2.txt");if (!file3.exists()) {try {file3.createNewFile();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}

new File可以传入绝对路径,也可以传入相对路径。
file.exists()判断文件是否存在
file.isDirector判断file是否目录
file.isFile()判断file是否文件
file.mkdir[s]创建file目录,+s表示多层目录
file.createNewFile()创建file文件
file.getPath() 传入的路径
file.getAbsolutePath() 绝对路径
file.getCanonicalPath 规范路径
boolean canExecute():是否可执行;
long length():文件字节大小。
file.delete() 删除文件,成功返回true

注意Windows平台使用\作为路径分隔符,在Java字符串中需要用\表示一个\。Linux平台使用/作为路径分隔符

// 假设当前目录是C:\Docs
File f1 = new File("sub\\javac"); // 绝对路径是C:\Docs\sub\javac
File f3 = new File(".\\sub\\javac"); // 绝对路径是C:\Docs\sub\javac
File f3 = new File("..\\sub\\javac"); // 绝对路径是C:\sub\javac
规范路径
 File f = new File("..");System.out.println(f.getPath());System.out.println(f.getAbsolutePath());try {System.out.println(f.getCanonicalPath());} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}

打印结果

..
/Users/test/Desktop/java/..
/Users/test/Desktop

可以看到,绝对路径就是/Users/test/Desktop/java/…,规范路径就是把.和…转换成标准的绝对路径后的路径
/Users/test/Desktop/java/..实际上就是/Users/test/Desktop

打印系统分隔符

System.out.println(File.separator); // 根据当前平台打印"“或”/"

其他文件操作

程序需要读写一些临时文件,File对象提供了createTempFile()来创建一个临时文件,以及deleteOnExit()在JVM退出时自动删除该文件。

  public static void main(String[] args) throws IOException {File f = File.createTempFile("tmp-", ".txt"); // 提供临时文件的前缀和后缀f.deleteOnExit(); // JVM退出时自动删除System.out.println(f.isFile());System.out.println(f.getAbsolutePath());}
遍历文件和目录

当File对象表示一个目录时,可以使用list()和listFiles()列出目录下的文件和子目录名。

 public static void main(String[] ages) {File f = new File("./");String[] fs1 = f.list();System.out.println(Arrays.toString(fs1));// printFiles(fs1);File[] fs2 = f.listFiles(new FilenameFilter() {public boolean accept(File dir, String name) {return name.endsWith(".txt");}});printFiles(fs2);}static void printFiles(File[] files) {System.out.println("==========");if (files != null) {for (File f : files) {System.out.println(f);}}System.out.println("==========");}

file.list()获取当面文件目录的名称,返回字符串数组,即使是目录也会返回。
file.listFiles可以遍历当前文件目录下的所有文件/文件夹,参数可以传入FilenameFilter实例,过滤数据。

Path

Java标准库还提供了一个Path对象,它位于java.nio.file包。Path对象和File对象类似,但操作更加简单:

 Path p1 = Paths.get(".", "text.txt"); // 构造一个Path对象 System.out.println(p1); //传入的路径Path p2 = p1.toAbsolutePath(); // 转换为绝对路径System.out.println(p2);Path p3 = p2.normalize(); // 转换为规范路径System.out.println(p3);File f = p3.toFile(); // 转换为File对象System.out.println(f.isFile());

结果就是

./text.txt
/Users/test/Desktop/java/./text.txt
/Users/test/Desktop/java/text.txt
/Users/test/Desktop/java/text.txt

p1 传入的路径
p1.toAbsolutePath()转为绝对路径
p1.normalize转为规范路径
p1.toFile转为file对象

如果需要对目录进行复杂的拼接、遍历等操作,使用Path对象更方便。

小结

Java标准库的java.io.File对象表示一个文件或者目录:

  • 创建file对象不涉及IO操作
  • 通过file.getPath, getAbsoultePath(), getCanonicalPath() 可以获取传入的路径,绝对路径和规范路径
  • file.lists() 获取当前目录下的所有文件/文件夹的名称,返回数组,file.listFiles返回目录下的所有file对象
  • 可以创建或删除文件和目录。
InputStream 输入流

InputStream就是Java标准库提供的最基本的输入流。它位于java.io这个包里。java.io包提供了所有同步IO的功能。
InputStream并不是一个接口,而是一个抽象类,它是所有输入流的超类。这个抽象类定义的一个最重要的方法就是int read()
public abstract int read() throws IOException;
这个方法会读取输入流的下一个字节,并返回字节表示的int值(0~255)。如果已读到末尾,返回-1表示不能继续读取了。

FileInputStream

FileInputStream是InputStream的子类

public void readFile() throws IOException {try {InputStream input = new FileInputStream("./test.txt");for (;;) {int n;n = input.read();if (n == -1) {break;}System.out.println(n); // 打印byte的值}} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally{if (input != null) { input.close(); }}
}

文件不存在导致无法读取,没有写权限导致写入失败,等等,这些底层错误由Java虚拟机自动封装成IOException异常并抛出
所有与IO操作相关的代码都必须正确处理IOException

用try … finally来编写上述代码会感觉比较复杂,更好的写法是利用Java 7引入的新的try(resource)的语法,只需要编写try语句,让编译器自动为我们关闭资源。

public void readFile() throws IOException {try (InputStream input = new FileInputStream("src/readme.txt")) {int n;while ((n = input.read()) != -1) {System.out.println(n);}} // 编译器在此自动为我们写入finally并调用close()
}

实际上,编译器并不会特别地为InputStream加上自动关闭。编译器只看try(resource = …)中的对象是否实现了java.lang.AutoCloseable接口,如果实现了,就自动加上finally语句并调用close()方法。InputStream和OutputStream都实现了这个接口,因此,都可以用在try(resource)中。

缓冲

在读取流的时候,一次读取一个字节并不是最高效的方法。很多流支持一次性读取多个字节到缓冲区,对于文件和网络流来说,利用缓冲区一次性读取多个字节效率往往要高很多。

InputStream提供了两个重载方法来支持读取多个字节:

int read(byte[] b):读取若干字节并填充到byte[]数组,返回读取的字节数
int read(byte[] b, int off, int len):指定byte[]数组的偏移量和最大填

read()方法会尽可能多地读取字节到缓冲区, 但不会超过缓冲区的大小。read()方法的返回值不再是字节的int值,而是返回实际读取了多少个字节。如果返回-1,表示没有更多的数据了。

 try (InputStream input = new FileInputStream("src\\test.txt")) {byte[] buffer = new byte[5];int n;while ((n = input.read(buffer)) != -1) { // 读取到缓冲区System.out.println("read" + n + "bytes");// read5bytes// read5bytes// read1bytes}}

一次读取5个字节

阻塞

read方法读取数据的时候,read方法是阻塞的,他的意见是

int n;
n = input.read(); // 必须等待read()方法返回才能执行下一行代码
int m = n;

执行到第二行代码时,必须等read()方法返回后才能继续。因为读取IO流相比执行普通代码,速度会慢很多,因此,无法确定read()方法调用到底要花费多长时间。

InputStream实现类

用FileInputStream可以从文件获取输入流,这是InputStream常用的一个实现类。此外,ByteArrayInputStream可以在内存中模拟一个InputStream

public class Main {public static void main(String[] args) throws IOException {byte[] data = { 72, 101, 108, 108, 111, 33 };try (InputStream input = new ByteArrayInputStream(data)) {int n;while ((n = input.read()) != -1) {System.out.println((char)n);}}}
}

用ByteArrayInputStream,实际上是把一个byte[]数组在内存中变成一个InputStream。
我们稍微改变下

   String s;try (InputStream input = new FileInputStream("src\\test.txt")) {s = readAsString(input);}System.out.println(s);byte[] data = { 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 };try (InputStream input2 = new ByteArrayInputStream(data)) {String s2 = readAsString(input2);System.out.println(s2);}public static String readAsString(InputStream input) throws IOException {int n;StringBuilder sb = new StringBuilder();while ((n = input.read()) != -1) {System.out.print(n + "\n");sb.append((char) n);}return sb.toString();}

结果都是hello world
因为接受的是InputStream抽象类,所以所有实现InputStream的类都能传入。这就是面向对象编程原则的应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/720407.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

codeTop01:LRU (最近最少使用) 缓存的实现

问题 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; ● LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 ● int get(int key) 如果关键字 key 存在于缓存中&#xff0c;则返回关键字的值&#xff0c;…

opencart3 添加速卖通商品脚本

非爬虫&#xff0c;只能把速卖通商品信息拿下来解析插入到自己的项目里。 刚接触opencart3没多久&#xff0c;有一些新项目需要添加商品&#xff0c;每次手动从速卖通复制信息又很慢&#xff0c;就自己写了一个脚本。 思路&#xff1a;速卖通商品详情页有一段数据包含了几乎所…

初识Hive

官网地址为&#xff1a; Design - Apache Hive - Apache Software Foundation 一、架构 先来看下官网给的图&#xff1a; 图上显示了Hive的主要组件及其与Hadoop的交互。Hive的主要组件有&#xff1a; UI&#xff1a; 用户向系统提交查询和其他操作的用户界面。截至2011年&…

基于STC12C5A60S2系列1T 8051单片机的TM1638键盘数码管模块的按键扫描、数码管显示按键值、显示按键LED应用

基于STC12C5A60S2系列1T 8051单片机的TM1638键盘数码管模块的按键扫描、数码管显示按键值、显示按键LED应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍TM1638键盘…

C# WinForm AndtUI第三方库 Tree控件使用记录

环境搭建 1.在NuGet中搜索AndtUI并下载至C# .NetFramework WinForm项目。 2.添加Tree控件至窗体。 使用方法集合 1.添加节点、子节点 using AntdUI; private void UpdateTreeView() {Tree tvwTestnew Tree();TreeItem rootTreeItem;TreeItem subTreeItem;Dictionary<str…

高级软件开发知识点

流程 算法题简历上项目用到技术、流程、遇到问题HR 准备 常考的题型和回答思路刷100算法题&#xff0c;理解其思想&#xff0c;不要死记最近一家公司所负责的业务和项目&#xff1a; 项目背景、演进之路&#xff0c;有哪个阶段&#xff0c;每个阶段主要做什么项目中技术选型…

STM32 TIM编码器接口

单片机学习&#xff01; 目录 文章目录 前言 一、编码器接口简介 1.1 编码器接口作用 1.2 编码器接口工作流程 1.3 编码器接口资源分布 1.4 编码器接口输入引脚 二、正交编码器 2.1 正交编码器功能 2.2 引脚作用 2.3 如何测量方向 2.4 正交信号优势 2.5 执行逻辑 三、编码器定时…

15-Java责任链模式 ( Chain of Responsibility)

Java责任链模式 摘要实现范例 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;为请求创建了一个接收者对象的链 责任链模式给予请求的类型&#xff0c;对请求的发送者和接收者进行解耦 责任链模式中通常每个接收者都包含对另一个接收者的引用&#xff0c…

头像剪切上传

头像剪切上传 文章说明核心Api示例源码效果展示源码下载 文章说明 本文主要为了学习头像裁剪功能&#xff0c;以及熟悉canvas绘图和转文件的相关操作&#xff0c;参考教程&#xff08;Web渡一前端–图片裁剪上传原理&#xff09; 核心Api 主要就一个在canvas绘图的操作 context…

2.8k star! 用开源免费的edge-tts平替科大讯飞的语音合成服务

edge-tts是github上的一个开源项目&#xff0c;可以免费将文本转为语音&#xff0c;别看它只有2.8k star&#xff0c;替代科大讯飞的收费TTS服务完全没问题&#xff0c;因为这个项目实际是调用的微软edge的在线语音合成服务&#xff0c;支持40多种语言&#xff0c;300多种声音&…

注意力机制(代码实现案例)

学习目标 了解什么是注意力计算规则以及常见的计算规则.了解什么是注意力机制及其作用.掌握注意力机制的实现步骤. 1 注意力机制介绍 1.1 注意力概念 我们观察事物时&#xff0c;之所以能够快速判断一种事物(当然允许判断是错误的), 是因为我们大脑能够很快把注意力放在事物…

EdgeX Foundry 安装部署

文章目录 一、概述1.官方文档2.Docker Compose 生成器3.创建 docker-compose 文件 二、安装准备1. 克隆服务器2.安装 Docker3.安装 docker-compose 三、非安全模式部署1.docker-comepse2.启动 EdgeX Foundry3.访问 UI3.1. consul3.2. EdgeX Console EdgeX Foundry # EdgeX Fou…

Android之Handler原理解析与问题分享

一、Handler运行原理剖析 1.关系剖析图 如果把整个Handler交互看做一个工厂&#xff0c;Thread就是动力MessageQueue是履带Looper是转轴Loooper的loop方法就是开关&#xff0c;当调用loop方法时整个工厂开始循环工作&#xff0c;处理来自send和post提交到MessageQueue的消息&a…

08. Nginx进阶-Nginx动静分离

简介 什么是动静分离&#xff1f; 通过中间件将动态请求和静态请求进行分离。分离资源&#xff0c;减少不必要的请求消耗&#xff0c;减少请求延时。 动静分离的好处 动静分离以后&#xff0c;即使动态服务不可用&#xff0c;静态资源仍不受影响。 动静分离示意图 动静分离…

Day16:信息打点-语言框架开发组件FastJsonShiroLog4jSpringBoot等

目录 前置知识 指纹识别-本地工具-GotoScan&#xff08;CMSEEK&#xff09; Python-开发框架-Django&Flask PHP-开发框架-ThinkPHP&Laravel&Yii Java-框架组件-Fastjson&Shiro&Solr&Spring 思维导图 章节知识点 Web&#xff1a;语言/CMS/中间件/…

Python的http模块requests

模块简介&#xff1a; requests 库是一个 python中比较有名的 http请求的库&#xff0c;能处理 get,post,put,delete 等 restful请求&#xff0c;能设置 header&#xff0c;cookie,session 等操作&#xff0c;也是作为爬虫的基础库&#xff0c;它目前还不能异步请求,如果要支持…

适用于 Windows 的 5 款最佳免费数据恢复软件榜单

每个计算机用户都曾经历过数据丢失的情况。很容易错误地删除重要的文件和文件夹&#xff0c;当发生这种情况时&#xff0c;可能会导致不必要的心痛和压力。值得庆幸的是&#xff0c;可以恢复 Windows PC 上丢失的数据。在本文中&#xff0c;我们将分享您可以使用的五种最佳 Win…

苹果因在iOS音乐流媒体市场上的反向引导行为,在欧盟被罚款18.4亿欧元

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Docker之自定义镜像上传阿里云

目录 一. Alpine制作jdk镜像 1.1 alpine Linux 简介 1.2 基于alpine制作jdk8镜像 1.2.1 下载镜像 1.2.2 创建并编辑Dockerfile 1.2.3 上传文件 1.2.4 执行Dockerfile构建镜像 1.2.5 测试 二. Alpine制作jre镜像 2.1 首先下载jre或者上传 2.2 解压 2.3 测试 2.4 返回上级目录&a…

云手机的境外舆情监控应用——助力品牌公关

在当今数字化时代&#xff0c;社交媒体已成为品牌传播和互动的主要平台。随之而来的是海量的信息涌入&#xff0c;品牌需要及时了解并应对海外社交媒体上的舆情变化。本文将介绍如何通过云手机进行境外舆情监控&#xff0c;更好地帮助企业公关及时作出决策。 1. 境外舆情监控与…