java基础(6) IO[下] 线程(补充) XML Servlet

输出流 OutputStream

OutputStream是Java标准库提供的最基本的输出流。

和InputStream类似,OutputStream也是抽象类,它是所有输出流的超类。这个抽象类定义的一个最重要的方法就是void write(int b)
public abstract void write(int b) throws IOException;
这个方法会写入一个字节到输出流。要注意的是,虽然传入的是int参数,但只会写入一个字节,即只写入int最低8位表示字节的部分

复制一个文件
通过InputStream读取数据,通过OutputStream写入数据

 public static void copy(String from, String to) throws IOException {
        File f = new File(from);
        File t = new File(to);
        if (!f.exists()) {
            throw new FileNotFoundException();
        }
        if (!t.exists()) {
            try {
                t.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try (InputStream input = new FileInputStream(f);
                OutputStream output = new FileOutputStream(t)) {
            int fLenght = (int) f.length();

            byte[] data = new byte[fLenght];
            System.out.println("文件长度为" + fLenght);
            int n;
            while ((n = input.read(data)) != -1) {
             
                 output.write(data);
            }
           
        }

    }

Filter模式(或者装饰器模式:Decorator)

当我们需要给一个“基础”InputStream附加各种功能时,我们先确定这个能提供数据源的InputStream,因为我们需要的数据总得来自某个地方,例如,FileInputStream,数据来源自文件:

InputStream file = new FileInputStream("test.gz")

我们希望FileInputStream能提供缓冲的功能来提高读取的效率,因此我们用BufferedInputStream包装这个InputStream,得到的包装类型是BufferedInputStream,但它仍然被视为一个InputStream:

InputStream buffered = new BufferedInputStream(file);

最后,如果这个文件已经被gzip压缩,我们可以封装一个GZIPInputStream,

InputStream gzip = new GZIPInputStream(buffered);

无论我们封装多少次,得到的对象始终是InputStream,我们直接用InputStream来引用它,就可以正常读取
在这里插入图片描述
上述这种通过一个“基础”组件再叠加各种“附加”功能组件的模式,称之为Filter模式(或者装饰器模式:Decorator)在这里插入图片描述
在这里插入图片描述

操作Zip

ZipInputStream是一种FilterInputStream,他直接读取zip包的内容。
在这里插入图片描述

读取
创建一个 ZipInputStream,循环调用getNextEntry(),直到返回null,表示zip流结束
getNextEntry返回一个zipEntry,一个ZipEntry表示一个压缩文件或目录,如果是压缩文件,我们就用read()方法不断读取,直到返回-1

try (ZipInputStream zip = new ZipInputStream(new FileInputStream(...))) {
    ZipEntry entry = null;
    while ((entry = zip.getNextEntry()) != null) {
        String name = entry.getName();
        if (!entry.isDirectory()) {
            int n;
            while ((n = zip.read()) != -1) {
                ...
            }
        }
    }
}
写入zip包
try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(...))) {
    File[] files = ...
    for (File file : files) {
        zip.putNextEntry(new ZipEntry(file.getName()));
        zip.write(Files.readAllBytes(file.toPath()));
        zip.closeEntry();
    }
}
序列化

序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]数组。
为什么需要序列化,因为通过byte[]可以保存到文件,也可以通过网络发送出去。
顾名思义反序列化就是将byte[]数组转位java对象。

java对象序列化

要实现序列化,就要实现java.io.Serializable接口,而Serializable接口没有定义任何方法和属性,

public interface Serializable {
}

它是一个空接口。我们把这样的空接口称为“标记接口”(Marker Interface),实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法。

借助ObjectOutputStream,他负责将数据对象写入字节流

public class Main {
    public static void main(String[] args) throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        try (ObjectOutputStream output = new ObjectOutputStream(buffer)) {
            // 写入int:
            output.writeInt(12345);
            // 写入String:
            output.writeUTF("Hello");
            // 写入Object:
            output.writeObject(Double.valueOf(123.456));
        }
        System.out.println(Arrays.toString(buffer.toByteArray()));
    }
}

打印的buffer是一个byte数组

反序列化
try (ObjectInputStream input = new ObjectInputStream(...)) {
    int n = input.readInt();
    String s = input.readUTF();
    Double d = (Double) input.readObject();
}

反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行。

字符流 Reader

Reader是IO提供的另一个输入流接口,跟InputStream区别是,InputStream是以字节流为准,单位是byte,而Reader是一个字符流,单位是char。
在这里插入图片描述
这个方法读取字符流的下一个字符,并返回字符表示的int(也就是可以用char来转换。),范围是0~65535(char表示范围,像InputStream的byte只能是0-255)。如果已读到末尾,返回-1。

FileReader

FileReader是Reader的子类,类似于FileInputStream于InputStream的关系。

// Java8不支持UTF_8
try (Reader reader = new FileReader("./test.txt", StandardCharsets.UTF_8)) {
            int n;
            while ((n = reader.read()) != -1) {
                System.out.println((char) n);
            }
        }

也支持缓冲区读取

 try (Reader reader = new FileReader("./test.txt")) {
            char[] buffer = new char[5];
            int n;
            // n返回读取的char数
            while ((n = reader.read(buffer)) != -1) {
                System.out.println(buffer);
            }
        }

打印结果

hello
 worl
d.中文l
CharArrayReader

CharArrayReader可以在内存中模拟一个Reader,它的作用实际上是把一个char[]数组变成一个Reader,这和ByteArrayInputStream非常类似:

try (Reader reader = new CharArrayReader("Hello".toCharArray())) {
}
StringReader

直接把String作为数据源。

try (Reader reader = new StringReader("Hello")) {
}
Reader和InputStream

普通的Reader实际上是基于InputStream构造的,因为Reader需要从InputStream中读入字节流(byte),然后,根据编码设置,再转换为char就可以实现字符流。如果我们查看FileReader的源码,它在内部实际上持有一个FileInputStream;

InputStreamReader可以把任务InputStream转位Reader

// 持有InputStream:
InputStream input = new FileInputStream("src/readme.txt");
// 变换为Reader:
Reader reader = new InputStreamReader(input, "UTF-8");
Writer

Reader是带编码转换器的InputStream,它把byte转换为char,而Writer就是带编码转换器的OutputStream,它把char转换为byte并输出(把char转位byte然后写进去)。
在这里插入图片描述
Writer是所有字符输出流的超类,它提供的方法主要有:

  • 写入一个字符(0~65535):void write(int c);
  • 写入字符数组的所有字符:void write(char[] c);
  • 写入String表示的所有字符:void write(String s)。
FileWirter

跟FileReader类似


        try (Reader reader = new FileReader("./test.txt"); Writer writer = new FileWriter("./test2.txt")) {
            char[] buffer = new char[5];
            int n;
            while ((n = reader.read(buffer)) != -1) {
                System.out.println("正在写入数据" + buffer);
                writer.write(buffer);
            }
            System.out.println("写入成功");
        }

CharArrayWriter

CharArrayWriter可以在内存中创建一个Writer,它的作用实际上是构造一个缓冲区,可以写入char,最后得到写入的char[]数组,这和ByteArrayOutputStream非常类似:

try (CharArrayWriter writer = new CharArrayWriter()) {
    writer.write(65);
    writer.write(66);
    writer.write(67);
    char[] data = writer.toCharArray(); // { 'A', 'B', 'C' }
}

内存中创建一个Writer,模拟写入。

StringWriter

StringWriter也是一个基于内存的Writer,它和CharArrayWriter类似。实际上,StringWriter在内部维护了一个StringBuffer,并对外提供了Writer接口。

OutputStreamWriter

与InputStreamReader相似。OutputStreamWriter可以讲OutputStream转位Writer

try (Writer writer = new OutputStreamWriter(new FileOutputStream("readme.txt"), "UTF-8")) {
    // TODO:
}
小结
  • inputStream用户读取文件的字节流,单位是byte,read方法返回字节的int表示(0-255)
  • Reader基于InputStream封装,他是以char为单位的字符流,read方法返回char的int表示,可以用(char) n来转换返回的数据。
  • outputStream用户用来写入的字节流,单位是byte。
  • Writer是基于outputStream封装的字符流,单位是char。
  • InputStream: FileInputStream. ByteArrayInputStream
  • outStream: FIleOutputStream byteArrayOutputStream
  • Reader: FIleReader CharByteReader StringReader InputStreamReader(InputStream->Reader)
  • Writer: FileWriter charByteWriter StringWriter OutputStreamWriter(OutputStream->Writer)
PrintStream和PrintWriter

PrintStream是一种FilterOutputStream,它在OutputStream的接口上,额外提供了一些写入各种数据类型的方法:

  • 写入int:print(int)
  • 写入boolean:print(boolean)
  • 写入String:print(String)
  • 写入Object:print(Object),实际上相当于print(object.toString())


以及对应的一组println()方法,它会自动加上换行符。
看着很像System.out.xxx

事实上System.out.println()实际上就是使用PrintStream打印各种数据。其中,System.out是系统默认提供的PrintStream

PrintStream和OutputStream相比,除了添加了一组print()/println()方法,可以打印各种数据类型,比较方便外,它还有一个额外的优点,就是不会抛出IOException,这样我们在编写代码的时候,就不必捕获IOException。

有PirntStream就有PrintWriter,

PrintStream最终输出的总是byte数据,而PrintWriter则是扩展了Writer接口,它的print()/println()方法最终输出的是char数据。

public class Main {
    public static void main(String[] args)     {
        StringWriter buffer = new StringWriter();
        try (PrintWriter pw = new PrintWriter(buffer)) {
            pw.println("Hello");
            pw.println(12345);
            pw.println(true);
        }
        System.out.println(buffer.toString());
    }
}

小结
PrintStream是一种能接收各种数据类型的输出,打印数据时比较方便:

  • System.out是标准输出;
  • System.err是标准错误输出。

PrintWriter是基于Writer的输出。

Files

虽然Files是java.nio包里面的类,但他俩封装了很多读写文件的简单方法,极大的方便了我们读写文件。

byte[] data = Files.readAllBytes(Path.of("/path/to/file.txt"));
// 默认使用UTF-8编码读取:
String content1 = Files.readString(Path.of("/path/to/file.txt"));
// 可指定编码:
String content2 = Files.readString(Path.of("/path", "to", "file.txt"), StandardCharsets.ISO_8859_1);
// 按行读取并返回每行内容:
List<String> lines = Files.readAllLines(Path.of("/path/to/file.txt"));

Files.readString是java11后支持的

写入文件
// 写入二进制文件:
byte[] data = ...
Files.write(Path.of("/path/to/file.txt"), data);
// 写入文本并指定编码:
Files.writeString(Path.of("/path/to/file.txt"), "文本内容...", StandardCharsets.ISO_8859_1);
// 按行写入文本:
List<String> lines = ...
Files.write(Path.of("/path/to/file.txt"), lines);

Files工具类还有copy()、delete()、exists()、move()等快捷方法操作文件和目录。

Files提供的读写方法,受内存限制,只能读写小文件,例如配置文件等,不可一次读入几个G的大文件。读写大型文件仍然要使用文件流,每次只读写一部分文件内容。

对于简单的小文件读写操作,可以使用Files工具类简化代码。

线程(补充)

死锁
public void add(int m) {
    synchronized(lockA) { // 获得lockA的锁
        this.value += m;
        synchronized(lockB) { // 获得lockB的锁
            this.another += m;
        } // 释放lockB的锁
    } // 释放lockA的锁
}

public void dec(int m) {
    synchronized(lockB) { // 获得lockB的锁
        this.another -= m;
        synchronized(lockA) { // 获得lockA的锁
            this.value -= m;
        } // 释放lockA的锁
    } // 释放lockB的锁
}

对于上述代码,线程1和线程2如果分别执行add()和dec()方法时:

线程1:进入add(),获得lockA;
线程2:进入dec(),获得lockB。
随后:

线程1:准备获得lockB,失败,等待中;
线程2:准备获得lockA,失败,等待中。

两个线程持有不同的所,然后各自试图获取对方手里的所,造成双方无限等待,这就是死锁。

避免死锁的方法是多线程获取锁的顺序要一致。

使用wait和notify

synchronied解决了多线程竞争的问题,但没解决多线程协调问题。

class TaskQueue {
    Queue<String> queue = new LinkedList<>();

    public synchronized void addTask(String s) {
        this.queue.add(s);
    }

    public synchronized String getTask() {
        while (queue.isEmpty()) {
        }
        return queue.remove();
    }
}

如上,看似getTask通过while循环调用queu.isEmpty判断是否有数据,没有就一直等待,等其他线程调用addTask加入数据后,就能取出数据。
但是,getTask这个方法已经锁了当前的实例,导致其他线程,根本无法调用addTask。解决这个方法就是调用wait方法

public synchronized String getTask() {
    while (queue.isEmpty()) {
        this.wait();
    }
    return queue.remove();
}

调用getTask时候已经获得所,然后调用到wait之后,线程会进入等待状态,直到未来某个时刻,被其他线程唤醒。wait才会返回。

必须在synchronized块中才能调用wait()方法,因为wait()方法调用时,会释放线程获得的锁,wait()方法返回后,线程又会重新试图获得锁。

public synchronized String getTask() {
    while (queue.isEmpty()) {
        // 释放this锁:
        this.wait();
        // 重新获取this锁
    }
    return queue.remove();
}

如何唤醒呢?通过notify/notifyAll

public synchronized void addTask(String s) {
    this.queue.add(s);
    this.notifyAll(); // 唤醒在this锁等待的线程
}

注意到在往队列中添加了任务后,线程立刻对this锁对象调用notify()方法,这个方法会唤醒一个正在this锁等待的线程(就是在getTask()中位于this.wait()的线程),从而使得等待线程从this.wait()方法返回。

ReentrantLock

XML

XML是可扩展标记语言(eXtensible Markup Language)的缩写,它是一种数据表示格式,可以描述非常复杂的数据结构,常用于传输和存储数据。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE note SYSTEM "book.dtd">
<book id="1">
    <name>Java核心技术</name>
    <author>Cay S. Horstmann</author>
    <isbn lang="CN">1234567</isbn>
    <tags>
        <tag>Java</tag>
        <tag>Network</tag>
    </tags>
    <pubDate/>
</book>
  • 25
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coderlin_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值