20145330第六周《Java学习笔记》

时间:2023-02-25 14:32:15

20145330第六周《Java学习笔记》

.

这周算是很忙碌的一周。因为第六周陆续很多实验都开始进行,开始要准备和预习的科目日渐增多,对Java分配的时间不知不觉就减少了,然而第十和十一章内容相对有很多,所以效率是一件很重要的事情。

第十章 输入与输出

  • Java将输入输出抽象化为串流,数据有来源及目的地,衔接两者的是串流对象。
    • 比喻来说,数据就好比水,串流就好比水管,通过水管的衔接,水由一端流向另一端
    • 如果想要将数据从来源取出,可以使用输入串流。
    • 如果想要将数据写入目的地,可以使用输出串流。
  • 标准输入输出
    • System.in: 标准输入,默认关联到键盘(终端输入)
    • System.out: 标准输出,默认关联到显示器(终端输出)
    • System.err: 标准错误,默认关联到显示器(终端输出)
    • 输入输出重定向:setIn,setOut,setErr
  • 来源与目的都不知道的情况下,可以设计一个通用的dump()方法,如果要将某个文档读入并另存为另一个文档,则可以这么使用:

import java.io.*;

public class StreamIO {
public static void dump(InputStream src, OutputStream dest)
throws IOException {
try (InputStream input = src; OutputStream output = dest) {
byte[] data = new byte[1024];
int length;
while ((length = input.read(data)) != -1) {
output.write(data, 0, length);
}
}
}
}

import java.io.*;

public class Copy {
public static void main(String[] args) throws IOException {
StreamIO.dump(
new FileInputStream(args[0]),
new FileOutputStream(args[1])
);
}
}

20145330第六周《Java学习笔记》

  • 这个程序可以由命令行自变量指定读取的文档来源与写出的目的地
  • 如果要从HTTP服务器读取某个网页,另存为文档,也可以使用这里设计的dump()方法,例如:

import java.io.*;
import java.net.URL;

public class Download {
public static void main(String[] args) throws IOException {
URL url = new URL(args[0]);
InputStream src = url.openStream();
OutputStream dest = new FileOutputStream(args[1]);
StreamIO.dump(src, dest);
}
}

20145330第六周《Java学习笔记》

  • 串流继承架构
    • 标准输入输出
      • 可以使用systemsetIn()方法指定InputStream实例,重新指定便准输入来源。

import java.io.*;
import java.util.*;

public class StandardIn {
public static void main(String[] args) throws IOException {
System.setIn(new FileInputStream(args[0]));
try (Scanner file = new Scanner(System.in)) {
while (file.hasNextLine()) {
System.out.println(file.nextLine());
}
}
}
}

20145330第六周《Java学习笔记》

- 除了 system.in 与 system.out 之外,还有个system.err 为PrintStream 实例,成为标准错误输出串流,它是用来立即显示错误信息。
  • FileInputStream与FileOutputStream
    • FileInputStream是InputStream的子类,可以指定文件名创建实例,一旦创建文档就开启,接着就可以用来读取数据。
    • FileOutputStream是OutputStream的子类,可以指定文件名创建实例,一旦创建文档就开启,接着就可以用来读出数据。
    • FileInputStream与FileOutputStream在读取、写入文档时,是以字节为单位,通常会使用一些高阶类加以打包,进行一些高阶操作。
  • ByteArrayInputStream与ByteArrayOutputStream
    • ByteArrayInputStream是InputStream的子类,可以指定byte数组创建实例,一旦创建就可将byte数组当作数据源进行读取。
    • ByteArrayOutputStream同理
  • 串流处理装饰器
    • BufferedInputStream与BufferedOutputStream
      • BufferedInputStream与BufferedOutputStream主要在内部提供缓冲区功能,操作上与InputStream与OutputStream并没有太大差别。例如,改写前面的IO.dump()为BufferedIO.dump()方法:

import java.io.*;

public class BufferedIO {
public static void dump(InputStream src, OutputStream dest)
throws IOException {
try(InputStream input = new BufferedInputStream(src);
OutputStream output = new BufferedOutputStream(dest)) {
byte[] data = new byte[1024];
int length;
while ((length = input.read(data)) != -1) {
output.write(data, 0, length);
}
}
}
}
  • DataInputStream与DataOutputStream
    • 实际应用例子,下面的Member类可以调用save()储存Member实例本身的数据:

import java.io.*;

public class Member {

private String number;
private String name;
private int age;

public Member(String number, String name, int age) {
this.number = number;
this.name = name;
this.age = age;
}

public String getNumber() {
return number;
}

public void setNumber(String number) {
this.number = number;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return String.format("(%s, %s, %d)", number, name, age);
}

public void save() throws IOException {
try(DataOutputStream output =
new DataOutputStream(new FileOutputStream(number))) {
output.writeUTF(number);
output.writeUTF(name);
output.writeInt(age);
}
}

public static Member load(String number) throws IOException {
Member member;
try(DataInputStream input =
new DataInputStream(new FileInputStream(number))) {
member = new Member(
input.readUTF(), input.readUTF(), input.readInt());
}
return member;
}
}
  • ObjectInputStream与ObjectOutputStream
    • ObjectInputStream提供readObject()方法将数据读入为对象,ObjectOutputStream提供writeObject()方法将对象写至目的地
  • 字符处理类
    • Reader与Writer继承结构
      • 针对字符数据的读取,JavaSE提供了java.io.Reader类,其抽象化了字符数据读入的来源。
      • 针对字符数据的写入,JavaSE提供了java.io.Writer类,其抽象化了字符数据读入的目的地。
      • FileReader与FileWriter则可以对文档做读取与写入,读取或写入时默认会实用操作系统默认编码来做字符转换。

import java.io.*;

public class CharUtil {


public static void dump(Reader src, Writer dest) throws IOException {

try(Reader input = src; Writer output = dest) {
char[] data = new char[1024];
int length;
while((length = input.read(data)) != -1) {
output.write(data, 0, length);
}
}
}
}
  • 字符处理装饰器
    • InputStreamReader与OutputStreamWriter
      • 串流处理的字节数据,实际上代表某些字符的编码数据
    • BufferedReader与BufferedWriter
      • 可对Reader、Writer提供缓冲区作用,在处理字符输入输出时,对效率也会有所帮助
    • PrintWriter
      • 除了可以对OutputStream打包之外,还可以对Writer进行打包,提供print()、printLn()、format()等方法。

import java.io.*;

public class CharUtil2 {

public static void dump(Reader src, Writer dest) throws IOException {
try(Reader input = src; Writer output = dest) {
char[] data = new char[1024];
int length;
while((length = input.read(data)) != -1) {
output.write(data, 0, length);
}
}
}

public static void dump(InputStream src, OutputStream dest,
String charset) throws IOException {
dump(
new InputStreamReader(src, charset),
new OutputStreamWriter(dest, charset)
);
}

//
public static void dump(InputStream src, OutputStream dest)
throws IOException {
dump(src, dest, System.getProperty("file.encoding"));
}
}

线程与并行API

  • 要让目前流程暂停指定时间,可以使用java.lang.Thread的静态sleep()方法,指定的单位是毫秒,调用这个方法必须处理java.lang.InterruptedException

  • Thread与Runnable
  • 撰写多线程程序的方式:操作Runnable接口,在run()中定义额外流程;继承Thread类,在run()中定义额外流程;
  • 差别:操作Runnable接口的好处就是较有弹性,类还有机会继承其他类;若继承了Thread类,那该类就是一种Thread,通常是为了直接利用Thread中定义的一些方法,才会继承Thread来操作。
  • 如果主程序中启动了额外线程,默认会等待被启动的所有线程都执行完run()方法才中止JVM。如果一个Thread被标示为Daemon线程,在所有的非Daemon线程都结束时,JVM自动就会终止。


public class DaemonDemo {

public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("Orz");
}
});
// thread.setDaemon(true);
thread.start();
}
}
  • 在调用Thread实例start()方法后,基本状态为可执行(Runnable)、被阻断(Blocked)、执行中(Running)。
  • 线程有其优先权,可使用Thread的setPriority()方法设定优先权,可设定值为1到10,默认是5,超出1到10外的设定值会抛出IllegalArgumentException。数字越大优先权越高,排班器越优先排入CPU,如果优先权相同,则输流执行。
  • 线程完成run()方法后,就会进入Dead,进入Dead(或已经调用过start()方法)的线程不可以再次调用start()方法,否则会抛出IllegalArgumentException。
  • 如果要停止线程,最好自行操作,让线程跑完应有的流程,而非调用Thread的stop()方法。不仅停止线程必须自行根据条件操作,线程的暂停、重启,也必须视需求操作,而不是直接调用suspend()、resume()等方法。
  • 每个线程产生时,都会归入某个线程群组,这视线程是在哪个群组中产生,如在main()主流程中产生一个线程,该线程会属于main线程群组。如果没有指定,则归入产生该子线程的线程群组,也可以自行指定线程群组,线程一旦归入某个群组,就无法更换群组。
  • 每个对象都会有个内部锁定,或称为监控锁定。被标示为synchronized的区块将会被监控,任何线程要执行synchronized区块都必须先取得指定的对象锁定。
  • 如果在方法上标示synchronized,则执行方法必须取得该实例的锁定。synchronized不只可声明在方法上,也可以描述句方式使用。不正确的使用可能会造成死结。

class Resource {

private String name;
private int resource;

Resource(String name, int resource) {
this.name = name;
this.resource = resource;
}

String getName() {
return name;
}

synchronized int doSome() {
return ++resource;
}

synchronized void cooperate(Resource resource) {
resource.doSome();
System.out.printf("%s 整合 %s 的资源%n",
this.name, resource.getName());
}
}

public class DeadLockDemo {

public static void main(String[] args) {
Resource resource1 = new Resource("resource1", 10);
Resource resource2 = new Resource("resource2", 20);

Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource1.cooperate(resource2);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource2.cooperate(resource1);
}
});

thread1.start();
thread2.start();
}
}
  • Java的synchronized提供的是可重入同步,也就是线程取得某对象锁定后,若执行过程中又要执行synchronized,尝试取得锁定的对象又是同一个,则可以直接执行。
  • 执行synchronized范围的程序代码期间,若调用锁定对象的wait()方法,线程会释放对象锁定,并进入对象等待集合而处于阻断状态,其他线程可以竞争对象锁定,取得锁定的线程可以执行synchronized范围的程序代码。
  • 放在等待集合的线程不会参与CPU排班,wait()可以指定等待时间,时间到之后线程会再次加入排班,如果指定时间0或不指定,则线程会持续等待,直到被中断(调用interrupt()或是告知notify())可以参与排班。
  • 在使用高级并行API时,Lock接口的操作对象可实现synchronized的功能。

import java.util.Arrays;


public class ArrayList {
private Object[] list;
private int next;

public ArrayList(int capacity) {
list = new Object[capacity];
}

public ArrayList() {
this(16);
}

public void add(Object o) {
if(next == list.length) {
list = Arrays.copyOf(list, list.length * 2);
}
list[next++] = o;
}

public Object get(int index) {
return list[index];
}

public int size() {
return next;
}
}
  • 在使用高级并行API时,Condition接口的操作对象可实现Object的wait()、notify()、notifyAll()功能。在使用高级并行API时,Future接口的操作对象可以让你在未来取得执行结果。

教材学习中的问题

  • 撰写单线程序、多线程程序的方式差别:操作Runnable接口的好处就是较有弹性,类还有机会继承其他类;若继承了Thread类,那该类就是一种Thread,通常是为了直接利用Thread中定义的一些方法,才会继承Thread来操作。

代码托管截图

20145330第六周《Java学习笔记》
20145330第六周《Java学习笔记》

学习进度条

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
目标 5000行 30篇 400小时
第一周 200/200 2/2 20/20
第二周 300/500 2/4 18/38
第三周 500/1000 3/7 22/60
第四周 300/1300 2/9 30/90
第五周 300/1600 1/10 20/110
第六周 250/1850 2/12 20/130

参考资料

  • 《Java学习笔记》