知识梳理:字节流(InputStream&OutputStream) & 字符类 & RandomAccessFile & 对象序列化 & 线程创建与启动 & 守护线程 线程中断

目录:字节流(InputStream&OutputStream)&字符类&RandomAccessFile&对象序列化& 线程创建与启动&守护线程 线程中断

字节流(InputStream&OutputStream)

InputStream常见方法

  • int available() 获取流中的可读字节数
  • void close() 关闭此输入流并释放跟流相关的系统资源
  • abstract int read() 从流中读取一个字节
  • int read(byte[] b) 从流中读取字节并存入字节缓冲区,返回实际读取字节数
  • skip(long n) 发生下一次读取之前跳过n个字节
//获取文件(标准文件)对象
File f = new File("d:/tempfile/a.txt");
//根据提供的文件获取一个文件输入流
InputStream is = new FileInputStream(f);
//声明一个字节数组
int byts = is.available();
byte[] b = new byte[byts];
//将流中的所有字节读入字节数组中
int i = is.read(b);
//将字节数组转换为字符串
String s = new String(b);

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) 将指定的字节写出到输出源
/**
	 * 	将传入的源文件拷贝到指定的目录中
	 * @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

案例:

public class ReaderDemo {

	/**
	 * 	使用字符流实现文本文档拷贝
	 * 	FileReader
	 * 	FileWriter
	 * @param args
	 */
	public static void main(String[] args)  {
		Reader reader = null;
		Writer writer = null;
		try{
            //声明源文件的字符输入流
			reader = new FileReader("D:\\tempfile\\a.txt");
            //声明目标文件的输出流
			writer = new FileWriter("c:/a.txt");
			//声明字符数组作为缓冲区
			char[] c = new char[50];
			//用于表示真实读取字符数的临时变量
			int len = 0;
            //循环读取流中的字符到字符数组中,并判断是否有读到文件末尾
			while((len = reader.read(c)) != -1) {
                //将字符数组转换为字符串输出
				//String s = new String(c,0,len);
				//System.out.print(s);
                
                //通过字符输出流输出
				writer.write(c, 0, len);
			}
		}catch (IOException e) {
			e.printStackTrace();
		}finally {
			try {
                //关闭资源
				if(reader != null) {
					reader.close();
				}
				if(writer != null) {
					writer.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

处理流

处理流也称之包装流,用于将节点流或者其他流进行包装,以提高数据的传输效率或者转换流的类型,常见的处理流:

  • InputStreamReader(构造器中可加编码类型)

    new InputStreamReader(fis,oldCharset);

  • OutputStreamWriter(构造器中可加编码类型)

    new OutputStreamWriter(fos,newCharset);

  • 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);其中缓冲流是自带缓冲区的高级流,实现原理为内部自带字符(字节)缓冲区,以提高数据的读写效率。

Properties类&getResourceAsStream

打印流

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

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

RandomAccessFile

两个构造器

  • RandomAccessFile(File file,String mode)
  • RandomAccessFile(String file,String mode)

关于构造器中的参数,第一个为目标文件或者目标文件的所在路径,第二个参数为对文件操作模式,模式参考如下

模式 解释
r 以只读模式打开文件,不可在文件上发生写操作
rw 以可读可写的模式打开文件
rws 在rw基础上运行将数据结果,以及元数据同步到底层存储设备
rwd 在rw基础上运行将数据结果同步到底层存储设备
public static void main(String[] args) throws IOException {
    
    RandomAccessFile raf = new 	RandomAccessFile("d:/tempfile/c.txt","rw");
    long pos = raf.getFilePointer();
    System.out.println("当前的文件指针:"+pos);
    
    //向文件中写入整数
    raf.writeLong(1000L); 

    //获取当前的文件指针所在位置
    pos = raf.getFilePointer();
    System.out.println("当前的文件指针:"+pos);

    //设置指针的位置
    raf.seek(0);
    //读取一个long类型数据
    long lon = raf.readLong();
    System.out.println(lon);
    raf.close();
}

对象序列化

Java中对象的序列化分为两个步骤:

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

Serializable

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

public class Hero implements Serializable{
	/**
	 * 序列号
	 */
	private static final long serialVersionUID = 4628406667968229383L;

注意事项:

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

另外如果有对源代码修改,同时也需要将序列号更新。

对象序列化是对属性序列化,不是方法

ObjectOutputStream

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

ObjectInputStream

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{
/**
	 * 	对需要序列化的属性手动写入
	 */
    @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();
    }

注意事项:

需要对实现序列化的类定义无参构造器,避免:InvalidClassException

扩展:Jxl读写Excel

在实际开发中对于Excel文件的读写是很常见的功能需求,比如:银行流水,OA系统考勤统计,等涉及到excel表格的操作;Java中也有很多开源项目提供了对于Excel的操作,常见的Excel操作工具:

  • apache-POI:常用的重量级的Office处理工具
  • jxl :是由一位韩国人开发的开源项目,轻量级的Excel处理工具,专注于处理Excel文件(xls)
  • EasyExcel:国产,阿里巴巴开源的用于对Excel处理工具,具备poi强大功能,同时对其优化

JXL使用流程

  1. 导入插件到项目中(jxl.jar)
  2. 获取/创建Workbook
  3. 基于Workbook获取/创建Sheet
  4. 操作行或列
    1. 对于读取:先获取行,再获取列
    2. 对于写入:创建单元格设置所在列和行以及文本内容,再添加到sheet中
  5. 关闭Workbook

读取Excel

实现代码

    // 声明一个集合用于存储读取到的每一行数据
    List<Record> list = new ArrayList<>();

    // 根据提供的文件获取一个工作簿(Excel表格)
 Workbook book = Workbook.getWorkbook(excelFile);
    // 获取工作簿中的所有表单
    Sheet[] sheets = book.getSheets();
    // 获取第一个表单
    Sheet s = sheets[0];
	//等同于 book.getSheets()[0] 
   	//Sheet s = book.getSheet(0); 

    // 获取数据的总行数
    int rows = s.getRows();
    // 遍历每一行
    for (int i = 1; i < rows; i++) {
        // 获取每一行
        Cell[] cells = s.getRow(i);
        // 声明一个引用变量用于表示读取的一个record对象
        Record record = new Record();
        // 设置序号
        record.setNum(i);
        // 设置学生名称
        record.setStuName(cells[0].getContents());
        // 设置观看时长
        record.setViewTime(cells[2].getContents());
        // 将读取到的一个对象存入集合
        list.add(record);
    }
    //关闭资源
    book.close();
    return list;
}

写入Excel

	// 创建一个工作簿
    WritableWorkbook book = Workbook.createWorkbook(target);
    // 创建表单(参数1:表单名称;参数2:索引)
    WritableSheet sheet = book.createSheet("sheet1", 0);

    // 创建单元格(设置单元格所在的列,行,文本内容)
    Label label1 = new Label(0, 0, "序号");
    Label label2 = new Label(1, 0, "姓名");
    Label label3 = new Label(2, 0, "观看时长");
    // 将单元格加入到表单中
    sheet.addCell(label1);
    sheet.addCell(label2);
    sheet.addCell(label3);

    for (int i = 0; i < list.size(); i++) {
        //获取一条记录
        Record record = list.get(i);

        //创建单元格并设置位置和内容
        label1 = new Label(0, i+1, String.valueOf(record.getNum()));
        label2 = new Label(1, i+1, record.getStuName());
        label3 = new Label(2, i+1, record.getViewTime());

        // 将单元格加入到表单中
        sheet.addCell(label1);
        sheet.addCell(label2);
        sheet.addCell(label3);
    }
        //将数据写入到目标
        book.write();
        book.close();

线程创建与启动

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

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

继承Thread类

/**
 * 	线程创建的方式一:
 *  	1.创建普通java类继承Thread类
 *  	2.重写run方法	
 *	启动线程
 *  	创建当前类的对象调用start()启动
 * @author mrchai
 *
 */
public class MyThread1 extends Thread{
	@Override
	public void run() {
		for(int i = 1;i < 20;i++) {
			System.out.println("子线程--->"+i);
		}
	}
	public static void main(String[] args) {	
		//创建线程对象
		MyThread1 t1 = new MyThread1();
//		t1.run();//不叫线程启动,称之为方法调用
		t1.start(); //启动线程

		for (int i = 0; i < 20; i++) {
			System.out.println("主线程:"+i);
		} 
	}
}

实现Runnable

/**
 * 	线程创建的方式二:
 * 	1.创建类实现Runnable接口
 * 	2.重写run方法
 * @author mrchai
 *
 */
public class MyThread2 implements Runnable{
	@Override
	public void run() {	
		for(int i = 0;i<20;i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("子线程:"+i);
		}	
	}
	public static void main(String[] args) {
		MyThread2 t2 = new MyThread2();
		Thread t = new Thread(t2);
		t.start();
		for (int i = 0; i < 20; i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("main线程:"+i);
		}
	}
}

实现Callable

实现在JDK5新增的并发编程包(java.util.concurrent包)中的Callable接口

/**
 * 	线程的创建方式三:
 * 	1.创建类,实现Callable接口
 * 	2.实现call()方法
 *     启动方式:
 *     1.创建Callable对象
 *     2.基于Callable创建FutureTask
 *     3.将FutureTask作为参数传递给Thread并启动
 * @author mrchai
 */
public class MyThread3 implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100;i++) {
            sum += i;
            System.out.println("本次计算结果:"+sum);
        }
        return sum;
    }
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        MyThread3 t3 = new MyThread3();
        FutureTask<Integer> task = new FutureTask<Integer>(t3);
        Thread t = new Thread(task);
        t.start();
        //获取返回结果
        int count = task.get();
        System.out.println("最终执行结果"+count);
        for (int i = 0; i < 10; i++) {
            System.out.println(i+"=====");
        }
    }
}

三种不同创建方式的区别:

  1. 使用Thread的方式为继承,一旦继承Thread就无法再继承其他类,扩展性存在一定影响 ;可以直接创建对象并调用start启动
  2. 实现Runnable接口,类还可以再继承其他类或者实现其他接口,扩展性方面不受影响,run方法不能抛出异常;启动线程时还是需要由Thread类启动
  3. 实现Callable接口,实现的方法call有返回值,还提供了泛型支持,并且call方法允许抛出异常,一般用于并行计算,Callable接口需要有FutureTask包装并且被Thread启动。

线程启动

继承Thread的启动方式

MyThread1 t1 = new MyThread1();
t1.start(); //启动线程

实现Runnable接口

MyThread2 t2 = new MyThread2();
Thread t = new Thread(t2);
t.start();

实现Callable

MyThread3 t3 = new MyThread3();
FutureTask<Integer> task = new FutureTask<Integer>(t3);
Thread t = new Thread(task);
t.start();

守护线程

守护线程也称之为后台线程,即为其他线程提供服务的线程;守护线程会随着主线程的结束而结束;如果需要设置一条线程为守护线程,则只需要调用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则目标线程会在正在执行的线程之前先执行完,类似插队概念

new Thread() {
    @Override
    public void run() {
        for (int i = 20; i < 40; i++) {
            System.out.println("子线程333--->"+i);
            if(i == 30) {
                try {
                //将t2线程加入到当前线程
                //等待t2执行完之后,才继续执行当前线程
                //插队
                    t2.join();
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}.start();

线程中断

在线程运行时需要将正在运行的线程中断,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;
    }
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);
        }
    }
}

你可能感兴趣的:(知识梳理:字节流(InputStream&OutputStream) & 字符类 & RandomAccessFile & 对象序列化 & 线程创建与启动 & 守护线程 线程中断)