Day21
编码问题
ASCII:美国标准信息交换码,用一个字节的7位可以表示
ISO8859-1:拉丁码表。欧洲码表,用一个字节的8位表示
GB2312:中国的中文编码表
GBK:中国的中文编码表升级,融合了更多的中文文字符号
GB18030:GBK的取代版本
BIG-5码 :通行于台湾、香港地区的一个繁体字编码方案,俗称“大五码”
Unicode:国际标准码,融合了多种文字,所有文字都用两个字节来表示,Java语言使用的就是unicode
UTF-8:最多用三个字节来表示一个字符
String中的编码问题
String(byte[] bytes, String charsetName):通过指定的字符集解码字节数组
byte[] getBytes(String charsetName):使用指定的字符集合把字符串编码为字节数组
编码:把看得懂的变成看不懂的
String -- byte[]
解码:把看不懂的变成看得懂的
byte[] -- String
注意:编码和解码的格式要一致
public class StringDemo {
public static void main(String[] args) throws UnsupportedEncodingException {
String s = "你好";
// String -- byte[]
byte[] bys = s.getBytes(); // [-60, -29, -70, -61]
// byte[] bys = s.getBytes("GBK");// [-60, -29, -70, -61]
// byte[] bys = s.getBytes("UTF-8");// [-28, -67, -96, -27, -91, -67]
System.out.println(Arrays.toString(bys));
// byte[] -- String
String ss = new String(bys); // 你好
// String ss = new String(bys, "GBK"); // 你好
// String ss = new String(bys, "UTF-8"); // ???
System.out.println(ss);
}
}
字符流
因为字节流操作中文不是特别的方便,所以Java就提供了字符流
字符流=字节流+编码表
写数据
OutputStreamWriter(OutputStream out):根据默认编码把字节流的数据转换为字符流
OutputStreamWriter(OutputStream out,String charsetName):根据指定编码把字节流数据转换为字符流
读数据
InputStreamReader(InputStream is):用默认的编码读取数据
InputStreamReader(InputStream is,String charsetName):用指定的编码读取数据
//写数据
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
// 创建对象
// OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt")); // 默认GBK
// OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt"), "GBK"); // 指定GBK
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(
"osw.txt"), "UTF-8"); // 指定UTF-8
// 写数据
osw.write("中国");
// 释放资源
osw.close();
}
}
//读数据
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
// 创建对象
// InputStreamReader isr = new InputStreamReader(new FileInputStream("osw.txt"));
// InputStreamReader isr = new InputStreamReader(new FileInputStream("osw.txt"), "GBK");
InputStreamReader isr = new InputStreamReader(new FileInputStream("osw.txt"), "UTF-8");
// 读取数据
// 一次读取一个字符
int ch = 0;
while ((ch = isr.read()) != -1) {
System.out.print((char) ch);
}
// 释放资源
isr.close();
}
}
字符流OutputStreamWriter的方法
flush():刷新字符缓冲区,不刷新字符无法写入
public void write(int c):写一个字符
public void write(char[] cbuf):写一个字符数组
public void write(char[] cbuf,int off,int len):写一个字符数组的一部分,从off开始写len个
public void write(String str):写一个字符串
public void write(String str,int off,int len):写一个字符串的一部分,从off开始写len个
面试题:close()和flush()的区别
close()关闭流对象,但是先刷新一次缓冲区,关闭之后,流对象不可以继续再使用了
flush()仅仅刷新缓冲区,刷新之后,流对象还可以继续使用
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
// 创建对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw2.txt"));
// 写数据
// public void write(int c):写一个字符
// osw.write('a');
// osw.write(97);
// 为什么数据没有进去呢?
// 原因是:字符 = 2字节
// 文件中数据存储的基本单位是字节。
// void flush()
// public void write(char[] cbuf):写一个字符数组
// char[] chs = {'a','b','c','d','e'};
// osw.write(chs);
// public void write(char[] cbuf,int off,int len):写一个字符数组的一部分
// osw.write(chs,1,3);
// public void write(String str):写一个字符串
// osw.write("我爱你中国");
// public void write(String str,int off,int len):写一个字符串的一部分
osw.write("我爱你中国", 2, 3);
// 刷新缓冲区
osw.flush();
// osw.write("我爱你中国", 2, 3);
// 释放资源
osw.close();
// java.io.IOException: Stream closed,流已经关闭,无法在使用
// osw.write("我爱中国", 2, 3);
}
}
读数据InputStreamReader的方法
int read():一次读取一个字符
int read(char[] chs):一次读取一个字符数组
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
// 创建对象
InputStreamReader isr = new InputStreamReader(new FileInputStream(
"StringDemo.java"));
// 一次读取一个字符
// int ch = 0;
// while ((ch = isr.read()) != -1) {
// System.out.print((char) ch);
// }
// 一次读取一个字符数组
char[] chs = new char[1024];
int len = 0;
while ((len = isr.read(chs)) != -1) {
System.out.print(new String(chs, 0, len));
}
// 释放资源
isr.close();
}
}
复制文件
/*
* 需求:把当前项目目录下的a.txt内容复制到当前项目目录下的b.txt中
*
* 数据源:
* a.txt -- 读取数据 -- 字符转换流 -- InputStreamReader
* 目的地:
* b.txt -- 写出数据 -- 字符转换流 -- OutputStreamWriter
*/
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 封装数据源
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));
// 封装目的地
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("b.txt"));
// 读写数据
// 方式1
// int ch = 0;
// while ((ch = isr.read()) != -1) {
// osw.write(ch);
// }
// 方式2
char[] chs = new char[1024];
int len = 0;
while ((len = isr.read(chs)) != -1) {
osw.write(chs, 0, len);
// osw.flush();
}
// 释放资源
osw.close();
isr.close();
}
}
转换流的简便写法
FileWriter = OutputStreamWriter
OutputStreamWriter = FileOutputStream + 编码表(GBK)
FileWriter = FileOutputStream + 编码表(GBK)
FileReader = InputStreamReader
InputStreamReader = FileInputStream + 编码表(GBK)
FileReader = FileInputStream + 编码表(GBK)
* 需求:把当前项目目录下的a.txt内容复制到当前项目目录下的b.txt中
*
* 数据源:
* a.txt -- 读取数据 -- 字符转换流 -- InputStreamReader -- FileReader
* 目的地:
* b.txt -- 写出数据 -- 字符转换流 -- OutputStreamWriter -- FileWriter
*/
public class CopyFileDemo2 {
public static void main(String[] args) throws IOException {
// 封装数据源
FileReader fr = new FileReader("a.txt");
//相当于InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));
// 封装目的地
FileWriter fw = new FileWriter("b.txt");
//相当于OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("b.txt"));
// 一次一个字符
// int ch = 0;
// while ((ch = fr.read()) != -1) {
// fw.write(ch);
// }
// 一次一个字符数组
char[] chs = new char[1024];
int len = 0;
while ((len = fr.read(chs)) != -1) {
fw.write(chs, 0, len);
fw.flush();
}
// 释放资源
fw.close();
fr.close();
}
}
字符缓冲流
字符流为了高效读写,也提供了对应的字符缓冲流
BufferedWriter:字符缓冲输出流
BufferedReader:字符缓冲输入流
字符缓冲流的特殊方法:
BufferedWriter:
public void newLine():根据系统来决定换行符
BufferedReader:
public String readLine():一次读取一行数据,包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null
BufferedWriter:字符缓冲输出流
将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入
可以指定缓冲区的大小,或者接受默认的大小,在大多数情况下,默认值就足够大了
public class BufferedWriterDemo {
public static void main(String[] args) throws IOException {
// BufferedWriter(Writer out)
// BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("bw.txt")));
//new FileWriter("bw.txt")相当于new OutputStreamWriter(new FileOutputStream("bw.txt"))
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
bw.write("hello");
bw.write("world");
bw.write("java");
bw.flush();
bw.close();
}
}
BufferedReader:字符缓冲输入流
从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取
可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了
子类
LineNumberReader
public int getLineNumber()获得当前行号
public void setLineNumber(int lineNumber)设置当前行号
public class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
// 创建字符缓冲输入流对象
// BufferedReader br1 = new BufferedReader(new InputStreamReader(new FileInputStream("bw.txt")));
//new FileReader("bw.txt")相当于new InputStreamReader(new FileInputStream("bw.txt"))
BufferedReader br = new BufferedReader(new FileReader("bw.txt"));
// 方式1
// int ch = 0;
// while ((ch = br.read()) != -1) {
// System.out.print((char) ch);
// }
// 方式2
char[] chs = new char[1024];
int len = 0;
while ((len = br.read(chs)) != -1) {
System.out.print(new String(chs, 0, len));
}
// 释放资源
br.close();
}
}
字符输入流复制文件
/*
* 需求:把当前项目目录下的a.txt内容复制到当前项目目录下的b.txt中
*
* 数据源:
* a.txt -- 读取数据 -- 字符转换流 -- InputStreamReader -- FileReader -- BufferedReader
* 目的地:
* b.txt -- 写出数据 -- 字符转换流 -- OutputStreamWriter -- FileWriter -- BufferedWriter
*/
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 封装数据源
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
// 封装目的地
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
// 两种方式其中的一种一次读写一个字符数组
char[] chs = new char[1024];
int len = 0;
while ((len = br.read(chs)) != -1) {
bw.write(chs, 0, len);
bw.flush();
}
// 释放资源
bw.close();
br.close();
}
}
字符缓冲流的特殊方法
public class BufferedDemo {
public static void main(String[] args) throws IOException {
// write();
read();
}
private static void read() throws IOException {
// 创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("bw2.txt"));
// public String readLine():一次读取一行数据
// String line = br.readLine();
// System.out.println(line);
// line = br.readLine();
// System.out.println(line);
// 最终版代码
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
//释放资源
br.close();
}
private static void write() throws IOException {
// 创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("bw2.txt"));
for (int x = 0; x < 10; x++) {
bw.write("hello" + x);
// bw.write("\r\n");
bw.newLine();
bw.flush();
}
bw.close();
}
}
/*
* 需求:把当前项目目录下的a.txt内容复制到当前项目目录下的b.txt中
*
* 数据源:
* a.txt -- 读取数据 -- 字符转换流 -- InputStreamReader -- FileReader -- BufferedReader
* 目的地:
* b.txt -- 写出数据 -- 字符转换流 -- OutputStreamWriter -- FileWriter -- BufferedWriter
*/
public class CopyFileDemo2 {
public static void main(String[] args) throws IOException {
// 封装数据源
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
// 封装目的地
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
// 读写数据
String line = null;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
// 释放资源
bw.close();
br.close();
}
}
IO流图解
IO流练习之复制文本文件
/*
*复制文本文件
*
* 分析:
* 复制数据,如果我们知道用记事本打开并能够读懂,就用字符流,否则用字节流
* 通过该原理,我们知道我们应该采用字符流更方便一些
* 而字符流有5种方式
* 数据源:
* a.txt -- FileReader -- BufferdReader
* 目的地:
* b.txt -- FileWriter -- BufferedWriter
* */
public class CopyFile {
public static void main(String[] args) throws IOException {
//字符流复制文件方式一,FileReader一次读取一个字符
//method1();
//字符流复制文件方式二,FileReader一次读取一个字符数组
//method2();
//字符流复制文件方式三,BufferedWriter一次读取一个字符
//method3();
//字符流复制文件方式四,BufferedWriter一次读取一个字符数组
//method4();
//字符流复制文件方式五,BufferedWriter的特殊功能,读取和写入一行
method5();
}
//字符流复制文件方式五,BufferedWriter的特殊功能,读取和写入一行
private static void method5() throws IOException{
//封装数据源
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
//封装目的地
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
//读写数据
String line = null;
while ((line = br.readLine()) != null){
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
br.close();
bw.close();
}
private static void method4() throws IOException{
//封装数据源
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
//封装目的地
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
//读写数据
char []chs = new char[1024];
int len = 0;
while ((len = br.read(chs))!= -1){
bw.write(chs,0,len);
bw.flush();
}
//释放资源
br.close();
bw.close();
}
//字符流复制文件方式三,BufferedWriter一次读取一个字符
private static void method3() throws IOException {
//封装数据源
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
//封装目的地
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
//读写数据
int ch = 0;
while ((ch = br.read())!= -1){
bw.write(ch);
bw.flush();
}
//释放资源
br.close();
bw.close();
}
//字符流复制文件方式二,FileReader一次读取一个字节数组
private static void method2() throws IOException {
//封装数据源
FileReader fr = new FileReader("a.txt");
//封装目的地
FileWriter fw = new FileWriter("b.txt");
//读写字符数组
char[] chs = new char[1024];
int len = 0;
while ((len = fr.read(chs)) != -1) {
fw.write(chs, 0, len);
fw.flush();
}
//释放资源
fr.close();
fw.close();
}
//字符流复制文件方式一,FileReader一次读取一个字节
private static void method1() throws IOException {
//封装数据源
FileReader fr = new FileReader("a.txt");
//封装目的地
FileWriter fw = new FileWriter("b.txt");
//读写数据
int ch = 0;
while ((ch = fr.read()) != -1) {
fw.write(ch);
fw.flush();
}
//释放资源
fr.close();
fw.close();
}
}
IO流练习之复制图片
/*
* 复制图片
*
* 分析:
* 复制数据,如果我们知道用记事本打开并能够读懂,就用字符流,否则用字节流。
* 通过该原理,我们知道我们应该采用字节流。
* 而字节流有4种方式,所以做这个题目我们有4种方式。推荐掌握第4种。
*
* 数据源:
* a.jpg -- FileInputStream -- BufferedInputStream
* 目的地:
* b.jpg -- FileOutputStream -- BufferedOutputStream
* */
public class CopyImage {
public static void main(String[] args) throws IOException {
//封装数据源
File srcFile = new File("a.jpg");
//封装目的地
File destFile = new File("b.jpg");
//复制图片方式一,字节流一次读取一个字节
//method1(srcFile, destFile);
//复制图片方式二,字节流一次读取一个字节数组
//method2(srcFile, destFile);
//复制图片方式三,字节流一次读取一个字节
//method3(srcFile, destFile);
//复制图片方式四,字节流一次读取一个字节数组
method4(srcFile, destFile);
}
//复制图片方式四,字节流一次读取一个字节数组
private static void method4(File srcFile, File destFile) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bis.close();
bos.close();
}
//复制图片方式三,字节流一次读取一个字节
private static void method3(File srcFile, File destFile) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
int ch = 0;
while ((ch = bis.read()) != -1) {
bos.write(ch);
}
bis.close();
bos.close();
}
//复制图片方式一,字节流一次读取一个字节数组
private static void method2(File srcFile, File destFile) throws IOException {
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(destFile);
byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
fis.close();
fos.close();
}
//复制图片方式一,字节流一次读取一个字节
private static void method1(File srcFile, File destFile) throws IOException {
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(destFile);
int ch = 0;
while ((ch = fis.read()) != -1) {
fos.write(ch);
}
fis.close();
fos.close();
}
}
IO流练习ArrayList集合到文本文件
/*
* 需求:把ArrayList集合中的字符串数据存储到文本文件
*
* 分析:
* 通过题目的意思我们可以知道如下的一些内容,
* ArrayList集合里存储的是字符串
* 遍历ArrayList集合,把数据获取到
* 然后存储到文本文件中
* 文本文件说明使用字符流
*
* 数据源:
* ArrayList -- 遍历得到每一个字符串数据
* 目的地:
* a.txt -- FileWriter -- BufferedWriter
*/
public class ArrayListToFileDemo {
public static void main(String[] args) throws IOException {
// 封装数据与(创建集合对象)
ArrayList array = new ArrayList();
array.add("hello");
array.add("world");
array.add("java");
// 封装目的地
BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt"));
// 遍历集合
for (String s : array) {
// 写数据
bw.write(s);
bw.newLine();
bw.flush();
}
// 释放资源
bw.close();
}
}
IO流练习文本文件到ArrayList集合
/*
* 需求:从文本文件中读取数据(每一行为一个字符串数据)到集合中,并遍历集合
*
* 分析:
* 通过题目的意思我们可以知道如下的一些内容,
* 数据源是一个文本文件。
* 目的地是一个集合。
* 而且元素是字符串。
*
* 数据源:
* b.txt -- FileReader -- BufferedReader
* 目的地:
* ArrayList
*/
public class FileToArrayListDemo {
public static void main(String[] args) throws IOException {
// 封装数据源
BufferedReader br = new BufferedReader(new FileReader("b.txt"));
// 封装目的地(创建集合对象)
ArrayList array = new ArrayList();
// 读取数据存储到集合中
String line = null;
while ((line = br.readLine()) != null) {
array.add(line);
}
// 释放资源
br.close();
// 遍历集合
for (String s : array) {
System.out.println(s);
}
}
}
IO流练习之获取ArrayList集合随机索引的值
/*
* 需求:我有一个文本文件中存储了几个名称,请大家写一个程序实现随机获取一个人的名字。
*
* 分析:
* 把文本文件中的数据存储到集合中
* 随机产生一个索引
* 根据该索引获取一个值
*/
public class GetName {
public static void main(String[] args) throws IOException {
// 把文本文件中的数据存储到集合中
BufferedReader br = new BufferedReader(new FileReader("name.txt"));
ArrayList array = new ArrayList();
String line = null;
while ((line = br.readLine()) != null) {
array.add(line);
}
br.close();
// 随机产生一个索引
Random r = new Random();
int index = r.nextInt(array.size());
// 根据该索引获取一个值
String name = array.get(index);
System.out.println("该幸运者是:" + name);
}
}
IO流练习之复制单级文件夹
/*
* 需求:复制单极文件夹
*
* 数据源:e:\\demo
* 目的地:e:\\test
*
* 分析:
* 封装目录
* 获取该目录下的所有文本的File数组
* 遍历该File数组,得到每一个File对象
* 把该File进行复制
*/
public class CopyFolderDemo {
public static void main(String[] args) throws IOException {
// 封装目录
File srcFolder = new File("e:\\demo");
// 封装目的地
File destFolder = new File("e:\\test");
// 如果目的地文件夹不存在,就创建
if (!destFolder.exists()) {
destFolder.mkdir();
}
// 获取该目录下的所有文本的File数组
File[] fileArray = srcFolder.listFiles();
// 遍历该File数组,得到每一个File对象
for (File file : fileArray) {
// System.out.println(file);
// 数据源:e:\\demo\\e.mp3
// 目的地:e:\\test\\e.mp3
String name = file.getName(); // e.mp3
File newFile = new File(destFolder, name); // e:\\test\\e.mp3
copyFile(file, newFile);
}
}
private static void copyFile(File file, File newFile) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(newFile));
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
}
IO流练习之复制指定目录下的指定文件,并修改后缀名
/*
* 需求:复制指定目录下的指定文件,并修改后缀名。
* 指定的文件是:.java文件。
* 指定的后缀名是:.jad
* 指定的目录是:jad
*
* 数据源:e:\\java\\A.java
* 目的地:e:\\jad\\A.jad
*
* 分析:
* 封装目录
* 获取该目录下的java文件的File数组
* 遍历该File数组,得到每一个File对象
* 把该File进行复制
* 在目的地目录下改名
*/
public class CopyFolderDemo {
public static void main(String[] args) throws IOException {
// 封装目录
File srcFolder = new File("e:\\java");
// 封装目的地
File destFolder = new File("e:\\jad");
// 如果目的地目录不存在,就创建
if (!destFolder.exists()) {
destFolder.mkdir();
}
// 获取该目录下的java文件的File数组
File[] fileArray = srcFolder.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return new File(dir, name).isFile() && name.endsWith(".java");
}
});
// 遍历该File数组,得到每一个File对象
for (File file : fileArray) {
// System.out.println(file);
// 数据源:e:\java\DataTypeDemo.java
// 目的地:e:\\jad\DataTypeDemo.java
String name = file.getName();
File newFile = new File(destFolder, name);
copyFile(file, newFile);
}
// 在目的地目录下改名
File[] destFileArray = destFolder.listFiles();
for (File destFile : destFileArray) {
// System.out.println(destFile);
// e:\jad\DataTypeDemo.java
// e:\\jad\\DataTypeDemo.jad
String name =destFile.getName(); //DataTypeDemo.java
String newName = name.replace(".java", ".jad");//DataTypeDemo.jad
File newFile = new File(destFolder,newName);
destFile.renameTo(newFile);
}
}
private static void copyFile(File file, File newFile) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(newFile));
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
}
IO流练习之复制多级文件夹
/*
* 需求:复制多极文件夹
*
* 数据源:E:\JavaSE\day21\code\demos
* 目的地:E:\\
*
* 分析:
* 封装数据源File
* 封装目的地File
* 判断该File是文件夹还是文件
* 是文件夹
* 就在目的地目录下创建该文件夹
* 获取该File对象下的所有文件或者文件夹File对象
* 遍历得到每一个File对象
* 回到第三步
* 是文件
* 就复制(字节流)
*/
public class CopyFoldersDemo {
public static void main(String[] args) throws IOException {
// 封装数据源File
File srcFile = new File("E:\\JavaSE\\day21\\code\\demos");
// 封装目的地File
File destFile = new File("E:\\");
// 复制文件夹的功能
copyFolder(srcFile, destFile);
}
private static void copyFolder(File srcFile, File destFile)throws IOException {
// 判断该File是文件夹还是文件
if (srcFile.isDirectory()) {
// 文件夹
File newFolder = new File(destFile, srcFile.getName());
newFolder.mkdir();
// 获取该File对象下的所有文件或者文件夹File对象
File[] fileArray = srcFile.listFiles();
for (File file : fileArray) {
copyFolder(file, newFolder);
}
} else {
// 文件
File newFile = new File(destFile, srcFile.getName());
copyFile(srcFile, newFile);
}
}
private static void copyFile(File srcFile, File newFile) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(newFile));
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
}
IO流练习之集合存储学生信息并写入文本文件
/*
* 键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩),按照总分从高到低存入文本文件
*
* 分析:
* 创建学生类
* 创建集合对象
* TreeSet
* 键盘录入学生信息存储到集合
* 遍历集合,把数据写到文本文件
*/
public class StudentDemo {
public static void main(String[] args) throws IOException {
// 创建集合对象
TreeSet ts = new TreeSet(new Comparator() {
@Override
public int compare(Student s1, Student s2) {
int num = s2.getSum() - s1.getSum();
int num2 = num == 0 ? s1.getChinese() - s2.getChinese() : num;
int num3 = num2 == 0 ? s1.getMath() - s2.getMath() : num2;
int num4 = num3 == 0 ? s1.getEnglish() - s2.getEnglish() : num3;
int num5 = num4 == 0 s1.getName().compareTo(s2.getName(): num4;
return num5;
}
});
// 键盘录入学生信息存储到集合
for (int x = 1; x <= 5; x++) {
Scanner sc = new Scanner(System.in);
System.out.println("请录入第" + x + "个的学习信息");
System.out.println("姓名:");
String name = sc.nextLine();
System.out.println("语文成绩:");
int chinese = sc.nextInt();
System.out.println("数学成绩:");
int math = sc.nextInt();
System.out.println("英语成绩:");
int english = sc.nextInt();
// 创建学生对象
Student s = new Student();
s.setName(name);
s.setChinese(chinese);
s.setMath(math);
s.setEnglish(english);
// 把学生信息添加到集合
ts.add(s);
}
// 遍历集合,把数据写到文本文件
BufferedWriter bw = new BufferedWriter(new FileWriter("students.txt"));
bw.write("学生信息如下:");
bw.newLine();
bw.flush();
bw.write("姓名,语文成绩,数学成绩,英语成绩");
bw.newLine();
bw.flush();
for (Student s : ts) {
StringBuilder sb = new StringBuilder();
sb.append(s.getName()).append(",").append(s.getChinese()).append(",").append(s.getMath()).append(",").append(s.getEnglish());
bw.write(sb.toString());
bw.newLine();
bw.flush();
}
// 释放资源
bw.close();
System.out.println("学习信息存储完毕");
}
}
IO流练习之读取文件并排序然后写入到新文件中
/*
* 已知s.txt文件中有这样的一个字符串:“hcexfgijkamdnoqrzstuvwybpl”
* 请编写程序读取数据内容,把数据排序后写入ss.txt中
*
* 分析:
* 把s.txt这个文件给做出来
* 读取该文件的内容,存储到一个字符串中
* 把字符串转换为字符数组
* 对字符数组进行排序
* 把排序后的字符数组转换为字符串
* 把字符串再次写入ss.txt中
*/
public class StringDemo {
public static void main(String[] args) throws IOException {
// 读取该文件的内容,存储到一个字符串中
BufferedReader br = new BufferedReader(new FileReader("s.txt"));
String line = br.readLine();
br.close();
// 把字符串转换为字符数组
char[] chs = line.toCharArray();
// 对字符数组进行排序
Arrays.sort(chs);
// 把排序后的字符数组转换为字符串
String s = new String(chs);
// 把字符串再次写入ss.txt中
BufferedWriter bw = new BufferedWriter(new FileWriter("ss.txt"));
bw.write(s);
bw.newLine();
bw.flush();
bw.close();
}
}
Day22
用户登录IO文件版
package io.pojo;
//学生类
public class User {
// 用户名
private String username;
// 密码
private String password;
public User() {
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
//功能接口
import io.pojo.User;
public interface UserDao {
//登录
public abstract boolean isLogin(String username, String password);
//注册
public abstract void regist(User user);
}
package cn.itcast.dao.impl;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import io.dao.UserDao;
import io.pojo.User;
//功能接口的实现类
public class UserDaoImpl implements UserDao {
// 为了保证文件一加载就创建
private static File file = new File("user.txt");
static {
try {
file.createNewFile();
} catch (IOException e) {
System.out.println("创建文件失败");
// e.printStackTrace();
}
}
@Override
public boolean isLogin(String username, String password) {
boolean flag = false;
BufferedReader br = null;
try {
// br = new BufferedReader(new FileReader("user.txt"));
br = new BufferedReader(new FileReader(file));
String line = null;
while ((line = br.readLine()) != null) {
// 用户名=密码
String[] datas = line.split("=");
if (datas[0].equals(username) && datas[1].equals(password)) {
flag = true;
break;
}
}
} catch (FileNotFoundException e) {
System.out.println("用户登录找不到信息所在的文件");
// e.printStackTrace();
} catch (IOException e) {
System.out.println("用户登录失败");
// e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
System.out.println("用户登录释放资源失败");
// e.printStackTrace();
}
}
}
return flag;
}
@Override
public void regist(User user) {
/*
* 为了让注册的数据能够有一定的规则,我就自己定义了一个规则: 用户名=密码
*/
BufferedWriter bw = null;
try {
// bw = new BufferedWriter(new FileWriter("user.txt"));
// bw = new BufferedWriter(new FileWriter(file));
// 为了保证数据是追加写入,必须加true
bw = new BufferedWriter(new FileWriter(file, true));
bw.write(user.getUsername() + "=" + user.getPassword());
bw.newLine();
bw.flush();
} catch (IOException e) {
System.out.println("用户注册失败");
// e.printStackTrace();
} finally {
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
System.out.println("用户注册释放资源失败");
// e.printStackTrace();
}
}
}
}
}
package io.game;
import java.util.Scanner;
//这是猜数字小游戏,用户登录成功后可以玩
public class GuessNumber {
private GuessNumber() {
}
public static void start() {
// 产生一个随机数
int number = (int) (Math.random() * 100) + 1;
// 定义一个统计变量
int count = 0;
while (true) {
// 键盘录入一个数据
Scanner sc = new Scanner(System.in);
System.out.println("请输入数据(1-100):");
int guessNumber = sc.nextInt();
count++;
// 判断
if (guessNumber > number) {
System.out.println("你猜的数据" + guessNumber + "大了");
} else if (guessNumber < number) {
System.out.println("你猜的数据" + guessNumber + "小了");
} else {
System.out.println("恭喜你," + count + "次就猜中了");
break;
}
}
}
}
package cn.itcast.test;
import java.util.Scanner;
import io.dao.UserDao;
import io.dao.impl.UserDaoImpl;
import io.game.GuessNumber;
import io.pojo.User;
//这是用户测试类
public class UserTest {
public static void main(String[] args) {
// 为了能够回来
while (true) {
// 欢迎界面,给出选择项
System.out.println("--------------欢迎光临--------------");
System.out.println("1 登录");
System.out.println("2 注册");
System.out.println("3 退出");
System.out.println("请输入你的选择:");
// 键盘录入选择,根据选择做不同的操作
Scanner sc = new Scanner(System.in);
// 为了后面的录入信息的方便,我所有的数据录入全部用字符接收
String choiceString = sc.nextLine();
// switch语句的多个地方要使用,我就定义到外面
UserDao ud = new UserDaoImpl();
// 经过简单的思考,我选择了switch
switch (choiceString) {
case "1":
// 登录界面,请输入用户名和密码
System.out.println("--------------登录界面--------------");
System.out.println("请输入用户名:");
String username = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
// 调用登录功能
// UserDao ud = new UserDaomImpl();
boolean flag = ud.isLogin(username, password);
if (flag) {
System.out.println("登录成功,可以开始玩游戏了");
System.out.println("你玩吗?y/n");
while (true) {
String resultString = sc.nextLine();
if (resultString.equalsIgnoreCase("y")) {
// 玩游戏
GuessNumber.start();
System.out.println("你还玩吗?y/n");
} else {
break;
}
}
System.out.println("谢谢使用,欢迎下次再来");
System.exit(0);
// break; //这里写break,结束的是switch
} else {
System.out.println("用户名或者密码有误,登录失败");
}
break;
case "2":
// 欢迎界面,请输入用户名和密码
System.out.println("--------------注册界面--------------");
System.out.println("请输入用户名:");
String newUsername = sc.nextLine();
System.out.println("请输入密码:");
String newPassword = sc.nextLine();
// 把用户名和密码封装到一个对象中
User user = new User();
user.setUsername(newUsername);
user.setPassword(newPassword);
// 调用注册功能
// 多态
// UserDao ud = new UserDaoImpl();
// 具体类使用
// UserDaoImpl udi = new UserDaoImpl();
ud.regist(user);
System.out.println("注册成功");
break;
case "3":
default:
System.out.println("谢谢使用,欢迎下次再来");
System.exit(0);
break;
}
}
}
}
操作基本数据类型的流
可以读写基本数据类型的数据
数据输入流:DataInputStream
DataInputStream(InputStream in)
数据输出流:DataOutputStream
DataOutputStream(OutputStream out)
public class DataStreamDemo {
public static void main(String[] args) throws IOException {
// 写
// write();
// 读
read();
}
private static void read() throws IOException {
// DataInputStream(InputStream in)
// 创建数据输入流对象
DataInputStream dis = new DataInputStream(
new FileInputStream("dos.txt"));
// 读数据
byte b = dis.readByte();
short s = dis.readShort();
int i = dis.readInt();
long l = dis.readLong();
float f = dis.readFloat();
double d = dis.readDouble();
char c = dis.readChar();
boolean bb = dis.readBoolean();
// 释放资源
dis.close();
System.out.println(b);
System.out.println(s);
System.out.println(i);
System.out.println(l);
System.out.println(f);
System.out.println(d);
System.out.println(c);
System.out.println(bb);
}
private static void write() throws IOException {
// DataOutputStream(OutputStream out)
// 创建数据输出流对象
DataOutputStream dos = new DataOutputStream(new FileOutputStream(
"dos.txt"));
// 写数据了
dos.writeByte(10);
dos.writeShort(100);
dos.writeInt(1000);
dos.writeLong(10000);
dos.writeFloat(12.34F);
dos.writeDouble(12.56);
dos.writeChar('a');
dos.writeBoolean(true);
// 释放资源
dos.close();
}
}
内存操作流
用于处理临时存储信息的,程序结束,数据就从内存中消失
字节数组(不需要释放资源)
ByteArrayInputStream
ByteArrayOutputStream()构造方法
ByteArrayOutputStream
ByteArrayInputStream(byte[] buf)构造方法,需要一个字符数组,但是ByteArrayInputStream没有生成字符数组,所以要用public byte[] toByteArray()转换一下
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 写数据
for (int x = 0; x < 10; x++) {
baos.write(("hello" + x).getBytes());
}
// public byte[] toByteArray()
byte[] bys = baos.toByteArray();
// 读数据
// ByteArrayInputStream(byte[] buf)
ByteArrayInputStream bais = new ByteArrayInputStream(bys);
int by = 0;
while ((by = bais.read()) != -1) {
System.out.print((char) by);
}
字符数组(与字节数组用法相同)
CharArrayReader
CharArrayWriter
字符串(与字节数组用法相同)
StringReader
StringWriter
打印流
字节流打印流 PrintStream
字符打印流(属于字符流,可以使用父类Writer的方法) PrintWriter
打印流的特点:
只有写数据的,没有读取数据。只能操作目的地,不能操作数据源
可以操作任意类型的数据
如果启动了自动刷新,能够自动刷新,不需要手动flush刷新
该流是可以直接操作文本文件的
可以直接操作文本文件的流对象
FileInputStream
FileOutputStream
FileReader
FileWriter
PrintStream
PrintWriter
看API,查流对象的构造方法,如果同时有File类型和String类型的参数,一般来说就是可以直接操作文件的
流分为两种
基本流:就是能够直接读写文件的
高级流:在基本流基础上提供了一些其他的功能
字符打印流PrintWriter作为Writer子类使用
public class PrintWriterDemo {
public static void main(String[] args) throws IOException {
// 作为Writer的子类使用
PrintWriter pw = new PrintWriter("pw.txt");
pw.write("hello");
pw.write("world");
pw.write("java");
pw.close();
}
}
打印流的特点之可以操作任意类型的数据
print()和println()都可以操作任意类型数据
启动自动刷新
PrintWriter pw = new PrintWriter(new FileWriter("pw2.txt"), true);
启用自动刷新后print()还是不能自动刷新,只能调用println()的方法才可以,这个时候不仅仅自动刷新了,还实现了数据的换行
println()其实等价于于:bw.write(); + bw.newLine(); + bw.flush();
public class PrintWriterDemo2 {
public static void main(String[] args) throws IOException {
// 创建打印流对象
// PrintWriter pw = new PrintWriter("pw2.txt");没有启动自动刷新
PrintWriter pw = new PrintWriter(new FileWriter("pw2.txt"), true);//启动自动刷新
// write()不能操作基本数据类型
// 我们就应该看看它的新方法
// pw.print(true);
// pw.print(100);
// pw.print("hello");
pw.println("hello");
pw.println(true);
pw.println(100);
pw.close();
}
}
通过打印流复制
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
/*
* 需求:DataStreamDemo.java复制到Copy.java中
* 数据源:
* DataStreamDemo.java -- 读取数据 -- FileReader -- BufferedReader
* 目的地:
* Copy.java -- 写出数据 -- FileWriter -- BufferedWriter -- PrintWriter
*/
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 以前的版本
// 封装数据源
// BufferedReader br = new BufferedReader(new FileReader("DataStreamDemo.java"));
// // 封装目的地
// BufferedWriter bw = new BufferedWriter(new FileWriter("Copy.java"));
//
// String line = null;
// while ((line = br.readLine()) != null) {
// bw.write(line);
// bw.newLine();
// bw.flush();
// }
//
// bw.close();
// br.close();
// 打印流的改进版
// 封装数据源
BufferedReader br = new BufferedReader(new FileReader("DataStreamDemo.java"));
// 封装目的地
PrintWriter pw = new PrintWriter(new FileWriter("Copy.java"), true);
String line = null;
while((line=br.readLine())!=null){
pw.println(line);
}
pw.close();
br.close();
}
}
标准输入输出流
System类中的两个成员变量:
public static final InputStream in “标准”输入流
public static final PrintStream out “标准”输出流
InputStream is 等价于 System.in;
PrintStream ps 等价于 System.out;
public class SystemOutDemo {
public static void main(String[] args) {
//这个输出语句其本质是IO流操作,把数据输出到控制台
System.out.println("helloworld");
// 获取标准输出流对象
PrintStream ps = System.out;
ps.println("helloworld");
ps.println();
// ps.print();//这个方法不存在
// System.out.println();
// System.out.print();这个方法不存在
}
}
标准输入流
System.in 获取键盘输入的数据
键盘录入数据:
main方法的args接收参数
java HelloWorld hello world java
Scanner(JDK5以后的)
Scanner sc = new Scanner(System.in);
String s = sc.nextLine(); //获取字符串
int x = sc.nextInt(); //获取整数
通过字符缓冲流包装标准输入流实现
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
public class SystemInDemo {
public static void main(String[] args) throws IOException {
// 获取标准输入流
// InputStream is = System.in;
// 一次读取一行的方法
//readLine()
//而这个方法在BufferedReader类中
// 所以,你这次应该创建BufferedReader的对象,但是底层还是的使用标准输入流
// BufferedReader br = new BufferedReader(is);
// 按照我们的推想,现在应该可以了,但是却报错了
// 原因是:字符缓冲流只能针对字符流操作,而你现在是字节流,所以不能使用可以把字节流转换为字符流,然后在通过字符缓冲流操作
// InputStreamReader isr = new InputStreamReader(is);
// BufferedReader br= new BufferedReader(isr);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入一个字符串:");
String line = br.readLine();
System.out.println("你输入的字符串是:" + line);
System.out.println("请输入一个整数:");
//把字符串转换为整型
// int i = Integer.parseInt(br.readLine());
line = br.readLine();
int i = Integer.parseInt(line);
System.out.println("你输入的整数是:" + i);
}
}
/*
* 转换流的应用。
*/
public class SystemOutDemo2 {
public static void main(String[] args) throws IOException {
// 获取标准输入流
// // PrintStream ps = System.out;
// // OutputStream os = ps;
// OutputStream os = System.out; // 多态
// // 我能不能按照刚才使用标准输入流的方式一样把数据输出到控制台
// OutputStreamWriter osw = new OutputStreamWriter(os);
// BufferedWriter bw = new BufferedWriter(osw);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
System.out));
bw.write("hello");
bw.newLine();
// bw.flush();
bw.write("world");
bw.newLine();
// bw.flush();
bw.write("java");
bw.newLine();
bw.flush();
bw.close();
}
}
随机访问流
RandomAccessFile类不属于流,是Object类的子类,但它融合了InputStream和OutputStream的功能。支持对文件的随机访问读取和写入。
构造方法
public RandomAccessFile(String name,String mode):第一个参数是文件路径,第二个参数是操作文件的模式
public RandomAccessFile(File file,String mode):第一个参数是文件路径,第二个参数是操作文件的模式
该文件指针可以通过 getFilePointer方法读取,并通过 seek 方法设置,以字节为单位,整型占四个,字符型占两个等
模式有四种
"r" 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException
"rw" 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件
"rws" 打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备
"rwd" 打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备
我们最常用的一种叫"rw",这种方式表示我既可以写数据,也可以读取数据
public class RandomAccessFileDemo {
public static void main(String[] args) throws IOException {
// write();
read();
}
private static void read() throws IOException {
// 创建随机访问流对象
RandomAccessFile raf = new RandomAccessFile("raf.txt", "rw");
int i = raf.readInt();
System.out.println(i);
// 该文件指针可以通过 getFilePointer方法读取,并通过 seek 方法设置。
System.out.println("当前文件的指针位置是:" + raf.getFilePointer());
char ch = raf.readChar();
System.out.println(ch);
System.out.println("当前文件的指针位置是:" + raf.getFilePointer());
String s = raf.readUTF();
System.out.println(s);
System.out.println("当前文件的指针位置是:" + raf.getFilePointer());
// 我不想重头开始了,我就要读取a,可以用seek指定位置
raf.seek(4);
ch = raf.readChar();
System.out.println(ch);
}
private static void write() throws IOException {
// 创建随机访问流对象
RandomAccessFile raf = new RandomAccessFile("raf.txt", "rw");
raf.writeInt(100);
raf.writeChar('a');
raf.writeUTF("中国");
raf.close();
}
}
**合并流**
SequenceInputStream(InputStream s1, InputStream s2):将s1和s2读入,可以写到一个文件中
SequenceInputStream(Enumeration e)将Enumeration读入,可以写到一个文件中,Enumeration是Vector中的一个方法的返回值类型
将a.txt和b.txt一起复制到c.txt
public class SequenceInputStreamDemo {
public static void main(String[] args) throws IOException {
// SequenceInputStream(InputStream s1, InputStream s2)
// 需求:把ByteArrayStreamDemo.java和DataStreamDemo.java的内容复制到Copy.java中
InputStream s1 = new FileInputStream("ByteArrayStreamDemo.java");
InputStream s2 = new FileInputStream("DataStreamDemo.java");
SequenceInputStream sis = new SequenceInputStream(s1, s2);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("Copy.java"));
// 如何写读写呢,其实很简单,你就按照以前怎么读写,现在还是怎么读写
byte[] bys = new byte[1024];
int len = 0;
while ((len = sis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
sis.close();
}
}
将多个文件复制到一个文件
public class SequenceInputStreamDemo2 {
public static void main(String[] args) throws IOException {
// 需求:把下面的三个文件的内容复制到Copy.java中
// ByteArrayStreamDemo.java,CopyFileDemo.java,DataStreamDemo.java
// SequenceInputStream(Enumeration e)
// Enumeration elements()
Vector v = new Vector();
InputStream s1 = new FileInputStream("ByteArrayStreamDemo.java");
InputStream s2 = new FileInputStream("CopyFileDemo.java");
InputStream s3 = new FileInputStream("DataStreamDemo.java");
v.add(s1);
v.add(s2);
v.add(s3);
Enumeration en = v.elements();
SequenceInputStream sis = new SequenceInputStream(en);
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("Copy.java"));
// 如何写读写呢,其实很简单,你就按照以前怎么读写,现在还是怎么读写
byte[] bys = new byte[1024];
int len = 0;
while ((len = sis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
sis.close();
}
}
序列化流
序列化流:把对象按照流一样的方式存入文本文件或者在网络中传输。对象 -- 流数据(ObjectOutputStream)
反序列化流:把文本文件中的流对象数据或者网络中的流对象数据还原成对象。流数据 -- 对象(ObjectInputStream)
看到类实现了序列化接口的时候,要想解决黄色警告线问题,就可以自动产生一个序列化id值,而且产生这个值以后,我们对类进行任何改动,它读取以前的数据是没有问题的
注意:
我一个类中可能有很多的成员变量,有些我不想进行序列化可以使用transient关键字声明不需要序列化的成员变量
public class ObjectStreamDemo {
public static void main(String[] args) throws IOException,ClassNotFoundException {
// 由于我们要对对象进行序列化,所以我们先自定义一个类
// 序列化数据其实就是把对象写到文本文件
// write();
read();
}
private static void read() throws IOException, ClassNotFoundException {
// 创建反序列化对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("oos.txt"));
// 还原对象
Object obj = ois.readObject();
// 释放资源
ois.close();
// 输出对象
System.out.println(obj);
}
private static void write() throws IOException {
// 创建序列化流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("oos.txt"));
// 创建对象
Person p = new Person("张三", 27);
// public final void writeObject(Object obj)
oos.writeObject(p);
// 释放资源
oos.close();
}
}
public class Person implements Serializable {
private static final long serialVersionUID = -2071565876962058344L;
private String name;
// private int age;
private transient int age;
// int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
**Properties **
属性集合类,是一个可以和IO流相结合使用的集合类。
Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串,是Hashtable的子类,说明是一个Map集合
特殊功能
public Object setProperty(String key,String value):添加元素
public String getProperty(String key):获取元素
public Set
Properties与IO的结合使用
这里的集合必须是Properties集合
public void load(Reader reader):把文件中的数据读取到集合中
public void store(Writer writer,String comments):把集合中的数据存储到文件
public class PropertiesDemo {
public static void main(String[] args) {
// 作为Map集合的使用
// 下面这种用法是错误的,一定要看API,如果没有<>,就说明该类不是一个泛型类,在使用的时候就不能加泛型
// Properties prop = new Properties();
Properties prop = new Properties();
// 添加元素
prop.put("it002", "hello");
prop.put("it001", "world");
prop.put("it003", "java");
// System.out.println("prop:" + prop);
// 遍历集合
Set
//特殊功能
public class PropertiesDemo2 {
public static void main(String[] args) {
// 创建集合对象
Properties prop = new Properties();
// 添加元素
prop.setProperty("张三", "30");
prop.setProperty("李四", "40");
prop.setProperty("王五", "50");
// public Set stringPropertyNames():获取所有的键的集合
Set set = prop.stringPropertyNames();
for (String key : set) {
String value = prop.getProperty(key);
System.out.println(key + "---" + value);
}
}
}
//Properties集合与IO的结合使用
public class PropertiesDemo3 {
public static void main(String[] args) throws IOException {
// myLoad();
myStore();
}
private static void myStore() throws IOException {
// 创建集合对象
Properties prop = new Properties();
prop.setProperty("张三", "27");
prop.setProperty("李四", "30");
prop.setProperty("王五", "18");
//public void store(Writer writer,String comments):把集合中的数据存储到文件
Writer w = new FileWriter("name.txt");
prop.store(w, "helloworld");
w.close();
}
private static void myLoad() throws IOException {
Properties prop = new Properties();
// public void load(Reader reader):把文件中的数据读取到集合中
// 注意:这个文件的数据必须是键值对形式
Reader r = new FileReader("prop.txt");
prop.load(r);
r.close();
System.out.println("prop:" + prop);
}
}
/*
* 我有一个文本文件(user.txt),我知道数据是键值对形式的,但是不知道内容是什么。
* 请写一个程序判断是否有“lisi”这样的键存在,如果有就改变其实为”100”
*
* 分析:
* A:把文件中的数据加载到集合中
* B:遍历集合,获取得到每一个键
* C:判断键是否有为"lisi"的,如果有就修改其值为"100"
* D:把集合中的数据重新存储到文件中
*/
public class PropertiesTest {
public static void main(String[] args) throws IOException {
// 把文件中的数据加载到集合中
Properties prop = new Properties();
Reader r = new FileReader("user.txt");
prop.load(r);
r.close();
// 遍历集合,获取得到每一个键
Set set = prop.stringPropertyNames();
for (String key : set) {
// 判断键是否有为"lisi"的,如果有就修改其值为"100"
if ("lisi".equals(key)) {
prop.setProperty(key, "100");
break;
}
}
// 把集合中的数据重新存储到文件中
Writer w = new FileWriter("user.txt");
prop.store(w, null);
w.close();
}
}
/*
* 我有一个猜数字小游戏的程序,请写一个程序实现在测试类中只能用5次,超过5次提示:游戏试玩已结束,请付费。
*/
public class PropertiesTest2 {
public static void main(String[] args) throws IOException {
// 读取某个地方的数据,如果次数不大于5,可以继续玩。否则就提示"游戏试玩已结束,请付费。"
// 创建一个文件
// File file = new File("count.txt");
// if (!file.exists()) {
// file.createNewFile();
// }
// 把数据加载到集合中
Properties prop = new Properties();
Reader r = new FileReader("count.txt");
prop.load(r);
r.close();
// 我自己的程序,我当然知道里面的键是谁
String value = prop.getProperty("count");
int number = Integer.parseInt(value);
if (number > 5) {
System.out.println("游戏试玩已结束,请付费。");
System.exit(0);
} else {
number++;
prop.setProperty("count", String.valueOf(number));
Writer w = new FileWriter("count.txt");
prop.store(w, null);
w.close();
GuessNumber.start();
}
}
}
Day23
进程:
正在运行的程序,是系统进行资源分配和调用的独立单位
每一个进程都有它自己的内存空间和系统资源
线程:
是进程中的单个顺序控制流,是一条执行路径
一个进程如果只有一条执行路径,则称为单线程程序
一个进程如果有多条执行路径,则称为多线程程序
并行和并发
并发是逻辑上同时发生,指在某一个时间内同时运行多个程序
并行是物理上同时发生,指在某一个时间点同时运行多个程序
Java程序的运行原理:
由java命令启动JVM,JVM启动就相当于启动了一个进程
接着有该进程创建了一个主线程去调用main方法
思考题:
jvm虚拟机的启动是单线程的还是多线程的
多线程的
原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出
现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的
实现多线程的方式一
继承Thread类
步骤
-自定义类MyThread继承Thread类
-MyThread类里面重写run()方法
-创建对象
-启动线程
为什么要重写run()方法
因为不是类中的所有代码都需要被线程执行的,而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码
调用run()方法为什么是单线程的
因为run()方法直接调用其实就相当于普通的方法调用,所以你看到的是单线程的效果
要想看到多线程的效果,就要使用方法:start()
面试题:run()和start()的区别
run():仅仅是封装被线程执行的代码,直接调用是普通方法
start():首先启动了线程,然后再由jvm去调用该线程的run()方法
IllegalThreadStateException:非法的线程状态异常
多线程的实现
//自定义类MyThread继承Thread类
public class MyThread extends Thread {
//MyThread类里面重写run()方法
@Override
public void run() {
// 自己写代码
// System.out.println("好好学习,天天向上");
// 一般来说,被线程执行的代码肯定是比较耗时的。所以我们用循环改进
for (int x = 0; x < 200; x++) {
System.out.println(x);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
// 创建线程对象
// MyThread my = new MyThread();
// // 启动线程
// my.run();
// my.run();
// 调用run()方法为什么是单线程的呢?
// 因为run()方法直接调用其实就相当于普通的方法调用,所以你看到的是单线程的效果
// 要想看到多线程的效果,就必须说说另一个方法:start()
// 面试题:run()和start()的区别?
// run():仅仅是封装被线程执行的代码,直接调用是普通方法
// start():首先启动了线程,然后再由jvm去调用该线程的run()方法
// MyThread my = new MyThread();
// my.start();
// // IllegalThreadStateException:非法的线程状态异常
// // 为什么呢?因为这个相当于是my线程被调用了两次。而不是两个线程启动。
// my.start();
// 创建两个线程对象
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
//启动线程
my1.start();
my2.start();
}
}
如何获取线程对象的名称
public final String getName():获取线程的名称
如何设置线程对象的名称
public final void setName(String name):设置线程的名称
针对不是Thread类的子类中如何获取线程对象名称呢
public static Thread currentThread():返回当前正在执行的线程对象
Thread.currentThread().getName()
//带参构造,name为线程名
public MyThread(String name){
super(name);
}
设置线程名的两种方式
通过getName和setName设置
通过Thread的子类实现Thread类的带参构造
//通过Thread的子类实现Thread类的带参构造
//带参构造,name为线程名
public MyThread(String name){
super(name);
}
//带参构造方法给线程起名字
MyThread my1 = new MyThread("线程1");
MyThread my2 = new MyThread("线程2");
public class MyThread extends Thread {
public MyThread() {
}
//带参构造,name为线程名
public MyThread(String name){
super(name);
}
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
// 创建线程对象
//无参构造+setXxx()
// MyThread my1 = new MyThread();
// MyThread my2 = new MyThread();
// //调用方法设置名称
// my1.setName("线程1");
// my2.setName("线程2");
// my1.start();
// my2.start();
//带参构造方法给线程起名字
// MyThread my1 = new MyThread("线程1");
// MyThread my2 = new MyThread("线程2");
// my1.start();
// my2.start();
//public static Thread currentThread():返回当前正在执行的线程对象
System.out.println(Thread.currentThread().getName());
}
}
线程优先级
获取线程对象的优先级
public final int getPriority():返回线程对象的优先级
设置线程对象的优先级呢
public final void setPriority(int newPriority):更改线程的优先级
注意:
线程默认优先级是5
线程优先级的范围是:1-10
线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果
IllegalArgumentException:非法参数异常,抛出的异常表明向方法传递了一个不合法或不正确的参数
public class ThreadPriority extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
public class ThreadPriorityDemo {
public static void main(String[] args) {
ThreadPriority tp1 = new ThreadPriority();
ThreadPriority tp2 = new ThreadPriority();
ThreadPriority tp3 = new ThreadPriority();
tp1.setName("线程1");
tp2.setName("线程2");
tp3.setName("线程3");
// 获取默认优先级
// System.out.println(tp1.getPriority());
// System.out.println(tp2.getPriority());
// System.out.println(tp3.getPriority());
// 设置线程优先级
// tp1.setPriority(100000);
//IllegalArgumentException:非法参数异常,抛出的异常表明向方法传递了一个不合法或不正确的参数
//设置正确的线程优先级
tp1.setPriority(10);
tp2.setPriority(1);
tp1.start();
tp2.start();
tp3.start();
}
}
线程休眠
public static void sleep(long millis):线程休息millis毫秒
public class ThreadSleep extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x + ",日期:" + new Date());
// 休眠1秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadSleepDemo {
public static void main(String[] args) {
ThreadSleep ts1 = new ThreadSleep();
ThreadSleep ts2 = new ThreadSleep();
ThreadSleep ts3 = new ThreadSleep();
ts1.setName("线程1");
ts2.setName("线程2");
ts3.setName("线程3");
ts1.start();
ts2.start();
ts3.start();
}
}
线程加入
public final void join():等待该线程终止
public class ThreadJoin extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
public class ThreadJoinDemo {
public static void main(String[] args) {
ThreadJoin tj1 = new ThreadJoin();
ThreadJoin tj2 = new ThreadJoin();
ThreadJoin tj3 = new ThreadJoin();
tj1.setName("线程1");
tj2.setName("线程2");
tj3.setName("线程3");
tj1.start();
try {
tj1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//表示只有线程1执行完毕之后,线程2和线程3才开始执行
tj2.start();
tj3.start();
}
}
线程礼让
public static void yield():暂停当前正在执行的线程对象,并执行其他线程,让多个线程的执行更和谐,但不能保证每个线程一人一次
public class ThreadYield extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
Thread.yield();
}
}
}
public class ThreadYieldDemo {
public static void main(String[] args) {
ThreadYield ty1 = new ThreadYield();
ThreadYield ty2 = new ThreadYield();
ty1.setName("线程1");
ty2.setName("线程2");
ty1.start();
ty2.start();
}
}
后台线程
public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程,true为守护线程
当正在运行的线程都是守护线程时,Java 虚拟机退出,该方法必须在启动线程前调用
public class ThreadDaemon extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
public class ThreadDaemonDemo {
public static void main(String[] args) {
ThreadDaemon td1 = new ThreadDaemon();
ThreadDaemon td2 = new ThreadDaemon();
td1.setName("关羽");
td2.setName("张飞");
// 设置守护线程,true为守护
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();
Thread.currentThread().setName("刘备");
for (int x = 0; x < 5; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
线程中断
public final void stop():让线程停止,过时了,但是还可以使用
public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException
public class ThreadStop extends Thread {
@Override
public void run() {
System.out.println("开始执行:" + new Date());
// 我要休息10秒钟
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("线程被终止了");
}
System.out.println("结束执行:" + new Date());
}
}
public class ThreadStopDemo {
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();
// 你超过三秒不醒来,我就中断你
try {
Thread.sleep(3000);
// ts.stop();会让线程后的东西不在执行,直接结束
ts.interrupt();//还可以继续执行后边的语句,只是把线程终止了
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程的生命周期
实现多线程的方式二
实现Runnable接口
步骤:
自定义类MyRunnable实现Runnable接口
重写run()方法
创建MyRunnable类的对象
创建Thread类的对象,并把MyRunnable类的对象作为构造参数传递
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
// 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
public class MyRunnableDemo {
public static void main(String[] args) {
// 创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
// 创建Thread类的对象,并把C步骤的对象作为构造参数传递
// Thread(Runnable target)
// Thread t1 = new Thread(my);
// Thread t2 = new Thread(my);
// t1.setName("线程1");
// t2.setName("线程2");
// Thread(Runnable target, String name)
Thread t1 = new Thread(my, "线程1");
Thread t2 = new Thread(my, "线程2");
t1.start();
t2.start();
}
}
多线程是实现方式二的好处:
可以避免单继承带来的局限性,比如一个类本身已经继承另外一个类了,还想实现多线程,不太可能让该类的父类区继承多线程类
适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效的分离,较好的体现了面向对象的思想
多线程的应用
/*
* 某电影院目前正在上映贺岁大片(红高粱,少林寺传奇藏经阁),共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
* 继承Thread类来实现。
*/
public class SellTicket extends Thread {
// 定义100张票
// private int tickets = 100;
// 为了让多个线程对象共享这100张票,我们其实应该用静态修饰
private static int tickets = 100;
@Override
public void run() {
// 定义100张票
// 每个线程进来都会走这里,这样的话,每个线程对象相当于买的是自己的那100张票,这不合理,所以应该定义到外面
// int tickets = 100;
// 是为了模拟一直有票
while (true) {
if (tickets > 0) {
System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
// 创建三个线程对象
SellTicket st1 = new SellTicket();
SellTicket st2 = new SellTicket();
SellTicket st3 = new SellTicket();
// 给线程对象起名字
st1.setName("窗口1");
st2.setName("窗口2");
st3.setName("窗口3");
// 启动线程
st1.start();
st2.start();
st3.start();
}
}
/*
* 实现Runnable接口的方式实现
*/
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第"+ (tickets--) + "张票");
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个线程对象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
/*
* 实现Runnable接口的方式实现
*
* 通过加入延迟后,就产生了连个问题:
* A:相同的票卖了多次
* CPU的一次操作必须是原子性的
* B:出现了负数票
* 随机性和延迟导致的
*/
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 100;
// @Override
// public void run() {
// while (true) {
// // t1,t2,t3三个线程
// // 这一次的tickets = 100;
// if (tickets > 0) {
// // 为了模拟更真实的场景,我们稍作休息
// try {
// Thread.sleep(100); // t1就稍作休息,t2就稍作休息
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//
// System.out.println(Thread.currentThread().getName() + "正在出售第"
// + (tickets--) + "张票");
// // 理想状态:
// // 窗口1正在出售第100张票
// // 窗口2正在出售第99张票
// // 但是呢?
// // CPU的每一次执行必须是一个原子性(最简单基本的)的操作。
// // 先记录以前的值
// // 接着把ticket--
// // 然后输出以前的值(t2来了)
// // ticket的值就变成了99
// // 窗口1正在出售第100张票
// // 窗口2正在出售第100张票
//
// }
// }
// }
@Override
public void run() {
while (true) {
// t1,t2,t3三个线程
// 这一次的tickets = 1;
if (tickets > 0) {
// 为了模拟更真实的场景,我们稍作休息
try {
Thread.sleep(100); //t1进来了并休息,t2进来了并休息,t3进来了并休息,
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (tickets--) + "张票");
//窗口1正在出售第1张票,tickets=0
//窗口2正在出售第0张票,tickets=-1
//窗口3正在出售第-1张票,tickets=-2
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个线程对象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
解决线程安全的思想
要想解决问题,就要知道哪些原因会导致出问题:(而且这些原因也是以后我们判断一个程序是否会有线程安全问题的标准)
是否是多线程环境
是否有共享数据
是否有多条语句操作共享数据
同步代码块
synchronized(对象){
需要同步的代码;
}
对象是什么呢
我们可以随便创建一个对象试试。
需要同步的代码是哪些呢?
把多条语句操作共享数据的代码的部分给包起来
注意:
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能
多个线程必须是同一把锁
同步的特点:
前提:
多个线程
解决问题的时候要注意:
多个线程使用的是同一个锁对象
同步的好处
同步的出现解决了多线程的安全问题
同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
如果出现了同步嵌套就容易出现死锁的问题
同步代码块的锁对象是任意对象
同步方法的格式及锁对象问题
格式把同步关键字加在方法上
同步方法是this
静态方法及锁对象问题?
静态方法的锁对象是类的字节码文件对象
电影票问题的解决
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 100;
//创建锁对象
private Object obj = new Object();
// @Override
// public void run() {
// while (true) {
// synchronized(new Object()){//要同一把锁
// if (tickets > 0) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName() + "正在出售第"
// + (tickets--) + "张票");
// }
// }
// }
// }
@Override
public void run() {
while (true) {
synchronized (obj) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票");
}
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个线程对象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
//解释
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 100;
// 定义同一把锁
private Object obj = new Object();
@Override
public void run() {
while (true) {
// t1,t2,t3都能走到这里
// 假设t1抢到CPU的执行权,t1就要进来
// 假设t2抢到CPU的执行权,t2就要进来,发现门是关着的,进不去。所以就等着。
// 门(开,关)
synchronized (obj) { // 发现这里的代码将来是会被锁上的,所以t1进来后,就锁了。(关)
if (tickets > 0) {
try {
Thread.sleep(100); // t1就睡眠了
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
//窗口1正在出售第100张票
}
} //t1就出来可,然后就开门。(开)
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 线程安全的类
StringBuffer sb = new StringBuffer();
Vector v = new Vector();
Hashtable h = new Hashtable();
// Vector是线程安全的时候才去考虑使用的,但是我还说过即使要安全,我也不用你
// 那么到底用谁呢?
// public static List synchronizedList(List list)
List list1 = new ArrayList();// 线程不安全
List list2 = Collections
.synchronizedList(new ArrayList()); // 线程安全
}
}
Day24
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock
void lock(): 获取锁
void unlock():释放锁
ReentrantLock是Lock的实现类
电影票用Lock改进
public class SellTicket implements Runnable {
// 定义票
private int tickets = 100;
// 定义锁对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
// 加锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票");
}
} finally {
// 释放锁
lock.unlock();
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个窗口
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
死锁问题
同步的弊端:
效率低
容易产生死锁
死锁:两个或两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象
public class MyLock {
// 创建两把锁对象
public static final Object objA = new Object();
public static final Object objB = new Object();
}
public class DieLock extends Thread {
private boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (MyLock.objA) {
System.out.println("if objA");
synchronized (MyLock.objB) {
System.out.println("if objB");
}
}
} else {
synchronized (MyLock.objB) {
System.out.println("else objB");
synchronized (MyLock.objA) {
System.out.println("else objA");
}
}
}
}
}
public class DieLockDemo {
public static void main(String[] args) {
DieLock dl1 = new DieLock(true);
DieLock dl2 = new DieLock(false);
dl1.start();
dl2.start();
}
}
线程间的通信问题
不同种类的线程针对同一种资源的操作
多线程之生产者消费者的问题
/*问题1:按照思路写代码,发现数据每次都是:null---0
* 原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
* 如何实现呢
* 在外界把这个数据创建出来,通过构造方法传递给其他的类
*/
//学生类
public class Student {
String name;
int age;
}
//设置类(生产者)
public class SetThread implements Runnable {
private Student s;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
// Student s = new Student();
s.name = "张三";
s.age = 27;
}
}
//获取类(消费者)
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
// Student s = new Student();
System.out.println(s.name + "---" + s.age);
}
}
//测试类
public class StudentDemo {
public static void main(String[] args) {
//创建资源
Student s = new Student();
//设置和获取的类
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//线程类
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//启动线程
t1.start();
t2.start();
}
}
问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题
同一个数据出现多次
姓名和年龄不匹配
原因:
同一个数据出现多次
CPU的一点点时间片的执行权,就足够你执行很多次
姓名和年龄不匹配
线程运行的随机性
线程安全问题:
是否是多线程环境 是
是否有共享数据 是
是否有多条语句操作共享数据 是
解决方案:
加锁
注意:
不同种类的线程都要加锁
不同种类的线程加的锁必须是同一把
public class Student {
String name;
int age;
}
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if (x % 2 == 0) {
s.name = "张三";//刚走到这里,就被别人抢到了执行权
s.age = 27;
} else {
s.name = "李四"; //刚走到这里,就被别人抢到了执行权
s.age = 30;
}
x++;
}
}
}
}
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
System.out.println(s.name + "---" + s.age);
}
}
}
}
public class StudentDemo {
public static void main(String[] args) {
//创建资源
Student s = new Student();
//设置和获取的类
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//线程类
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//启动线程
t1.start();
t2.start();
}
}
等待唤醒机制
等待唤醒:
Object类中提供了三个方法:
wait():等待,还可以释放锁
notify():唤醒单个线程
notifyAll():唤醒所有线程
为什么这些方法不定义在Thread类中呢?
这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象,所以这些方法必须定义在Object类中
问题3:虽然数据安全了,但是呢,一次一大片不好看,我就想依次的一次一个输出,如何实现呢?
通过Java提供的等待唤醒机制解决
public class Student {
String name;
int age;
boolean flag; // 默认情况是没有数据,如果是true,说明有数据
}
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
//判断有没有
if(s.flag){
try {
s.wait(); //t1等着,释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (x % 2 == 0) {
s.name = "张三";
s.age = 27;
} else {
s.name = "李四";
s.age = 30;
}
x++; //x=1
//修改标记
s.flag = true;
//唤醒线程
s.notify(); //唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。
}
//t1有,或者t2有
}
}
}
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if(!s.flag){
try {
s.wait(); //t2就等待了。立即释放锁。将来醒过来的时候,是从这里醒过来的时候
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + "---" + s.age);
//张三---27
//李四---30
//修改标记
s.flag = false;
//唤醒线程
s.notify(); //唤醒t1
}
}
}
}
public class StudentDemo {
public static void main(String[] args) {
//创建资源
Student s = new Student();
//设置和获取的类
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//线程类
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//启动线程
t1.start();
t2.start();
}
}
最终版代码
把Student的成员变量给私有的了
把设置和获取的操作给封装成了功能,并加了同步
设置或者获取的线程里面只需要调用方法即可
public class Student {
private String name;
private int age;
private boolean flag; // 默认情况是没有数据,如果是true,说明有数据
public synchronized void set(String name, int age) {
// 如果有数据,就等待
if (this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 设置数据
this.name = name;
this.age = age;
// 修改标记
this.flag = true;
this.notify();
}
public synchronized void get() {
// 如果没有数据,就等待
if (!this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 获取数据
System.out.println(this.name + "---" + this.age);
// 修改标记
this.flag = false;
this.notify();
}
}
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
s.get();
}
}
}
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
if (x % 2 == 0) {
s.set("张三", 27);
} else {
s.set("李四", 30);
}
x++;
}
}
}
public class StudentDemo {
public static void main(String[] args) {
//创建资源
Student s = new Student();
//设置和获取的类
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//线程类
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//启动线程
t1.start();
t2.start();
}
}
线程状态转换及常见的执行情况
线程组
线程组: 把多个线程组合到一起,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制
ThreadGroup(String name)创建一个新的线程组
例ThreadGroup tg = new ThreadGroup("这是一个新的组");
线程类里面的方法:public final ThreadGroup getThreadGroup() 获取当前线程的线程组
线程组里面的方法:public final String getName() 得到当前线程组的名称,线程默认情况下属于main线程组
public class ThreadGroupDemo {
public static void main(String[] args) {
// method1();
// 我们如何修改线程所在的组呢?
// 创建一个线程组
// 创建其他线程的时候,把其他线程的组指定为我们自己新建线程组
method2();
// t1.start();
// t2.start();
}
private static void method2() {
// ThreadGroup(String name)
ThreadGroup tg = new ThreadGroup("这是一个新的组");
MyRunnable my = new MyRunnable();
// Thread(ThreadGroup group, Runnable target, String name)
Thread t1 = new Thread(tg, my, "林青霞");
Thread t2 = new Thread(tg, my, "刘意");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
//通过组名称设置后台线程,表示该组的线程都是后台线程
tg.setDaemon(true);
}
private static void method1() {
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my, "张三");
Thread t2 = new Thread(my, "李四");
// 我不知道他们属于那个线程组,我想知道,怎么办
// 线程类里面的方法:public final ThreadGroup getThreadGroup()
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
// 线程组里面的方法:public final String getName()
String name1 = tg1.getName();
String name2 = tg2.getName();
System.out.println(name1);
System.out.println(name2);
// 通过结果我们知道了:线程默认情况下属于main线程组
// 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组
System.out.println(Thread.currentThread().getThreadGroup().getName());
}
}
线程池
线程池的好处:线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
如何实现线程的代码呢?
创建一个线程池对象,控制要创建几个线程对象。Executors的newFixedThreadPool方法
public static ExecutorService newFixedThreadPool(int nThreads),int 表示可以创建几个线程
这种线程池的线程可以执行:
可以执行Runnable对象或者Callable对象代表的线程
做一个类实现Runnable接口
调用如下方法即可
Future> submit(Runnable task)
我就要结束,可以吗?
可以,通过shutdown()方法
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
public class ExecutorsDemo {
public static void main(String[] args) {
// 创建一个线程池对象,控制要创建几个线程对象。
// public static ExecutorService newFixedThreadPool(int nThreads)
ExecutorService pool = Executors.newFixedThreadPool(2);//2代表的创建两个线程
// 可以执行Runnable对象或者Callable对象代表的线程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//结束线程池
pool.shutdown();
}
}
多线程程序的实现方案三实现Callable接口
代码比较复杂,不常用
//Callable:是带泛型的接口。
//这里指定的泛型其实是call()方法的返回值类型。
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
return null;
}
}
public class CallableDemo {
public static void main(String[] args) {
//创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
//可以执行Runnable对象或者Callable对象代表的线程
pool.submit(new MyCallable());
pool.submit(new MyCallable());
//结束
pool.shutdown();
}
}
多线程方式求和
public class MyCallable implements Callable {
private int number;
public MyCallable(int number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int x = 1; x <= number; x++) {
sum += x;
}
return sum;
}
}
public class CallableDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象或者Callable对象代表的线程
Future f1 = pool.submit(new MyCallable(100));
Future f2 = pool.submit(new MyCallable(200));
// V get()
Integer i1 = f1.get();
Integer i2 = f2.get();
System.out.println(i1);
System.out.println(i2);
// 结束
pool.shutdown();
}
}
匿名内部类实现多线程
匿名内部类的格式:
new 类名或者接口名() {
重写方法;
};
本质:是该类或者接口的子类对象
public class ThreadDemo {
public static void main(String[] args) {
// 继承Thread类来实现多线程
new Thread() {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":"
+ x);
}
}
}.start();
// 实现Runnable接口来实现多线程
new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":"
+ x);
}
}
}) {
}.start();
}