第一章.String
1.String介绍
1.概述:String是一个类,代表的是字符串类型
2.特点:
a.Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现
凡是带双引号的,都是String类的对象
b.字符串是常量,它们的值在创建之后不能更改
String s1 = "hello"
s1+="world"
c.因为 String 对象是不可变的,所以可以共享
String s1 = "abc"
String s2 = "abc"
2.String的实现原理
1.String的实现原理:
底层是一个被final修饰的数组
2.jdk8:
String底层是char数组
jdk9以及以后:
String底层是byte数组
char类型占内存2个字节
byte类型占内存1个字节
3.我们定义的字符串,都会自动装载到String底层的数组中
比如:
String s = "abc"
char[] chars = {'a','b','c'}
private final char value[];
数组的前面修饰符为final,为最终的数据,数组一旦建立,数组的地址就被锁死(相当于是一个常量)
3.String的创建
1.构造:
String() -> 利用空参构造创建String对象
String(char[] value)-> 根据char数组创建String对象
String(String original) ->根据字符串创建String对象
String(byte[] bytes) ->通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
默认平台:当前操作系统 -> GBK
2.简化:
String 变量名 = ""
public class Test01 {
public static void main(String[] args) {
//String() -> 利用空参构造创建String对象
String s1 = new String();
System.out.println(s1);
//String(char[] value)-> 根据char数组创建String对象
char[] chars = {'a','b','c'};
String s2 = new String(chars);
System.out.println(s2);
//String(String original) ->根据字符串创建String对象
String s3 = new String("abc");
System.out.println(s3);
/*
中文对应的字节都是负数
String(byte[] bytes) ->通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
默认平台:当前操作系统 -> GBK
GBK: 一个中文,占2个字节
UTF-8: 一个中文,占3个字节
idea启动时,是按照UTF-8编码去运行->在运行时 idea会添加一个启动参数:-Dfile.encoding=UTF-8
*/
byte[] bytes = {-28, -67, -96, -27, -91, -67};
String s4 = new String(bytes);
System.out.println(s4);//你好
byte[] bytes1 = {97,98};
String s5 = new String(bytes1);
System.out.println(s5);//ab
}
}
扩展构造:
String(char[] value, int offset, int count) -> 将char数组的一部分转成String对象
value:要转的char数组
offset:从数组的哪个索引开始转
count:转多少个字符
String(byte[] bytes, int offset, int length) -> 将byte数组的一部分转成String对象
bytes:要转的byte数组
offset:从数组的哪个索引开始转
length:转多少个字节
String(byte[] bytes, String charsetName) -> 按照指定的字符集将byte数组转成字符串
charsetName:传递的是字符编码,不区分大小写
public class Test02 {
public static void main(String[] args) throws UnsupportedEncodingException {
/* String(char[] value, int offset, int count) -> 将char数组的一部分转成String对象
value:要转的char数组
offset:从数组的哪个索引开始转
count:转多少个字符
*/
char[] chars = {'a','b','c','d'};
String s1 = new String(chars,1,2);
System.out.println(s1);
/*String(byte[] bytes, int offset, int length) -> 将byte数组的一部分转成String对象
bytes:要转的byte数组
offset:从数组的哪个索引开始转
length:转多少个字节*/
byte[] bytes = {97,98,99,100,101};
String s2 = new String(bytes,0,4);
System.out.println(s2);
/*
String(byte[] bytes, String charsetName) -> 按照指定的字符集将byte数组转成字符串
charsetName:传递的是字符编码,不区分大小写
*/
byte[] bytes1 = {-28, -67, -96, -27, -91, -67};
String s3 = new String(bytes1,"gbk");
System.out.println(s3);
}
}
new String要主要导包,我们使用的是lang包下的String
4.String 面试题
public class Test03 {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
System.out.println(s1==s2);//true
System.out.println(s1==s3);//false
System.out.println(s2==s3);//false
}
}
问题:String s = new String("abc")-> 在这个过程中产生了几个对象?
答: 1个或者2个
5.字符串常见问题
public class Test04 {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
String s4 = "hello"+"world";
String s5 = s1+"world";
String s6 = s1+s2;
System.out.println(s3==s4);//true
System.out.println(s3==s5);//false
System.out.println(s3==s6);//false
System.out.println(s5==s6);//false
}
}
1.字符串拼接,如果等号右边都是字符串字面值,不会创建新对象
2.字符串拼接,如果等号右边有变量参与拼接,会创建新对象
第二章.String的方法
1.判断方法
boolean equals(Object anObject) -> 比较字符串内容
boolean equalsIgnoreCase(String anotherString) -> 比较字符串内容,忽略大小写
public class Test01_If {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
//boolean equals(Object anObject) -> 比较字符串内容
System.out.println(s1.equals(s2));
//boolean equalsIgnoreCase(String anotherString) -> 比较字符串内容,忽略大小写
String s3 = "ABC";
System.out.println(s1.equalsIgnoreCase(s3));
}
}
2.练习1
已知用户名和密码,请用程序实现模拟用户登录。总共给三次机会,登录之后,给出相应的提示
步骤:
1.定义两个字符串,分别表示username,password(之前注册过的用户名和密码)
2.创建Scanner对象,录入用户名和密码
3.判断,如果用户名和密码都和已存在的一样,登录成功
否则,登录失败
public class Test02 {
public static void main(String[] args) {
//1.定义两个字符串,分别表示username,password(之前注册过的用户名和密码)
String username = "root";
String password = "1234";
//2.创建Scanner对象,录入用户名和密码
Scanner sc = new Scanner(System.in);
for (int i = 0; i < 3; i++) {
System.out.println("请您输入用户名:");
String name = sc.next();
System.out.println("请您输入密码:");
String pwd = sc.next();
/* 3.判断,如果用户名和密码都和已存在的一样,登录成功
否则,登录失败*/
if (username.equals(name) && password.equals(pwd)){
System.out.println("登录成功");
break;
}else{
if (i==2){
System.out.println("账户冻结");
}else{
System.out.println("登录失败");
}
}
}
}
}
3.获取功能
public int length():返回 此字符串的长度
public String concat(String str):将指定的字符串拼接到老串的末尾,产生一个新串,老串不动
char charAt(int index) :返回指定索引处的char值
int indexOf(String str) :获取的是指定字符串在老串中第一次出现的索引位置
String substring(int beginIndex):返回一个子字符串,从beginIndex开始截取字符串到字符串末尾,老串不动
String substring(int beginIndex, int endIndex) :返回一个子字符串,从beginIndex到endIndex截取字符串, 含beginIndex,不含endIndex
public class Test03_Get {
public static void main(String[] args) {
String s = "abcdefg";
//public int length():返回 此字符串的长度
System.out.println("s.length() = " + s.length());
//public String concat(String str):将指定的字符串拼接到老串的末尾,产生一个新串,老串不动
String newStr1 = s.concat("哈哈哈哈哈");
System.out.println("newStr1 = " + newStr1);
//char charAt(int index) :返回指定索引处的char值
char element1 = s.charAt(2);
System.out.println("element1 = " + element1);
//int indexOf(String str) :获取的是指定字符串在老串中第一次出现的索引位置
int index = s.indexOf("a");
System.out.println("index = " + index);
//String substring(int beginIndex):返回一个子字符串,从beginIndex开始截取字符串到字符串末尾,老串不动
String newStr2 = s.substring(2);
System.out.println("newStr2 = " + newStr2);
/* String substring(int beginIndex, int endIndex) :
返回一个子字符串,从beginIndex到endIndex截取字符串, 含beginIndex,不含endIndex*/
String newStr3 = s.substring(2, 4);
System.out.println("newStr3 = " + newStr3);
}
}
4.练习2
遍历字符串
public class Test04 {
public static void main(String[] args) {
String s = "abcdefg";
for (int i = 0; i < s.length(); i++) {
System.out.println(s.charAt(i));
}
}
}
5.转换功能
char[] toCharArray() -> 将字符串转成char数组
byte[] getBytes() -> 使用默认字符集,将字符串转成字节数组
String replace(CharSequence target, CharSequence replacement) ->将与target匹配的字符串使用replacement字符串替换
扩展:
byte[] getBytes(String charsetName)->按照指定的编码表将字符串转成byte数组
charsetName:指定的编码集->不区分大小写
public class Test05_ZhuanHuan {
public static void main(String[] args) throws UnsupportedEncodingException {
String s1 = "abcdefg";
//char[] toCharArray() -> 将字符串转成char数组
char[] chars = s1.toCharArray();
for (int i = 0; i < chars.length; i++) {
System.out.println(chars[i]);
}
//byte[] getBytes() -> 使用默认字符集,将字符串转成字节数组
byte[] bytes = s1.getBytes();
for (int i = 0; i < bytes.length; i++) {
System.out.println(bytes[i]);
}
/*
String replace(CharSequence target, CharSequence replacement)
->将与target匹配的字符串使用replacement字符串替换
*/
String newStr = s1.replace("a", "z");
System.out.println("newStr = " + newStr);
System.out.println("==========================");
/*
byte[] getBytes(String charsetName)->按照指定的编码表将字符串转成byte数组
charsetName:指定的编码集->不区分大小写
在GBK中:一个中文占2个字节
在UTF-8中:一个中文占3个字节
*/
String s2 = "你";
byte[] bytes1 = s2.getBytes("gbk");
System.out.println(Arrays.toString(bytes1));
}
}
7.练习4
键盘录入一个字符串,统计该字符串中大写字母字符,小写字母字符,数字字符出现的次数(不考虑其他字符)
步骤:
1.创建Scanner对象,键盘录入一个字符串
2.定义三个变量,分别用来统计大写字母,小写字母,数字的个数
int big = 0;
int small = 0;
int number = 0;
3.遍历字符串,将每一个字符获取出来
4.判断如果在A-Z范围内,证明是大写字母,big++
A-Z-> 65-90
假如: B -> 66 判断 66是否在65-90之间,如果在证明就是大写字母
5.判断如果在a-z范围内,证明是小写字母,small++
a-z -> 97-122
假如: b -> 98 判断98是否在97-122之间,如果在,证明是小写字母
6.判断如果在0-9范围内,证明是数字,number++
0-9 -> 48-57
假如: 1->49 判断49是否在48-57之间,如果在,证明是数字
public class Test06 {
public static void main(String[] args) {
//1.创建Scanner对象,键盘录入一个字符串
Scanner sc = new Scanner(System.in);
String data = sc.next();
/*2.定义三个变量,分别用来统计大写字母,小写字母,数字的个数
int big = 0;
int small = 0;
int number = 0;*/
int big = 0;
int small = 0;
int number = 0;
//3.遍历字符串,将每一个字符获取出来
char[] chars = data.toCharArray();
for (int i = 0; i < chars.length; i++) {
char element = chars[i];
/* 4.判断如果在A-Z范围内,证明是大写字母,big++
A-Z-> 65-90
假如: B -> 66 判断 66是否在65-90之间,如果在证明就是大写字母*/
if (element >= 'A' && element <= 'Z') {
big++;
}
/*5.判断如果在a-z范围内,证明是小写字母,small++
a-z -> 97-122
假如: b -> 98 判断98是否在97-122之间,如果在,证明是小写字母*/
if (element >= 'a' && element <= 'z') {
small++;
}
/*6.判断如果在0-9范围内,证明是数字,number++
0-9 -> 48-57
假如: 1->49 判断49是否在48-57之间,如果在,证明是数字*/
if (element>='0' && element<='9'){
number++;
}
}
System.out.println("大写字母:"+big+"个");
System.out.println("小写字母:"+small+"个");
System.out.println("数字:"+number+"个");
}
}
8.分割功能
String[] split(String regex) -> 按照指定字符对字符串进行切割
public class Test07_Split {
public static void main(String[] args) {
String s = "abc,java,bigdata";
String[] split = s.split(",");
System.out.println(Arrays.toString(split));
System.out.println("=======================");
String s1 = "java.txt";
String[] split1 = s1.split("\.");
System.out.println(Arrays.toString(split1));
}
}
第三章.扩展方法
boolean contains(CharSequence s) -> 判断老串中是否包含指定的字符串
boolean endsWith(String suffix) -> 判断老串是否以指定字符串结尾
boolean startsWith(String prefix) -> 判断老串是否以指定字符串开头
String toLowerCase() -> 将字符串转型小写
String toUpperCase() -> 将字符串转成大写
String trim() -> 去掉字符串两端空格
public class Test08_Method {
public static void main(String[] args) {
String s = "abcdefg";
//boolean contains(CharSequence s) -> 判断老串中是否包含指定的字符串
System.out.println(s.contains("abc"));
//boolean endsWith(String suffix) -> 判断老串是否以指定字符串结尾
System.out.println(s.endsWith("f"));
//boolean startsWith(String prefix) -> 判断老串是否以指定字符串开头
System.out.println(s.startsWith("ab"));
//String toLowerCase() -> 将字符串转型小写
String s1 = "ABCDEFG";
System.out.println(s1.toLowerCase());
//String toUpperCase() -> 将字符串转成大写
System.out.println(s.toUpperCase());
System.out.println("============================");
//String trim() -> 去掉字符串两端空格
String s2 = " abc haha hehe ";
System.out.println(s2);
String trim = s2.trim();
System.out.println(trim);
System.out.println("============================");
String newStr = s2.replace(" ", "");
System.out.println(newStr);
}
}
第四章.StringBuilder
1.StringBuilder的介绍
1.概述:java自带的类,一个可变的字符序列
2.作用:主要用于字符串拼接
3.问题:String可以直接用+作字符串拼接,那么为什么还要用StringBuilder去拼接呢?
原因:
a.String拼接,每拼接一次,会产生一个新的串儿,如果要是拼接次数过多,会很占用内存资源,效率会变低
b.StringBuilder拼接,创建出来,会有一个缓冲区(char数组,没有被final修饰),字符串拼接之后,会直接保存到这个缓冲区中
此缓冲区始终就这一个,不会过多的占用内存资源
4.StringBuilder的特点:
a.可变的字符序列->没有被final修饰的char数组
b.创建之后,会产生一个缓冲区(char数组),始终就这一个缓冲区,除非我们重新new了
c.缓冲区默认长度为16,拼接之后,直接在缓冲区中存储
d.如果拼接字符串,超出了默认为16的容量范围,会自动扩容
e.怎么扩容?
Arrays.copyOf-> 数组扩容(先扩容,再将源数组数据复制到新数组中)
f.扩容多少倍呢? 2倍+2
2.StringBuilder的使用
1.构造方法:
StringBuilder()
StringBuilder(String str)
public class Test01 {
public static void main(String[] args) {
StringBuilder sb1 = new StringBuilder();
System.out.println(sb1);
StringBuilder sb2 = new StringBuilder("abc");
System.out.println(sb2);
}
}
常用方法:
StringBuilder append(任意类型的数据)->拼接字符串,返回的是StrinBuilder对象自己
StringBuilder reverse()->字符串翻转
String toString()-> 将StringBuilder转成String
public class Test02 {
public static void main(String[] args) {
method01_Append();
method02_Reverse();
method03_Reverse();
}
/*
翻转练习
判断字符串是否是回文内容
上海自来水来自海上
*/
private static void method03_Reverse() {
String s = "蝶恋花香花恋蝶1";
StringBuilder sb = new StringBuilder(s);
sb.reverse();
//将sb转成String
String s1 = sb.toString();
if (s.equals(s1)){
System.out.println("回文");
}else{
System.out.println("不是回文");
}
}
public static void method02_Reverse() {
StringBuilder sb = new StringBuilder("abcdefg");
sb.reverse();
System.out.println(sb);
}
public static void method01_Append() {
StringBuilder sb1 = new StringBuilder();
/*
newSb和sb1其实是同一个对象
*/
//StringBuilder newSb = sb1.append("张无忌");
//链式调用
sb1.append("张无忌").append("赵敏").append("涛哥").append("柳岩");
System.out.println(sb1);
}
}
3.练习
int[] arr = {1,2,3,4} 输出结果为:[1,2,3,4]
public class Test03 {
public static void main(String[] args) {
//int[] arr = {1,2,3,4} 输出结果为:[1,2,3,4]
//1.定义数组
int[] arr = {1,2,3,4};
//2.创建StringBuilder对象
StringBuilder sb = new StringBuilder();
sb.append("[");
//3.遍历数组
for (int i = 0; i < arr.length; i++) {
if (i==arr.length-1){
sb.append(arr[i]).append("]");
}else{
sb.append(arr[i]).append(",");
}
}
//4.将StringBuilder变成String类型
String s = sb.toString();
System.out.println(s);
}
}
4.String 和StringBuilder以及StringBuffer区别
1.相同点:
三个都可以用于字符串拼接
2.不同点:
a.String,每拼接一次,都会产生一个新串儿,占用内存空间
b.StringBuilder和StringBuffer始终只有一个缓冲区,不过多占用内存空间
c.StringBuilder 速度快,线程不安全
StringBuffer 速度慢,线程安全
从拼接字符串效率上来说:
StringBuilder>StringBuffer>String
public class Test04 {
public static void main(String[] args) {
long start = System.currentTimeMillis();
String s = "";
for (int i = 0; i < 100; i++) {
s+="haha";
}
/*StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append("haha");
}*/
long end = System.currentTimeMillis();
System.out.println(end-start);
}
}
第五章.Math类
1.Math类介绍
1.概述:java自带的类,数学工具类
2.作用:做数学运算的
3.特点:
a.Math的构造是私有的->不能new对象
b.方法都是静态的
4.使用:
类名直接调用
2.Math类方法
static int abs(int a) -> 求参数的绝对值
static double ceil(double a) -> 向上取整
static double floor(double a) ->向下取整
static long round(double a) -> 四舍五入
static int max(int a, int b) ->求两个数之间的较大值
static int min(int a, int b) ->求两个数之间的较小值
public class Test01 {
public static void main(String[] args) {
//static int abs(int a) -> 求参数的绝对值
System.out.println(Math.abs(-1));
//static double ceil(double a) -> 向上取整
System.out.println(Math.ceil(2.3));
System.out.println(Math.ceil(-2.3));
//static double floor(double a) ->向下取整
System.out.println(Math.floor(2.5));
System.out.println(Math.floor(-2.9));
//static long round(double a) -> 四舍五入
System.out.println(Math.round(1.5));
System.out.println(Math.round(-1.8));
//static int max(int a, int b) ->求两个数之间的较大值
System.out.println(Math.max(10,20));
//static int min(int a, int b) ->求两个数之间的较小值
System.out.println(Math.min(10,20));
}
}
round()方法的实现原理:先给参数+0.5,取小于或者等于这个数的最大整数
第六章.BigInteger
1.BigInteger介绍
1.为什么要学习BigInteger?
将来我们操作的整数会很大,有可能比long型接收的数还要大,超过long的整数,可以认为是一个对象
2.作用:
处理超大整数
2.BigInteger使用
1.构造:
BigInteger(String val)
2.方法:
BigInteger add(BigInteger val)-> 加 (this + val)
BigInteger subtract(BigInteger val)->减 (this - val)
BigInteger multiply(BigInteger val) ->乘 (this * val)
BigInteger divide(BigInteger val) ->除 (this / val)
public class Test01 {
public static void main(String[] args) {
/*1.构造:
BigInteger(String val)*/
BigInteger b1 = new BigInteger("1212121212121212121212121212");
BigInteger b2 = new BigInteger("1212121212121212121212121212");
//BigInteger add(BigInteger val)-> 加 (this + val)
BigInteger add = b1.add(b2);
System.out.println("add = " + add);
//BigInteger subtract(BigInteger val)->减 (this - val)
BigInteger subtract = b1.subtract(b2);
System.out.println("subtract = " + subtract);
//BigInteger multiply(BigInteger val) ->乘 (this * val)
BigInteger multiply = b1.multiply(b2);
System.out.println("multiply = " + multiply);
//BigInteger divide(BigInteger val) ->除 (this / val)
BigInteger divide = b1.divide(b2);
System.out.println("divide = " + divide);
}
}
第七章.BigDecimal类
1.BigDecimal介绍
1.为甚要学BigDecimal?
之间用double和float两个类型的数据直接参与运算时,会出现精度损失的问题
所以用BigDecimal做小数的运算,不会出现精度损失问题
2.BigDecimal使用
1.构造:
BigDecimal(String str) -> str必须是数字类型
2.方法:
BigDecimal add(BigDecimal val)-> 加
BigDecimal subtract(BigDecimal val)->减
BigDecimal multiply(BigDecimal val) ->乘
BigDecimal divide(BigDecimal val) ->除->除不尽,会报错
3.注意:
要想小数是精确的,创建BigDecimal时,参数要用字符串形式
public class Test01 {
public static void main(String[] args) {
//创建两个BigDecimal对象
BigDecimal b1 = new BigDecimal("3.55");
BigDecimal b2 = new BigDecimal("2.12");
//BigDecimal add(BigDecimal val)-> 加
BigDecimal add = b1.add(b2);
System.out.println("add = " + add);
//BigDecimal subtract(BigDecimal val)->减
BigDecimal subtract = b1.subtract(b2);
System.out.println("subtract = " + subtract);
//BigDecimal multiply(BigDecimal val) ->乘
BigDecimal multiply = b1.multiply(b2);
System.out.println("multiply = " + multiply);
/*
BigDecimal divide(BigDecimal val) ->除
BigDecimal如果除不尽,会报错
*/
BigDecimal divide = b1.divide(b2);
System.out.println("divide = " + divide);
}
}
BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
divisor:要除的那个数
scale:保留几位小数
roundingMode:舍入方式
static int ROUND_UP :向上加1
static int ROUND_DOWN :直接舍去
static int ROUND_HALF_UP:四舍五入
public class Test02 {
public static void main(String[] args) {
//创建两个BigDecimal对象
BigDecimal b1 = new BigDecimal("3.55");
BigDecimal b2 = new BigDecimal("2.12");
/*
BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
divisor:要除的那个数
scale:保留几位小数
roundingMode:舍入方式
static int ROUND_UP :向上加1
static int ROUND_DOWN :直接舍去
static int ROUND_HALF_UP:四舍五入
*/
BigDecimal divide = b1.divide(b2, 2, BigDecimal.ROUND_UP);
System.out.println(divide);
}
}
第八章.Date日期类
1.Date类的介绍
1.概述:表示特定的瞬间,精确到毫秒
2.毫秒:时间单位
1000毫秒 = 1秒
3.基本地理知识:
a.时间原点:1970年1月1日 0时0分0秒
b.北京时区:东八区->一个时区相差一个小时
东八区要比时间原点所在时区,晚8个小时
c.北京经纬度:
东经116 北纬40
d.季风气候:温带大陆性季风气候-> 四季分明
e.0度经线->本初子午线
f.赤道->分南北半球
2.Date类的使用
1.构造:
Date()->创建Date对象,获取当前系统时间
Date(long time)->设置时间,从时间原点开始算后面推指定的时间,传递的是毫秒值
public class Test01 {
public static void main(String[] args) {
//Date()->创建Date对象,获取当前系统时间
/*Date date = new Date();
System.out.println(date);*/
//Date(long time)->设置时间,从时间原点开始算后面推指定的时间
Date date = new Date(1000L);
System.out.println(date);
}
}
3.Date类的常用方法
long getTime() -> 获取时间的毫秒值
void setTime(long time) -> 设置时间,从时间原点开始算,传递毫秒值
package com.atguigu.d_date;
import java.util.Date;
public class Test02 {
public static void main(String[] args) {
//method01();
method02();
}
private static void method02() {
Date date = new Date(1000L);
long time = date.getTime();
System.out.println("time = " + time);
}
public static void method01() {
Date date = new Date();
System.out.println(date);
//long getTime() -> 获取时间的毫秒值,返回的是对应的毫秒值
long time = date.getTime();
System.out.println(time);
//void setTime(long time) -> 设置时间,从时间原点开始算,传递毫秒值
date.setTime(1646104558853L);
System.out.println(date);
}
}
第九章.Calendar日历类
1.Calendar介绍
1.概述:日历类,如果涉及到操作具体的时间字段了,Calendar中的方法代替Date使用,是一个抽象类
2.获取:本类中的方法
static Calendar getInstance()
3.
国外月份:
0 1 2 3 4 5 6 7 8 9 10 11
国内月份:
1 2 3 4 5 6 7 8 9 10 11 12
4.常用方法:
int get(int field) ->返回给定日历字段的值
void set(int field, int value) :将给定的日历字段设置为指定的值
void add(int field, int amount) :根据日历的规则,为给定的日历字段添加或者减去指定的时间量
Date getTime():将Calendar转成Date对象
日历字段:
在Calendar中日历字段都是静态的
所以我们调用方法传递日历字段时:类名调用,指定要操作的字段
public class Test01 {
public static void main(String[] args) {
//get();
//set();
//add();
getTime();
}
/*
Date getTime():将Calendar转成Date对象
*/
private static void getTime() {
Calendar calendar = Calendar.getInstance();
Date date = calendar.getTime();
System.out.println(date);
}
/*
void add(int field, int amount) :
根据日历的规则,为给定的日历字段添加或者减去指定的时间量
*/
private static void add() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.YEAR,-1);
int year = calendar.get(Calendar.YEAR);
System.out.println("year = " + year);
}
/*
void set(int field, int value) :将给定的日历字段设置为指定的值
*/
private static void set() {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR,2000);
int year = calendar.get(Calendar.YEAR);
System.out.println("year = " + year);
}
/*
int get(int field) ->返回给定日历字段的值
*/
public static void get() {
Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR);
System.out.println(year);
}
}
快速将一段代码抽到一个方法中: 1.选中要抽取的代码 2.按:ctrl+alt+m扩展方法:
void set(int year, int month, int date) -> 设置年月日 设置的是:2022,2,1需求:求出2月份的最后一天(2月份一共有多少天) public class Test02 { public static void main(String[] args) { //获取Calendar对象 Calendar calendar = Calendar.getInstance(); /* 设置年月日:2022,2,1 其实对应的就是我们国内的3月1日 让3月1日减去1天,就是2月的最后一天 */ calendar.set(2000,2,1); //减去一天 calendar.add(Calendar.DATE,-1); //输出Date字段 int day = calendar.get(Calendar.DATE); System.out.println(day); } }
第十章.SimpleDateFormat日期格式化类
1.SimpleDateFormat介绍
1.概述:日期格式化类 SimpleDateFormat extends DateFormat
2.作用:
a.将Date对象按照我们指定的日期格式,格式化成String日期表示形式
b.将符合指定格式的日期字符串转成Date对象
3.如何去指定日期格式:
yyyy-MM-dd HH:mm:ss
分隔符可以改变,但是里面的字母不能随意写
4.构造:
SimpleDateFormat(String pattern)
pattern->代表的是指定的日期格式
yyyy-MM-dd HH:mm:ss
分隔符可以改变,但是里面的字母不能随意写
| 时间字母表示 | 说明 |
|---|---|
| y | 年 |
| M | 月 |
| d | 日 |
| H | 时 |
| m | 分 |
| s | 秒 |
2.SimpleDateFormat常用方法
String format(Date date)->格式化->将Date对象按照指定的格式格式化成String
Date parse(String source)->解析->将符合日期格式的字符串转成Date对象
public class Test01 {
public static void main(String[] args) throws ParseException {
format();
parse();
}
/*
Date parse(String source)->解析->将符合日期格式的字符串转成Date对象
*/
public static void parse() throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = "2000-01-01 00:00:00";
Date date = sdf.parse(time);
System.out.println(date);
}
public static void format() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
//String format(Date date)->格式化->将Date对象按照指定的格式格式化成String
String format = sdf.format(new Date());
System.out.println("format = " + format);
}
}
第十一章.JDK8新日期类
1. LocalDate 本地日期
1.1.获取LocalDate对象
1.概述:
是一个不可变的日期时间对象,表示日期,通常被视为年月日
这个类是不可变的和线程安全的
2.创建:
static LocalDate now() -> 获取当前系统时间的年-月-日
static LocalDate of(int year, int month, int dayOfMonth) -> 设置年-月-日
public class Test01_LocalDate {
public static void main(String[] args) {
//static LocalDate now() -> 获取当前系统时间的年-月-日
LocalDate localDate = LocalDate.now();
System.out.println(localDate);
//static LocalDate of(int year, int month, int dayOfMonth) -> 设置年-月-日
LocalDate localDate1 = LocalDate.of(2000, 01, 01);
System.out.println(localDate1);
}
}
LocalDateTime()->表示年月日,时分秒
创建方式和LocalDate一样
1.2.获取日期字段的方法 : 名字是get开头
int getYear()->获取年份
int getMonthValue()->获取月份
int getDayOfMonth()->获取月中的第几天
public class Test03_LocalDate {
public static void main(String[] args) {
LocalDate localDate = LocalDate.now();
//int getYear()->获取年份
System.out.println(localDate.getYear());
//int getMonthValue()->获取月份
System.out.println(localDate.getMonthValue());
//int getDayOfMonth()->获取月中的第几天
System.out.println(localDate.getDayOfMonth());
}
}
1.3.设置日期字段的方法 : 名字是with开头
LocalDate withYear(int year):设置年份
LocalDate withMonth(int month):设置月份
LocalDate withDayOfMonth(int day):设置月中的天数
public class Test04_LocalDate {
public static void main(String[] args) {
LocalDate localDate = LocalDate.now();
//LocalDate withYear(int year):设置年份
/* LocalDate localDate1 = localDate.withYear(2000);
System.out.println(localDate1);
//LocalDate withMonth(int month):设置月份
LocalDate localDate2 = localDate1.withMonth(4);
System.out.println(localDate2);
//LocalDate withDayOfMonth(int day):设置月中的天数
LocalDate localDate3 = localDate2.withDayOfMonth(12);
System.out.println(localDate3);*/
LocalDate localDate1 = localDate.withYear(2000).withMonth(2).withDayOfMonth(14);
System.out.println(localDate1);
}
}
1.4.日期字段偏移
设置日期字段的偏移量,方法名plus开头,向后偏移
设置日期字段的偏移量,方法名minus开头,向前偏移
public class Test05_LocalDate {
public static void main(String[] args) {
//plus();
minus();
}
/*
设置日期字段的偏移量,方法名minus开头,向前偏移
*/
private static void minus() {
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = localDate.minusYears(1);
System.out.println(localDate1.getYear());
}
/*
设置日期字段的偏移量,方法名plus开头,向后偏移
*/
private static void plus() {
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = localDate.plusYears(1);
System.out.println(localDate1.getYear());
}
}
2.Period和Duration类
2.1 Period 计算日期之间的偏差
方法:
static Period between(LocalDate d1,LocalDate d2):计算两个日期之间的差值
getYears()->获取相差的年
getMonths()->获取相差的月
getDays()->获取相差的天
public class Test06_Period {
public static void main(String[] args) {
//创建两个LocalDate对象,设置年月日
LocalDate localDate1 = LocalDate.of(2022, 12, 12);
LocalDate localDate2 = LocalDate.of(2021, 11, 11);
Period period = Period.between(localDate2, localDate1);
//getYears()->获取相差的年
System.out.println(period.getYears());
//getMonths()->获取相差的月
System.out.println(period.getMonths());
//getDays()->获取相差的天
System.out.println(period.getDays());
}
}
2.2 Duration计算时间之间的偏差
1.static Duration between(Temporal startInclusive, Temporal endExclusive) -> 计算时间差
2.Temporal接口:
LocalDate,LocalDateTime
3.注意:利用Duration计算时间偏差,是算的比较精准的,主要是时分秒,所以我们不应该调用between传递LocalDate对象
因为LocalDate对象不操作时分秒,需要传递LocalDateTime,因为LocalDateTime可以操作时分秒
5.利用Duration获取相差的时分秒 -> to开头
toDays():获取相差天数
toHours():获取相差小时
toMinutes():获取相差分钟
toMillis():获取相差秒(毫秒)
public class Test07_Duration {
public static void main(String[] args) {
LocalDateTime localDateTime1 = LocalDateTime.of(2022, 12, 12, 12, 12, 12);
LocalDateTime localDateTime2 = LocalDateTime.of(2011, 11, 11, 11, 11, 11);
Duration duration = Duration.between(localDateTime2, localDateTime1);
//toDays():获取相差天数
System.out.println(duration.toDays());
//toHours():获取相差小时
System.out.println(duration.toHours());
//toMinutes():获取相差分钟
System.out.println(duration.toMinutes());
//toMillis():获取相差秒(毫秒)
System.out.println(duration.toMillis());
}
}
总结:
如果要是计算年月日的时间偏差,用Period
如果要是计算时分秒的时间偏差,用Duration
3.DateTimeFormatter日期格式化类
1.概述:日期格式化对象
2.获取:
static DateTimeFormatter ofPattern(String pattern)
pattern:自己定义的日期格式
3.方法:
a.TemporalAccessor parse(CharSequence text)->将符合格式的字符串转成TemporalAccessor日期对象
TemporalAccessor接口:LocalDate , LocalDateTime都是此接口的实现类
LocalDateTime中的方法:
static LocalDateTime from(TemporalAccessor ta)->将TemporalAccessor转成LocalDateTime
b.format方法
String format(TemporalAccessor ta)->将日期按照格式转成String
public class Test08_DateTimeFormatter {
public static void main(String[] args) {
// parse();
format();
}
public static void format(){
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.now();
String format = dtf.format(localDateTime);
System.out.println(format);
}
public static void parse() {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String time = "2020-01-01 00:00:00";
TemporalAccessor temporalAccessor = dtf.parse(time);
/*
将TemporalAccessor转换成LocalDateTime对象
*/
LocalDateTime localDateTime = LocalDateTime.from(temporalAccessor);
System.out.println(localDateTime);
}
}
第十二章.System类
1.概述:java自带的类
2.特点:
a.构造私有,不能new对象
b.方法为静态的
3.使用:
类名直接调用
| 方法 | 说明 |
|---|---|
| static void gc() | 获取垃圾回收器 |
| static long currentTimeMillis() | 获取当前系统时间毫秒值 |
| static void exit(int status) | 退出正在运行的jvm |
| static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) | src:源数组 srcPos:从源数组的哪个索引开始复制 dest:目标数组 destPos:从目标数组的哪个索引开始粘贴 length:复制多少个 |
public class Test01 {
public static void main(String[] args) {
// static long currentTimeMillis()
long start = System.currentTimeMillis();
System.out.println(start);
//static void exit(int status)
for (int i = 0; i < 10; i++) {
if (i==5){
System.exit(0);
}
System.out.println("helloworld"+i);
}
}
}
public class Test02 {
public static void main(String[] args) {
/*
static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
src:源数组
srcPos:从源数组的哪个索引开始复制
dest:目标数组
destPos:从目标数组的哪个索引开始粘贴
length:复制多少个
*/
int[] arr = {1,2,3,4,5,6,7,8,9};
int[] arr1 = new int[9];
System.arraycopy(arr,0,arr1,0,8);
System.out.println(Arrays.toString(arr1));
}
}
第十三章.Arrays数组工具类
1.概述:数组工具类
2.特点:
a.Arrays构造私有
b.方法为静态的
3.使用:
类名直接调用
| 方法名 | 说明 |
|---|---|
| static String toString() | 按照指定格式打印数组 |
| static void sort(数组) | 排序->升序 |
| static int binarySearch(int[] arr,int data) | 二分查找 |
| static int[] copyOf(int[] original, int newLength) | 数组扩容 |
public class Test01 {
public static void main(String[] args) {
//static String toString()按照指定格式打印数组
int[] arr = {1, 2, 3, 4, 5};
System.out.println(Arrays.toString(arr));
//static void sort(数组)排序->升序
int[] arr1 = {5,4,3,2,1};
Arrays.sort(arr1);
System.out.println(Arrays.toString(arr1));
/*
static int binarySearch(int[] arr,int data)
二分查找
*/
int[] arr2 = {11,22,33,44,55,66,77};
System.out.println(Arrays.binarySearch(arr2,10));
/*
static int[] copyOf(int[] original, int newLength)->数组扩容
original:老数组
newLength:新数组的长度
返回的是一个新数组
*/
int[] arr3 = {1,2,3,4,5};
int[] arr4 = Arrays.copyOf(arr3, 10);
/*
一般的数组扩容
*/
arr3 = arr4;
System.out.println(Arrays.toString(arr3));
}
}
第十四章.包装类
1.基本数据类型对应的引用数据类型(包装类)
1.概述:基本类型对应的引用类型(基本类型都有对应的一个类(包装类))
2.拆箱和装箱
a.装箱:将基本类型转成对应的包装类
b.拆箱:将包装类转成对应的基本类型
基本类型和包装类之间是可以自动转换的(自动拆箱,自动装箱)
| 基本类型 | 包装类 |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
##2.Integer的介绍以及使用
1.概述:int的包装类
2.装箱:
a.构造:
Integer(int value)
Integer(String s) -> s必须是数字格式
b.方法:
static Integer valueOf(int i)
static Integer valueOf(String s) -> s必须是数字格式
public class Test01 {
public static void main(String[] args) {
/*
a.构造:
Integer(int value)
Integer(String s) -> s必须是数字格式
*/
Integer i1 = new Integer(1);
System.out.println("i1 = " + i1);
Integer i2 = new Integer("11");
System.out.println("i2 = " + i2);
/*
static Integer valueOf(int i)
static Integer valueOf(String s) -> s必须是数字格式
*/
Integer i3 = Integer.valueOf(1);
System.out.println("i3 = " + i3);
Integer i4 = Integer.valueOf("11");
System.out.println("i4 = " + i4);
}
}
3.拆箱:
int intValue()->将Integer转成int
public class Test02 {
public static void main(String[] args) {
/*
3.拆箱:
int intValue()->将Integer转成int
*/
Integer integer = new Integer(10);
int i = integer.intValue();
System.out.println(i+1);
/*
自动装箱
*/
Integer i1 = 10;
System.out.println(i1+1);
/*
自动拆箱
*/
int i2 = i1;
System.out.println(i2+1);
}
}
3.基本类型和String之间的转换
3.1 基本类型往String转
1.方式1:
+ -> 任意类型遇到字符串都会变成字符串
2.方式2:
String中的静态方法
static String valueOf(int i)
public class Test03_IntToString {
public static void main(String[] args) {
/*
1.方式1:
+ -> 任意类型遇到字符串都会变成字符串
2.方式2:
String中的静态方法
static String valueOf(int i)
*/
int i = 10;
String s1 = i+"";
System.out.println(s1+1111);
String s2 = String.valueOf(10);
System.out.println(s2+111111);
}
}
3.2 String转成基本数据类型
每个包装类中都有一个parsexxx的方法,可以将字符串转成基本类型
注意:要转的字符串必须是数字形式
| 位置 | 方法 | 说明 |
|---|---|---|
| Byte | parseByte(String s) | 将字符串转成byte |
| Short | parseShort(String s) | 将字符串转成short |
| Integer | parseInt(String s) | 将字符串转成int |
| Float | parseFloat(String s) | 将字符串转成float |
| Double | parseDouble(Stirng s) | 将字符串转成double |
| Long | parseLong(String s) | 将字符串转成long |
| Boolean | parseBoolean(String s) | 将字符串转成boolean |
public class Test04_StringToInt {
public static void main(String[] args) {
int i = Integer.parseInt("111");
System.out.println(i+1);
}
}
public class Test05 { public static void main(String[] args) { Integer i1 = 100; Integer i2 = 100; System.out.println(i1==i2);//true Integer i3 = 127; Integer i4 = 127; System.out.println(i3==i4);//true /* 超过-128到127范围,返回的是new Integer对象 */ Integer i5 = 128; Integer i6 = 128; System.out.println(i5==i6);//false } }
第十五章.多线程基本了解
1.多线程_线程和进程
进程:进入到内存中运行的应用程序
线程:是进程中的一个执行单元
线程作用:负责当前进程中程序的运行,一个进程中至少有一个线程.一个进程中是可以有多个线程的,这样的应用程序就称之为多线程程序
并发:同一个时刻多个线程同时操作了同一个数据
并行:同一个时刻多个线程同时执行不同的程序
2.CPU调度
1.分时调度:指的是让所有的线程轮流获取CPU使用权,并且平均分配每个线程占用的cpu的时间片
2.抢占式调度:java程序实现的多线程就是抢占式调度
多个线程之间抢占CPU使用权,谁的优先级高,先抢到CPU使用权几率就大
3.主线程介绍
public class Test01_Main {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
System.out.println("哈哈哈哈哈");
}
System.out.println(Math.abs(-10));
}
}
第十六章.创建线程的方式(重点)
1.第一种方式_extends Thread
1.实现多线程步骤:
a.定义一个类,继承Thread
b.重写Thread中的run方法(run方法专门用于设置线程任务的)
c.创建自定义线程类对象
d.调用Thread中的start方法
start()方法-> 开启线程,jvm自动执行run方法
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("myThread线程执行了......");
}
}
}
public class Test01_Main {
public static void main(String[] args) {
//创建线程对象
MyThread myThread = new MyThread();
//调用Thread类中的start方法,开启线程,jvm自动执行run方法
myThread.start();
/*
如果单纯的调用run方法
此线程并没有开启,只是简单调用方法而已
*/
//myThread.run();
for (int i = 0; i < 5; i++) {
System.out.println("Main线程执行了......");
}
}
}
2.多线程在内存中的运行原理
注意:同一个线程对象,不能连续调用多次start方法
3.Thread类中的方法
| 方法 | 说明 |
|---|---|
| String getName() | 获取线程名字 |
| void setName() | 设置线程名字 |
| static Thread currentThread() | 获取的是当前正在执行的线程对象 此方法在哪个线程中使用,获取的就是哪个线程对象 |
| static void sleep(long time) | 线程睡眠,设置的是睡眠时间,遇到sleep线程不走了,直到超时了,线程自动醒来,继续执行 |
public class Test01_Main {
public static void main(String[] args) throws InterruptedException {
//创建线程对象
MyThread myThread = new MyThread();
//设置线程名字
myThread.setName("赵四");
//调用Thread类中的start方法,开启线程,jvm自动执行run方法
myThread.start();
/*
如果单纯的调用run方法
此线程并没有开启,只是简单调用方法而已
*/
//myThread.run();
for (int i = 0; i < 5; i++) {
Thread.sleep(1000L);
System.out.println(Thread.currentThread().getName()+"线程执行了......");
}
}
}
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程执行了......");
}
}
}
问题:为甚在run方法中不能throws异常
原因:Thead(父类)中的run没有throws异常,所以子类重写之后就不能throws
4.第二种方式_实现Runnable接口
1.定义类,实现Runnable接口
2.重写Runnable接口中的run方法,设置线程任务
3.创建Thread对象,将自定义线程类封装到Thread对象中
4.调用Thread中的start方法,开启线程
public class Test01 {
public static void main(String[] args) {
//创建实现类对象
MyRunnable myRunnable = new MyRunnable();
/*
创建Thread对象
构造:
Thread(Runnable target, String name)
name:线程名字
*/
Thread thread = new Thread(myRunnable,"super屎蛋儿");
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了");
}
}
}
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了");
}
}
}
5.两种实现多线程的方式区别
1.继承:由于继承是单继承,所以是有局限性
2.接口:解决继承的局限性
6.第三种方式_匿名内部类创建多线程
1.匿名内部类回顾:
new 接口/抽象类(){
重写方法
}.重写的方法();
接口/抽象类 对象名 = new 接口/抽象类(){
重写方法
}
对象名.重写的方法名();
匿名内部类其实指向的是子类或者实现类对象
2.匿名内部类实现多线程
new Thread(new Runnable(){
run
}).start();
public class Test02 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了");
}
}
}).start();
}
}
小结:
1.继承Thread方式
a.定义一个类,继承Thread
b.重写run方法,设置线程任务
c.创建对象,调用start方法,开启线程,jvm自动执行run
2.实现Runnable接口
a.定义一个实现类,实现Runnable接口
b.重写run方法
c.创建实现类对象,放到Thread对象中,调用start方法
3.匿名内部类方式实现多线程
new Thread(new Runnable(){
重写run方法
}).start();
第十七章.线程安全
1.线程安全问题-->线程不安全的代码
当多个线程访问同一个资源时,很容易出现线程安全问题
public class MyTicket implements Runnable{
//定义100张票
int ticket = 100;
@Override
public void run() {
while(true){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
}
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket, "谢广坤");
Thread t2 = new Thread(myTicket, "赵四");
Thread t3 = new Thread(myTicket, "刘能");
//开启线程
t1.start();
t2.start();
t3.start();
}
}
以上代码有问题->线程安全问题,多个线程访问了同一张票,线程不安全了
原因:cpu在多个线程之间做切换
2.解决线程安全问题的第一种方式(使用同步代码块)
1.关键字:synchronized
2.格式:
synchronized(任意对象){
可能出现线程不安全的代码
}
3.当一个线程进入到synchronized中,抢到了锁,并将锁锁上,其他线程需要在代码块外面等待,当一个线程出了synchronized之后,释放锁,将锁打开,其他线程才可以抢锁,进入到synchronized中执行
4.注意:要想使用synchronized实现线程同步,线程安全,多个线程之间使用的锁对象必须是同一把锁(同一个锁对象)
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket, "谢广坤");
Thread t2 = new Thread(myTicket, "赵四");
Thread t3 = new Thread(myTicket, "刘能");
//开启线程
t1.start();
t2.start();
t3.start();
}
}
public class MyTicket implements Runnable {
//定义100张票
int ticket = 100;
//随便创建一个对象
Object obj = new Object();
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}
}
}
}
}
3.解决线程安全问题的第二种方式:同步方法
3.1.普通同步方法
1.格式:
修饰符 synchronized 返回值类型 方法名(参数){
方法体
return 结果
}
2.普通同步方法默认锁对象:this
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket, "广坤");
Thread t2 = new Thread(myTicket, "赵四");
Thread t3 = new Thread(myTicket, "刘能");
//开启线程
t1.start();
t2.start();
t3.start();
}
}
public class MyTicket implements Runnable {
//定义100张票
int ticket = 100;
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
method();
}
}
/* public synchronized void method(){
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}
}*/
public void method() {
synchronized (this) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}
}
}
}
3.2.静态同步方法
1.格式:
修饰符 static synchronized 返回值类型 方法名(参数){
方法体
return 结果
}
2.静态同步方法默认锁:当前类.class
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket, "广坤");
Thread t2 = new Thread(myTicket, "赵四");
Thread t3 = new Thread(myTicket, "刘能");
//开启线程
t1.start();
t2.start();
t3.start();
}
}
public class MyTicket implements Runnable {
//定义100张票
static int ticket = 100;
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
method();
}
}
/* public static synchronized void method(){
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}
}*/
public static void method() {
synchronized (MyTicket.class) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}
}
}
}
第十八章.死锁(了解)
1.死锁介绍(锁嵌套就有可能产生死锁)
指的是两个或者两个以上的线程在执行的过程中,由于竞争同步锁而产生的一种阻塞现象;如果没有外力的作用,他们将无法继续执行下去,这种情况就称之为死锁.
根据上图所示:线程T1正在持有R1锁,但是T1线程必须再拿到R2锁,才能继续执行
而线程T2正在持有R2锁,但是 T2线程需要再拿到R1锁,才能继续执行
此时两个线程处于互相等待的状态,就是死锁,在程序中的死锁将出现在同步代码块的嵌套中
2.死锁的分析
3.代码实现
public class LockA {
static LockA lockA = new LockA();
}
public class LockB {
static LockB lockB = new LockB();
}
public class MyThread implements Runnable{
private boolean flag;
public MyThread(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag){
synchronized (LockA.lockA){
System.out.println("if...lockA");
synchronized (LockB.lockB){
System.out.println("if...lockB");
}
}
}else{
synchronized (LockB.lockB){
System.out.println("else...lockB");
synchronized (LockA.lockA){
System.out.println("else...lockA");
}
}
}
}
}
public class Test01 {
public static void main(String[] args) {
MyThread myThread1 = new MyThread(true);
MyThread myThread2 = new MyThread(false);
Thread thread1 = new Thread(myThread1);
Thread thread2 = new Thread(myThread2);
thread1.start();
thread2.start();
}
}
第十九章.线程状态
1.线程状态介绍
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举中给出了六种线程状态:
这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析。
| 线程状态 | 导致状态发生条件 |
|---|---|
| NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
| Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
| Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
| Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
| Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
| Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。或者调用过时方法stop() |
2.线程状态图
第二十章.等待唤醒
1.等待唤醒案例分析(线程之间的通信)
2.等待唤醒案例实现
//包子铺
public class BaoZiPu {
//定义count,代表包子个数
private int count;
//定义flag,代表是否有包子
private boolean flag;
public BaoZiPu() {
}
public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}
/*
改造get方法 ,此方法给消费线程用
作为消费包子方法
*/
public void getCount() {
System.out.println("消费了......第"+count+"个包子");
}
/*
改造set方法,此方法给生产线程用
作为生产包子方法
*/
public void setCount() {
count++;
System.out.println("生产了..第"+count+"个包子");
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
public class Product implements Runnable {
//创建包子铺对象
private BaoZiPu baoZiPu;
public Product(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (baoZiPu) {
if (baoZiPu.isFlag() == true) {
try {
baoZiPu.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产包子
baoZiPu.setCount();
//改变flag状态,为true,证明有包子了
baoZiPu.setFlag(true);
//唤醒消费线程
baoZiPu.notify();
}
}
}
}
//消费者
public class Consumer implements Runnable {
//创建包子铺对象
private BaoZiPu baoZiPu;
public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (baoZiPu) {
if (baoZiPu.isFlag() == false) {
try {
baoZiPu.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费包子
baoZiPu.getCount();
//改变flag状态,为false,证明没有包子了
baoZiPu.setFlag(false);
//唤醒生产线程
baoZiPu.notify();
}
}
}
}
public class Test01 {
public static void main(String[] args) {
BaoZiPu baoZiPu = new BaoZiPu();
Product product = new Product(baoZiPu);
Consumer consumer = new Consumer(baoZiPu);
new Thread(product).start();
new Thread(consumer).start();
}
}
3.用同步方法改造等待唤醒案例
//包子铺
public class BaoZiPu {
//定义count,代表包子个数
private int count;
//定义flag,代表是否有包子
private boolean flag;
public BaoZiPu() {
}
public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}
/*
改造get方法 ,此方法给消费线程用
作为消费包子方法
*/
public synchronized void getCount() {
if (this.flag == false) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费包子
System.out.println("消费了......第" + count + "个包子");
//改变flag状态,为false,证明没有包子了
this.flag = false;
//唤醒生产线程
this.notify();
}
/*
改造set方法,此方法给生产线程用
作为生产包子方法
*/
public synchronized void setCount() {
if (this.flag == true) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产包子
count++;
System.out.println("生产了..第" + count + "个包子");
//改变flag状态,为true,证明有包子了
this.flag = true;
//唤醒消费线程
this.notify();
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
//生产者
public class Product implements Runnable {
//创建包子铺对象
private BaoZiPu baoZiPu;
public Product(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
baoZiPu.setCount();
}
}
}
//消费者
public class Consumer implements Runnable {
//创建包子铺对象
private BaoZiPu baoZiPu;
public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
baoZiPu.getCount();
}
}
}
public class Test01 {
public static void main(String[] args) {
BaoZiPu baoZiPu = new BaoZiPu();
Product product = new Product(baoZiPu);
Consumer consumer = new Consumer(baoZiPu);
new Thread(product).start();
new Thread(consumer).start();
}
}
第二十一章.多等待多唤醒
1.解决多生产多消费问题(if改为while,将notify改为notifyAll)
//包子铺
public class BaoZiPu {
//定义count,代表包子个数
private int count;
//定义flag,代表是否有包子
private boolean flag;
public BaoZiPu() {
}
public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}
/*
改造get方法 ,此方法给消费线程用
作为消费包子方法
*/
public synchronized void getCount() {
while (this.flag == false) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费包子
System.out.println("消费了......第" + count + "个包子");
//改变flag状态,为false,证明没有包子了
this.flag = false;
this.notifyAll();
}
/*
改造set方法,此方法给生产线程用
作为生产包子方法
*/
public synchronized void setCount() {
while (this.flag == true) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产包子
count++;
System.out.println("生产了..第" + count + "个包子");
//改变flag状态,为true,证明有包子了
this.flag = true;
this.notifyAll();
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
//生产者
public class Product implements Runnable {
//创建包子铺对象
private BaoZiPu baoZiPu;
public Product(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
baoZiPu.setCount();
}
}
}
//消费者
public class Consumer implements Runnable {
//创建包子铺对象
private BaoZiPu baoZiPu;
public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
baoZiPu.getCount();
}
}
}
public class Test01 {
public static void main(String[] args) {
BaoZiPu baoZiPu = new BaoZiPu();
Product product = new Product(baoZiPu);
Consumer consumer = new Consumer(baoZiPu);
new Thread(product).start();
new Thread(product).start();
new Thread(consumer).start();
new Thread(consumer).start();
}
}
第二十二章.Lock锁
1.Lock对象的介绍和基本使用
1.概述:Lock是一个接口
2.实现类:
ReentrantLock
3.方法:
void lock() 获取锁
void unlock() 释放锁
public class MyTicket implements Runnable {
//定义100张票
int ticket = 100;
//创建Lock锁对象
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
//获取锁
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放锁
lock.unlock();
}
}
}
}
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket, "谢广坤");
Thread t2 = new Thread(myTicket, "赵四");
Thread t3 = new Thread(myTicket, "刘能");
//开启线程
t1.start();
t2.start();
t3.start();
}
}
2.Lock和synchronized区别
2.1.Lock
1.底层实现原理: 乐观锁
2.乐观锁执行原理: CAS机制 Compare And Swap (比较并交换)
2.2.synchronized
属于悲观锁,当一个线程拿到锁对象之后,其他线程拿不到,执行不了,当一个线程做操作的时候,其他线程操作不了
2.3.Lock和synchronized区别(悲观锁和乐观锁的区别)
a. Lock属于乐观锁,使用多个线程操作的是同一个变量
synchronized属于悲观锁,使用多个线程操作一段代码
b.
乐观锁:线程A在操作变量时,允许线程B操作,只是会先判断,如果有问题,就放弃本次操作.判断如果没有问题,就会正常操作
悲观锁:当线程A正在操作的时候,不允许线程B执行,要等A出来之后B才有可能进入执行
c.相对来说,悲观锁效率比较低,乐观锁效率比较高
当多线程操作同一个数据时,会出现以下问题:
1.可见性
i=9,变量i的初始值为9,每一个线程的操作都是减1。两个线程A与B同时访问变量,B先执行i-1,在将结果i=8同步到内存中,A线程也执行i-1,这时i=9的状态就被执行两次,出现线程安全问题。 线程安全问题产生的原因:一个线程对共享数据的修改不能立即为其他线程所见。 解决:给共享的变量加上关键字:volatile2.有序性
多行代码的编写顺序和编译顺序。 有些时候,编译器在编译代码时,为了提高效率,会对代码“重排”: .java文件 int a = 0; //第一行 int b = 20; //第二行 int c = a / b; //第三行 在执行第三行之前,由于第一行和第二行的先后顺序无所谓,所以编译器可能会对“第一行”和“第二行”进行代码重排: .class int b = 20; int a = 0; int c = a / b; 但在多线程环境下,这种重排可能是我们不希望发生的,因为:重排,可能会影响另一个线程的结果,所以我们不需要代码进行重排 解决:给共享的变量加上关键字:volatile3.原子性
指的是一个操作不可中断,即在多线程并发的环境下,一个操作一旦开始,就会在同一个CPU时间片内执行完毕 volatile解决不了原子性问题,所以为了多线程操作同一个数据出现的原子性问题,我们可以使用原子类 Atomicxxx类->xxx代表具体数据类型 -> 原子类的实现原理就是乐观锁public class Test02 { public static void main(String[] args) { //原子类 AtomicInteger i = new AtomicInteger();//int i = 0 /*int addAndGet(int delta)以原子方式将给定值与当前值相加。*/ int i1 = i.addAndGet(2); System.out.println(i1); } }
第二十三章.Condition(阻塞队列)
1.为什么要学Condition
wait和notify,都是本地方法,如果在高并发的情况下频繁操作wait和notify,比较消耗系统资源
2.Condition:接口
3.获取:需要用到Lock对象中的方法
Condition newCondition()
4.方法:
void await() 等待
void signal() 唤醒
5.注意:
Condition必须配合Lock对象使用
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//包子铺
public class BaoZiPu {
//定义count,代表包子个数
private int count;
//定义flag,代表是否有包子
private boolean flag;
Lock lock = new ReentrantLock();
//创建一个生产者的队列,装生产者等待的线程
Condition productCondition = lock.newCondition();
//创建一个消费者的队列,装消费者等待的线程
Condition consumerCondition = lock.newCondition();
public BaoZiPu() {
}
public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}
/*
改造get方法 ,此方法给消费线程用
作为消费包子方法
*/
public void getCount() {
//获取锁
lock.lock();
while (this.flag == false) {
try {
consumerCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费包子
System.out.println("消费了......第" + count + "个包子");
//改变flag状态,为false,证明没有包子了
this.flag = false;
productCondition.signal();
//释放锁
lock.unlock();
}
/*
改造set方法,此方法给生产线程用
作为生产包子方法
*/
public void setCount() {
//获取锁
lock.lock();
while (this.flag == true) {
try {
productCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产包子
count++;
System.out.println("生产了..第" + count + "个包子");
//改变flag状态,为true,证明有包子了
this.flag = true;
consumerCondition.signal();
//释放锁
lock.unlock();
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
//消费者
public class Consumer implements Runnable {
//创建包子铺对象
private BaoZiPu baoZiPu;
public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
baoZiPu.getCount();
}
}
}
//生产者
public class Product implements Runnable {
//创建包子铺对象
private BaoZiPu baoZiPu;
public Product(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
baoZiPu.setCount();
}
}
}
public class Test01 {
public static void main(String[] args) {
BaoZiPu baoZiPu = new BaoZiPu();
Product product = new Product(baoZiPu);
Consumer consumer = new Consumer(baoZiPu);
new Thread(product).start();
new Thread(product).start();
new Thread(consumer).start();
new Thread(consumer).start();
}
}
第二十四章.线程池
1.为什么要学线程池:
之前如果要是有线程任务了,我们就要开线程,用完还要销毁线程,所以如果频繁的开启线程,销毁线程很耗费内存资源
所以我们想要创建线程,此线程可以循环利用
将创建出来的线程放到一个容器中,有线程任务了,从容器中获取线程去执行;用完还回去
1.线程池:Executors
2.创建线程池,指定最多能创建多少个线程->Executors中的方法
static ExecutorService newFixedThreadPool(int nThreads)
nThreads:代表的是线程池中最多能创建多少条线程对象
3.ExecutorService:用来管理线程池中的线程的
4.ExecutorService中的方法:
Future<?> submit(Runnable task) -> 提交线程任务
5.Future->接口
a.作用:用来接收线程任务(run方法)的返回值的
b.注意:run方法没有返回值,所以我们可以不用Future去接收
6.ExecutorService中的方法:
void shutdown()-> 关闭线程池
public class Test01 {
public static void main(String[] args) {
//创建线程池,指定线程池中最多能创建多少条线程
ExecutorService es = Executors.newFixedThreadPool(2);
//提交线程任务
es.submit(new MyRunnable());
es.submit(new MyRunnable());
es.submit(new MyRunnable());
es.shutdown();
}
}
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"...执行了");
}
}
第二十五章.Callable接口
1.ExecutorService中的方法:
Future submit(Callable<T> task)
2.Callable<V>接口:类似于Runnable
V call()->设置线程任务的,类似于Runnable中的run方法
3.call方法和run方法区别:
a.相同点:
call方法和run方法都是设置线程任务的
b.不同点:
run方法:没有返回值,而且不能在run方法上throws异常
call方法:有返回值的,而且能在call方法上throws异常
4.call方法的返回值是什么类型呢?
Callable中<V>是啥类型,call返回值就是什么类型
<v>尖括号中的类型只能是引用数据类型
<int>->不行
<Integer>->行
5.Future:
a.Future是一个接口
b.作用:用来接收run方法或者call方法的返回值的
run方法没有返回值,所以无需用Future接收
call方法有返回值,所以需要用Future接收
c.Future中的方法:
V get()->获取Future接收到的返回值结果
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池
ExecutorService es = Executors.newFixedThreadPool(2);
Future<String> future = es.submit(new MyCallable());
System.out.println(future.get());
}
}
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "柳岩和涛哥的....故事";
}
}
1.Callable练习
需求:创建两个线程任务,一个线程任务完成1-100的和,一个线程任务返回一个字符串
public class Test01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Integer> f1 = es.submit(new MySum());
System.out.println(f1.get());
Future<String> f2 = es.submit(new MyString());
System.out.println(f2.get());
}
}
//求和
public class MySum implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum+=i;
}
return sum;
}
}
public class MyString implements Callable<String> {
@Override
public String call() throws Exception {
return "刘备说:飞飞,不要这样,哥哥给你哭一个";
}
}
第二十六章.定时器_Timer
1.概述:Timer,定时器
2.作用:让一个线程任务,每隔一段时间就执行一次,此时间使我们自己指定的
3.使用:
a.创建Timer对象
b.调用方法
void schedule(TimerTask task, Date firstTime, long period)
TimerTask:抽象类,实现了Runnable接口,设置线程任务
firstTime:从哪个时间开始算起
period:每隔多长时间线程任务执行一次,传递毫秒值
public class Test01 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("柳岩说:涛哥,起床了");
}
},new Date(),2000L);
}
}