欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java学习笔记(I/O流和序列化和线程相关知识)

程序员文章站 2022-03-05 13:09:30
...

I/O流和序列化和线程相关知识

I/O流

Java中将不同的输入输出源抽象表示为流;
流是一种从输入源到接收地的抽像概念;

  1. ** 输入输出源:**
    • 文件
    • 网络
    • 内存
  2. 从流向考虑(站在程序角度考虑)
    输入流:从数据源到程序的流称之为输入流(读)
    输出流:从程序到输出源的流称之为输出流(写)
  3. 从流的类型划分
    字节流(byte)
    字符流(char)
    4.** 从功能划分**
    节点流
    处理流
    节点流主要用于直接跟输入输出源对接,处理流用于对节点流或其他流进行包装处理。

Java中的字节流主要包含两个抽象的流类型:

  • java.io.InputStream 是所有字节输入流的超类(最终父类)
  • java.io.OutputStream 是所有字节输出流的超类

字节流一般用于操作二进制文件,比如图片,音频,视频等,在对数据读写时以字节为单位;

字节流常见子类

  • FileInputStream
  • FileOutputStream
  • ByteArrayInputStream
  • ByteArrayOutputStream
  • BufferedInputStream
  • BufferedOutputStream

字节输入流InputStream

常见方法:

  • int available() 获取流中的可读字节数
  • void close() 关闭此输入流并释放跟流相关的系统资源
  • abstract int read() 从流中读取一个字节
  • int read(byte[] b) 从流中读取字节并存入字节缓冲区,返回实际读取字节数
  • skip(long n) 发生下一次读取之前跳过n个字节

字节输出流OutputStream

常见方法:

  • void close() 关闭此输出流并释放跟流相关的系统资源
  • void flush() 刷新此输出流并强制将缓冲区的数据写出到输出源
  • void write(byte[] b)将b.length个字节从数组中写出到输出源
  • void write(byte[] b,int offset,int len) 将数组b中len个字节从offset开始写出到输出源
  • abstract void write(int b) 将指定的字节写出到输出源

使用字节输入流(InputStream)读取文件内容

public class InputStreamDemo2 {

	public static void main(String[] args) throws IOException {
		
		//根据提供的文件路径获取与基于该文件字节输入流
		InputStream is = new FileInputStream("D:\\tempfile\\a.txt");
		//声明字节缓冲区
		byte[] buff = new byte[128];
		//声明变量表示实际的读取字节数
		int len = 0;
   //循环读取,将每次读取的内容装入字节缓冲区
		while((len = is.read(buff)) != -1) {
			String s = new String(buff,0,len);
			System.out.println(s);
		}
		//关闭资源
		is.close();
	}
}

文件拷贝

/**
	 * 	将传入的源文件拷贝到指定的目录中
	 * @param sourceFile 源文件对象
	 * @param targetDir	目标目录对象
	 */
public static void copyFile(File sourceFile,File targetDir) {
    //判断目标目录是否存在,若不存在创建
    if(!targetDir.exists()) {
        targetDir.mkdirs();
    }
    //根据提供的源文件文件名,结合目标目录构建一个目标文件对象
    File target = new File(targetDir,sourceFile.getName());
    InputStream is = null;
    OutputStream os = null;
    try {
        //获取源文件的输入流
        is = new FileInputStream(sourceFile);
        //获取目标文件的输出流
        os = new FileOutputStream(target);
        //声明字节缓冲区
        byte[] b = new byte[1024];
        //声明临时变量标记当前读取的实际字节数
        int len = 0;
        System.out.println("开始拷贝...");
        while((len = is.read(b)) != -1) {
            //使用输出流将数组从0开始写入len个字节到目标文件
            os.write(b, 0, len);
        }
        System.out.println("拷贝完成!");
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        try {
            //关闭资源
            if(Objects.nonNull(os)) {
                os.close();
            }
            if(Objects.nonNull(is)) {
                is.close(); 
            }
        } catch (IOException e) {
            e.printStackTrace();
        }			
    }

}


public static void main(String[] args) {
    File source = new File("D:\\素材\\音乐\\music素材\\梅艳芳 - 亲密爱人.mp3");
    File target = new File("d:\\mp3");
    copyFile(source,target);
}

字符流常见子类

  • FileReader
  • FileWriter
  • InputStreamReader
  • InputStreamWriter
  • BufferedReader
  • BufferedWriter

字符输入流Reader

常见方法

  • abstract void close() 关闭流
  • int read() 读一个字符
  • int read(char[] buf) 将流中的字符读取到字符数组,并返回读取的实际字节数
  • abstract int read(char[] c,int off,int len) 将流中的字符读取到字符中,从数组的off开始存储len个字符

字符输出流Writer

常见方法

  • Writer append(char c) 将指定的字符追加到流末尾
  • Writer append(charSequence cs) 将指定的字符序列(字符串)追加到流末尾
  • abstract void close() 关闭流先调用flush()
  • abstract void flush() 将缓冲区的数据强制通过流输出到输出源
  • void write(char[] c) 写入一个字符数组到输出流
  • void write(char[] c,int off,int len) 将字符数组c从off开始写入len位到输出流
  • void write(String str) 写入一个字符串到输出流

处理流

JavaIO中对流的功能划分又分为以下两种:

  • 节点流
  • 处理流

节点流:

  • 节点流也是低级流,主要用于跟输入输入源直接对接,常见的节点流:
  • FileInputStream
  • FileOutputStream
  • FileReader
  • FileWriter
  • ByteArrayInputStream
  • ByteArrayOutputStream
  • CharArrayReader
  • CharArrayWriter
    处理流:
    处理流也称之包装流(用于处理其他流的流),用于将节点流或者其他流进行包装,以提高数据的传输效率或者转换流的类型,常见的处理流:
  • InputStreamReader
  • OutputStreamWriter
  • BufferedInputStream
  • BufferedOutputStream
  • BufferedReader
  • BufferedWriter
    转换流&缓冲流:
public static void main(String[] args) throws IOException {

    //获取标准输入流(字节流)
    InputStream is = System.in;

    //字节流转换为字符流(转换流可以实现字节流和字符流之间的转换;不能转换流向)
    InputStreamReader isr = new InputStreamReader(is,"utf-8");

    //将字符流包装为字符缓冲流(装饰器模式)
    BufferedReader br = new BufferedReader(isr);

    String s = null;
    while((s = br.readLine()) != null) {
        if("quit".equals(s)) {
            System.out.println("bye !!!");
            System.exit(0);
        }
        System.out.println(s);
    }

}

将字符数据以字节的形式写入

public static void main(String[] args) throws IOException {
    //需要写入文件的内容
    String msg = "长江长江,我是黄河,收到请回答!!!!";
    //目标文件
    File f = new File("d:/tempfile/b.txt");

    //字节输出流
    FileOutputStream fos = new FileOutputStream(f,true);

    //将字节流包装为字符输出流
    OutputStreamWriter osw = new OutputStreamWriter(fos);

    //将字符输出流包装为缓冲字符输出流
    BufferedWriter bw = new BufferedWriter(osw);

    bw.write(msg);
    //使用字符流时需要使用flush将缓冲区的数据强行写入目标输出源
    bw.flush();
    bw.close();
}

以上程序中在使用到转换流(InputStreamReader/OutputStreamWriter)的同时还使用到了缓冲流(BufferedReader/BufferedWriter);其中缓冲流是自带缓冲区的高级流,实现原理为内部自带字符(字节)缓冲区,以提高数据的读写效率。

打印流

打印流是一个特殊的流,java中打印只有输出没有输入;打印流包含字节打印和字符打印:

  • java.io.PrintStream
  • java.io.PrintWriter

RandomAccessFile

RandomAccessFile可以用于对文件进行读和写操作,另外也支持直接以基本数类型的原始方式(元数据)将数据写入文件,RandomAccessFile提供了两个构造器

  • RandomAccessFile(File file,String mode)
  • RandomAccessFile(String file,String mode)
    关于构造器中的参数,第一个为目标文件或者目标文件的所在路径,第二个参数为对文件操作模式,模式参考如下
模式 解释
r 以只读模式打开文件,不可在文件上发生写操作
rw 以可读可写的模式打开文件
rws 在rw基础上运行将数据结果,以及元数据同步到底层存储设备
rwd 在rw基础上运行将数据结果同步到底层存储设备
public class RandomAccessFileDemo {

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

        //根据提供的目标文件路径以及操作模式(r,rw)获取对象
        RandomAccessFile raf = new RandomAccessFile("d:/tempfile/a.txt","rw");
        byte[] b = new byte[1024];
        int len = 0;
        while((len = raf.read(b)) != -1) {
            String s = new String(b,0,len);
            System.out.println(s);
        }

        raf.write("hello".getBytes());

        raf.close();
    }

}

文件转码

public class CharacterConvertor {

    /**
	 * 	实现将一个源文件从一种特定编码转换为另一种特定编码并存储到指定的目录中
	 * @param source	源文件
	 * @param dir	目标目录
	 * @param oldCharset	源文件的编码(GBK)
	 * @param newCharset	新文件的编码(UTF-8)
	 * @throws IOException 
	 */
    public static void convertor(File source,File dir,String oldCharset,String newCharset) throws IOException {

        //获取源文件的字节输入流
        FileInputStream fis = new FileInputStream(source);
        //获取新文件的字节输出流
        FileOutputStream fos = new FileOutputStream(new File(dir,source.getName()));

        //将源文件的字节输入流使用源文件的编码转换为字符输入流
        InputStreamReader isr = new InputStreamReader(fis,oldCharset);
        //将目标文件的字节输出流使用特定的编码转换为字符输出流
        OutputStreamWriter osw = new OutputStreamWriter(fos,newCharset);

        char[] c = new char[512];
        int len = 0;
        while((len = isr.read(c)) != -1) {
            osw.write(c, 0, len);
            osw.flush();
        }
        osw.close();
        isr.close();
    }

    /**
	 * 	将整个工作空间中的所有java文件全部转码为UTF-8,要求保持原来的目录结构
	 * @param args
	 * @throws IOException
	 */
    public static void main(String[] args) throws IOException {
        //准备源文件
        File f1 = new File("D:\\项目资料\\教学项目\\java\\j2003\\lesson33\\src\\com\\softeem\\lesson33\\TestProperties.java");
        //准备目标目录
        File f2 = new File("C:\\Users\\Administrator\\Desktop");
        //转换
        convertor(f1, f2, "gbk", "utf-8");
    }
}

序列化

Java的对象序列化实际就是将一个Java对象中的属性信息以元数据的方式写入文件(序列化),另外也可以以元数据的方式从输入源中读取对象数据(反序列化);Java中对象的序列化分为两个步骤:

  1. 让需要实现对象序列化的类实现Serializable接口
  2. 通过对象输出流将对象写入到指定输出源(ObjectOutputStream)

Serializable

​ Serializable是一个标记型接口(内部没有任何方法需要实现);任何的类型如果需要实现对象序列化都需要从这个接口实现,常见的比如:String、Date、File、ArrayList、HashSet、HashMap都实现过该接口。

注意事项:

任何实现过Seriablizable接口的类都需要生成一个唯一的***,***的作用是用于再反序列化的时候进行对象校验的。

另外如果有对源代码修改,同时也需要将***更新。
对象序列化是对属性序列化,不是方法

ObjectOutputStream

java.io.ObjectOutputStream(对象输出流),ObjectOutputStream是一个处理流(包装流);可以用于对另一个输出源包装,以实现序列化的过程:

File f = new File("d:/tempfile/savepoint.txt");
FileOutputStream fos = new FileOutputStream(f);
//将文件输出包装为对象输出流
ObjectOutputStream oos = new ObjectOutputStream(fos);
//写入对象到输出流中(对象序列化)
oos.writeObject(h);
oos.close();

ObjectInputStream

Java中实现对象的反序列化需要使用到:java.io.ObjectInputStream,该类可以对其他输入流包装为对象输入流,从而可以从输入流中读取到Java对象。

//获取文件输入流
FileInputStream fis = new FileInputStream(f);
//将文件输入流包装为对象输入流
ObjectInputStream ois = new ObjectInputStream(fis);
//读取一个对象(反序列化)
Object obj = ois.readObject();
System.out.println(obj);
ois.close();

transient

transient,瞬时,瞬间;如果在对象序列化时,有某些属性不需要序列化的时候,可以使用关键修饰

/**
* 	声明瞬时全局变量,对象序列化时不会将该属性序列化到输出流
*/
private transient int flag;

Externalizable

java的对象序列化技术中还提供了另一个中序列化方式,即Externalizable,对需要实现对象序列化的类实现该接口,并且重写接口中的writeExternal(),readExternal()两个方法,让开发者手动写入或读取需要序列化的属性,具体使用如下:

public class User implements Externalizable{

    private int id;
    private String name;

    //constract
    // settter/getter

    //toString

    /**
	 * 	对需要序列化的属性手动写入
	 */
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        //手动写入需要序列化的属性
        out.writeInt(id);
        out.writeUTF(name);
    }

    /**
	 * 	对需要反序列化的属性手动获取
	 */
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        //读取需要反序列化的属性,主要顺序必须跟写入一致
        id = in.readInt();
        name = in.readUTF();
    }

}

线程

并行与并发

并发:多个任务,在同一个时间段之内执行

并行:多个任务,在同一个时间点内执行

进程与线程

目前的操作系统是一个多进程的系统,在同一个时间之内可以运行多个任务,每一个任务都称之为进程

进程:
操作系统中正在执行的一个任务(程序),比如:QQ,微信,Eclipse,屏幕广播都称之为进程。
一个进程下包含多个线程
线程:
线程是进程中的一条执行路径,比如360安全卫士,在杀毒的同时,清理垃圾,文件整理等操作
一个线程必然包含在一个进程中

线程的调度(CPU)

  • 平均分配执行时间(正常)
  • 抢占式运行(设置优先级)

线程的状态

一条线程从创建到销毁具备一条完整的生命周期,操作系统将线程的状态分为5种:

  1. 新建状态(线程刚创建)
  2. 就绪状态(线程启动)
  3. 运行状态(线程得到CPU的时间片)
  4. 阻塞状态(线程还未执行完,但是CPU已经将时间片分配其他线程)
  5. 死亡状态(线程执行完毕或者正常中断)

注意事项:

java的多线程机制中将线程分为6种状态:

将阻塞状态划分为两种:

  1. 限时阻塞(在一个时间段之内阻塞,过了这个时间,重新进度调度队列)
  2. 无限阻塞(必须的到通知的情况下才能继续进度调度队列)

线程创建

java中的线程创建分为4种方式:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口,通过FuturaTask调度(JDK5新增并发编程)
  4. 使用线程池框架Executor创建(JDK5新增并发编程)

守护线程

守护线程也称之为后台线程,即为其他线程提供服务的线程;守护线程会随着主线程的结束而结束;如果需要设置一条线程为守护线程,则只需要调用setDaemon(true)即可

public class ThreadDemo2 extends Thread{

	@Override
	public void run() {
		for (int i = 0; i < 10000; i++) {
			System.out.println("子线程===>"+i);
			try {
				sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 *  	文件拷贝:
	 *  	1.主线程拷贝文件
	 *  	2.子线程作为守护线程存在计算文本拷贝进度
	 * @param args
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws InterruptedException {
		
		ThreadDemo2 t = new ThreadDemo2();
		//设置当前线程为守护线程
		t.setDaemon(true);
		t.start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println("主线程==>"+i);
			Thread.sleep(10);
		}
	}
}

Join方法(插队)

join方法用于将目标线程加入到正在执行的线程中,一旦join则目标线程会在正在执行的线程之前先执行完,类似插队概念

线程中断

在线程运行时需要将正在运行的线程中断,Threa类中提供了一些用于中断的方法:stop(),interrupt()但是这两个方法都具有一些固有的缺陷,stop()方法中断线程将会导致线程持有的对象锁被释放,从而使得对象成为共享对象导致并发安全问题;使用interrupt()方法时如果其他线程中断了当前正在运行的线程,将会抛出InterruptException;

推荐的线程中断方法是采用标记中断:

  1. 在线程类中声明一个标记(整数,布尔等)
  2. 当标记为运行状态时线程正常执行
  3. 一旦将当前线程的标记状态修改为终止则不再执行线程
public class InterruptDemo2 extends Thread {

    // 标记当前线程是否应该中断
    private boolean isOver = false;

    public void setOver(boolean isOver) {
        this.isOver = isOver;
    }

    @Override
    public void run() {
        int i = 0;
        //当标记中断状态为false时线程正常执行 
        while (!isOver) {
            // 执行业务操作
            i++;
            System.out.println("子线程" + i);
            try {
                sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //一旦标记状态为false则结束
        System.out.println("子线程结束");
    }

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

        InterruptDemo2 t = new InterruptDemo2();
        t.start();

        for (int i = 0; i < 100; i++) {
            sleep(200);
            System.out.println("主线程-->" + i);
            if (i == 20) {
                // 标记t线程应该中断
                t.setOver(true);
            }
        }

    }
}