目录
3.使用+拼接""和new出来的字符串对象创建字符串对象 (难点)
使用StringBuilder和StringBuffer时的注意点
使用FileInputStream和FileOutputStream实现-单文件的复制
使用套接字对象实现两个端点(Socket和ServerSocket)之间发送文件
String字符串
定义
是一个属于数据类型的引用类型,本质是一个类。
字符串对象的含义
使用“”引起来的内容
关于字符串对象内存地址的问题
1.使用“”赋值创建字符串对象
String name = "Java";
name = "OK";
Strng major = "Java";
String thing = "Ja" + "va"
说明:
- 第一段语句:创建了“Java”的字符串对象,确实将这个字符串对象的地址保存在了变量name中。(在字符串常量池中没找到“Java”字符,就会创建一个新的“Java”字符串对象)
- 第二段语句:表面上是重新给name变量赋了值,实质是创建了新的“OK”字符串对象。(过程就是执行时,发现“OK”在字符串常量池中不存在,就会创建一个新的“OK”字符串对象)
- 第三段语句:表面上创建了新的字符串对象,其实是直接引用“Java”字符对象(这时它已经在常量池中找到了,所以就直接使用。)
- 第四段语句:+两端都是""定义的字符串,值得注意的是:它是在拼接后再判断字符串常量池(缓冲区)中是否存在(拼接后的"Java"依然存在,将其地址保存到thing变量中)
以上变量(除却name创建新的“OK”字符串对象)的内存地址都是相等的(用的“==”做判断)
2.通过构造方法创建字符串对象
String str1 = "Java";
String str2 = "Java";
说明:
- 第一段语句:在字符串常量池中寻找"Java",不存在,所以在堆中newString(),将字符串常量池中的"Java"保存到new出来的区域,最后将堆中new出来的地址保存到栈中变量str1中。
- 第二段语句:在字符串常量池中寻找"Java",存在,直接引用,同样的在堆中newString(),将字符串常量池中的"ab"保存到new出来的区域,最后将堆中new出来的地址保存到栈中变量str2中。
以上str1和str2的内存地址不同(用的“==”做判断)(原因:str1和str2是堆中的两个区域)
3.使用+拼接""和new出来的字符串对象创建字符串对象 (难点)
String str2 = "Ja" + new String("va")
说明:
- 创建StringBuilder对象
- 在字符串常量池中创建"Ja"
- 在字符串常量池中创建"va"
- 创建String对象,保存"va"的地址
- 调用StringBuilder的append方法,将"Ja"和newString("va")拼接
以上过程一共创建4个对象("Ja","va",String, StringBuilder对象)
比较字符串时为什么要使用equals方法而不用“==”
两者比较的本质
- “==”判断的是内存地址
- 使用String类重写的equals方法进行判断,就是将两个字符串用字符数组保存,逐个判断字符数组中的每个字符,全部一致时返回true,所以比较的是字面值。
使用equals方法的注意点
要将已知不为空的字符串作为调用者,才不会出现空指针异常的问题。
可变字符串
使用StringBuilder和StringBuffer时的注意点
- 普通方法都是在直接操作同一个字符串对象,每次调用方法后,原字符串都会发生变化
- StringBuffer和StringBuilder并没有重写equals方法,所以可变字符串的值是否相同时,调用的是equals中原始的==判断。如果要判断两个可变字符串的值是否相同时,需要将其转换为String后调用equals判断
可变字符串相关面试题
比较String,StringBuilder和StringBuffer的区别
- 相同点
- 都可以表示字符串。都提供了一些操作字符串的方法。
- 都有相同的方法,如charAt()、indexOf等。
- 都是被final修饰的类,不能被继承。
- 不同点
- String定义的字符串是一个常量,可变字符串定义的字符串是一个变量。
- String类中的方法,调用后,不会改变原本字符串的值;可变字符串类中的方法,调用后,会改变原本字符串的值。
- StringBuilder是非线程安全的可变字符串类,StringBuffer是线程安全的可变字符串类,其中的方法被synchronized修饰。
- 总结
在频繁操作同一个字符串时,一定要使用可变字符串StringBuidler或StringBuffer类的对象,不能使用String类的对象。
方法调用的传值问题
只有参数是引用类型(类,数组,接口),在方法中是直接操作该参数,才会对实际参数造成影响。(引用类型如果在方法中再创建新对象则不会对实际参数造成影响)
与八大原始类型对应的包装类
包装类 | 原始类型 |
---|---|
Byte | byte |
Short | short |
Integer | int |
Long | long |
Float | float |
Double | double |
Character | char |
Boolean | boolean |
异常
方法
try-catch-finally
原理
先执行try中的代码,当出现异常,与后续catch中的异常类型进行匹配,如果匹配到对应的
类型或异常父类型,则执行大括号中的代码,最终一定执行finally中的内容
注意点
- try、catch、finally都不能单独使用,try需要配合catch或finally或catch和finally一起使用。
- 执行try中的内容时,当某行代码抛出异常,不再执行try中该行代码后续的内容。
- 无论try中的代码是否会抛出异常,finally中的代码一定会执行。
- 如果代码会抛出多个异常,可以使用多个catch进行捕获,需要将异常子类放在最前,异常父类放在最后。
- 如果代码会抛出多个异常,可以使用多个catch进行捕获,需要将异常子类放在最前,异常父类放在最后。
- 在try中定义的内容,无法在try之外的地方使用。
- try中如果有return,不影响finally的执行,finally优先于return执行。
try-catch-finally相关面试题
final、finally、finalize的区别
- final是一个修饰符,被final修饰的属性称为常量,方法不能被重写,类不能被继承。
- finally是try-catch-finally结构中的关键字,在无论是否抛出异常,都会执行的代码块。
- finalize是Object类中的方法,finalize()在某个对象被回收前调用的方法。
throws关键字
throw和throws的区别
- throws表示用于声明方法有可能出现的异常。使用时写在方法的小括号之后。
- throw用于手动抛出异常对象。使用时,写在方法体中,常用于满足某种情况时,强制中断程序用法:throw异常对象。
数组和集合的区别
数组的特点
- 数组中保存的元素都是有序的。可以通过下标快速访问。
- 数组中保存的数据都是同一种类型。
- 数组的长度在定义后,无法改变。
- 数组无法获取其中保存的元素实际数量。
集合的特点
- 能保存一组数据,可以有序也可以无序。
- 集合的容量可变。
- 集合中可以保存不同类型的数据。
- 可以获取集合中保存的元素实际数量。
泛型
定义
一种规范,常用于限制集合中元素的类型,省去遍历元素时判断是否为对应类型和转型的过程。
用法
在定义集合遍历时,在类后面写上<引用数据类型>
集合类或接口<引用数据类型> 集合变量名= new集合实现类();List<String> list =new ArrayList(); //当前集合只能保存String类型的元素 list.add("sdfsdf"); //list.add(123);//无法添加 List<Integer> list2=newArrayList(); list2.add(123);
集合家族
Collection接口
常用方法
方法名 | 返回值 | 作用 |
---|---|---|
add(Object obj) | boolean | 将元素添加到集合中 |
size() | int | 获取集合中的元素数量 |
isEmpty() | boolean | 判断集合是否为空 |
clear() | clear() | 清空集合 |
contains(Object obj) | boolean | 判断集合中是否存在指定元素 |
remove(Object obj) | boolean | 移除集合中的指定元素 |
toArray() | Object[] | 将集合转换为数组 |
iterator() | iterator | 获取集合的迭代器对象,用于遍历集合 |
lterable接口为什么不是集合的根接口
原因
- lterable被称为迭代器,是用于遍历集合元素的一个工具接口。
Collections集合工具类
与Collection的区别
- Collection是集合的根接口,定义了集合操作元素的方法。
- Collections是集合的工具类,定义了集合操作元素的静态方法。
常用方法
方法名 | 作用 |
---|---|
Collections.shuffle(List list) | 打乱集合中的元素顺序(洗牌) |
Collections.swap(List list,int a,int b) | 交换集合中的元素位置,参数为集合和两个索引 |
Collections.replaceAll(List list,Object obj,Object obj) | 替换集合中的旧元素为新元素 |
Collections.sort(List list) | 对集合中的元素进行排序,根据集合中元素的类的compareTo()方法排序 |
Collections.reverse(List[] list) | 翻转集合中的元素 |
list接口
特点
- 有序可重复
- 可以根据索引进行删除、获取元素等
实现类接口-ArrayList接口
特点
- 采用数组实现的集合。
- 可以通过索引访问元素,可以改变集合大小,如果要在其中插入和删除元素时,会影响后续元素。
- 该集合中保存的都是引用类型。
- 该集合查询效率高,中途增加和删除元素效率低。
构造方法
常用构造方法名· | 说明 |
---|---|
ArrayList() | 创建一个Object类型的空数组。在调用添加方法后,该数组大小为10 |
ArrayList(int initialCapacity) | 创建一个指定容量的Object数组,如果参数为负,会抛出IllegalArgumentException异常 |
常用方法实现了Collection接口和List接口。
实现类接口-LinkedList接口
特点
- 采用双向链表实现的集合。
- 集合中保存的每个元素也称为节点,除首尾节点外,其余节点都保存了自己的信息外,还保存了其前一个和后一个节点的地址。
- 如果在双向链表的数据结构中插入和删除操作节点时,不会影响其他节点的位置。如添加时新节点时,只需要重写定义新节点的前后节点位置即可。
- 如果要查询某个节点时,需要从头结点或尾结点开始一步步得到目标节点的位置。
- 双向链表在中间插入和删除的效率高,随机读取的效率低。
构造方法
常用构造方法名 | 说明 |
---|---|
LinkedList() | 创建一个空链表 |
常用方法不仅实现了Collection接口和List接口,还实现了Deque接口的方法。
set接口
特点
- 无序不重复。
- 允许保存null,没有索引。
- 没有自己定义的方法,都是继承于Collection接口中的方法。
哈希表(set集合实现原理)
- 哈希表,也称为散列表,是—种数据结构,能更快地访问数据。
- 要保存的数据称为原始值,这个原始值通过一个函数得到一个新的数据,这个函数称为哈希函数,这个新数据称为哈希码,哈希码和原始值之间有一个映射关系,这个关系称为哈希映射,可以构造一张映射表,这个表称为哈希表。在哈希表中,可以通过哈希码快速地访问对应的原始值。
- 哈希冲突,多出现在哈希函数有一定的几率让多个原始值得到相同的哈希码(哈希码一致,实际值不同)。
- 值得注意的是,如果两个对象的hashCode不同,这两个对象一定不同。如果两个对象的hashCode相同,这两个对象不—定相同。(此处就可能出现哈希冲突)
- 解决哈希冲突,就要使用"拉链法",将重复的这个哈希码所在的位置向链表—样进行延伸。
实现接口-HashSet接口
特点
- 采用哈希表实现。
- 元素不能重复,无序保存,允许保存null。
- 本质是一个HashMap对象。
- 使用HashSet集合时,通常要重写实体类中的equals和hashcode方法。
常用方法没有属于自定义的方法,都是重写了父接口Set和Collection中的方法。(注意没有与索引相关的方法。)
添加数据原理
- 如果两个的hashCode相同且equals结果为true,视为同一个对象,不能添加。
- 每次向集合中添加元素时,先判断该元素的hashCode是否存在:
如果不存在,视为不同对象,直接添加;
如果存在,再判断equals方法的结果(如果false,视为不同对象,可以添加;如果true,视为同一对象,不能添加)
equals方法和hashCode的关系
- 如果没有重写equals,默认是Object中使用==判断,如果结果为true,说明是同一个对象,hashCode一定相同。
- hashCode不同,说明不是同一个对象,没有重写equals,说明使用Object中equals的==判断,结果为false。
- 如果两个对象的hashCode相同,equals方法的比较结果为:可能是true(同一地址),可能是false(哈希冲突)。
实现接口-TreeSet接口
特点
- 特殊的Set实现类,数据可以有序保存,可以重复,不能添加null。
- 采用红黑树(自平衡二叉树实现的集合。
- 只能添加同一种类型的对象且该类实现了Comparable接口。
- compareTo()方法的返回值决定了能否添加新元素和新元素的位置。
- 添加的元素可以自动排序。
构造方法
常用构造方法名 | 说明 |
---|---|
TreeSet() | 创建一个空集合,实际上是创建了一个TreeMap对象。 |
使用场景
- 如果要保存的元素需要对其排序,使用该集合。
- 保存在其中的元素必须要实现Comparable接口,且重写compareTo()方法,自定义排序规则。
集合和数组之间的转换
集合转换为数组
使用Collection接口中的toArray()方法。
Object[] obj = 集合对象.toArray(); List<Integer> list = new ArrayList(); list.add(123); list.add(63); list.add(3); Integer[] nums = (Integer[])list.toArray();
数组转换为集合
遍历数组的同时添加到集合中。
//一个数组对象 int[] nums = {11,2,66,3,6,21}; //定义集合对象 List list=new ArrayList(); //遍历数组的同时添加到集合中 for(int i : nums){ list.add(i); }
一组数据转换为集合
使用Arrays工具类中的asList(一组数据)方法。
List<String> strings = Arrays.asList("XX","aa","qq","xx");
Map接口
特点
- Map成为映射,数据以键值对的形式保存。
- 键称为Key,值称为Value,键不能重复,键允许出现一个null作为键,值无限制。
- 键和值都是引用类型。
常用方法
方法名 | 作用 |
---|---|
size() | 得到键值对的数量 |
clear() | 清空所有键值对 |
put(Object key,Object value) | 向集合中添加一组键值对 |
get(Object key) | 在集合中根据键得到对应的值 |
remove(Object key)/remove(Object key,Object key) | 根据键或键值对移除 |
keyset() | 获取键的集合 |
values() | 获取值的集合 |
containsKey(Object key) | 判断是否存在某个键 |
containsValue(Object value) | 判断是否存在某个值 |
entrySet() | 得到集合中的所有键值对 |
实现类-HashMap接口
构造方法
常用构造方法名 | 说明 |
---|---|
HashMap() | 创建一个空的映射集合,默认大小为16,加载因子为0.75 |
常用方法继承自Map。
遍历集合中元素的方式
遍历List集合
ArrayList<String> nameList = newArrayList();
nameList.add("Tom");
nameList.add("Jerry");
nameList.add("LiHua");
nameList.add("Danny");
方式一:普通for循环
for(int i=0;i<nameList.size();i++){//从0遍历到size()
String name=nameList.get(i);//通过get(intindex)获取指定索引的元素
System.out.println(name);
}
方式二:增强for循环
for(String name : nameList){
System.out.println(name);
}
方式三:迭代器
//Collection类型的集合对象.iterator(),获取迭代器
Iterator<String> iterator=nameList.iterator();
//iterator.hasNext()判断集合中是否还有下一个元素
//iterator.next();获取下一个元素
while(iterator.hasNext()){
String name=iterator.next();
System.out.println(name);
}
遍历Set集合
Set hs = new HashSet();
hs.add(123);
hs.add("hello");
hs.add(null);
hs.add(987);
方式一:增强for循环
for(Object o:hs){
System.out.println(o);
}
方式二:迭代器
Iterator<Object> it=hs.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
遍历Map集合
Map<Integer,User> hm= new HashMap<>();
User u1=newUser("admin","123123");
User u2=newUser("tom","123123");
User u3=newUser("jerry","123123");
hm.put(1001,u1);
hm.put(1002,u2);
hm.put(1003,u3);
方式一:根据键得到对应的值
for(Integer id : hm.keySet()){//遍历键
//根据键得到对应的值
System.out.println(id+"\t"+hm.get(id).getUsername());
}
方式二:遍历键值对
//得到当前hashmap对象中的所有键值对的集合
Set<Map.Entry<Integer,User>> entries = hm.entrySet();
//遍历键值对
for(Map.Entry<Integer,User> entry:entries){
System.out.println(entry);
}
数组管理系统 VS 集合管理系统
使用数组完成-ATM系统
User类(封装的用户类)
/**
* 客户类
* 属性:
* 账户
* 密码
* 余额
* 上次登录时间
* 账户状态(0:表示正常;1:表示冻结)
* 账单
* 方法:
* 有参构造方法
* getter和setter
* toString(重写)
* equals(重写)
*/
public class User {
private String name;
private String password;
private double balance;
private String lastLogin;
private int status;
public Bill[] bills;
public User(String name, String password) {
this.name = name;
this.password = password;
this.lastLogin = null;
this.balance = 10000;
this.status = 0;
this.bills = new Bill[100];
}
//用户实体输出格式重写
@Override
public String toString() {
String s = status==0?"正常":"冻结";
return name + "\t" +
"\t" + password +
"\t" + lastLogin+
"\t" + s +
"\t";
}
//用户信息比较重写
@Override
public boolean equals(Object obj) {
if (this==obj){
return true;
}
if (obj instanceof User){
User user = (User) obj;
if (name.equals(user.getName())&&password.equals(user.getPassword())){
return true;
}
}
return false;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public String getLastLogin() {
return lastLogin;
}
public void setLastLogin(String lastLogin) {
this.lastLogin = lastLogin;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
}
Bill类(封装的账单类)
/**
* 账单类
* 属性:
* 操作类型(取款,存款)
* 金额
* 时间
* 方法
* setter和getter
* 重写toString()
*/
public class Bill {
public String operate;
public String money;
public String pointTime;
public Bill(String operate, String money, String pointTime) {
this.operate = operate;
this.money = money;
this.pointTime = pointTime;
}
@Override
public String toString() {
return operate + "\t" +
money + "\t" +
pointTime;
}
}
自定义异常类-MoneyException类(取多抛出的异常)
public class MoneyException extends Exception{
public MoneyException(String message) {
super(message);
}
}
自定义异常类-passwordException类(密码多次错误抛出的异常)
public class passwordException extends RuntimeException{
public passwordException(String message) {
super(message);
}
}
ATM类(主要方法集合)(注意整个过程返回对象之后,就会一直直接操作此对象,所有不需要再更新信息回数组中,此处本人犯过错,需谨慎!!!)
/**
* ATM提款机类
* 属性:
* 客户数组
* 方法:
* 客户登录
* 客户注册
* 查看余额
* 提款
* 存款
* 冻结账户
* 注销账户
* 更新账户信息
* 查看账户信息
* 记录个人账单
* 获得当前格式化的时间
* 查看账单
*/
public class ATM {
User[] users;
public ATM() {
users = new User[100];
}
//客户登录
public User login(User user){
if (user==null){
return null;
}
for (int i = 0; i < users.length; i++) {
if (users[i]!=null&&users[i].equals(user)){
return users[i];
}
}
return null;
}
//客户注册
public boolean register(User user){
if (user==null){
return false;
}
for (int i = 0; i < users.length; i++) {
if (users[i]!=null&&users[i].equals(user)){
return false;
}
if (users[i]==null){
users[i] = user;
return true;
}
}
return false;
}
//查看余额
public void viewBalance(User user){
System.out.println("当前用户"+user.getName()+"的余额为:"+user.getBalance()+"元");
}
//提款
public boolean withdrawal(User user,Double money){
if (user.getBalance()-money<=0){
return false;
}else {
Bill bill = new Bill("提款",money.toString(),getTime());
if (updateBill(user,bill)){
System.out.println("记录账单成功!");
}else {
System.out.println("记录账单失败!");
}
user.setBalance(user.getBalance()-money);
return true;
}
}
//存款
public boolean deposit(User user,Double money){
Bill bill = new Bill("存款",money.toString(),getTime());
if (updateBill(user,bill)){
System.out.println("记录账单成功!");
}else {
System.out.println("记录账单失败!");
}
user.setBalance(user.getBalance()+money);
return true;
}
//冻结账户
public boolean freeze(User user){
user.setStatus(1);
return true;
}
//解锁账户
public boolean unfreeze(User user){
user.setStatus(0);
return true;
}
//根据账号名查找账号
public User getUserByName(User user){
for (int i = 0; i < users.length; i++) {
if (users[i]!=null&&users[i].getName().equals(user.getName())){
return users[i];
}
}
return null;
}
//注销账户
public boolean cancelUser(User user){
for (int i = 0; i < users.length; i++) {
if (users[i]!=null&&users[i].equals(user)){
users[i]=null;
return true;
}
}
return false;
}
//更新账户信息
//public boolean updateUserInfo(User user){
// for (int i = 0; i < users.length; i++) {
// if (users[i]!=null&&users[i].equals(user)){
// users[i] = user;
// return true;
// }
// }
// return false;
//}
//查看本用户信息
public void viewUserInfo(User user){
System.out.println(user);
}
//记录账单
public boolean updateBill(User user,Bill bill){
for (int i = 0; i < user.bills.length; i++) {
if (user.bills[i]==null){
user.bills[i]= bill;
return true;
}
}
return false;
}
//获取格式化当前的时间
public String getTime() {
String mb = "YYYY/MM/dd hh:mm:ss EE";
SimpleDateFormat sdf = new SimpleDateFormat(mb);
return sdf.format(new Date());
}
//查看账单
public void viewBill(User user){
for (Bill bill : user.bills) {
if (bill!=null) {
System.out.println(bill);
}
}
}
}
Main类(主入口)
/**
* 主入口
* 方法:
* 首页
* 菜单
* 验证用户状态情况
* 重新更新密码失败次数
* 解锁冻结的账户
*/
public class Main {
static Scanner sc = new Scanner(System.in);
static ATM atm = new ATM();
static User user;
static int loginCount;
public static void main(String[] args) throws MoneyException {
System.out.println("----------------欢迎来到穷人如何变成富人银行系统------------------");
updateCount();
menu();
}
//银行首页
public static void menu() throws MoneyException {
System.out.println("---------------首页----------------");
System.out.println("1.登录账户");
System.out.println("2.注册账户");
System.out.println("3.解锁冻结的账户");
System.out.println("0.退出系统");
System.out.print("请输入您的选项:");
int menuChoose = sc.nextInt();
switch (menuChoose){
case 1:
loginMenu();
break;
case 2:
registerMenu();
menu();
break;
case 3:
unfreezeUser();
break;
case 0:
System.exit(0);
break;
}
}
//首页调用登录的方法
public static void loginMenu() throws MoneyException {
System.out.println("-------登录界面------");
System.out.println("【温馨提示:注册过的用户,密码不能错三次,否则将冻结账户】");
System.out.print("请输入账户名:");
String loginName = sc.next();
System.out.print("请输入密码:");
String loginPassword = sc.next();
user = new User(loginName,loginPassword);
//调用验证用户登录状态的方法
if (loginAndStatus(user)){
User userSys = atm.login(user);
//更新密码错误次数
updateCount();
//登录成功进入菜单
System.out.println("恭喜登录成功!请进入主菜单...");
enterSys(userSys);
};
//登录失败重新加载首页
menu();
}
//首页调用注册的方法
public static void registerMenu() throws MoneyException {
System.out.println("-------注册界面------");
System.out.print("请输入新账户名:");
String regName = sc.next();
System.out.print("请输入密码:");
String regPassword = sc.next();
user = new User(regName,regPassword);
if (atm.register(user)){
System.out.println("新账号注册成功!");
menu();
}else {
System.out.println("账户已存在!请重新输入...");
registerMenu();
}
}
//进入系统
public static void enterSys(User user) throws MoneyException {
System.out.println("--------------主菜单---------------");
System.out.println("1.查看余额");
System.out.println("2.取款");
System.out.println("3.存款");
System.out.println("4.查看用户信息");
System.out.println("5.查看用户账单");
System.out.println("6.注销账户");
System.out.println("7.切换账户");
System.out.println("0.返回首页");
System.out.print("请输入选项:");
int enterSysChoose = sc.nextInt();
switch (enterSysChoose){
case 1:
System.out.println("-------查看余额界面------");
//调用查看余额的方法
atm.viewBalance(user);
enterSys(user);
break;
case 2:
System.out.println("-------取款界面------");
System.out.print("请输入取款额:");
double moneyOut = sc.nextDouble();
try {
//单次取款额高于最高限额5000元,抛出异常,并不会取出成功
if (moneyOut>5000){
throw new MoneyException("单次取款额需在5000元以内,请重试...");
}
}catch (MoneyException e){
System.out.println(e);
enterSys(user);
}
try {
//调用取款的方法,如果余额不足,将强制退出登录
if (atm.withdrawal(user,moneyOut)){
System.out.println("已取款:"+moneyOut);
} else {
throw new MoneyException("余额不足,没钱还敢取这么多,将退出当前账号...");
}
}catch (MoneyException e){
System.out.println(e);
menu();
}
enterSys(user);
break;
case 3:
System.out.println("-------取款界面------");
System.out.print("请输入存款额:");
double moneyIn = sc.nextDouble();
if (atm.deposit(user,moneyIn)){
System.out.println("已存款"+moneyIn);
}else {
System.out.println("存款失败!");
}
enterSys(user);
break;
case 4:
System.out.println("-------查看用户信息界面------");
System.out.println("用户名\t密码\t上次登录时间\t状态");
//调用查看账户信息的方法
atm.viewUserInfo(user);
enterSys(user);
break;
case 5:
System.out.println("-------查看用户账单界面------");
System.out.println("操作\t金额\t时间");
//调用查看账单的方法
atm.viewBill(user);
enterSys(user);
break;
case 6:
//当注销当前账户后,此账户上所有信息将清空,同时返回首页页面
System.out.println("-------注销账户界面------");
if (atm.cancelUser(user)){
System.out.println("注销用户成功!");
}else {
System.out.println("注销用户失败!请重试...");
enterSys(user);
}
menu();
break;
case 7:
//调用首页登录的方法
loginMenu();
break;
case 0:
//当用户退出登录时,更新到数据库系统(也就是用户数组,减少资源消耗)
//if (atm.updateUserInfo(user)){
user.setLastLogin(atm.getTime());
System.out.println("正在返回首页请稍等...");
//}else {
// System.out.println("更新用户信息失败!请重试...");
// enterSys(user);
//}
menu();
break;
}
}
//验证登录和账户状态
public static boolean loginAndStatus(User user){
if (atm.login(user)==null){
User userFindByName = atm.getUserByName(user);
try {
if (userFindByName!=null) {
loginCount++;//尝试密码次数加1
System.out.println("第"+loginCount+"次尝试失败!");
//如果密码尝试次数到达3次将冻结账号,不能登录
if (loginCount == 3) {
atm.freeze(userFindByName);
throw new passwordException("登录错误次数达到3次!将冻结此用户,请注意...");
}
}
}catch (passwordException e){
System.out.println(e);
}
System.out.println("非常抱歉!账户信息错误,请重新输入...");
return false;
} else if(atm.login(user).getStatus()==1){
//判断存在的用户是否为冻结状态,如果是将不能登录。
System.out.println("非常遗憾!您的账户现在处于冻结状态,暂时无法登陆!");
return false;
}return true;
}
//重新更新密码失败次数(使用不同账户名登录时,要重置)
public static void updateCount(){
loginCount = 0;
}
//解锁冻结的账户
public static void unfreezeUser() throws MoneyException {
System.out.println("----------账户解锁界面------------");
System.out.println("请输入要解锁的账号名:");
String userName = sc.next();
System.out.println("请输入密码:");
String password = sc.next();
user = new User(userName,password);
User userSys = atm.login(user);
if (userSys!=null){
Random rd = new Random();
StringBuilder sbd1 = new StringBuilder();
//生成数字随机数
int num1=(rd.nextInt(10));
int num2=(rd.nextInt(10));
//生成小写字母随机数
int engLower = rd.nextInt(36)+65;
//生成大写字母随机数
int engBig = rd.nextInt(39)+97;
//转换成字符
char eL =(char)engLower;
char eB =(char)engBig;
//生成验证码数组集合
String[] yz = {eL+"",eB+"",num1+"",num2+""};
int chooseNum = 0;
for (int i = 0; i < 4; i++) {
chooseNum = rd.nextInt(4);
sbd1.append(yz[chooseNum]);
}
System.out.println("验证码:"+sbd1);
String yzSys=sbd1.toString();
System.out.print("请输入验证码:");
String yzUser=sc.next();
if (yzSys.equals(yzUser)){
System.out.println("验证码输入正确!");
System.out.println("正在解锁冻结的账户,请稍等...");
//调用解锁账户方法
if (atm.unfreeze(userSys)){
System.out.println("解锁成功!请登录...");
}
}else {
System.out.println("验证码错误!请重试...");
//验证码错误重新进入验证
unfreezeUser();
}
}else {
System.out.println("此账户未注册,请重新输入账户...");
unfreezeUser();
}
}
}
使用(List集合)ArrayList实现-新闻管理系统
News类(封装的新闻)
/**
*新闻类
* 属性:
* 标题
* 内容
* 编辑
* 发布时间
* 方法:
* 有参构造
* equals
* toString
* getter和setter
* 日期生成器
*/
public class News {
private String title;
private String content;
private String editor;
private String releaseTime;
public News(String title, String content, String editor) {
this.title = title;
this.content = content;
this.editor = editor;
this.releaseTime = getTime();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
News news = (News) o;
return Objects.equals(title, news.title) && Objects.equals(content, news.content) && Objects.equals(editor, news.editor) && Objects.equals(releaseTime, news.releaseTime);
}
@Override
public String toString() {
return "News{" +
"title='" + title + '\'' +
", content='" + content + '\'' +
", editor='" + editor + '\'' +
", releaseTime='" + releaseTime + '\'' +
'}';
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getEditor() {
return editor;
}
public void setEditor(String editor) {
this.editor = editor;
}
public String getReleaseTime() {
return releaseTime;
}
public void setReleaseTime(String releaseTime) {
this.releaseTime = releaseTime;
}
//日期生成器
public String getTime(){
String fp = "YYYY年MM月dd HH:ss:mm E";
SimpleDateFormat sdf = new SimpleDateFormat(fp);
String time = sdf.format(new Date());
return time;
}
}
NewsAgency类(方法集合体) (注意此处根据自动生成的有顺序的编号进行标识和索引,所以不用遍历整个集合,直接根据索引值判断即可)
public class NewsAgency {
ArrayList<News> newsArrayList = new ArrayList();
//添加新闻
public boolean addNews(News news){
boolean result = newsArrayList.add(news);
return result;
}
//删除新闻
public boolean deleteNewsById(int id){
if (newsArrayList.get(id-1)!=null){
newsArrayList.remove(id-1);
return true;
}
return false;
}
//查看所有新闻
public void showAllNews(){
System.out.println("编号\t标题");
int id = 0;
String title ="";
for (int i = 0; i < newsArrayList.size(); i++) {
id = i + 1;
title = newsArrayList.get(i).getTitle();
System.out.println(id+"\t"+title);
}
}
//查看指定新闻的内容(这里最好返回对象给其它方法使用)
public boolean showNewsById(int id){
//for (int i = 0; i < newsArrayList.size(); i++) {
// if ((i+1)==id) {
// String content = newsArrayList.get(i).getContent();
// System.out.println(id+"编号新闻的内容"+content);
// return true;
// }else {
// System.out.println(id+"编号新闻不存在!");
// }
//}
if (newsArrayList.get(id-1)!=null){
String content = newsArrayList.get(id-1).getContent();
String title = newsArrayList.get(id-1).getTitle();
String editor = newsArrayList.get(id-1).getEditor();
String releaseTime = newsArrayList.get(id-1).getTime();
System.out.println("编号\t标题\t内容\t编辑\t发布时间");
System.out.println(id+"\t"+title+"\t"+content+"\t"+editor+"\t"+releaseTime);
return true;
}
return false;
}
//修改指定新闻的内容
public boolean updateNewsById(int id,String newContent){
if (newsArrayList.get(id-1)!=null){
newsArrayList.get(id-1).setContent(newContent);
return true;
}
return false;
}
}
Main类(主入口)
public class Main {
static Scanner sc = new Scanner(System.in);
NewsAgency newsAgency = new NewsAgency();
News news;
public static void main(String[] args) {
Main main = new Main();
main.menu();
}
public void menu(){
System.out.println("------------首页-------------");
System.out.println("1.添加新闻");
System.out.println("2.删除新闻");
System.out.println("3.查看新闻");
System.out.println("4.查看详情");
System.out.println("5.修改新闻");
System.out.println("0.退出新闻管理系统");
System.out.print("请输入选项:");
int menuChoose = sc.nextInt();
switch (menuChoose){
case 1:
addN();
break;
case 2:
deleteNById();
break;
case 3:
viewAll();
break;
case 4:
viewDetail();
break;
case 5:
updateNById();
break;
case 0:
System.exit(0);
break;
}
menu();
}
//添加
public void addN(){
System.out.println("---------添加新闻界面----------");
System.out.print("请输入新闻标题:");
sc.nextLine();
String title = sc.nextLine();
System.out.print("请输入新闻内容:");
String content = sc.nextLine();
System.out.print("请输入新闻编辑人:");
String editor = sc.nextLine();
news = new News(title,content,editor);
if (newsAgency.addNews(news)){
System.out.println("添加成功!");
menu();
}else {
System.out.println("添加失败!请重试...");
addN();
}
}
//删除
public void deleteNById(){
System.out.println("---------删除新闻界面---------");
System.out.print("请输入待删除新闻的编号:");
int id = sc.nextInt();
if (newsAgency.deleteNewsById(id)){
System.out.println("删除编号为"+id+"的新闻成功!");
menu();
}else {
System.out.println("删除编号为"+id+"的新闻失败!请重试...");
deleteNById();
}
}
//查看所有
public void viewAll(){
System.out.println("---------查看所有新闻界面---------");
newsAgency.showAllNews();
}
//查看详情
public void viewDetail() {
System.out.println("---------查看新闻详情界面---------");
System.out.print("请输入待查新闻的编号:");
int id = sc.nextInt();
if (newsAgency.showNewsById(id)){
menu();
}else {
System.out.println("查询失败!请重试界面...");
viewDetail();
}
}
//修改
public void updateNById(){
System.out.println("---------修改新闻---------");
System.out.print("请输入待修改新闻的编号:");
int id = sc.nextInt();
System.out.println("请输入更新的新闻内容:");
String content = sc.next();
if (newsAgency.updateNewsById(id,content)){
System.out.println("修改编号为"+id+"的新闻成功!");
menu();
}else {
System.out.println("修改失败!请重试...");
updateNById();
}
}
}
使用(Map集合)HashMap实现-用户管理系统
User类(封装的用户)
public class User {
private String id;
private String name;
private String password;
private String date;
public User(String name, String password) {
this.name = name;
this.password = password;
this.date = new SimpleDateFormat("YYYY年MM月dd日 HH:mm:ss E").format(new Date());
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
", date='" + date + '\'' +
'}';
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
}
Main类(主方法)
/**
* 使用HashMap添加并查看
*/
public class Main {
HashMap users = new HashMap();
static Main main = new Main();
static Scanner sc = new Scanner(System.in);
public static void main(String[] args) {
main.menu();
}
public void menu(){
System.out.println("--------------用户信息管理---------------");
System.out.println("1.添加新用户");
System.out.println("2.查看所有用户");
System.out.println("0.退出系统");
int choose = sc.nextInt();
switch (choose){
case 1:
main.addUsers();
break;
case 2:
main.showUsers();
break;
case 0:
System.exit(0);
}
}
public void addUsers(){
System.out.println("----------添加新用户------------");
System.out.println("请输入编号:");
String id = sc.next();
System.out.println("请输入用户名:");
String name = sc.next();
System.out.println("请输入密码:");
String password = sc.next();
if (users.containsKey(id)){
System.out.println("编号已存在!请重新输入...");
addUsers();
}
//实例化新用户
User user = new User( name, password);
//使用HashMap集合添加用户
users.put(id,user);
System.out.println("已添加新用户...");
menu();
}
public void showUsers(){
System.out.println("----------所有用户信息------------");
System.out.println("编号\t用户详情");
for (Object o : users.keySet()) {
System.out.print(o+"\t");
System.out.println(users.get(o));
}
menu();
}
}
文件类File
定义
- Java中的File类,表示本地硬盘中的文件(文件和目录)的一个类。
- 通过这个类创建的对象,可以操作对应的文件。
构造方法
常用构造方法名 | 说明 |
---|---|
File(String pathName) | 根据文件的完整路径创建File对象 |
File(String parent,String child) | 根据文件的父目录路径和自身路径创建File对象 |
File(File parent,String child) | 根据文件的父目录对应的File对象和自身路径创建File对象 |
常用方法
方法名 | 说明 |
---|---|
exists() | 判断文件是否存在 |
isFile() | 判断是否为文件 |
isDirectory() | 判断是否为目录 |
getName() | 获取文件名 |
getPath() | 获取文件相对路径 |
getAbsolutePath() | 获取文件绝对路径 |
length() | 获取文件所占字节 |
delete() | 删除文件或空目录 |
mkdir() | 创建目录 |
list() | 获取某个目录下的第一层子文件的名称的数组 |
IO
I:Input输入
O:Output输出
流Stream
特点
- 在Java中,流用于表示计算机硬盘与内存之间传输数据的通道。
- 将内存中的数据存入到硬盘中,称为写write,也称为输出Output。
- 将硬盘中的数据存入到内存中,称为读read,也称为输入Input。
流的分类
字节输入流InputStream
FileInpuStream、ObjectInputStream
字节输出流OutputStream
FileOutputStream、ObjectOutputStream
字符输入流Reader
FileReader、BufferedReader、OutputStreamWriter
字符输出流Writer
FileWriter、BufferedWriter、InputStreamReader
按方向分类
- 输入流:InputStream、Reader(将硬盘中的数据读取到内存中)
- 输出流:OutputStream、Writer(将内存中的数据写入到硬盘中)
按类型分
- 字节流:InputStream、OutputStream(读写非文本类型文件。如图片、音视频、其他文件等。)
- 字符流:Reader、Writer(读写纯文本类型文件。如txt、md等)
应用场景
- 如要将硬盘中某个txt文件中的内容读取到程序中,使用Reader。
- 如要将硬盘中的某个图片读取到程序中,使用InputStream。
- 如要将程序中的文本写入到硬盘中为txt类型文件时,使用Writer。
- 如要将程序中的数据写入到硬盘中为非文本文件时,使用OutputStream。
流的四个父类的特点
- 这四个父类都是在java.io包下,都是抽象类,不能直接创建其对象,使用其子类创建对象。
- 这四个父类中都定义了close()方法,用于关闭流对象,释放资源。
- 输入流(InputStream和Reader)都有read()方法读取数据到内存中,输出流都有write()方法写入数据到硬盘中。
- 输出流(OutputStream和Writer)都有flush()方法,用于将流中的数据冲刷到硬盘中
在使用输出流对象时,一定要调用flush()或close()方法后,才能真正将数据写入到硬盘中。- 所有的流中,以Stream结尾,都是字节流,数据以字节传输;以Reader或Writer结尾的,都是字符流,数据以字符传输。
- 读取硬盘中的数据,使用输入流,读取的文件必须存在;将数据写入到硬盘中,使用输出流,文件可以不存在,但父目录必须存在。
- 读入或写入文本时,使用字符流;读取或写入非文本时,使用字节流。
使用FileInputStream和FileOutputStream实现-单文件的复制
public class CopyFile{
public static void main(String[]args) throws IOException{
//定义原文件和目标文件
File source= new File("F:\\FileInputStream和FileOutputStream.mp4");
File target=new File("F:\\copy.mp4");
//定义文件字节输入流,用于读取原文件
File InputStream fis=new FileInputStream(source);
//定义文件字节输出流,用于写入文件
FileOutputStream fos=new FileOutputStream(target);
/*
//调用无参的read()方法,表示读取一个字节,返回读取到的字节
int read=fis.read();
//如果能读取到内容
while(read>-1){
//将读取到的内容写入到文件中
fos.write(read);
//继续读取
read=fis.read();
}
*/
//定义一个字节数组,大小为8MB
byte[] bytes=new byte[1024*1024*8];
//按字节数组读取,返回读取到的字节数量
int count=fis.read(bytes);
//循环读取写入
while(count>-1){
//将读取的字节数组写入到文件中
//fos.write(bytes);//如果调用该方法,最后一次会多写入上一次残留的数据
fos.write(bytes,0,count);//如果调用该方法,实际读取到了多少字节就写入多少
count=fis.read(bytes);
}
fis.close();
fos.close();
if(target.exists()){
System.out.println("复制成功");
}
}
}
文件夹的复制
public class CopyDirectory{
public static void main(String[]args){
/*Filesource=newFile("F:\\流的基本概念.mp4");
Filetarget=newFile("F:\\copy.mp4");
copyFile(source,target);*/
Filesource=newFile("F:\\笔记");
Filetarget=newFile("F:\\笔记副本");
/*
*sourceF:\221001\笔记
*targetF:\221001\笔记副本
*1.调用copyDir方法,判断发现source是一个文件夹,创建目标文件夹
target:“F:\221001\笔记副本”
*2.遍历source,如其中有xxx.md文件,即child
*此时的source是F:\221001\笔记\xxx.md,即child
*此时的target是F:\221001\笔记副本\xxx.md,用File(Fileparent,Stringchild)
构造方法表示这个目标文件
*所以创建File newTarget=newFile(target,child.getName())
*
**/
copyDir(source,target);
}
/*
*定义复制文件夹的方法
**/
public static void copyDir(File source,File target){
//如果是文件,调用单文件复制的方法
if(source.isFile()){
copyFile(source,target);
}else{//如果是文件夹
//创建要复制的目标文件夹
target.mkdir();
//展开原文件夹
for(File child:source.listFiles()){
//定义复制后的新目标文件
//如source为F:\221001\笔记\day1.md时,递归调用的target为F:\221001\笔
记副本\day1.md
File newTarget=newFile(target,child.getName());//这里使用
File(File parent,String child)构造方法创建target对象
//递归调用的原文件依然是当前遍历出来的子文件,目标文件就是最终复制的
F:\221001\笔记副本\day1.md
copyDir(child,newTarget);
}
}
}
/*
*定义单文件复制的方法
**/
public static void copyFile(Filesource,Filetarget){
FileInputStream fis=null;
FileOutputStream fos=null;
try{
//创建用于输入输出的流对象
fis=new FileInputStream(source);
fos=new FileOutputStream(target);
//定义字节数组
byte[]bytes=new byte[1024*1024*8];
//按数组读取
int count=fis.read(bytes);
while(count!=-1){
fos.write(bytes,0,count);
count=fis.read(bytes);
}
}catch(FileNotFoundExceptione){
System.out.println("文件不存在"+e);
}catch(IOExceptione){
System.out.println("读写异常"+e);
}finally{
try{
if(fis!=null){
fis.close();
}
if(fos!=null){
fos.close();
}
}catch(IOExceptione){
System.out.println("关闭流对象异常"+e);
}
}
}
}
序列化和反序列化应用
Person类(实现Serializable接口)
/*
*如果希望该类的对象能序列化,写入对象到本地,必须要实现Serializable接口
*Serializable接口中没有任何方法,是一个标记接口,表示该类的对象可以被序列化
**/
public class Person implements Serializable{
private String name;
private intage;
private String sex;
//省略getter/setter和toString()
}
Main类
public class Test1{
public static void main(String[]args)throwsIOException,ClassNotFoundException{
Person p1=newPerson("王海",22,"男");
Person p2=newPerson("赵敏",24,"女");
Person p3=newPerson("刘涛",21,"女");
ArrayList<Person> list=newArrayList<>();
list.add(p1);
list.add(p2);
list.add(p3);
//创建OutStream的实现类,设置写入的文件路径
OutputStream os=new FileOutputStream("F:\\person.p");
//创建对象输出字节流,参数为OutStream类型
ObjectOutputStream oos=new ObjectOutputStream(os);
//调用writeObject(Objectobj)方法,将对象写入到硬盘中(序列化)
oos.writeObject(list);
oos.close();
//创建对象输入字节流,将上一步保存的文件进行反序列化
ObjectInputStream ois=new ObjectInputStream(newFileInputStream("F:\\person.p"));
//使用readObject()方法,将写入的文件进行读取(反序列化)
ArrayList<Person> pList=(ArrayList<Person>)ois.readObject();
for(Person person:pList){
System.out.println(person);
}
ois.close();
}
}
网络编程
InetAddress类
定义
表示IP对象的一个类。
Socket类和ServerSocket类
相同点
都属于Socket(套接字)对象,表示网络中的某个端点。
不同点
Socket指普通端。
ServerSocket指服务器端。
使用套接字对象实现两个端点(Socket和ServerSocket)之间发送文件
服务端
/*
*使用套接字对象,实现客户端向服务端发送文件
*
*定义服务端套接字对象
**/
public class Server{
public static void main(String[]args)throws IOException{
//以本机创建服务端套接字对象
ServerSocket server=new ServerSocket(8899,100,
InetAddress.getLocalHost());
//等待客户端连接,返回连接的客户端套接字对象
Socket client=server.accept();
//定义要将读取到的数据写入到本地的文件字节输出流对象
FileOutputStream fos=new FileOutputStream("上传文件.md");
//获取客户端与服务端的输入流对象,读取发送的数据
InputStream is=client.getInputStream();
//定义读取的字节数组
byte[]bytes=new byte[1024*1024*8];
int count=is.read(bytes);
while(count!=-1){
//将读取到的数据写入到本地
fos.write(bytes,0,count);
count=is.read(bytes);
}
fos.close();
is.close();
}
}
客户端
/*
*定义客户端套接字对象
**/
public class Client{
public static void main(String[]args)throws IOException{
//创建客户端套接字对象,连接指定的服务端套接字对象
Socket client=new Socket("192.168.31.39",8899);
//获取客户端与服务端的输出流对象
OutputStream os=client.getOutputStream();
//成功连接后,将某个文件发送给服务端
//定义要发送的文件对象
File file = new File("F:\\a.md");
//读取要发送的文件
FileInputStream fis=new FileInputStream(file);
//定义字节数组
byte[] bytes=new byte[1024*1024*8];
//循环读取要发送的文件
int count=fis.read(bytes);
while(count!=-1){
//将读取到的数据写入到客户端套接字与服务端套接字的通道中
os.write(bytes,0,count);
count=fis.read(bytes);
}
fis.close();
os.close();
}
}
进程和线程
进程Process
进程就是操作系统中执行的程序。一个程序就是一个执行的进程实体。每个运行中的进程,都有属于它独立的内存空间,各个进程互不影响。
线程Thread
- 线程是一个进程中的执行单元,一个进程中可以有多个线程。
- 多个线程,可以访问同一个进程中的资源。
- 每个线程都有一个独立的栈空间,这些线程所在的栈空间位于同一个进程空间中。
多线程
- 如果一个进程中,同时在执行着多个线程,就称为多线程。
- 多线程可以提高程序执行效率。如多个窗口卖票,可以加快卖票的效率。
- 其实每个执行的Java程序,都是多线程执行,main方法称为主线程,还有gc线程(守护线程)在同时运行。
并行和并发
并行
各个进程同时执行。
并发
多个线程同时执行,称为并发。
同步和异步
同步
所有的任务排队执行。
异步
在执行任务A的同时,执行任务B。
Java中的线程Thread类
获取当前正在运行的线程对象
Thread ct = Thread.cuurentThread();
创建一个线程对象(构造方法)
常用构造方法名 | 说明 |
---|---|
Thread() | 创建一个默认的线程对象 |
Thread(String name) | 创建一个指定名称的线程对象 |
Thread(Runnable target) | 将一个Runnable对象包装为线程对象 |
Thread(Runnable target,Stringname) | 将一个Runnable对象包装为线程对象同时设置线程名 |
常用方法
方法名 | 作用 |
---|---|
start() | 让线程进入就绪状态 |
run() | 线程获得执行权时执行的方法(线程要做的事情) |
Thread.sleep(long m) | 设置当前线程休眠m毫秒 |
Thread.currentThread() | 获取当前执行的线程对象 |
实现多线程
方式一:继承Thread类
- 创建一个类,继承Thread类。
- 重写Thread类中的run()方法。
- 创建自定义的线程子类对象后,调用start()方法。
方式二:实现Runnable接口(建议使用)
自定义一个类,实现Runnable接口。
重写run()方法,将多线程要执行的内容写在该方法中。
创建Runnable接口的实现类对象。
使用构造方法Thread(Runnable target)或Thread(Runnable target,String name)将上一步创建。
的Runnable实现类对象包装为Thread对象。
方式三:使用匿名内部类
使用匿名内部类充当Runnable接口的实现类。
生命周期
新生状态
- 当线程对象被创建后,就进入了新生状态。
就绪状态
- 当某个线程对象调用了start()方法后,就进入了就绪状态。
- 在这个状态下,线程对象不会做任何事情,只在等他CPU调度。
运行状态
- 当某个线程对象得到CPU时间片(CPU执行这个线程的机会所给的时间),则进入运行状态,开始执行run()方法。
- 不会等待run()方法执行完毕,只会在指定的时间内尽可能地执行run()方法。只要调用玩run()方法后,就会再进入就绪状态。
阻塞状态
- 如果某个线程遇到了sleep()方法或wait()方法时,就会进入阻塞状态。
- sleep()方法会在指定时间后,让线程重新就绪。
- wait()方法只有在被调用notify()或notifyAll()方法唤醒后才能重新就绪。
终止状态
- 当某个线程的run()方法中的所有内容都执行完,就会进入终止状态,意味着该线程的使命已经完成。
守护线程
特点
- 如果将一个线程设置setDeamon(true),表示该线程为守护线程。
- 守护线程会随着其他非守护线程终止而终止。
多线程访问同一个资源
可能出现的问题
多个线程对同一对象异步进行操作,导致结果不能达到预期。
出现问题的原因
- 由于线程调用start()方法后,就进入就绪状态。如果获得了CPU时间片,就开始调用run()方法,调用run()方法后,就会再次进入就绪状态,不会等待run()方法执行完毕,所以在线程A执行run()方法的时候,线程B也开始执行了,这样就会出现数据共享的问题。
- 因为现在所有的线程都是异步(同时)执行。
如何解决
让线程同步(排队)执行即可。这样一来,某个线程执行run()方法的时候,让其他线程等待run()方法的内容执行完毕。
synchronized关键字
可以修饰方法或代码块
修饰方法
写在方法的返回值之前,这时该方法就称为同步方法。
public synchronized void fun(){ //会排队执行的代码 }
修饰代码块
写在一个独立的{}前,这时该段内容称为同步代码块。
synchronized(要同步的对象或this){ //会排队执行的代码 }
原理
- 每个对象默认都有一把"锁",当某个线程运行到被synchronized修饰的方法时,该对象就会拥有这把锁,在拥有锁的过程中,其他线程不能同时访问该方法,只有等待其结束后,才会释放这把锁。
- 使用synchronized修饰后的锁称为"悲观锁"。
- 方法被synchronized修饰后,称为同步方法,就会让原本多线程变成了单线程(异步变为同步)。
死锁
场景
当多个线程同时获取不同资源时,恰好获得的资源都是对方下一步执行所需的资源,但是双方都在等待资源发放,导致多个线程一直不结束运行,也不继续执行下去。
解决方式
方式一
- 让两个线程获取资源的顺序保持一致。
方式二
- 让两个线程在获取资源A和B之前,再获取第三个资源,对第三个资源使用synchronized进行同步,这样某个线程在获取第三个资源后,将后续内容执行完毕,其他线程才能开始执行。
多线程相关面试题
实现多线程的方式?
- 继承Thread类
- 实现Runnable接口后,包装为Thread对象
- 匿名内部类
为什么说StringBuilder或ArrayList、HashMap是非线程安全的?
因为多个线程对这些类型创建的对象进行操作不能达到预期的结果。
个人总结
在这个阶段中能明显感觉到了知识点的增多,如果不能多次进行回顾,很可能会混淆和忘弃。所以这是很值得重视的一点。同时相关面试题也增多了,这也需要自己去理解和记忆。整理刚学知识点更要学会和以前类似的知识点做区分,更深层次的理解其原理。在做作业时的问题也有必要做一个笔记整理,这样能大大避免再次出错。每个程序员天生不是敲代码,但是却是天生有学习逻辑思维的能手,一次又一次的错误,我相信这会成为每一个人前行路上的“灯塔”!加油!!!