java杂项(I/O、线程)

本文最后更新于 2024-11-30 17:14:04

1、IO

本节的输入输出是相对于运行的java文件

1.字节流

FileInputStream : 字节输入流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FileInputStream stream=new FileInputStream('文件路径');

stream.read(); // 一个字节一个字节读取 英文:1个 汉字:3个
// 他的返回值为 int 类型
// 如果读取到最后一个的下一个,返回 -1

byte[] bytes=new byte[3]; // 三个字节的读取方式
stream.read(bytes);
// 如果文件有内容,则一直读取,如果不足三个字节,仍可继续读取,如果没有,则返回 -1
// 可用 stream.available() 获取该文件的字节数


// 注意:每次读取后要进行 close() ,否则该文件后续将由于占有进程而无法操作
/**

可以使用 try-with-resouse 写法

FileOutputStream : 字节输出流

1
2
3
4
5
6
7
8
9
FileOutputStream stream=new FileOutputStream('文件路径'); // 每次重新将文件内容更新

// 追加模式: new FileOutputStream('文件路径',true);

stream.write('a'); // 可以直接写入内容
stream.write('a'.getBytes()); // 也可转为 byte[] 写入
stream.write('abcde'.getBytes(),2,1); // 从字符串的第2个位置开始,写入一个字符
stream.flush(); // 最后要进行刷新操作 !!!!!!

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public static void main(String[] args) {

try (FileInputStream fis = new FileInputStream("test.txt");
FileOutputStream fos = new FileOutputStream("test_copy.txt")) {

byte[] buffer = new byte[1024];
int len = 0;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);// 写入的是读取的长度,而非 1024
}
} catch (IOException e) {

e.printStackTrace();
}
}

2.字符流

FileReader:输入

1
2
3
FileReader reader=new FileReader('文件路径');

reader.read(); // 读取的是一个字符!!!!

FileWriter: 输出

1
2
3
4
5
6
7
8
9
10
11
12
FileWriter writer=new FileWriter('路径');

writer.append('a')
.append("adfad"); // 允许链式写法

// 也有 flush()
/**

也可这样写入文件
writer.write("dad");

同时也可以开启追加,开启方法和字节流,一样的方式

字符流只支持char[] 类型作为存储

3.文件对象

File :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
File file=new File('路径文件 / 文件夹名字');

// 创建文件夹,返回 boolean
file.mkdir()
file.mkdirs() // 创建多级文件夹 比如 controller/index/map
// 创建文件
file.createNewFile(); // 注意,创建的时候路径必须存在,否则会失败

/**

方法有很多,需要可以查找

可将 file 作为读取文件写入 FileReader 等方法中例如:
new FileReader(file)

4.缓冲流

BufferedInputStream:

BufferedOutputStream:

1
2
 BufferedInputStream buff=new BufferedInputStream(new FileInputStream("test_copy.txt"),index);
// 可以填参 index 意义为 缓冲区有几个字节

认为:自来水 ===> 自来水厂 ===> 用户

1
2
3
4
5
6
7
8
9
10
11
/**
可以倒回去读取数据,使用 buff.mark() 和 buff.reset(); 倒回到 mark 所在的位置
mark中可以填参数,参数(int) 为:从这里开始最多可以读取重新几个字 符
**/

.line() // 可以读取一行


/**

api 很多,比较方便吧

5.转换流

InputStreamReader : 可以使用 Reader的方式读取

OutputStreamWriter: 可以使用writer 的方式写入

1
2
OutputStreamWriter writer=new OutputStreamWriter(new FileOutputStream("test_copy.txt"),"UTF-8");
InputStreamReader reader=new InputStreamReader(buff,"UTF-8");

6.打印流

PrintStream:

·Scanner·

用法和上述几种大同小异

所用的sout(简写)也是这种类型

7.数据流

DataInputStream

DataOutputStream

8.对象流

ObjectOutputStream

不仅支持基本数据类型,而且通过对对象的序列化操作,以某种格式保存对象,来支持对象类型的 IO ,注意:他不是继承FilterInputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

public class Main {
public static void main(String[] args) {
try(ObjectOutputStream ois = new ObjectOutputStream(new FileOutputStream("person.text"));
ObjectInputStream ois2 = new ObjectInputStream(new FileInputStream("person.text"))){
Person p1 = new Person("Alice");
Person p2 = new Person("Bob");

ois.writeObject(p1);
ois.writeObject(p2);
Object object ;
try {
while((object = ois2.readObject())!= null) {
System.out.println(object);
}
}catch (EOFException e){// 读取到文件末尾时抛出EOFException异常
System.out.println("EOF");
}
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
static class Person implements Serializable {
private static final long serialVersionUID = 1;
// 序列化版本号, 用于反序列化, 必须为static final long类型, 且唯一, 一般为类的全限定名的hash值,
private String name;
Person(String name) {
this.name =name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
}

2.多线程

进程含有多个线程

注意:是并发操作,不是并行

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程");
System.out.println("Hello World");
}
});
t.start();
System.out.println("主线程");
}

/**
输出结果可能为:

主线程
子线程
Hello World

也可使用 lambda 表达式

1
2
3
4
5
6
7
8
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("子线程");
System.out.println("Hello World");
},"线程名字");
t.start();
System.out.println("主线程");
}

image-20240824004411370

1.线程休眠和中断、优先级

Thread.interrupt(); : 使线程中断

Thread.interrupted(); :使线程中断回复原来的状态(即:不中断)

.yield() :线程的让位,尽可能执行其他的

.join(): 线程的加入,先执其行他的


i==3 之后,尽可能执行 t1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("线程 t 启动");
for (int i = 0; i < 10; i++) {
System.out.println("线程t "+i);
if(i==3)
Thread.yield();
}
});
Thread t1 = new Thread(() -> {
System.out.println("线程 t1 启动");
for (int i = 0; i < 10; i++) {
System.out.println("线程t1 "+i);
}
});

t.start();
t1.start();
}

同时开始,但是当 t1i == 5 的时候,只执行 tt 结束后才执行 t1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("线程 t 启动");
for (int i = 0; i < 100; i++) {
System.out.println("线程t "+i);

}
System.out.println("线程 t 结束");
});
Thread t1 = new Thread(() -> {
System.out.println("线程 t1 启动");
for (int i = 0; i < 10; i++) {
System.out.println("线程t1 "+i);
if (i == 5) {{
try {
System.out.println("线程 t1 等待 t 结束");
t.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}}
}
System.out.println("线程 t1 结束");
});

t.start();
t1.start();
}

2.线程锁和线程同步

有下面这个例子引出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private static int p = 0;

public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {

for (int i = 0; i < 10000; i++) {
p++;
}
});
Thread t1 = new Thread(() -> {

for (int i = 0; i < 10000; i++) {
p++;
}
});
t.start();
t1.start();
Thread.sleep(1000);
System.out.println(p);
}
/**

输出本该是 10000+10000=20000
但是由于线程的同步进行则可能会导致线程 t ,t1 在执行 ++ 操作的时候,是同时进行的,那么此时就会导致本该加两次的 p
只加了一次

高速缓存通过保存内存中数据的副本来提供更加快速的数据访问,但是如果多个处理器的运算任务都涉及同一块内存区域,就可能导致各自的高速缓存数据不一致,在写回主内存时就会发生冲突,这就是引入高速缓存引发的新问题,称之为:缓存一致性

synchronized

该关键字,可以通过给变量,或者方法加锁,使得不能同时操作同一个方法或者变量

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private static int p = 0;

private static synchronized void add() {
p++;
}

public static void main(String[] args) throws InterruptedException {

Object lock = new Object();
Thread t = new Thread(() -> {

for (int i = 0; i < 10000; i++) {
synchronized (lock) {
p++;
}
}
});
Thread t1 = new Thread(() -> {

for (int i = 0; i < 10000; i++) {
synchronized (lock) {
p++;
}
}
});
t.start();
t1.start();
Thread.sleep(100);
System.out.println(p);
}
/**
那么,对 i 进行加锁操作后,就不会出现两个线程同时操作 i
同时的 对于 add() 方法加锁后,也是一样的,不会同时被两个线程调用

对于 synchronized () 中的形参,他是一个 Object 类型,可以 new 一个空的 Object放进去,也可以使用本类写入
(例如类名为Main,则上述两个位置的形参可以写为 Main.class )
注意: 如果说,两个线程中的形参不是同一个 Object ,那么同样会导致可能出现同时操作 i 的情况,
对于成员方法来讲,默认是本类的 Object, 如果说在 new 一个本类的对象的话,会导致同时操作该方法

3.死锁

两个线程互相卡锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
死锁的检查
*/

jps // 查看要检查的程序的线程
jstack processID // 检查id为 processID 的进程的死锁情况


/*
死锁的检查可视化
*/

jconsloe

.wait(): 使当前进程进入等待,同时释放当前的锁,可以让其他进程获取

可输入一个形参变量,用于设置最大等待时间 单位 ms

.notify() : 唤醒刚才等待的进程(此时并没有立即释放锁),等待当前进程结束,才会执行刚才等待的进程

.notifyAll() : 释放所有等待的进程,上面的为随机选取一个释放

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
private static int p = 0;

private static synchronized void add() {
p++;
}

public static void main(String[] args) throws InterruptedException {

Object lock = new Object();

Thread t = new Thread(() -> {
synchronized (lock) {
System.out.println("进程1开始");
try {
lock.wait();
System.out.println("进程1结束");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread t1 = new Thread(() -> {
synchronized (lock) {
lock.notify();
try {
System.out.println("进程2开始");
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("进程2结束");

}
});

t.start();
t1.start();
}
/**

虽然线程是并发的,但是根据线程的创建先后,先创建的线程开始的肯定早一点 !!!!!!!!!!


输出:

进程1开始
进程2开始
进程2结束
进程1结束

4.定时任务

Timer

5. 守护进程

在主进程结束的时候,子线程也结束。

如果不使用的话,当主进程结束后,子进程没有结束的条件下,该进程仍然在执行

守护进程必须在进程开始之前!!!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) throws InterruptedException {

Object lock = new Object();

Thread t = new Thread(() -> {
// 设置子进程为死循环
while(true){
System.out.println("我是守护线程");
try {
Thread.sleep(1000);// 防止测试的时候,输出过多
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.setDaemon(true); // 设置为守护线程
Thread.sleep(3000);
t.start();
}

6.生产者和消费者

大概就是使用线程来进行操作,设置一个死循环用于不断地执行。

不妨令A、B分别为生产者和消费者

则A、B分别开启一个线程,并不断执行,当A生产出东西的时候,B才可以调用,其他时间一直等待,具体的可以参考下面的例子(仅供参考,考虑可能真的不够周全!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

public class Main {
private static Queue<Object> queue=new LinkedList<>();

public static void main(String[] args) throws InterruptedException {

new Thread(Main::add,"add-thread-1").start();
new Thread(Main::add,"add-thread-2").start();

new Thread(Main::take,"take-thread-1").start();
new Thread(Main::take,"take-thread-2").start();

}

public static void add(){
while(true){
try {
Thread.sleep(1000);
synchronized (queue){
String name=Thread.currentThread().getName();
System.out.println(new Date().toString()+" "+name+" add ");
queue.offer(new Object());
queue.notifyAll();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

}

public static void take(){
while(true){
try {
synchronized (queue){
while(queue.isEmpty()) queue.wait();
String name=Thread.currentThread().getName();
Object obj=queue.poll();
System.out.println(new Date().toString()+" "+name+" take "+obj);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

}

java杂项(I/O、线程)
https://one-and-one-fourth.github.io/2024/11/26/java/java杂项/
作者
一又四分之一
发布于
2024-11-26 17:16:00
更新于
2024-11-30 17:14:04
许可协议