20145215《Java程序设计》第6周学习总结
20145215《Java程序设计》第六周学习总结
教材学习内容总结
输入/输出
InputStream与OutputStream
- 从应用程序角度来看,如果要将数据从来源取出,可以使用输入串流;如果要将数据写入目的地,可以使用输出串流。在Java中,输入串流代表对象为
java.io.InputStream
实例,输出串流代表对象为java.io.OutputStream
实例。无论数据源或目的地为何,只要设法取得InputStream或OutputStream的实例,接下来操作输入/输出的方式都是一致的,无须理会来源或目的地的真正形式。 - java.io包中包含了流式I/O所需要的所有类。在java.io包中有四个基本类:InputStream、OutputStream及Reader、Writer类,它们分别处理字节流和字符流:
输入/输出 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
- 在不使用InputStream与OutputStream时,必须使用close()方法关闭串流。由于InputStream与OutputStream操作了
java.io.Closeable
接口,其父接口为java.lang.AutoCloseable
接口,因此可使用JDK7尝试自动关闭资源语法。
InputStream主要的子类有:
- FileInputStream:把一个文件作为InputStream,实现对文件的读取操作。
- ByteArrayInputStream:把内存中的一个缓冲区作为InputStream使用,可以指定byte数组创建实例,一旦创建就可将byte数组当作数据源进行读取。
- BufferedInputStream:主要在内部提供缓冲区功能,操作上与InputStream没有太大差别。
- DataInputStream:提供读取、写入Java基本数据类型的方法,像是读写int、double、boolean等的方法,这些方法会自动在指定的类型与字节间转换。
- ObjectInputStream:提供readObject()方法将数据读入为对象,具备对象串行化能力。
OutputStream主要的子类有:
- FileOutputStream:把信息存入文件中,可以指定文件名创建实例,一旦创建文档就开启,接着就可以用来写出数据。
- ByteArrayOutputStream:把信息存入内存中的一个缓冲区中,可以指定byte数组创建实例,一旦创建就可将byte数组当作目的地写出数据。
- BufferedOutputStream:主要在内部提供缓冲区功能,操作上与OutputStream没有太大差别。
- DataOutputStream:提供读取、写入Java基本数据类型的方法,像是读写int、double、boolean等的方法,这些方法会自动在指定的类型与字节间转换。
- ObjectOutputStream:提供writeObject()方法将对象写至目的地,具备对象串行化能力。
字符处理类
-
Reader抽象类:用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率或其他功能,其子类如下:
-
Writer抽象类:写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率或其他功能,其子类如下:
-
FileReader、FileWriter则可以对文档做读取与写入,读取或写入时默认会使用操作系统默认编码来做字符转换。在启动JVM时,可以指定
-Dfile.encoding
来指定FileReader、FileWriter所使用的编码。 -
Reader、Writer中也有一些装饰器类,InputStreamReader和OutputStreamWriter可以对串流数据打包,BufferedReader、BufferedWriter可对Reader、Writer提供缓冲区作用,在处理字符输入/输出时,对效率也会有所帮助。PrintWriter与PrintStream使用上极为类似,不过除了可以对OutputStream打包之外,PrintWriter还可以对Writer进行打包,提供print()、println()、format()等方法。
线程与并行API
线程
- 在java中,如果想在main()以外独立设计流程,可以撰写类操作
java.lang.Runnable
接口,流程的进入点是操作在run()方法中。如果想要为JVM加装CPU,就是创建Thread实例,要启动额外CPU就是调用Thread实例的start()方法,额外CPU执行流程的进入点,可以定义在Runnale接口的run()方法中。 - 操作Runnable接口的好处就是较有弹性,你的类还有机会继承其他类。若继承了Thread,那该类就是一种Thread,通常是为了直接利用Thread中定义的一些方法,才会继承Thread来操作。
- 在Java中实现一个线程有两种方法,第一是实现Runnable接口实现它的run()方法,第二种是继承Thread类,覆盖它的run()方法。例如下面的代码:
public class DoSomething implements Runnable {
public void run(){
// here is where you do something
}
}
public class DoAnotherThing extends Thread {
public void run(){
// here is where you do something
}
}
这两种方法的区别是,如果你的类已经继承了其它的类,那么你只能选择实现Runnable接口了,因为Java只允许单继承的。
- 主线程会从main()方法开始执行,直到main()方法结束后停滞JVM。如果主线程中启动了额外线程,默认会等待被启动的所有线程都执行完run()方法才终止JVM。如果一个Thead被表示为Daemon线程,在所有的非Daemon线程都结束时,JVM自动就会终止。
- 每个线程产生都属于某个线程群组,线程产生时,都会归入某个线程群组,这视线程实在那个群组中产生的,如果没有指定,则归入产生孩子线程的线程群组。也可以自行指定线程群组,线程一旦归入某个群组,就无法再更换。java.lang.ThreadGroup类正如其名,也可以自行指定线程群组。
- Thread基本状态图:在调用Thread实例start()方法后,基本状态为可执行(Runnable)、被阻断(Blocked)、执行中(Running)。实例化Thread并执行start()方法后,线程进入Runnable状态,此时线程尚未真正开始执行run()方法,必须等待排班器排入CPU执行,线程才会执行run()方法,进入Running状态,线程看起来像是同时执行,但事实上,同一个时间点,一个CPU还是只能执行一个线程,只是CPU会不断切换线程,且切换动作很快,所以看起来像是同时执行。线程有优先权,可用Thread的setPriority()方法设定优先权。最小值为1,最大值为10,默认是5。数字越大,优先权越高,排班器越优先排入CPU,如果优先权相同,则输流执行。运用多线程,当某线程进入Blocked时,让另一线程排入CPU执行,避免CPU闲下来,经常是改进效能的方式之一。
并行API
- 基于Thread类和Runnable接口编程很容易实现基本的并行编程任务,但实现复杂的并行程序就会比较困难,因为要详细考虑资源的同步访问以及设计必要数据结构支持同步访问。从Java5后,Java平台提供了
java.util.concurrent
包以及HighLevelAPI简化并行编程模型,并提供了很多支持同步访问数据结构满足编程需要。 - Lock(锁对象):相对与Thread模型的隐式的锁对象,Lock提供了显式的锁操作从而简化应用程序。Executors:提供了一组HighLevelAPI用来执行和管理并行任务。 ConcurrentCollections(并行集合):包含了一组支持并行处理的数据结构,大大简化了并行编程难度。AtomicVariables(原子变量):减少了同步操作并且避免数据不一致。Fork/Join框架:提供了进程操作的支持。
- CopyOnWriteArrayList操作了List接口,顾名思义,这个类的实例在写入操作时,内部会建立新数组,并复制原有数组索引的参考,然后在新数组上进行写入操作,写入完成后,再将内部原参考旧数组的变量参考至新数组。对于一个很少进行写入操作,而使用迭代器频繁的情景下,可以使用CopyOnWriteArrayList提高迭代器操作的效率。BlockingQueue是Queue的子接口,新定义了put()与take()等方法,线程若调用put()方法,在队列已满的情况下会被阻断,线程若调用了take()方法,在队列为空的情况下会被阻断。ConcurrentMap是Map的子接口,其定义了putIfAbsent()、remove()、replace()等方法。这些方法都是原子操作。putIfAbsent()在键对象不存在ConcurrentMap中时,才可置入键/值对象,否则返回键对应的值对象。remove()只有在键对象存在,且对应的值对象等于指定的值对象,才将键/值对象移除。replace()有两个版本,其中一个版本是只有在键对象存在,且对应的值对象等于指定的值对象,才将值对象置换,另外一个版本是在键对象存在时,将值对象置换。
教材学习中的问题和解决过程
- 对于
-Dfile.encoding
的一些补充说明:1)在命令行中输入 java,在给出的提示中会出现-D 的说明:
-D= # set a system property
,-D 后面需要跟一个键值对,作用是设置一项系统属性。
2)对-Dfile.encoding=UTF-8
来说就是设置系统属性file.encoding
为 UTF-8,file.encoding
字面意思为文件编码,搜索 java 源码,只能找到 4 个文件中包含file.encoding
的文件,也就是说,只有四个文件调用了file.encoding
这个属性。
3)默认字符集是在 java 虚拟机启动时决定的,依赖于 java 虚拟机所在的操作系统的区域以及字符集,默认字符集就是从file.encoding
这个属性中获取的。 - 教材327页TortoiseHareRace2.java代码运行结果:
代码运行的时候先执行完乌龟走完10步,再执行兔子走完10步,而不是按乌龟和兔子每轮走的情况来执行的。
代码调试中的问题和解决过程
- 下面是我编的多线程一个小例子:
public class TestThread extends Thread{
public TestThread(String name) {
super(name);
}
public void run() {
for(int i = 0;i<5;i++){
System.out.println(this.getName()+" :"+i);
}
}
public static void main(String[] args) {
Thread t1 = new TestThread("Boss cai");
Thread t2 = new TestThread("AV bear");
t1.start();
t2.start();
}
}
虽然有运行结果,但是它并不能按照我想象的交替输出,后来我在for循环后增加了一个for(long k= 0; k <100000000;k++);
用来模拟一个非常耗时的操作,接着就可以交替输出了。
心得体会
这周令我感受最深的就是API的使用在Java的学习中非常重要,在之前的学习过程中,对API的使用较少,因此对许多类的用法也不是很清楚,只是知道按照教材上的方法去使用。但这周接触到了串流的概念之后,我感觉IO流中的许多概念及用法在我脑海中模糊不清,这时,我想到了API,因此我就尝试着去API查了一下,API中介绍的都非常详细,这也让我渐渐对API产生了兴趣。我相信,学好用好API对于还处在学习阶段的我们来说,帮助是非常大的,在今后的学习过程中,我也会更多的去熟悉掌握API的功能,让自己的Java水平能够有所提高。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | 编写了Hello Java代码 |
第一周 | 100/100 | 2/2 | 12/12 | 编写了Hello Java代码 |
第二周 | 200/300 | 2/4 | 15/27 | 理解了printf和println的区别 |
第三周 | 450/750 | 1/5 | 22/49 | 对对象有了更深层次的理解 |
第四周 | 869/1619 | 1/6 | 28/77 | 对对象的三大特征有了更全面的认识 |
第五周 | 1123/2742 | 1/7 | 25/102 | 学会了异常处理 |
第六周 | 863/3605 | 2/9 | 30/132 | 理解了线程 |
【附1】本周学习的代码已经成功托管,截图如下:
【附2】利用wc统计代码行数,截图如下: