//获取文件(标准文件)对象
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);
/**
* 将传入的源文件拷贝到指定的目录中
* @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);
}
案例:
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);其中缓冲流是自带缓冲区的高级流,实现原理为内部自带字符(字节)缓冲区,以提高数据的读写效率。
打印流是一个特殊的流,java中打印只有输出没有输入;打印流包含字节打印和字符打印:
两个构造器
关于构造器中的参数,第一个为目标文件或者目标文件的所在路径,第二个参数为对文件操作模式,模式参考如下
模式 解释 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中对象的序列化分为两个步骤:
Serializable是一个标记型接口(内部没有任何方法需要实现);任何的类型如果需要实现对象序列化都需要从这个接口实现,常见的比如:String、Date、File、ArrayList、HashSet、HashMap都实现过该接口。
public class Hero implements Serializable{
/**
* 序列号
*/
private static final long serialVersionUID = 4628406667968229383L;
注意事项:
任何实现过Seriablizable接口的类都需要生成一个唯一的序列号,序列号的作用是用于再反序列化的时候进行对象校验的。
另外如果有对源代码修改,同时也需要将序列号更新。
对象序列化是对属性序列化,不是方法
FileOutputStream fos = new FileOutputStream(f);
//将文件输出包装为对象输出流
ObjectOutputStream oos = new
ObjectOutputStream(fos);
//写入对象到输出流中(对象序列化)
oos.writeObject(h);
oos.close();
FileInputStream fis = new FileInputStream(f);
//将文件输入流包装为对象输入流
ObjectInputStream ois = new
ObjectInputStream(fis);
//读取一个对象(反序列化)
Object obj = ois.readObject();
System.out.println(obj);
ois.close();
transient,瞬时,瞬间;如果在对象序列化时,有某些属性不需要序列化的时候,可以使用关键修饰
/**
* 声明瞬时全局变量,对象序列化时不会将该属性序列化到输出流
*/
private transient int flag;
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
在实际开发中对于Excel文件的读写是很常见的功能需求,比如:银行流水,OA系统考勤统计,等涉及到excel表格的操作;Java中也有很多开源项目提供了对于Excel的操作,常见的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;
}
// 创建一个工作簿
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.创建普通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);
}
}
}
/**
* 线程创建的方式二:
* 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);
}
}
}
实现在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+"=====");
}
}
}
三种不同创建方式的区别:
- 使用Thread的方式为继承,一旦继承Thread就无法再继承其他类,扩展性存在一定影响 ;可以直接创建对象并调用start启动
- 实现Runnable接口,类还可以再继承其他类或者实现其他接口,扩展性方面不受影响,run方法不能抛出异常;启动线程时还是需要由Thread类启动
- 实现Callable接口,实现的方法call有返回值,还提供了泛型支持,并且call方法允许抛出异常,一般用于并行计算,Callable接口需要有FutureTask包装并且被Thread启动。
MyThread1 t1 = new MyThread1();
t1.start(); //启动线程
MyThread2 t2 = new MyThread2();
Thread t = new Thread(t2);
t.start();
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则目标线程会在正在执行的线程之前先执行完,类似插队概念
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;
推荐的线程中断方法是采用标记中断:
示例代码:
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);
}
}
}