复习
1.Java.util.ArrayList
2.Java.util.HashMap需要在key 对应的类型中重写hashCode 和 equals方法,来保证key 的唯一性 和 无序。
3.ArrayList 和 LinkedList 的异同
4.Vector 和 ArrayList 的异同
5.Hashtable 和 HashMap 的异同
6.Collections 和 Collection 的异同
7.Iterator 和 Iterable 异同
8.TreeMap 元素的 key 是有序的,唯一的,不能是null。有序是升序,排序依赖于内部或者外部比较器。
9.根据流向:输入流、输出流。
10.数据单元:字节流、字符流。
11.数据源头:节点流、处理流(包装流、高级流)。
12.字符输入流的工作原理:读取底层的文本的字节数据,然后使用解码器,将字节数据解码为字符数据,要依赖字符集。
13.字符输出流工作原理:将内存中的字符数据,使用编码器,依赖于某一个字符集,将字符数据编码为字节数据,写入到底层。
14.InputStream、OutputStream、Reader、Writer、FileInputStream、FileOutputStream、FileReader、FileWriter、BufferedInputStream、BufferedOutputStream。
第一节 带缓冲区的字符流、转换流
1.BufferedReader、BufferedWriter
缓冲区默认大小8192 chars.
**
*BufferedReader带缓冲区的字符输入流
* BufferedWriter带缓冲区的字符输出流
*/
public class TestBufferedReaderWriter {
public static void main(String[] args) {
copy("e:/b.txt","e:/b_copy.txt");//使用的是默认的字符集utf-8;
}
static void copy(String srcPath,String destPath){
BufferedReader br=null;
BufferedWriter bw=null;
try {
//从输入流中读取一行文本数据,当到达数据源的末尾,返回null
//该方法一旦读取到\r\n就立即返回,本行读取到的数据,返回的内容不包含换行字符
br=new BufferedReader(new FileReader(srcPath));
bw=new BufferedWriter(new FileWriter(destPath));
String str=br.readLine();
while(str!=null){
//这样写存在一个问题,移植性,不同的国家或者平台,文件分隔符不同。
//bw.write(str+"\n");
//往输出流中写入一个系统当前的换行符
bw.newLine();
bw.flush();
str=br.readLine();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
br.close();
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.转换流
FileReader : 该类型对象具有解码器的功能。
BufferedReader:该类型对象只是为了提高其他流的工作的效率。没有解码器的功能。增强其他流的流。
FileWriter:该类型对象具有编码器的功能。
InputStreamReader 解码器
该流是字符流,是一个数据源为字节流的字符流。功能是,负责读取底层的字节数据,依赖于默认的或者是显式指定的字符集,进行解码,将字节数据解码为字符数据。解码器,
注意:所有的字符输入流的解码的功能都依赖于该类。
InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
吃的是字节,吐的是字符。
2.OutputStreamWriter 编码器
该流是字符输出流,负责将字符数据,使用平台默认的字符集,或者显式指定字符集,将字符数据编码为字节数据写出到底层。
是字符流通向字节流的桥梁,所有的字符输出流的编码工作都依赖于该类。
吃的是字符,吐的是字节。
import java.io.*;
/**
*InputStreamReader 解码器
* OutputStreamReader编码器
*/
public class TestInputStreamReader {
public static void main(String[] args) throws Exception {
test0();
//test1();
}
static void test0() throws Exception{
BufferedReader br=new BufferedReader(new FileReader("E:/1.txt"));
String str=br.readLine();
while(str!=null){
System.out.println(str);
str=br.readLine();
}
br.close();
}
//读取其他编码的文件,
static void test() throws Exception{
//读取底层的字节数据,然后使用GDK解码
InputStreamReader isr=new InputStreamReader(new FileInputStream("e:/a.txt"),"GBK");
BufferedReader br=new BufferedReader(isr);
String str=br.readLine();
while (str!=null){
System.out.println(str);
str=br.readLine();
}
br.close();
}
//考虑频繁从磁盘读写的问题
static void test1() throws Exception{
InputStreamReader isr=new InputStreamReader(new FileInputStream("e:/a.txt"),"GBK");
char[] buf=new char[10];
int count= isr.read(buf);
while(count!=-1){
System.out.println(new String(buf,0,count));
count= isr.read(buf);
}
}
//接收键盘输入,将输入的内容写入到指定的文件中。输入bye 的时候结束。
static void test3() throws Exception{
InputStreamReader isr=new InputStreamReader(System.in);
BufferedReader br=new BufferedReader(isr);
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream("1copy.txt"),"GBK"));
String str=br.readLine();
while(true){
if(str.equals("bye")){
break;
}
bw.write(str);
bw.newLine();
bw.flush();
}
br.close();
bw.close();
}
//使用GBK编码,将字符数据写出到指定的文件
static void test4() throws Exception {
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream("E:/a.txt"),"GBK"));
bw.write("千万里我追寻着你");//这个write方法的这个“千万里。。。”写到了bw的8192的缓存区中去了,
bw.close();
}
}
说明:
1.FileReader说一下字符流的工作原理,从底层读取字节,使用解码器将底层的字节转化为字符(当然要依赖于某一个字符集),那么解码器在哪里呢?
这个解码器应该是这个对象内部的功能,那么该类型的对象具有解码器的功能,有可能是它的某个方法,也有可能是它的某个属性。
2.BufferedReader也是从底层读取字节转化为字符,那么 它的解码器在哪里呢?注意它只要是用来提高效率的,还要依赖于FileReader来读取底层,它并没有解码器,解码器的功能还是FileReader的。这个类就是为了增强FileReader这个流的,这就涉及到设计模式了,这是一个装饰设计模式的一个类。它的源头的谁就装饰了谁,就增强了谁。
3.FileReader的解码功能依赖于InputStreamReader,那么它俩是什么关系呢?Reader——>InputStreamReeader———>FileReader,FileReader的解码功能是从他的父类哪里继承下来的,所以它能直接用。
4.InputStreamReader 只能读文本,里面传的参数是字节流。
5.代码:new InputStreamReader(new FileInputStream(“e:/a.txt”),“GBK”);我们以 FileInputStream这个流以字节的形式读取底层的数据,"e:/a.txt"这个是文本,我们读取的是它底层的字节数据, InputStreamReader解码,解码的时候用的字符集是“GBk”,不写字符集的话,就使用默认的字符集utf-8;
这里我们不用InputerStreamReader 用FileReader也行,但是有时候必须用InputStreamReader,比如,上例中的a.txt我建文件的时候编码形式不是utf-8,而是用ANSI(不同的国家表示不同的编码,这里指GBK)编码的,这时候如果使用FileReader(FileReader只能使用平台默认的额字符集,我们这个Idea用的啥字符集,它就用啥字符集)会由于字符集的不同出现乱码的问题。所以我们必须要使用InputSreamReader来指出字符集。(InputStreamReader(new FileInputStream(“e:/a.txt”),“GBK”))所以这里就是把字节读进来解码的时候将这些字节数据使用GBK进行解码,所有的乱码问题都是由于编码和解码使用 的字符集不同导致的。
6.System.in就是出入流,这个in是InputStream类型的,in是InputStream的一个实例,就是我们的键盘。键盘输入的是字符数据,是纯文本,还是一个字节流,那么使用InputStreamTeader转为字符流。
7.InputStreamReader 这个是非常有用的,(字符集的指出可以隐式使用平台默认的,也可以显示式使用),比如在网络传输的时候,只能使用字节进行传输,所有的文本数据也要转化为字节,
所以要将这些字节数据,使用某个字符集,转化为字符数据进行处理。因为是字符数据,所以最终,用字符流好处理,如果使用字节流处理呢,最终还要转。所以当我们拿到一堆字节数据并且是纯文本数据的时候,一般要使用InputStreamReader.还有像我们本例中这种编码不一致的情况也要使用这个。
8.FileWriter将内存中的字符数据,编码为字节写入到底层。该类型的对象具有编码器的功能。OutputStreamReader是字符流,是字符流还是字节流是相对于我们自身来说的,我们操作的是字节呢?还是字符呢?我们操作的字节,就是字节流,我们操作的字符就是字符流。
如果我们用FileWriter写文件的话,那么这个编码字符集就是utf-8.如果要求把一堆字数据以GBK为编码字符集,写到文件中去这时候就得用OutputStreamReader了
第二节 对象流
1.对象的持久化存储相关的概念
序列化:将内存中的对象转换为字节数据的过程。称为对象的序列化。
反序列化:将字节数据还原为内存中的对象的过程,称为对象的反序列化。
public interface Serializable
类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。
标记性接口,实现了该接口,才可以进行序列化和反序列化。
2.序列化和反序列化注意的问题
1.所有需要序列化和反序列化的类的对象都需要实现java.io.Serializable 该接口。该接口是一个标记性接口,没有任何的内容,代表了一种可以被序列化和反序列化的能力。
2.如果一个类的对象可以被序列化,那么它的子类对象也可以。
3.如果想正确的实现序列化和反序列化,那么对应的类型中,有必要显式的定义public static final long serialVersionUID = 1L;。
4.如果一个对象可以被序列化,那么该对象内部的所有的属性应该都可以被序列化。
5.类中的哪些成员可以不被序列化。静态的成员变量是不可以被序列化的,静态的成员依赖于类,不依赖于对象。使用关键字 transient 修饰的实例成员变量,也不可以被序列化,那么反序列化得到的对象中的 使用 transient 修饰的成员 的值为默认值。
import java.io.*;
/**
* ObjectStream流的学习
*/
public class TestObjectStream {
public static void main(String[] args) throws Exception {
writeObject();
readObject();
}
//将指定的对象写出到文件中,序列化过程
static void writeObject() throws Exception{
//对象流:将对象转化为字节数据
//缓冲流:负责提高效率,缓存数据,避免多次写入
//文件流:将字节数据写入文件的
ObjectOutputStream oos=new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("1.txt")));
Student s=new Student("韩梅梅",17);
oos.writeDouble(1.1);//在内存中的字节形式
oos.writeObject(s);
oos.close();
}
//将文件中的对象读取到内存当中来,反序列化的过程
static void readObject() throws Exception{
ObjectInputStream ois=new ObjectInputStream(new BufferedInputStream(new FileInputStream("1.txt")));
//按写入的顺序进行读取
double value=ois.readDouble();
//反序列化过程中没有使用构造方法对对象进行初始化
//所有对象的初始化操作是依赖于对象的字节数据(还原肯定要用到学生类,但是没有调用构造方法)
Student o=(Student)ois.readObject();
System.out.println(o);
ois.close();
}
}
class Student implements Serializable{ //所有需要序列化和反序列化的类的对象都需要实现java.io.Serializable 该接口,
// 不实现会抛出异常NotSerializableException
//把类的串行版本号显式定义,序列化前后 的版本号要求一致。
private static final long serialVersionUID=1L;
private String name;
private transient int age;
private int score;
private int ID;
private String gender;
private Book book=new Book();//学生拥有一本书,拥有关系就是成员
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", score=" + score +
", book=" + book +
'}';
}
}class Book implements Serializable{
}
/*
如果某一个对象已经通过writeObject()写入到了文件中。这时对类进行添加属性,再使用方法readObject()从文件中读取对象的时候,
会报下面的错误。
local class incompatible: stream classdesc serialVersionUID = 4861162618705949631, local class serialVersionUID = -706830872169961173
为了解决这个问题,我们需要把类的串行版本号显示定义。
可以不被序列化的两种方式:静态成员和,用transient修饰的成员,使用 transient 修饰的成员 的值为默认值。
*/
第三节 打印流 数据流 字节数组流
1 打印流
import java.io.*;
/**
* PrintStream:打印流、字节流、输出流(往外写字节)
* PrintWriter:是字符流(往外写字符)
* PrintStream:该流只有输出流,没有输入流,该流不会抛出IOException。有自动刷新的功能。
* 主要的作用:该类提供了针对于任何对象转换为字符串,再转换为字节写出到底层的功能
*/
public class TestPrintStream {
public static void main(String[] args) {
test4();
}
static void test1() throws Exception{
//源码的文档注释public PrintStream(OutputStream out)
// ----The out put stream to which values and objects will be printed)
PrintStream ps=new PrintStream(new BufferedOutputStream(new FileOutputStream("e:/a.txt")));
ps.print('A');
ps.println(1.1);
ps.print(111111111111L);
ps.print(new TestPrintStream());
ps.close();
}
static void test2() throws Exception{
//第一个true尾部追加;
//第二个true:A boolean; if true, the output buffer will be flushed whenever a byte array is written
PrintStream ps=new PrintStream(new BufferedOutputStream(new FileOutputStream("E:/a.txt",true)),true);
//System.out就是ps了
System.setOut(ps);
System.out.println("hello");
System.out.println("world");
}
static void test3() throws Exception{
FileInputStream fis=new FileInputStream("e:/txt");
//System.in 就是fis 了
System.setIn(fis);
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
//读取的是fis对应的数据源中的内容
System.out.println(br.readLine());
fis.close();
}
//面试题
static void test4(){
int x=10;
PrintStream ps=new PrintStream(System.out){//重写一个类的方法最快速的就是使用方法匿名内部类
@Override
public void println(int x) {
println("x="+x);
}
};
System.setOut(ps);
System.out.println(x);
}
}
/*
System.in:键盘
System.out:控制台
*/
2 数据流
DataInputStream、DataOutputStream
作用:提供了对所有的基本数据类型的在内存中的原始字节样式的读写,以及String的字节的读写功能。
import java.io.*;
/**
*DataInputeStream数据输入流
* DataOutputStream数据输出流
*/
public class TestDataStream {
public static void main(String[] args) throws Exception {
test1();
test2();
}
static void test1() throws Exception{
DataOutputStream dos=new DataOutputStream(new BufferedOutputStream(new FileOutputStream("6.txt")));
dos.writeByte(127);//1个字符
dos.writeShort(0);//2个字符
dos.writeInt(2100000000);//4个字符
dos.writeLong(1L);//8个字符
dos.writeFloat(1.1f);//4个字符
dos.writeDouble(1.1);//8个字符
dos.writeChar('A');//2
//29个字符
dos.writeBoolean(true);//1个字符
//在真正的将 字符串的字节数组写入之前,先调用了writeShort 写入了 字符串的对应的字节数组的长度。
dos.writeUTF("你好");
//38个字符
dos.close();
}
static void test2() throws Exception{
DataInputStream dis=new DataInputStream(new BufferedInputStream(new FileInputStream("6.txt")));
System.out.println(dis.readByte());
System.out.println(dis.readShort());
System.out.println(dis.readInt());
System.out.println(dis.readLong());
System.out.println(dis.readFloat());
System.out.println(dis.readDouble());
System.out.println(dis.readChar());
System.out.println(dis.readBoolean());
System.out.println(dis.readUTF());
//在readUTF 方法内部,先调用了readShort ,来获得即将要使用多少个字节数据来还原字符串。
dis.close();
}
}
3.字节数组流
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
*ByteArrayInputStream字节数字输入流 (以前讲的那些类的源要么是流,要么是字节数据,而这个类的源是字节数组)
* ByteArrayOutputStream字节数组输出流
*/
public class TestByteArrayStream {
public static void main(String[] args) throws Exception {
test1();
}
//得到一个对象和一个int类型的字节数组
static void test1() throws Exception{
//ByteArrayOutputStream 对象自带了一个字节数组缓冲区。我们往外写的所有的内容都写到缓存区了
ByteArrayOutputStream baos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(baos);
oos.writeObject("你好");
oos.writeInt(10);
oos.close();
//字节数组中就包含了 写出的对象和数据内容。
byte[] bytes=baos.toByteArray(); //如何得到自带的字节数组的缓冲区呢?
//将字节数组中的数据还原
//将字节数组转为流数组
ByteArrayInputStream bais=new ByteArrayInputStream(bytes);
//ois 负责读取底层的字节流数据去还原对象。
ObjectInputStream ois=new ObjectInputStream(bais);
System.out.println(ois.readObject());
System.out.println(ois.readInt());
ois.close();
}
}
4 练习
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
* 文件夹的复制
*/
public class CopyDir {
public static void main(String[] args) throws Exception {
copyDir(new File("e:/first"),new File("e:/second"));
}
//复制单个文件
private static void copyFile(File srcFile,File destFile) throws Exception{
FileInputStream fis=new FileInputStream(srcFile);
FileOutputStream fos=new FileOutputStream(destFile);
byte[] buf=new byte[100];
int count=fis.read(buf);
while(count!=-1){
fos.write(buf,0,count);
count=fis.read(buf);
}
fis.close();
fos.close();
}
//复制文件夹
static void copyDir(File srcFile,File destDir) throws Exception{
//创建目的目录
if(!destDir.exists()){
destDir.mkdir();
}
//获得所有的子文件和子文件夹
File[] files=srcFile.listFiles();
for (int i = 0; i < files.length; i++) {
String fileName=files[i].getName();
if(files[i].isFile()){ //子文件复制
copyFile(files[i],new File(destDir,fileName));
}else{
copyDir(files[i],new File(destDir,fileName));
}
}
}
}
5 总结:
1:读取文件内容
1:文本文件 br = new BufferedReader(new FileReader(file)) br.readLIne()
2:字节文件 bis = new BufferedInputStream(new FileInputStream(file)) bis.read
2:写入文件内容
1 : 文本文件 bw = new BufferedWriter(new FileWriter(file)) bw.writer newLine
2 : 字节文件 bos = new BufferedOutputStream(new FileOutputStream(file))
3 : 对象的读写操作 ObjectInpustream ObjectOutputStream
4:基本数据类型的读写 DataInputStream DataOutputStream
5 : 读取键盘输入 br = new BufferedReader(new InputStreamReader(System.in))
6:ByteArrayInputStream ByteArrayOutputStream 将内存中的某些数据转换为字节数组,或者从字节数组中还原某些数据使用。
7:System.out 是 PrintStream 的实例。 System.in 是 InpuStream 的实例。
6 IO体系图
第四节 线程
1 线程相关的概念
1.程序:program 是一个静态的概念。是计算机指令的有序的集合。
2.进程:process 动态的概念。程序的一次执行;执行中的程序。
进程特点:
1:多个进程可以并发执行
2:进程是系统进行资源分配的最小单位。
3:多个进程之间不共享相互的资源,每个进程都独享系统分配的资源。
4:系统创建和销毁进程消耗的资源是比较大的。
5:一个进程至少包含一个线程。可以包含多个线程。
线程:进程中的一条任务线;进程中一条完整的执行的路径。
线程的特点:
1:多个线程可以并发执行。
2:线程是不能独立存在,必须存在于某一个进程中。
3:进程中的所有的任务都是依靠线程来完成的。进程对应的程序中的每一行代码都是要靠某一个线程来执行的。
4:当一个进程启动的时候,系统会创建一个线程,java程序中称为main线程–主线程。主线程的执行的路径就是main 方法的主体部分。
5:一个进程中的所有的线程共享所在的进程的资源。
6:线程的创建和销毁消耗的资源相比进程是小的。线程是轻量级进程。
7:线程是cpu进行调度执行的最小单位。
今日练习:
1:使用BufferedReader 和 BufferedWriter 实现:将指定的文件中的字符数据复制到另一个文件中。
要求:使用readLine 方法。读取行数据,只将包含了数字的行数据进行复制。将不包含数字的行数据过滤掉。
2:将编码格式为utf-8的文本文件的内容读取并显示到控制台。
3:将编码格式为GBK的文本文件的内容读取并显示到控制台。
4:将“我要好好学习,天天向上”字符串,使用utf-8 编码写出到某一个文件中。
5:将“我要好好学习,天天向上”字符串,使用 gbk 编码写出到某一个文件中。
6:使用带缓冲区的字节流,实现对某一个文件的复制。
(转)ByteArrayOutputStream 理解
第一次看到ByteArrayOutputStream的时候是在Nutch的部分源码,后来在涉及IO操作时频频发现这两个类的踪迹,觉得确实是很好用,所以把它们的用法总结一下。
ByteArrayOutputStream的用法
以下是JDK中的记载:
public class ByteArrayOutputStream extends OutputStream
此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray()和 toString()获取数据。
关闭 ByteArrayOutputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何IOException。
我的个人理解是ByteArrayOutputStream是用来缓存数据的(数据写入的目标(output stream原义)),向它的内部缓冲区写入数据,缓冲区自动增长,当写入完成时可以从中提取数据。由于这个原因,ByteArrayOutputStream常用于存储数据以用于一次写入。
实例:
从文件中读取二进制数据,全部存储到ByteArrayOutputStream中。
FileInputStream fis=new FileInputStream("test");
BufferedInputStream bis=new BufferedInputStream(fis);
ByteArrayOutputStream baos=new ByteArrayOutputStream();
int c=bis.read();//读取bis流中的下一个字节
while(c!=-1){
baos.write(c);
c=bis.read();
}
bis.close();
byte retArr[]=baos.toByteArray();
ByteArrayInputStream的用法
相对而言,ByteArrayInputStream比较少见。先看JDK文档中的介绍:
public class ByteArrayInputStreamextends InputStreamByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。
关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
构造函数:
ByteArrayInputStream(byte[] buf)
注意它需要提供一个byte数组作为缓冲区。
与大部分Inputstream的语义类似,可以从它的缓冲区中读取数据,所以我们可以在它的外面包装另一层的inputstream以使用我们需要的读取方法。
个人认为一个比较好的用途是在网络中读取数据包,由于数据包一般是定长的,我们可以先分配一个够大的byte数组,比如byte buf[]=new byte[1024];
然后调用某个方法得到网络中的数据包,例如:
Socket s=…;
DataInputStream dis=new DataInputStream(s.getInputStream());
dis.read(buf);//把所有数据存到buf中
ByteArrayInputStream bais=new ByteArrayInputStream(buf); //把刚才的部分视为输入流
DataInputStream dis_2=new DataInputStream(bais);
//现在可以使用dis_2的各种read方法,读取指定的字节
比如第一个字节是版本号,dis_2.readByte();
等等……
上面的示例的两次包装看上去有点多此一举,但使用ByteArrayInputStream的好处是关掉流之后它的数据仍然存在。