数学类API
绝对值:
System.out.println(Math.abs(-12));
最大值:
System.out.println(Math.max(1,2));
最小值:
System.out.println(Math.min(1,2));
求次幂:
System.out.println(Math.pow(3,3)); //3*3=9
四舍五入:
System.out.println(Math.round(4.7)); //四舍五入
求平方根:
System.out.println(Math.sqrt(9)); //3 平方根
例:解一元二次方程组
public class TextMath {
public static void main(String[] args) {
System.out.println("计算一元二次方程的解:");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入a:");
double a = scanner.nextInt();
System.out.println("请输入b:");
double b = scanner.nextInt();
System.out.println("请输入c:");
double c = scanner.nextInt();
double dt = Math.pow(b,2) - 4*a*c;
if (dt<0){
System.out.println("无解");
}else if (dt==0){
System.out.println("此方程只有一个解");
double x = (-b) / (2*a);
System.out.println("解:x=" + x);
}else {
System.out.println("此方程有两个解");
double x1 = (-b+Math.sqrt(dt)) / (2*a);
double x2 = (-b-Math.sqrt(dt)) / (2*a);
System.out.println("解:x1=" + x1);
System.out.println("解:x2=" + x2);
}
}
}
基本数据类型包装类
String a = "1";
String b = "2";
System.out.println(a+b); //11
int c = Integer.parseInt(a); //字符串转化为int唯一的方案
int d = Integer.parseInt(b);
System.out.println(a+b); //2
String e = "1.25";
double f = Double.parseDouble(e); //字符串转化为double
System.out.println(f*6);
Java中的时间
Date 日期
new Date()获取到系统时间
getTime()获取到时间的long的表示形式,可以用来计算时间差
不擅长时间加减法
Date d = new Date();
System.out.println(d); //获取当前系统时间:Sat Nov 09 23:04:17 HKT 2024
System.out.println(d.getYear() + 1900); //从1900年开始算 124
System.out.println(d.getMonth() + 1); //月份从0开始
System.out.println(d.getHours());//小时
System.out.println(d.getMinutes());//分钟
System.out.println(d.getSeconds());//秒
//重点
System.out.println(d.getTime());//获取到时间的毫秒表示形式 返回的是long
Calendar 日历
get和set
最主要的作用是计算时间
Calendar cal = Calendar.getInstance();
cal.set(Calendar.DATE, cal.get(Calendar.DATE) +130); //获取当前日期添加130天
System.out.println(cal); //全部信息
System.out.println(cal.get(Calendar.YEAR)); //获取年份
System.out.println(cal.get(Calendar.MONTH + 1)); //获取月份 都是从0开始
System.out.println(cal.get(Calendar.DATE)); //获取日期
System.out.println(cal.get(Calendar.HOUR_OF_DAY)); //获取小时
System.out.println(cal.get(Calendar.MINUTE)); //获取分钟
System.out.println(cal.get(Calendar.SECOND)); //获取秒
}
时间格式化
将时间的显示更加美观
SimpleDateFormat
格式:yyy-MM-dd HH:mm:ss
format(Date) 可以将时间转化为字符串
Date d = new Date();
System.out.println(d); //Sun Nov 10 18:04:37 HKT 2024
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-ss HH:mm:ss");
String s = sdf.format(d); //格式化时间 按照上方格式拼凑
System.out.println(s); //2024-11-04 18:24:04
parse(String) 将字符串转化为时间
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个时间 yyy-MM-dd HH:mm:ss");
String s = sc.nextLine(); //字符串
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse(s); //将字符串转化为时间
System.out.println(d);
/*
请输入一个时间 yyy-MM-dd HH:mm:ss
2024-11-10 18:18:20
Sun Nov 10 18:18:20 HKT 2024
*/
计算时间差
String s1 = "2024-11-11 11:11:00";
String s2 = "2024-11-11 12:12:00";
//格式化时间的工具
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//将字符串转化为时间
Date d1 = sdf.parse(s1);
Date d2 = sdf.parse(s2);
//转化为long类型的时间
long l1 = d1.getTime();
long l2 = d2.getTime();
long diff = Math.abs(l1 - l2);//计算毫秒时间差 求绝对值
long diffSec = diff/1000;//秒级别的时间差
long diffMin = diffSec/60;//分钟级别的时间差
System.out.println(diffMin); //输出时差(分钟)
long diffHourDisPlay = diffMin / 60; //小时
long diffMinDisplay = diffMin % 60; //分钟
System.out.println("时间差为:" + diffHourDisPlay + "小时" + diffMinDisplay + "分钟");
//输出:时间差为:1小时1分钟
字符串详解
public class TestStr {
public static void main(String[] args) {
String s = "零一二三四五六七八九十";
System.out.println(s.charAt(0));//获取到第0个位置的字符串 零
s.concat("哈哈哈哈");//在字符串后面拼接
System.out.println(s);//无反应
//字符串是不可变的数据类型
//几乎所有的字符串操作都会返回一个新字符串
String s1 = s.concat("哈哈哈哈");
System.out.println(s1);
System.out.println(s.contains("零")); //判断字符串内是否包含这个东西 返回值为true和false
System.out.println("我很开心".endsWith("开心")); //判断是否以xxx结尾
System.out.println("真是太厉害了".startsWith("开心")); //判断是否以xxx开始
// String code = "KwFm";
// Scanner sc = new Scanner(System.in);
// System.out.println("请输入验证码:" + code);
// String userInput = sc.nextLine();
// if (code.equalsIgnoreCase(userInput)){ //判断左右两端内容忽略大小写后是否一致
// System.out.println("Yes");
// }else {
// System.out.println("No");
// }
System.out.println(s.indexOf("五")); //计算给出字符串出现的位置
System.out.println(s.length()); //返回字符串的字符数量
String s3 = "我是_彭于晏";
String s31 = s3.replace("彭于晏","吴彦祖"); //替换字符串
System.out.println(s31);
String[] s4 = s3.split("_"); //将字符串从下划线切割
System.out.println(s4[0]); //我是
System.out.println(s4[1]); //彭于晏
System.out.println(s3.substring(3,6)); //截取字符串 输出:彭于晏 左闭右开可以取到3取不到6
String s5 = " 哈哈哈 哈哈哈 ";
System.out.println(s5.trim()); //去除前后空格
int i =100;
System.out.println(String.valueOf(i)); //转换为字符串
System.out.println(i+""); //野路子方法也可以实现
}
}
StringBuilder和StringBuffer
String是一个不可变的类型,StringBuilder和StringBuffer是可变的字符串
StringBuffer 不可共享,StringBuilder不可共享。参考下方线程同步内。
StringBuilder可替换为StringBuffer 可视为一模一样
StringBuilder sb = new StringBuilder(); //一个空的字符串
StringBuilder sb2 = new StringBuilder("666666");
System.out.println(sb2);
sb2.append("7777777");//在后面拼接
System.out.println(sb2);
sb2.insert(3,"333");//插入
System.out.println(sb2);
//把StringBuilder转化为String
String s = sb2.toString();
System.out.println(s);
DecimalFormat
10 / 3.0 前int 后double 这样计算的结果很长 结果为3.3333333333333335
double d = 10 / 3.0;
System.out.println(d); //3.3333333333333335
// . 表示小数点
// 0和# 表示数字
DecimalFormat df = new DecimalFormat(".00"); //填写需要的格式 表示两位小数
String s = df.format(d);
System.out.println(s); //3.33
小任务
计算在规定时间内按键次数和频率
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
sc.nextLine(); //让程序暂停到这里了
//游戏开始
//准备游戏结束时间
Calendar cal = Calendar.getInstance();
cal.set(Calendar.SECOND,cal.get(Calendar.SECOND)+5); //在当前时间推迟20秒 SECOND 秒
Date end = cal.getTime(); //结束时间
long endLong = end.getTime(); //结束时间的时间戳
int count = 0;
//结束时间 - 当前时间 >=0 肯定还没结束
while (endLong - new Date().getTime() >=0){
sc.nextLine(); //停一下,等待用户输入enter
System.out.println("你按了");
count++;
}
//计算一共拿了几次
System.out.println("您在20秒内一共按了" + count + "次");
double pin = count/2.0; //计算频率
//将频率格式化
DecimalFormat df = new DecimalFormat(".00");
System.out.println("您的手速是" + df.format(pin) +"次/秒");
}
容器
容器:能装对象的对象
List 线性结构 可变长度
set集合 非线性 去除重复
Map 映射 以Key:value的形式存储数据 例:名字:电话
在Java中所有集合的根:collection接口
List
列表可以存放重复数据,按照我们的存放顺序进行存储。
ArrayList -> 列表
常用操作:
add() 添加
remove() 删除
size() 列表的大小(数量)
get(i) 获取到第i个元素 列表的下标也是0开始
contains() 判断列表是否包含xx元素
List list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
list.add("3");
System.out.println(list);
}
遍历list
记得强转数据
for (int i = 0; i < list.size(); i++) {
String s = (String) list.get(i); //从列表拿东西需要强转 向下转型 强转
System.out.println(s);
}
LinkedList -> 链表
使用最多的是Arraylist,基本上使用无区别,且Arraylist查询效率更高。
set集合
set
不允许有重复数据,数据是无序的,并不是按照添加顺序排列
Set s = new TreeSet();
s.add("a");
s.add("b");
s.add("c");
s.add("cd");
s.add("c"); //重复数据添加不进去的
System.out.println(s); //默认进行排序[a, b, c, cd]
TreeSet
默认排序
Set s = new TreeSet();
s.add("a");
s.add("b");
s.add("c");
s.add("cd");
s.add("c"); //重复数据添加不进去的
System.out.println(s); //默认进行排序[a, b, c, cd]
set操作
add()添加
remove()删除
contains() 判断列表是否包含xx元素,注意区分数据类型
Set s = new HashSet();
s.add(1);
s.add(2);
s.add(3);
s.add(4);
System.out.println(s.contains(1)); //true
System.out.println(s.contains("1")); //false
Map
key -> Value
HashMap
Map map = new HashMap();
map.put("jay","周杰伦");
map.put("wf","汪峰");
map.put("gem","邓紫棋");
map.put("tz","陶喆");
System.out.println(map);//{jay=周杰伦, tz=陶喆, gem=邓紫棋, wf=汪峰}
TreeMap
可以根据Key进行排序
Map map = new TreeMap(); //根据Key排序
map.put(11,"周杰伦");
map.put(33,"汪峰");
map.put(22,"邓紫棋");
map.put(66,"陶喆");
System.out.println(map);//{11=周杰伦, 22=邓紫棋, 33=汪峰, 66=陶喆}
Map操作
put(key,value) 存储数据
remove(key) 删除数据
size() map存储了多少个键值(key)
containsKey() / containsValue() 判断是否存在 Key / Value
KeySet() 将map中的key打包成set集合返回
get(key) 通过key查询到value
相同的key时后出现的会顶掉前方的key
练习:更换敏感词
List list = new ArrayList();
list.add("厉害");
list.add("牛逼");
list.add("真牛");
list.add("太强了");
Scanner sc = new Scanner(System.in);
String content = sc.nextLine(); //获取输入字符
for (int i = 0; i < list.size(); i++) {
String ci = (String) list.get(i);//拿到敏感词
//根据敏感词来更换敏感词长度
if (content.contains(ci)){
String s = "";
for (int j = 0; j < ci.length(); j++) {
s+="*";
}
content = content.replace(ci,s);
}
}
System.out.println(content);
}
迭代器lterator
Set集合
迭代:意思就是一个一个往外拿
核心操作:下一个
next()下一个 输出完毕继续输出会报错
hasnext()判断是否有下一个
Set set = new HashSet();
set.add("周杰伦");
set.add("王力宏");
set.add("蔡徐坤");
set.add("鹿晗");
Iterator it = set.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
List集合
List list = new ArrayList();
list.add("周杰伦");
list.add("王力宏");
list.add("蔡徐坤");
list.add("鹿晗");
//创建迭代器 创建一个医生
Iterator it = list.iterator();
// String s = (String) it.next();
// System.out.println(s); //周杰伦
// String s1 = (String) it.next();
// System.out.println(s1); //王力宏
// String s3 = (String) it.next();
// System.out.println(s3); //蔡徐坤
// String s4 = (String) it.next();
// System.out.println(s4); //鹿晗
// 输出完毕再写输出是会报错 此方法过于臃肿
while (it.hasNext()){ //判断是否有下一个
String s = (String) it.next();
System.out.println(s);
}
}
Map集合
先转化为Set集合
Map map = new HashMap();
map.put("1","周杰伦");
map.put("2","王力宏");
map.put("3","蔡徐坤");
map.put("4","鹿晗");
// //方案1
// Set set = map.keySet(); //拿到所有的Key
// Iterator it = set.iterator();
// while (it.hasNext()){
// String key = (String) it.next();
// System.out.println(map.get(key));
//方案2 推荐
Set set = map.entrySet(); //set里面装的是entry
Iterator it = set.iterator();
while (it.hasNext()){
Map.Entry entry = (Map.Entry) it.next();
System.out.println(entry.getKey()); //输出Key
System.out.println(entry.getValue()); //输出Value
泛型
规范容器内的数据类型
容器<数据类型>
Set 和 List的泛型规范是一样的
List<String> strList = new ArrayList<String>(); //规定数据类型
strList.add("哈哈哈"); //无异常
strList.add("呵呵呵"); //无异常
strList.add("嘿嘿嘿"); //无异常
// strList.add(123); //报错 不可以装int
String s = strList.get(1); //因为已经规定了数据类型,所以不需要强转了
System.out.println(s);
Map有所不同
Map<String,String> map = new HashMap<String,String>();
增强for
for (数据类型 变量: arr){ 方法体 }
优点:方法简单
缺点:看不见索引
String[] arr = {"洛杉矶","北京","迈阿密","夏威夷",};
for (String s: arr){
System.out.println(s);
}
List<String> list = new ArrayList<String>();
list.add("洛杉矶2");
list.add("北京2");
list.add("迈阿密2");
list.add("夏威夷2");
for (String obj:list){
System.out.println(obj);
}
collections工具类
常用的方法为排序
方法演示
List<Integer> list = new ArrayList<Integer>();
list.add(123);
list.add(456);
list.add(111);
list.add(222);
Integer min = Collections.min(list);//最小值
Integer max = Collections.max(list);
System.out.println("最小值"+ min + " 最大值:" +max);
Collections.sort(list); //对列表进行排序
Collections.shuffle(list); //打乱列表
Collections.addAll(list,444,785,789); //一次性全部加入到列表
一次性全部加入方法演示
//将数据列表更换为 ...
public static void chi(String... args) {
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
public static void main(String[] args) {
chi("黄瓜","西瓜","哈密瓜"); //这样可以一次性传输多个数据
}
中途练习
String s = "k:1,k1:2,k2:3,k3:4"; 处理成 {"k" = 1}
String s = "k:1,k1:2,k2:3,k3:4"; //处理成{"k" = 1}
String[] s1 = s.split(",");
Map<String,Integer> map = new HashMap<String,Integer>();
for (String s2:s1) {
String[] s3 = s2.split(":");
map.put(s3[0],Integer.parseInt(s3[1]));
}
System.out.println(map);
int[] li = {11,22,33,44,55,66,77,88,99,90};
Map<String,List<Integer>> map = new HashMap<String,List<Integer>>();
for (int n:li){
if (n >=66){
//判断是否存在key1 如果不存在创建一个新的
if (!map.containsKey("key1")){
map.put("key1",new ArrayList<>());
}
//确定map容器里有List
map.get("key1").add(n);
}else {
if (!map.containsKey("key2")){
map.put("key2",new ArrayList<>());
}
map.get("key2").add(n);
}
}
System.out.println(map); //{key1=[66, 77, 88, 99, 90], key2=[11, 22, 33, 44, 55]}
IO流
file文件操作
IO流 input和output System.in System.out
文件流
处理流:带有缓冲区的流 转换流 对象流
File类
表示操作系统中的文件或文件夹
文件的路径:
1.绝对路径 (很少使用)
从磁盘的根目录 例如 C:/adc/def/a.txt
2.相对路径
相对路径是相对于当前项目 文件夹/文件
相关操作
try {
File file = new File("abc/def/哈哈哈.txt"); //指向的文件可有可无不会报错
// file.createNewFile(); //创建一个文件,不包含文件夹
System.out.println(file.getParent()); //abc\def 拿到上一层文件夹的对象
System.out.println(file.getParent());
// file.mkdir();//new File("哈哈哈"); 只能创建单个文件夹
//创建文件夹一定使用下方这个,可以帮我们把上级目录创建出来
// file.mkdirs();//new File("哈哈哈/吼吼吼"); 可以创建多层文件
// file.renameTo(new File("abc/def/哈哈哈.txt")); //文件重命名
// file.delete();//删除文件
//查看相关
System.out.println(file.exists());//查看文件是否存在 返回为布尔值
System.out.println(file.isFile());//判断是否为绝对路径
System.out.println(file.isDirectory());//判断是否为文件夹
System.out.println(file.isFile());//判断是否为文件(只存在 文件/文件夹 两种)
System.out.println(file.length());//查看文件大小
System.out.println(file.getName());//查看文件名称
} catch (Exception e) {
throw new RuntimeException(e);
}
创建文件的完整步骤
try {
//创建文件的完整步骤
File file = new File("abc/def/吃饭.txt");
//1.判断上层文件夹是否存在
File parenFile = file.getParentFile(); //获取文件夹上层目录
if (!parenFile.exists()){ //如果不存在
parenFile.mkdirs();//创建上层文件夹
}
//2.创建文件
file.createNewFile();
} catch (Exception e) {
throw new RuntimeException(e);
}
节点流
1.按照读写的方向,分为输入流和输出流,要站在程序的角度去分析,而非文件
2.按照读写的内容的但未来讲,分为字节流和字符流
3.按照流的功能不同分为,节点流和处理流 处理流套在节点流之上
节点流:直接连接在文件上的
处理流:套在其他流上的 对流进一步处理 相当于净化器
以上四个是抽象类
文件流
FileInputStream 文件字节输入流
FileOutputStream
FileReader
FileWrite
FileInputStream 读取文件
// //创建流 有大小限制
FileInputStream fis = new FileInputStream(new File("a.txt"));
// byte[] bs = new byte[1024]; //创建字节数组 方便批量传入
// int len = fis.read(bs); //读取返回了多少个字节 否则后面会跟很多的方框
// System.out.println(new String(bs,0,len)); //将字节转换为字符串
//读取文件最重要的一套写法
byte[]bytes = new byte[1024];
int len = 0;
//仅限在IO流使用 判断长度是否等于-1 等于-1就是没有了
while ((len = fis.read(bytes)) !=-1 ){
String s = new String(bytes,0,len); //读取bytes 从0开始 到len结束
System.out.println(s);
}
//养成好习惯 关闭流
fis.close();
FileOutputStream写入文件
//FileOutputStream默认先把文件清空 添加true不会清空
FileOutputStream fos = new FileOutputStream(new File("a.txt"),true);
fos.write("666".getBytes()); //把字符串通过getBytes转化为字符数组写入
//好习惯
fos.flush(); //刷新管道
fos.close(); //关闭
字符流的区别
读取时区别byte[]bytes = new byte[1024];
写入时区别fos.write("666");
可以直接写入字符串
如何选择流
字符流:读取文件中的文字信息
字节流:读取非文本文件的时候
复制图片
使用IO流把一张图片从C盘复制到D盘
FileInputStream fis = new FileInputStream(new File("jp.png"));
FileOutputStream fos = new FileOutputStream(new File("pic/666.png"));
byte[] bs = new byte[1024];
int len = 0; //设置文件的长度
while ((len=fis.read(bs)) != -1){
fos.write(bs,0,len);
}
fis.close();
fos.flush();
fos.close();
简单修改实现剪切效果
File f = new File("jp.png");
FileInputStream fis = new FileInputStream(f);
FileOutputStream fos = new FileOutputStream(new File("pic/666.png"));
byte[] bs = new byte[1024];
int len = 0; //设置文件的长度
while ((len=fis.read(bs)) != -1){
fos.write(bs,0,len);
}
fis.close();
fos.flush();
fos.close();
f.delete();//实现剪切效果
处理流
1.缓冲流:带有缓冲区的数据流,将处理的数据放入缓冲区,后期统一发放(将水接满一杯再喝)
BufferedInputStream
BufferedOutputStream
BufferedReader -> 重点 读取文本文件最好的方法
BufferedWrite
public static void main(String[] args) throws Exception{
// BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("a.txt")));
BufferedReader br = new BufferedReader(new FileReader(new File("a.txt")));
// System.out.println(br.readLine()); //是读物文本文件最好的方法
String str = "";
while ((str = br.readLine()) != null){
System.out.println(str);
}
br.close();
}
2.转换流:将字节流转换为字符流
InputStreamReader
OutputStreamWriter
// Scanner sc = new Scanner(System.in); //System.in默认就是字节流、
//把字节流转换成字符流
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
System.out.println("从 System.in 接收到的信息是:" + s);
3.对象流
ObjectInputStream
ObjectOutputStream
报错:Exception in thread "main" java.io.NotSerializableException
原因:没有序列化
序列化:把一个对象转化成字节的过程
反序列化:把字节转化成对象
在Java中只需要给类添加一个实现接口 Serialiable 你就是可以被序列化一个类了
public class Person implements Serializable
写入
public static void main(String[] args) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("person.dat")));
Person p = new Person(1,"小程",18);
oos.writeObject(p);
oos.flush();
oos.close();
读取
public static void main(String[] args) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("person.dat")));
Object obj = ois.readObject();
Person p = (Person) obj;
System.out.println(p.getName()); //小程
文件修改操作(更替文字)
将下方内容中的李白更换为李太白
唐诗三百首
静夜思,李白,床前明月光,疑是地上霜。举头望明月,低头思故乡。
望庐山瀑布,李白,日照香炉生紫烟,遥看瀑布挂前川。飞流直下三千尺,疑是银河落九天。
早发白帝城,李白,朝辞白帝彩云间,千里江陵一日还。两岸猿声啼不住,轻舟已过万重山。
修改文件思路(偷梁换柱):逐行读取文件中的内容,根据内容进行替换,把替换的结果记录在一个新文件里面,直到数据写入完毕,把源文件删除,把新文件的名字改成源文件的名字。
public static void main(String[] args) throws Exception {
File res = new File("唐诗三百首.txt"); //将源文件拿出
File another = new File("副本_唐诗三百首.txt"); //副本文件
BufferedReader br = new BufferedReader(new FileReader(res)); // 获取文件
BufferedWriter bw = new BufferedWriter(new FileWriter(another)); //准备一个要写入的文件
String line = "";
while ((line = br.readLine()) != null){ //逐行读取不为空
line = line.replace("李白","李太白"); //将读取的数据中的李白更换为李太白
bw.write(line); //写入新文件
bw.newLine(); //因为是逐行读取,每次读完另起一行
}
br.close();
bw.flush();
bw.close();
//删除源文件
res.delete();
//重命名文件
another.renameTo(res);
}
修改水果信息并拼接
public static void main(String[] args) throws Exception{
DecimalFormat df = new DecimalFormat(".00"); //格式化小数
//1.能从文件中读取水果信息
//1.1需要创建输入流BufferedReader
//1.2第一行要特殊处理,第一行是表头
File f1 = new File("fruit.txt");
File f2 = new File("n_fruit.txt");
BufferedReader br = new BufferedReader(new FileReader(f1));
BufferedWriter bw = new BufferedWriter(new FileWriter(f2));
String title = br.readLine(); //读取第一行内容
bw.write(title+"_总价"); //先写入第一行内容
bw.newLine(); //新一行
//2.计算水果的总价格
//2.1读取所有水果的信息
String content = "";
while ((content = br.readLine()) != null){
String[] fs = content.split("_");
double price = Double.parseDouble(fs[1]); //获取价格
double num = Double.parseDouble(fs[2]); //获取数量
double totle = price * num; //计算总价
String totleStr = df.format(totle); //格式化小数后转换为字符串
content += "_" + totleStr; //拼接总价和_符号 香蕉_3.33_20_66.60
bw.write(content); //写入新数据
bw.newLine(); //新艺航
}
br.close();
bw.flush();
bw.close();
//3.删除源文件 重命名新文件
f1.delete();
f2.renameTo(f1);
}
多线程
操作系统概述
没有操作系统
没有操作系统时候我们编写Java程序是建立在一堆硬件之上的,然而我们无法直接链接硬件,需要硬件驱动才可以链接,当不同的硬件都需要编写不同的硬件驱动时,此时我们的学习成本过高较为麻烦。
有操作系统
我们可以直接寻找操作系统(中间人),这样可以节省我们需要寻找硬件的繁琐步骤、
存在的意义:帮助上层应用程序屏蔽掉硬件丑陋的接口
进程和线程:
进程:正在执行的程序,其实就是一块内存区域,内存区域存储着程序的资源,打开软件使用过程就是进程。
线程,程序被CPU调度的最小单位。
java多线程
实现方案
1.继承Thread类,冲刺额run方法,想做的东西写道run方法里面
主线程
public class TestThread1 {
public static void main(String[] args) { //主线程,其他运行的都是子线程
// 1.创建线程对象
MyThread mt = new MyThread();
// 2.调用start()方法启动一个线程
// mt.run(); //当前方法只是调用方法,不是启动线程
mt.start(); //启动线程 继承自Thread类,启动一个子线程 -> 子线程 ->自动执行run()方法
for (int i = 0; i < 1000; i++) {
System.out.println(">>>我是主线程" + i);
}
}
}
子线程
//继承Thread
public class MyThread extends Thread{
@Override
public void run() {
// 要把子线程执行的内容卸载run里
for (int i = 0; i < 1000; i++) {
System.out.println("我是子线程" + i);
}
}
}
执行时两个线程相互交替执行可以执行多个线程
2.实现Runnable接口,实现run方法
和第一个方法略微不同。
子线程
//继承Runnable
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("我是子线程" + i);
}
}
}
主线程
public class TestThread2 {
public static void main(String[] args) {
// 1.先创建Runnable对象
Runnable r = new MyRunnable();
// 2.创建线程对象,必须指向我的Runnable
Thread t = new Thread(r);
t.start();
for (int i = 0; i < 1000; i++) {
System.out.println(">>>我是主线程" + i);
}
}
}
同样可以实行多线程和结果交替运行
总结:无论哪种方法都需要有run()和start()。
run():线程执行的时候要执行的代码。
start():启动一个线程。
线程中的相关方法
1.setPriority() 设置优先级
一共 1 - 10 是个等级,默认为5,设置后不代表操作按照你的优先级进行操作,而仍然是系统自行选择。
2.sleep() 睡眠
Thread.sleep(1000);
1000毫秒等于1秒。
3.join() 让主线程等待这个子线程执行完毕
4.yieId() 让出CPU,让别人执行一下,具体旧看CPU让谁执行
5.interrupt() 打断正在睡眠中的线程
线程同步
加入我的账户有1000元钱,但是一个人去ATM机取钱,一个人去柜台取钱,这样会产生冲突,两个人同时取1000,可是卡里只有1000,如果都取会变成复数,而我们需要做的事当有一个人去取钱时,账户直接上锁,第二个人无法操作,这样的话就可以避免同时发生。这也就是线程同步,必须等其中一个线程执行完毕才可以执行另一个线程。
线程同步:当多个线程共享一个资源的时候,我们可以在某一个线程在访问到这个资源的时候,把这个资源暂时封锁,等待执行结束,释放这个锁,其他线程才可以进行执行。
总结:等待其他线程释放锁,让线程更加安全 。
取钱案例:
创建一个账户
public class Account {
private double balance; // 账户余额
public Account(double balance) { //外界传递一个钱
this.balance = balance;
}
public void getMoney(){
if(this.balance <= 0){
System.out.println("余额不足");
return; //回去
}
System.out.println("我要取钱了,目前剩下余额为::" + this.balance);
this.balance -= 1000;
System.out.println("取款结束,剩余余额为:" + this.balance);
}
}
创建一个取钱线程
public class GetMoneyThread extends Thread {
private Account acc;
public GetMoneyThread(Account acc) {
this.acc = acc; //保证柜台和ATM用的同一个账户
}
@Override
public void run() {
acc.getMoney();
}
}
创建主程序开始取钱
public static void main(String[] args) {
//创建一个账户
Account acc = new Account(1000);
//创建一个ATM线程
GetMoneyThread atm = new GetMoneyThread(acc);
//创建一个柜台的线程
GetMoneyThread table = new GetMoneyThread(acc);
//开始取钱
atm.start();
table.start();
}
这样的情况下会发生同时取钱的操作出现且if并没有成功拦截,这样的结果是不正确的
我要取钱了,目前剩下余额为::1000.0
我要取钱了,目前剩下余额为::1000.0
取款结束,剩余余额为:0.0
取款结束,剩余余额为:-1000.0
解决方法(1):
在方法声明上加一个synchronized
关键字。但是这样的话线程多了多线程会变得没有意义,所有的线程都在排队。但是大家都在用一个资源时可以使用。
//取钱 添加一个 synchronized方法
public synchronized void getMoney(){ //添加后一旦进入该方法,瞬间锁定acc
if(this.balance <= 0){
System.out.println("余额不足");
return; //回去
}
System.out.println("我要去签了,目前剩下余额为::" + this.balance);
this.balance -= 1000;
System.out.println("取款结束,剩余余额为:" + this.balance);
}
解决方法(2)推荐使用:
将某个具体的对象锁定,在方法内部使用synchronized{}
语句块对特定的对象上锁,更为灵活确定上锁区域。
//取钱 添加一个 synchronized方法
public void getMoney(){ //添加后一旦进入该方法,瞬间锁定acc
synchronized (this){
if(this.balance <= 0){
System.out.println("余额不足");
return; //回去
}
System.out.println("我要去签了,目前剩下余额为::" + this.balance);
this.balance -= 1000;
System.out.println("取款结束,剩余余额为:" + this.balance);
}
}
解决方法(3):
手动上锁,用的最少,上锁后还需要手动解锁,忘记后较为麻烦。
在Account方法中创建一个锁
private double balance; // 账户余额
private Lock lock = new ReentrantLock();
上锁和解锁
public void getMoney(){ //添加后一旦进入该方法,瞬间锁定acc
lock.lock();//进入方法的一瞬间,上锁
if(this.balance <= 0){
System.out.println("余额不足");
return; //回去
}
System.out.println("我要去签了,目前剩下余额为::" + this.balance);
this.balance -= 1000;
System.out.println("取款结束,剩余余额为:" + this.balance);
lock.unlock();//执行完毕后解锁
}
死锁
线程同步的弊端就是发生死锁
线程A锁定了资源1等待获取资源2,但是线程B锁定了资源2等待拿资源1,这样的情况下大家都锁住了各自的资源但又要获取对面的资源形成了死循环互相等待对方解锁这就是死锁。
使用synchronized
的时候一定要格外注意有没有互相调用的方法被锁定,精神直接在方法使用synchronized
线程的生命周期
1.创建线程 Thread t = new Thread();
此时只是创建了一个线程,CPU时没有动静的。
2.开启一个线程 t.start();
开启后不代表立刻执行,处于就绪状态。
3.当CPU调度到线程时则处于运行状态。
4.当多个线程时,可能第一个线程执行一会后再执行第二个线程,第二个线程执行一会再执行第一个线程,这个轮换的线程状态就是阻塞状态。
5.当线程运行完毕就处于消亡状态。
上方的状态都是因为CPU需要在不同线程之间来回切换形成的状态。
生产者和消费者模型
比如我们现在需要做一个色彩视频的检测,检测视频是否违反规定,然后将违规视频打包检举。
那我们的初步步骤就是 读取视频,对视频鉴定 ---->>将违规视频发送到相关部门
这其中就会发生一件事情,当我们读取视频时候,发送视频就会停下来等识别,而发送视频时候读取视频也会停下来等待发送视频,这样的话这个单线程模型就会特别的慢,
无论如何增加线程始终都会发生等待的问题,因为这里是一个串行的关系,因为前面不读取后面就无法发送。所以我们要优化逻辑。
异步操作:读取和鉴定视频 ---->> 存储盒子 ---->>发送到相关部门
这样我们把它分成两个不同的操作,前锋的步骤只读取和鉴定视频,然后存储到盒子里,而后面的发送视频只需要看盒子有没有视频,有就发送,没有就不管,两部分开执行互不干扰这样就是异步操作
这样的操作左侧读取则被称为生产者, 产品是<违规视频>,右边发送给相关部门是消费者,消费的是<视频>
案例
产品
//产品:视频
public class Video {
private String name;
public Video(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
读取视频
//读取视频的线程
public class ReadVideoThread extends Thread{
//线程安全数字 AtomicInteger 原子性的整数 他不可以直接使用i++
private static AtomicInteger i = new AtomicInteger(); //int i = 0
//Queue队列:BlockingQueue 阻塞队列,当队列没有数据的时候需要拿去数据,队列将会阻塞程序,阻塞到有数据再执行程序。
//准备能装视频的缓冲区(队列)
private BlockingDeque<Video>videoQueue;
//从外面传入一个视频缓冲区
public ReadVideoThread(BlockingDeque<Video> videoQueue){
this.videoQueue = videoQueue;
}
//生产视频
@Override
public void run() {
while (true){
String name = "测试视频" + i.incrementAndGet();
Video v = new Video(name); //.incrementAndGet()相当于i++
// i++; 线程不安全,可能会发生抢银行事件,三个生产者同时 i++
//添加到缓冲区
try {
videoQueue.put(v);
Thread.sleep(200);//睡一会
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发现了一个视频" + name);
}
}
}
发送视频
//获取视频的线程
public class SendVideoThread extends Thread{
//线程安全
private static AtomicInteger i = new AtomicInteger();
private BlockingDeque<Video> videosQueue;
public SendVideoThread(BlockingDeque<Video> videosQueue){
this.videosQueue = videosQueue;
}
@Override
public void run() {
while (true){
try {
Video video = videosQueue.take();
System.out.println("发送视频:::" + video.getName());
Thread.sleep(150); //让两个线程的执行效率不一样
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
测试类
public class TestVideo {
public static void main(String[] args) {
//前接口 后接口实现类
BlockingDeque<Video> videos = new LinkedBlockingDeque<Video>();
//创建三个读取视频线程
ReadVideoThread rvt1 = new ReadVideoThread(videos);
ReadVideoThread rvt2 = new ReadVideoThread(videos);
ReadVideoThread rvt3 = new ReadVideoThread(videos);
//创建2个发送视频的
SendVideoThread syt1 = new SendVideoThread(videos);
SendVideoThread syt2 = new SendVideoThread(videos);
//启动五个线程
rvt1.start();
rvt2.start();
rvt3.start();
syt1.start();
syt2.start();
}
}
作业
使用Java中的多线程来模拟火车票的多个窗口,假设一共有1000张票,分为4个窗口同时售出,请模拟出每个窗口售出的票。
票张数据
public class Traintc {
private int id;
private String name;
public Traintc(int id, String name) {
this.id = id;
this.name = name;
}
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 class TableThread extends Thread{
private static AtomicInteger i = new AtomicInteger();
//接收车票数组
private List<Traintc>traintList;
public TableThread(List<Traintc> traintList,String name){
this.traintList = traintList;
super.setName(name); //设置窗口的名字
}
@Override
public void run() {
while (true){
if (i.get() + 4 < traintList.size()){
try {
Thread.sleep(new Random().nextInt(150)); //让随机睡眠时长 150
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Traintc t = traintList.get(i.getAndIncrement()); //i++
System.out.println(super.getName() + "卖出:" + t.getName());
}else {
System.out.println("卖完了");
break;
}
}
}
}
测试类
public class TestSell {
public static void main(String[] args) {
//创建1000张票
List<Traintc> traintcList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
Traintc t = new Traintc(i,"火车票" + i);
traintcList.add(t);
}
//创建窗口
TableThread tt1 = new TableThread(traintcList,"一号窗口");
TableThread tt2 = new TableThread(traintcList,"二号窗口");
TableThread tt3 = new TableThread(traintcList,"三号窗口");
TableThread tt4 = new TableThread(traintcList,"四号窗口");
tt1.start();
tt2.start();
tt3.start();
tt4.start();
}
}
end
评论区