day 13 - IO流(上)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X21B6NoN-1595159883943)(E:\软帝java\笔记\20200714\笔记\assets\1594715148692.png)]
流概述
IO(Input/Ouput),Java中将不同的输入输出源抽象表示为流;流是一种从输入源到接收地的抽像概念,根据流的不同状态划分,将流做以下区分:
-
从流向考虑(站在程序角度考虑)
- 输入流
- 输出流
输入流:从数据源到程序的流称之为输入流(读)
输出流:从程序到输出源的流称之为输出流(写)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-imPzuxha-1595159883945)(E:\软帝java\笔记\20200714\笔记\assets\1594716214335.png)]
-
从流的类型划分
- 字节流(byte)
- 字符流(char)
字节流:数据以字节的形式进行输入输出,字节流在实际开发中比较常见,一般字节流用于操作二进制数据,比如:图片,音频,视频等资源。
字符流:数据以字符的形式进行输入输出,1字符=2字节;字节流一般用于对文本信息操作,比如:纯文本消息,记事本等。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-la7Ncy4b-1595159883949)(E:\软帝java\笔记\20200714\笔记\assets\1594715118331.png)]
-
从功能划分
- 节点流
- 处理流
节点流主要用于直接跟输入输出源对接,处理流用于对节点流或其他流进行包装处理。关于节点流和处理流划分如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3OFebqq-1595159883958)(E:\软帝java\笔记\20200714\笔记\assets\1594715176330.png)]
字节流
Java中的字节流主要包含两个抽象的流类型:
- java.io.InputStream 是所有字节输入流的超类(最终父类)
- java.io.OutputStream 是所有字节输出流的超类
字节流一般用于操作二进制文件,比如图片,音频,视频等,在对数据读写时以字节为单位;
常见子类
- FileInputStream
- FileOutputStream
- ByteArrayInputStream
- ByteArrayOutputStream
- BufferedInputStream
- BufferedOutputStream
InputStream&OutputStream
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)读取文件内容
使用字节输入流(InputStream)读取文件内容
public class InputStreamDemo {
public static void main(String[] args) throws IOException {
//获取文件(标准文件)对象
File f = new File("d:/tempfile/a.txt");
//根据提供的文件获取一个文件输入流
InputStream is = new FileInputStream(f);
int byts = is.available();
System.out.println("可读字节数:"+byts);
//声明一个字节数组
byte[] b = new byte[byts];
//将流中的所有字节读入字节数组中
int i = is.read(b);
//将字节数组转换为字符串
String s = new String(b);
System.out.println(s);
}
}
对于以上程序的实现思路是:
- 将源文件通过FileInputStream读取为字节流,获取流中的可读字节数
- 根据可读字节数创建一个对应大小的字节数组
byte[] b = new byte[is.available()]
- 一次性将流中的数据读入到字节数组中
is.read(b)
- 将字节数组通过String提供的构造器转换为String字符串
String s = new String(b)
对于以上思路的第二步,由于实际文件大小可能会比较大(超出可用内存大小),因此直接创建一个长度为总字节大小的数组是不明智的做法(会占用大量内存空间,降低其他程序运行效率),所以常规的做法是,定义长度固定的字节数组(
byte[] b = new byte[1024]
)作为缓冲区,采用一边读取,一边解析的方式实现文件读取功能: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(); } }
文件拷贝
下面的例子演示的是如何通过FileInputStream和FileOutputStream实现文件拷贝的过程:
/**
* 将传入的源文件拷贝到指定的目录中
* @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);
}
字符流
Java中的字符流主要包含以下两个抽象类型
- java.io.Reader
- java.io.Writer
字符流一般用于操作文本文件等与纯文本信息相关的资源,在对数据读写操作时以字符为单位
常见子类
- FileReader
- FileWriter
- InputStreamReader
- InputStreamWriter
- BufferedReader
- BufferedWriter
Reader&Writer使用
从使用层面来看,字符输入输出流的使用与字节输入输出流使用方式基本一致,不同之处在与前者是一个字符一个字符读取,而字节流则是一个字节一个字节读取。
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) 写入一个字符串到输出流
从使用层面来看,字符输入输出流的使用与字节输入输出流使用方式基本一致,不同之处在与前者是一个字符一个字符读取,而字节流则是一个字节一个字节读取。案例如下:
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();
}
}
}
}
处理流
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);其中缓冲流是自带缓冲区的高级流,实现原理为内部自带字符(字节)缓冲区,以提高数据的读写效率。
day14 - IO流(中)
目录拷贝
关键技术参数:
- 递归
- File类使用
- 输入输出流
public class FileTools {
/**
* 将目标File对象关联的本地文件(或者目录)拷贝到目标目录中
* @param source 需要被拷贝File关联的文件(或目录)
* @param targetDir 目标目录
*/
public static void copy(File source,File targetDir) {
//判断源文件是否是标准文件
if(source.isFile()) {
//如果源文件是标准文件,则直接进行文件拷贝
copyFile(source, targetDir);
}else {
//在目标目录下创建一个跟源目录同名的子目录(未创建)
targetDir = new File(targetDir,source.getName());
//如果目标目录不存在则创建
if(!targetDir.exists()) {
targetDir.mkdirs();
}
//获取源目录下所有的子文件
File[] subFiles = source.listFiles();
if(subFiles != null) {
//遍历所有的子文件(File)
for(int i = 0;i<subFiles.length;i++) {
copy(subFiles[i],targetDir);
}
}
}
}
/**
* 将传入的源文件(标准文件)拷贝到指定的目录中
* @param sourceFile 源文件对象
* @param targetDir 目标目录对象
*/
private 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:/tempfile");
File targetDir = new File("C:\\Users\\Administrator\\Desktop");
copy(source,targetDir);
}
}
缓冲流对比节点流效率
/**
* 对比节点流和缓冲流的读写效率
*
* @author mrchai
*
*/
public class TestTransferSpeed {
/**
* 使用节点流拷贝 FileInputStream/FileOutputStream
*
* @param source
* @param dir
* @throws IOException
*/
public static void copy1(File source, File dir) throws IOException {
// 获取源文件的输入流
FileInputStream fis = new FileInputStream(source);
// 获取目标文件的输出流
FileOutputStream fos = new FileOutputStream(new File(dir, source.getName()));
int b = 0;
while ((b = fis.read()) != -1) {
fos.write(b);
}
fos.close();
fis.close();
}
/**
* 使用缓冲流拷贝 BufferedInputStream/BufferedOutputStream
*
* @param source
* @param dir
* @throws IOException
*/
public static void copy2(File source, File dir) throws IOException {
// 获取源文件的输入流
FileInputStream fis = new FileInputStream(source);
// 获取目标文件的输出流
FileOutputStream fos = new FileOutputStream(new File(dir, source.getName()));
//包装节点流
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
int b = 0;
while((b = bis.read()) != -1) {
bos.write(b);
}
bos.close();
bis.close();
}
public static void main(String[] args) throws IOException {
// 准备源文件
File source = new File("D:\\素材\\音乐\\music素材\\Jason Chen - We Are Young.mp3");
// 准备目标目录
File dir = new File("D:\\tempfile");
long start = System.currentTimeMillis();
System.out.println("开始拷贝:" + start);
copy1(source, dir);
//copy2(source,dir);
long end = System.currentTimeMillis();
System.out.println("结束拷贝:" + end);
System.out.println("耗时:" + (end - start));
}
}
//结果
//copy1 方法执行耗时 18765;
//copy2 方法执行耗时 135
经过上述对比得出结论:
使用节点流进行文件读写耗时较长,
使用缓冲流进行文件读写,效率提升明显,因为缓冲流自带缓冲区,因此进行读写操作时会将读写的数据写入缓存区,然后再将缓冲区的数写入目标文件;
另外节点流也可以通过声明字节缓冲区的方式提高读写效率,核心代码如下:
//声明字节缓冲区 byte[] b = new byte[1024]; int len = 0; //循环读取字节并存入缓冲区 while ((len = fis.read(b)) != -1) { fos.write(b,0,len); }
以空间换时间
Properties类&getResourceAsStream
打印流
打印流是一个特殊的流,java中打印只有输出没有输入;打印流包含字节打印和字符打印:
- java.io.PrintStream
- java.io.PrintWriter
RandomAccessFile
在我们之前学习的所有的流都有一定规律,以Stream结尾的称之字节流,以Reader/Writer结尾的是字符流;以及之前所有的流都有流向的区别:输入流,输出流;但是java IO包中还存在一个特殊的流:java.io.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();
}
}
通过以上的使用不难发现,RandomAccessFile在进行文件操作的方式上与之前的输入输出流没有太大区别,但是由于RandomAccessFile类从DataInput,DataOutput实现而来,因此,内部提供了对于原始数据类型的读写操作:
public class RandomAccessFileDemo2 {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
//ctrl+1
RandomAccessFile raf = new RandomAccessFile("d:/tempfile/c.txt","rw");
long pos = raf.getFilePointer();
System.out.println("当前的文件指针:"+pos);
//向文件中写入整数
raf.writeLong(1000L);
raf.writeInt(1000);
//获取当前的文件指针所在位置
pos = raf.getFilePointer();
System.out.println("当前的文件指针:"+pos);
//设置指针的位置
raf.seek(0);
//读取一个long类型数据
long lon = raf.readLong();
System.out.println(lon);
raf.close();
}
}
练习讲解
-
类FileUtil提供了常见的文件操作函数,如读取文件内容到byte[]、将byte[]存到指定文件名等:
public class FileUtil{ /** * 加载目标文件为字节数组 * @param filePath * @return * @throws IOException */ public static byte[] loadFileData(String filePath) throws IOException{ //根据提供的文件路径构建File对象 File file = new File(filePath); if(file.isDirectory()) { throw new IOException("请提供标准文件,无法读取目录"); } //根据提供的文件获取文件输入流 FileInputStream fis = new FileInputStream(file); //创建与流中可读字节长度一致的字节数组 byte[] b = new byte[fis.available()]; //将流中的数据读入到字节数组 fis.read(b); fis.close(); return b; } /** * 将字节数组写入文件 * @param filePath * @param data * @throws IOException */ public static void saveDataToFile(String filePath, byte[] data) throws IOException{ //根据提供的文件路径构建File对象 File file = new File(filePath); if(file.isDirectory()) { throw new IOException("请提供标准文件,无法读取目录"); } FileOutputStream fos = new FileOutputStream(file, true); fos.write(data); fos.close(); } public static void main(String[] args) throws IOException { byte[] data = loadFileData("d:/tempfile/a.txt"); saveDataToFile("d:/tempfile/b.txt", data); } }
-
有如下数据存储在一个文件中(server.txt),其中每一行是一个server的配置信息,每行5列列间以 tab字符分隔。
#
为注释符号,即首字符是#
的行为注释。public class ServiceInfo { /** 服务名 */ private String serviceName; /** 地址 */ private String address; /** 端口 */ private int port; /** 服务类型 */ private String serviceType; /** 系统名称 */ private String systemName; public ServiceInfo() { } public ServiceInfo(String serviceName, String address, int port, String serviceType, String systemName) { super(); this.serviceName = serviceName; this.address = address; this.port = port; this.serviceType = serviceType; this.systemName = systemName; } //setter/getter @Override public String toString() { return serviceName + "\t" + address + "\t" + port + "\t" + serviceType + "\t" + systemName; } }
# day15 - 对象序列化&Excel文件读写
## 文件转码
如何实现将一个GBK编码的文件转换为UTF-8编码?
```java
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的对象序列化实际就是将一个Java对象中的属性信息以元数据的方式写入文件(序列化),另外也可以以元数据的方式从输入源中读取对象数据(反序列化);Java中对象的序列化分为两个步骤:
- 让需要实现对象序列化的类实现Serializable接口
- 通过对象输出流将对象写入到指定输出源(ObjectOutputStream)
Serializable
Serializable是一个标记型接口(内部没有任何方法需要实现);任何的类型如果需要实现对象序列化都需要从这个接口实现,常见的比如:String、Date、File、ArrayList、HashSet、HashMap都实现过该接口。
public class Hero implements Serializable{
/**
* 序列号
*/
private static final long serialVersionUID = 4628406667968229383L;
/**昵称*/
private String nickName;
/**等级*/
private int level;
/**血量*/
private int blood;
/**攻击力*/
private int attackPower;
/**防御值*/
private int defence;
/**经验值*/
private int exp;
//setter/getter...
}
注意事项:
任何实现过Seriablizable接口的类都需要生成一个唯一的序列号,序列号的作用是用于再反序列化的时候进行对象校验的。
private static final long serialVersionUID = 4628406667968229383L;
另外如果有对源代码修改,同时也需要将序列号更新。
对象序列化是对属性序列化,不是方法
ObjectOutputStream
上一步完成对类的标记(标记该类对应的对象可以实现序列化),下一步就需要将对象存储到输出源中;Java中将对象存储到文件或者其他输出源需要使用到一个类:java.io.ObjectOutputStream(对象输出流),ObjectOutputStream是一个处理流(包装流);可以用于对另一个输出源包装,以实现序列化的过程:
Hero h = new Hero();
h.setNickName("黑暗之女");
h.setBlood(66);
h.setAttackPower(89);
h.setLevel(5);
h.setDefence(55);
h.setExp(10500);
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();
}
}
测试类:
public class TestUser {
/**
* 将一个java对象序列化到文件中
* @param file
* @param user
* @throws FileNotFoundException
* @throws IOException
*/
public static void write(File file,User user) throws FileNotFoundException, IOException {
//获取对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
//序列化
oos.writeObject(user);
//关闭资源
oos.close();
}
/**
* 从文件中读取一个java对象
* @param file
* @return
* @throws FileNotFoundException
* @throws IOException
* @throws ClassNotFoundException
*/
public static Object read(File file) throws FileNotFoundException, IOException, ClassNotFoundException {
//获取对象输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
//反序列化
Object obj = ois.readObject();
//关闭资源
ois.close();
return obj;
}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
User user = new User(1,"softeem");
File f = new File("d:/tempfile/savepoint.txt");
//写
// write(f, user);
//读
Object obj = read(f);
System.out.println(obj);
}
}
注意事项:
需要对实现序列化的类定义无参构造器,避免:InvalidClassException
对象序列化的意义
再实际开发中,特别是javaweb/javaEE项目中,大量存在缓存(cache)的概念,而实现将对象向缓存中存储,以及从缓存中读取对象都需要使用到对象序列化技术。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FdGs1YG3-1595159961526)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200716\笔记\assets\1594891947650.png)]
扩展:Jxl读写Excel
再实际开发中对于Excel文件的读写是很常见的功能需求,比如:银行流水,OA系统考勤统计,等涉及到excel表格的操作;Java中也有很多开源项目提供了对于Excel的操作,常见的Excel操作工具:
- apache-POI:常用的重量级的Office处理工具
- jxl :是由一位韩国人开发的开源项目,轻量级的Excel处理工具,专注于处理Excel文件(xls)
- EasyExcel:国产,阿里巴巴开源的用于对Excel处理工具,具备poi强大功能,同时对其优化
JXL使用流程
- 导入插件到项目中(jxl.jar)
- 获取/创建Workbook
- 基于Workbook获取/创建Sheet
- 操作行或列
- 对于读取:先获取行,再获取列
- 对于写入:创建单元格设置所在列和行以及文本内容,再添加到sheet中
- 关闭Workbook
读取Excel
-
文件结构参考
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6PdaCOIM-1595159961531)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200716\笔记\assets\1594891415802.png)]
-
实现代码
/** * 读取Excel文件(xls,非xlsx) * * @param excelFile * @throws IOException * @throws BiffException */ public static List<Record> readExcel(File excelFile) throws BiffException, IOException { // 声明一个集合用于存储读取到的每一行数据 List<Record> list = new ArrayList<>(); // 根据提供的文件获取一个工作簿(Excel表格) Workbook book = Workbook.getWorkbook(excelFile); // 获取工作簿中的所有表单 Sheet[] sheets = book.getSheets(); // 获取第一个表单 Sheet s = sheets[0]; //Sheet s = book.getSheet(0); //等同于 book.getSheets()[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; }
实体类结构参考:
/** * 观看记录类 * @author mrchai */ public class Record { /** 序号 */ private int num; /** 学生姓名 */ private String stuName; /** 观看时长 */ private String viewTime; public Record() { // TODO Auto-generated constructor stub } //setter/getter }
写入Excel
/**
* 向指定的文件中写入集合数据
*
* @param target 目标文件(生成的文件)
* @param list 数据集
* @throws IOException
* @throws WriteException
* @throws RowsExceededException
*/
public static void writeExcel(File target, List<Record> list)
throws IOException, RowsExceededException, WriteException {
// 创建一个工作簿
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();
}
day15 - 多线程(一)
多线程概述
并行与并发
并发:多个任务,在同一个时间段之内执行
并行:多个任务,在同一个时间点内执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L0v6Si6w-1595159982919)(E:\软帝java\笔记\笔记07.17\assets\1594950390252.png)]
进程与线程
目前的操作系统是一个多进程的系统,在同一个时间之内可以运行多个任务,每一个任务都称之为进程
**进程:**操作系统中正在执行的一个任务(程序),比如:QQ,微信,Eclipse,屏幕广播都称之为进程。
**线程:**线程是进程中的一条执行路径,比如360安全卫士,在杀毒的同时,清理垃圾,文件整理等操作
一个线程必然包含在一个进程中
一个进程下包含多个线程
线程的调度(CPU)
- 平均分配执行时间(正常)
- 抢占式运行(设置优先级)
线程的状态
一条线程从创建到销毁具备一条完整的生命周期,操作系统将线程的状态分为5种:
- 新建状态(线程刚创建)
- 就绪状态(线程启动)
- 运行状态(线程得到CPU的时间片)
- 阻塞状态(线程还未执行完,但是CPU已经将时间片分配其他线程)
- 死亡状态(线程执行完毕或者正常中断)
注意事项:
java的多线程机制中将线程分为6种状态:
将阻塞状态划分为两种:
- 限时阻塞(在一个时间段之内阻塞,过了这个时间,重新进度调度队列)
- 无限阻塞(必须的到通知的情况下才能继续进度调度队列)
线程创建与启动
线程创建
java中的线程创建分为4种方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口,通过FuturaTask调度(JDK5新增并发编程)
- 使用线程池框架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+"=====");
}
}
}
三种不同创建方式的区别:
- 使用Thread的方式为继承,一旦继承Thread就无法再继承其他类,扩展性存在一定影响 ;可以直接创建对象并调用start启动
- 实现Runnable接口,类还可以再继承其他类或者实现其他接口,扩展性方面不受影响,run方法不能抛出异常;启动线程时还是需要由Thread类启动
- 实现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则目标线程会在正在执行的线程之前先执行完,类似插队概念
public class ThreadJoinDemo extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(getName()+"---"+i);
try {
sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ThreadJoinDemo t1 = new ThreadJoinDemo();
ThreadJoinDemo t2 = new ThreadJoinDemo();
t1.start();
t2.start();
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;
}
@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);
}
}
}
}
day17 练习讲解
-
对一个目录实现拷贝(目录中只有标准文件),要求根据文件的个数开启对应个数的线程实现拷贝
/** * 对一个目录实现拷贝(目录中只有标准文件),要求根据文件的个数开启对应个数的线程实现拷贝 * @author mrchai */ public class DirCopyByThread extends Thread{ /**需要被拷贝源文件*/ private File f; /**拷贝到的目标文件*/ private File target; public DirCopyByThread(File f,File target) { this.f = f; //根据目标目录,结合源文件名构建目标文件 this.target = new File(target,f.getName()); } @Override public void run() { //try...with... java7新增 无需手动关闭流,自动关闭(前提:获取的流对象必须有实现过Closeable接口) try( FileInputStream fis = new FileInputStream(f); FileOutputStream fos = new FileOutputStream(target); ){ byte[] b = new byte[1024]; int len = 0; //int read() 读取一个字节,返回是字节内容 //int read(byte[] b) 将读取的数据装入字节数组,返回真实读取长度 System.out.println(getName()+"开始拷贝:"+f.getName()); while((len = fis.read(b)) != -1) { fos.write(b, 0, len); } System.out.println(getName()+"拷贝完成!"); }catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { //声明需要被拷贝源目录 File source = new File("d:/tempfile"); //声明需要存储到的目标目录 File target = new File("d:/tempfile2"); //获取目标目录中的所有子文件(标准文件,非目录) File[] files = source.listFiles(f->f.isFile()); //循环遍历 for(File f:files) { //创建并启动线程 new DirCopyByThread(f,target).start(); } // File[] files = source.listFiles(new FileFilter() { // @Override // public boolean accept(File f) { // return f.isFile(); // } // }); } }
-
使用一个线程实现文件拷贝,开启另一个线程计算并显示当前拷贝的进度
public class FileCopyByThread extends Thread{ private File source; private File target; /**文件总大小*/ private double totalSize; /**当前已拷贝大小*/ private double currentSize; public FileCopyByThread(File source, File target) { super(); this.source = source; this.target = target; } @Override public void run() { try( FileInputStream fis = new FileInputStream(source); FileOutputStream fos = new FileOutputStream(target); ){ //获取文件的实际大小 totalSize = source.length(); byte[] b = new byte[1024]; int len = 0; //int read() 读取一个字节,返回是字节内容 //int read(byte[] b) 将读取的数据装入字节数组,返回真实读取长度 System.out.println(getName()+"开始拷贝:"+source.getName()); //创建并启动进度监控的线程 ProgressListener pl = new ProgressListener(); //设置当前线程为守护线程 pl.setDaemon(true); pl.start(); while((len = fis.read(b)) != -1) { fos.write(b, 0, len); //每次循环类增已拷贝的字节 currentSize += len; } System.out.println(getName()+"拷贝完成!"); }catch (IOException e) { e.printStackTrace(); } } /** * 计算文件拷贝的线程 * @author mrchai */ class ProgressListener extends Thread{ @Override public void run() { while(true) { //计算进度 double d = currentSize / totalSize; //将浮点进度格式化为百分比 String progress = NumberFormat.getPercentInstance().format(d); System.out.println("拷贝进度------->"+progress); try { sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { File source = new File("D:\\集团资料\\宣讲\\video\\软帝集团.mpg"); File target = new File("d:/tempfile/软帝集团.mpg"); //启动文件拷贝线程 new FileCopyByThread(source, target).start(); } }
-
将一个大文件(500M)分成四个线程拷贝到指定目录中
package Exp03;
import java.io.File;
import java.io.RandomAccessFile;
/**
* 文件分4个线程去拷贝
* @author 法海打印
*
*/
public class FileSplitCopy extends Thread {
/** 源文件 */
private File source;
/** 目标文件 */
private File target;
/** 开始拷贝位置 */
private long start;
/** 结束拷贝位置 */
private long end;
public FileSplitCopy(File source, File target, long start, long end) {
super();
this.source = source;
this.target = target;
this.start = start;
this.end = end;
}
@Override
public void run() {
target = new File(target, source.getName());
try(
RandomAccessFile read=new RandomAccessFile(source, "r");
RandomAccessFile write=new RandomAccessFile(target, "rw");
){
//跳过指定个字节发生下次读写
read.seek(start);
write.seek(start);
//用于统计一条线程读取的字节数
int count=0;
byte[] b=new byte[1024];
int len=0;
System.out.println(getName()+"开始拷贝..."+start);
while ((len=read.read(b))!=-1) {
write.write(b,0,len);
count+=len;
//如果目前读取的总字节数超过了每一段长度,则停止读取(当前线程任务结束)
//前提是: 不是最后一条线程
if (count>=(end-start)&&!"t3".equals(getName())) {
break;
}
}
System.out.println(getName()+"拷贝完成... 拷贝长度: "+count);
}catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
File source = new File("E:\\软帝java\\阿里云.mp4");
File target = new File("E:\\tempFile");
//平均获取每一段长度(如果除不尽,剩余字节数可能是1~3)
long item = source.length()/4;
for (int i = 0; i < 4; i++) {
FileSplitCopy fsc = new FileSplitCopy(source,target,i*item,(i+1)*item);
//给当前线程设置名称 t0,t1,t2,t3
fsc.setName("t"+i);
fsc.start();
}
}
}
- 斗地主
package Exp04;
/**
* 牌类
* @author 法海打印
*
*/
public class Card implements Comparable<Card>{
/**牌的ID*/
private int id;
/**牌面值*/
private String name;
/**牌的点数*/
private int point;
public Card(int id, String name, int point) {
super();
this.id = id;
this.name = name;
this.point = point;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPoint() {
return point;
}
public void setPoint(int point) {
this.point = point;
}
@Override
public int compareTo(Card c) {
return this.point-c.point;
}
@Override
public String toString() {
return name;
}
}
package Exp04;
/**
* 普通牌类(3~2 : 3 4 5 6 7 8 9 10 J Q K A 2)
* @author 法海打印
*
*/
public class NormalCard extends Card {
/**花色*/
private String flower;
public NormalCard(int id, String name, int point,String flower) {
super(id, name, point);
this.flower=flower;
}
public String getFlower() {
return flower;
}
public void setFlower(String flower) {
this.flower = flower;
}
@Override
public String toString() {
return flower+"."+getName();
}
}
package Exp04;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Player {
/**玩家ID*/
private int id;
/**玩家昵称*/
private String name;
/**是否是地主*/
private boolean boss;
/**玩家手中牌*/
private List<Card> cards=new ArrayList<Card>();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isBoss() {
return boss;
}
public void setBoss(boolean boss) {
this.boss = boss;
}
public List<Card> getCards() {
return cards;
}
public void setCards(List<Card> cards) {
this.cards = cards;
}
@Override
public String toString() {
//对牌排序
Collections.sort(cards);
return "Player [id=" + id + ", 玩家姓名= " + name + ", boss=" + boss + ", 手牌=" + cards + "]";
}
}
package Exp04;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
public class Poke {
private static String[] names= {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
private static String[] flowers= {"♥","♦","♠","♣"};
private static List<Card> allCards;
private static int id=0;
/**声明存储玩家的引用*/
private List<Player> players;
private Random r=new Random();
static {
allCards=new ArrayList<Card>();
for (int i = 0; i < names.length; i++) {
for (int j = 0; j < flowers.length; j++) {
id++;
//创建一张牌
Card c=new NormalCard(id, names[i], i+1, flowers[j]);
//将创建的牌加入集合
allCards.add(c);
}
}
allCards.add(new Card(++id, "小王", 666));
allCards.add(new Card(++id, "大王", 888));
//System.out.println(allCards.size());
//allCards.forEach(c->System.out.println(c));
}
/**
* 初始化玩家
*/
public void initPlayer() {
players=new ArrayList<Player>();
Player p1=new Player();
p1.setId(1);
p1.setName("孙悟空");
Player p2=new Player();
p2.setId(2);
p2.setName("猪八戒");
Player p3=new Player();
p3.setId(3);
p3.setName("沙和尚");
//将玩家加入集合
players.add(p1);
players.add(p2);
players.add(p3);
//随机地主
randomBOss();
}
/**
* 随机地主
*/
public void randomBOss() {
//随机地主索引
int i = r.nextInt(players.size());
//设置指定索引的玩家为地主
players.get(i).setBoss(true);
}
/**
* 发牌
*/
public void sendCard() {
//声明临时变量表示该谁抽取
int pos=0;
//遍历所有牌
for (int i = 0; i < allCards.size()-3; i++) {
//获取一张牌
Card card = allCards.get(i);
//获取抽牌玩家
Player player = players.get(pos);
//将牌发给玩家
player.getCards().add(card);
//players.get(pos).getCards().add(allCards.get(i));
pos=pos==0?1:pos==1?2:0;
// if (pos==0) {
// //玩家1抽牌
// pos=1;
// }else if (pos==1) {
// //玩家2抽牌
// pos=2;
// }else {
// //玩家3抽牌
// pos=0;
// }
}
//将剩余的牌发给地主
for (Player p : players) {
if (p.isBoss()) {
//获取最后三张牌
List<Card> cards=allCards.subList(allCards.size()-3, allCards.size());
//将最后三张牌给地主
p.getCards().addAll(cards);
}
}
}
/**
* 显示所有牌
*/
public void show() {
for (Player p : players) {
System.out.println(p);
}
}
/**
* 洗牌
*/
public void shuffeCard() {
Collections.shuffle(allCards);
Collections.shuffle(allCards);
Collections.shuffle(allCards);
}
public static void main(String[] args) {
Poke pg=new Poke();
pg.initPlayer();
pg.shuffeCard();
pg.sendCard();
pg.show();
}
}