java参考材料-2023-3-29

java笔试
学习材料:
1.

PDF文档
1.jvm,jre和jdk
JDK是Java的开发工具
JRE是Java程序运行所需的环境
JVM是Java虚拟机.
它们之间的关系是JDK包含JRE和JVM,JRE包含JVM.
1.Java 变量命名规则
变量命名规则:
基本要求:
1,首字母:字母,下划线(_)或符号(不能含有空格,开头不能以数字开头)
2,其他部分:数字,字母,下划线,符号(和首字母相比,多了可以用数字)
3.变量名不能是Java关键字(即保留字)
4.除了下划线、之外,不包括任何其他特殊字符
其他要求:
(1)变量名当有多个单词组成时,第一个单词首字母小写,后一个单词的首字母大写(如:myName)
(2)见名知意
(3)变量名的长度没有限制,但却区分大小写(如:power和Power是两个不同的变量)
1.java基本类型与转换
整型:byte,short,int,long,一共四种
由小到大不需要转换,由大到小需要强制转换
浮点型默认类型是double

1.堆和栈
基本数据类型创建在栈中
引用数据类型,首先在栈中分配一块空间,然后在堆中也分配空间来存储数据的具体值。栈的地址指向堆的具体数值。

1.java的==(valueof)
https://blog.csdn.net/qq_40913465/article/details/111862367
Integer a= 47;
int b=47;
Integer c=Integer.valueOf(47);
Integer d=new Integer(47);

              System.out.println(a==b);//true,因为会自动装箱
              System.out.println(a==c);//true,Integer.valueOf介于(-127,,128)之间结果是true
              System.out.println(c==d);//false,因为new了,结合ValueOf地址,因此是false
              System.out.println(b==d);//true,
              
              
              Integer aa= 300;
              int bb=300;
              Integer cc=Integer.valueOf(300);
              Integer dd=new Integer(300);
              
              
              System.out.println(aa==bb);//true
              System.out.println(aa==cc);//false,超过128之后,valueOf会是false
              System.out.println(cc==dd);//false
              System.out.println(bb==dd);//true

大概说的就是在通过 valueOf 方法创建 Integer 对象的时候,如果数值在[-128,127] 之间,便返回指向 IntegerCache.cache 中已经存在的对象的引用;不在[-128,127] 之间的会创建一个新的 Integer 对象。
Double 的 valueOf 方法,每次返回都是重新 new 一个新的对象,因为地址不同,所以上面代码中的结果都不相等。
boolean是静态方法
总结:Integer 、 Short 、 Byte 、 Character 、 Long 这几个类的 valueOf 方法的实现是类似的。 Double 、 Float 的valueOf 方法的实现是类似的。然后是 Boolean 的 valueOf 方法是单独一组的
1.a+=b和a=a+b的区别
+=可以隐式转换,所以不会报错
a=a+b不可以。因为不能进行类型的隐式转换,所以会报错
byte a = 127;
byte b = 127;
b = a + b; // 报编译错误:cannot convert from int to byte.a+b变成了int类型,但是不能赋值给byte b
b += a;//可以进行隐形转换
1.String和StringBuffer、StringBuilder的区别是什么
因为String具有不可变性,是final类,不能继承,因此是线程安全的。所以在经常改变字符串内容的情况下最好不要使用 String。因为每次对String的修改,都会创建一个新的String对象,这样会消耗很多的对象地址,效率慢。
StringBuffer:线程安全,所以有同步锁,性能低于StringBuider,适用于多线程
StringBuilder:线程不安全,效率最高。适用于单线程

1.public,protected,private的区别

访问权限由大到小:public>protected>默认(包访问权限)>private
关于权限访问:
外部类:public 、default
内部类:public 、private、protect
局部类:无
1.运算符(&&,&,||,|)的区别:
&&和||具有短路功能(2个符号的具有短路功能)。即如果第一个条件满足之后,就不会继续判断
&和| 所有条件都判断(1个符号的没有短路功能)
1.static关键字
主要用途就是方便在没有创建对象时调用方法和变量,以及优化程序性能
1)static变量(静态变量/类变量)
静态变量被所有对象共享,仅在类初次加载的时候被初始化,只有一个副本。
非静态变量在创建对象初始化,在new的时候初始化,并且有多个副本,副本之间相互独立
2)static方法(静态方法)
static 方法不依赖于任何对象就可以进行访问,在 static 方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用,
但是在非静态成员方法中是可以访问静态成员方法/变量的。
总结:静态方法不能访问非静态变量和方法;非静态方法可以访问静态方法和变量,函数中不能有静态变量,会报’Illegal modifier for parameter z; only final is permitted’的错误

3)static代码块
静态代码块的主要用途是可以用来优化程序的性能,用于初始化操作。如果程序中有多个 static 块,在类初次被加载的时候,会按照 static 块的顺序来执行每个 static 块。
4)this与静态变量
this 代表当前对象,可以访问静态变量,而静态方法中是不能访问非静态变量,也不能使用 this引用。
5)初始化顺序:(常考考点)
初始化顺序为父类中的静态变量和静态代码块——子类中的静态变量和静态代码块——父类中的实例变量和普通代码块——父类的构造函数——子类的实例变量和普通代码块——子类的构造函数
先父子类静态块,然后先父类之后再子类,普通代码块 早于 构造函数
1.final关键字
1)final类:被 final 修饰的类不可以被继承
2)final方法:被 final 修饰的方法不可以被重写
3)final变量:被 final 修饰的变量是基本类型,变量的数值不能改变;被修饰的变量是引用类型,变量便不能再引用其他对象,但是变量所引用的对象本身是可以改变的。
4)final finally finalize区别
final 主要用于修饰类,变量,方法 ,final修饰变量的时候,不能改变引用变量,可以改变所指向的内容
finally 一般作用在 try-catch 代码块中,在处理异常的时候,通常我们将一定要执行的代码方法 finally 代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。PS:如果在finally之前return了变量,那么把变量返回给main函数之后,也会执行finally里面的函数,只不过不会将finally里面改变的内容返回给main函数。
finalize 是一个属于 Object 类的一个方法,该方法一般由垃圾回收器来调用,当我们调用System.gc() 方法的时候,由垃圾回收器调用 finalize() ,回收垃圾,但Java语言规范并不保证 initialize 方法会被及时地执行、而且根本不会保证它们会被执行。
1.static和final的区别

1.this关键字

1.super关键字
super()一定要放到子类构造函数的第一行,否则会编译不通过

1.(封装、继承、多态)
封装:隐藏对象的属性和实现细节,仅对外公开接口
在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
要访问该类的代码和数据,必须通过严格的接口控制。
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
封装的优点
1)良好的封装能够减少耦合。
2)类内部的结构可以自由修改。
3)可以对成员变量进行更精确的控制。
4) 隐藏信息,实现细节。
/* 文件名: EncapTest.java */
public class EncapTest{

private String name;
private String idNum;
private int age;

public int getAge(){
return age;
}

public String getName(){
return name;
}

public String getIdNum(){
return idNum;
}

public void setAge( int newAge){
age = newAge;
}

public void setName(String newName){
name = newName;
}

public void setIdNum( String newId){
idNum = newId;
}
}
/* F文件名 : RunEncap.java */
public class RunEncap{
public static void main(String args[]){
EncapTest encap = new EncapTest();
encap.setName(“James”);
encap.setAge(20);
encap.setIdNum(“12343ms”);

  System.out.print("Name : " + encap.getName()+ 
                         " Age : "+ encap.getAge());
}

}
继承:子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
class Animal{
public void move(){
System.out.println(“动物可以移动”);
}
}
class Dog extends Animal{
public void move(){
System.out.println(“狗可以跑和走”);
}
public void bark(){
System.out.println(“狗可以吠叫”);
}
}
public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象

  a.move();// 执行 Animal 类的方法
  b.move();//执行 Dog 类的方法
  b.bark();//此句会报错,因为b本质上是Animal类,编译看左边,因为Animal类没有bark函数,因此报错

}
}
以上实例编译运行结果如下:
TestDog.java:30: cannot find symbol
symbol : method bark()
location: class Animal
b.bark();
^
该程序将抛出一个编译错误,因为b的引用类型Animal没有bark方法。
多态:在程序运行期间才确定(向上转型,运行右边对象的函数),即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。【编译看左边,运行看右边】
定义格式:父类类型 变量名=new 子类类型();
eg:
class Shape {
void draw() {}
}

class Circle extends Shape {
void draw() {
System.out.println(“Circle.draw()”);
}
}

class Square extends Shape {
void draw() {
System.out.println(“Square.draw()”);
}
}

class Triangle extends Shape {
void draw() {
System.out.println(“Triangle.draw()”);
}
}

public class Test {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 对象调用 show 方法
show(new Dog()); // 以 Dog 对象调用 show 方法

  Animal a = new Cat();  // 向上转型  
  a.eat();               // 调用的是 Cat 的 eat。
  Cat c = (Cat)a;        // 向下转型  
  c.work();        // 调用的是 Cat 的 work

}

public static void show(Animal a)  {
  a.eat();  
    // 类型判断
    if (a instanceof Cat)  {  // 猫做的事情 
        Cat c = (Cat)a;  
        c.work();  
    } else if (a instanceof Dog) { // 狗做的事情 
        Dog c = (Dog)a;  
        c.work();  
    }  
}  }

abstract class Animal {
abstract void eat(); }
class Cat extends Animal {
public void eat() {
System.out.println(“吃鱼”);
}
public void work() {
System.out.println(“抓老鼠”);
} }
class Dog extends Animal {
public void eat() {
System.out.println(“吃骨头”);
}
public void work() {
System.out.println(“看家”);
} }
多态的三个必要条件:继承、重写、向上转型。继承和重写很好理解,向上转型是指在多态中需要将子类的引用赋给父类对象。
1.抽象类和接口的对比
抽象类 接口
抽象类不能实例化
抽象类可以有构造函数
抽象类的声明是任意的
抽象类是可以有静态代码块和静态方法。
所有对象通过类来描绘的,但是不是所有类由对象描绘(抽象类就不是由对象描绘)
抽象类可以有成员变量,普通方法,构造方法
抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
抽象类中的抽象方法只是声明,没有方法体,具体方法体的实现由子类实现。public abstract void run();
抽象类可以有构造方法
继承抽象类不需要实现所有方法,只需要实现所有抽象方法 接口不能实例化
接口不能有构造函数
接口默认是public static final
接口中不能含有静态代码块以及静态方法(用 static 修饰的方法)
接口中不能有构造方法,所有方法必须是抽象的,不能有非抽象的普通方法
jdk1.8+,接口中可以有方法体
实现接口必须实现接口所有方法

在面向对象的概念中,所有的对象都是通过类来描绘的。
但是反过来,并不是所有的类都是用来描绘对象的(反例:抽象类),如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
抽象类总结规定
1. 抽象类和接口都不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
https://www.cnblogs.com/zyx110/p/10648119.html#:~:text=%25E6%258A%25BD%25E8%25B1%25A1%25E7%25B1%25BB%25E8%2583%25BD%25E5%25AE%259E%25E4%25BE%258B%25E5%258C%2596%25E5%2590%2597%25EF%25BC%259F.%2520%25E6%258A%25BD%25E8%25B1%25A1%25E7%25B1%25BB%25E4%25B8%258D%25E8%2583%25BD%25E7%259B%25B4%25E6%258E%25A5%25E9%2580%259A%25E8%25BF%2587new%25E5%258E%25BB%25E5%25AE%259E%25E4%25BE%258B%25E5%258C%2596%25E4%25B8%2580%25E4%25B8%25AA%25E5%25AF%25B9%25E8%25B1%25A1%25EF%25BC%258C%25E9%2582%25A3%25E5%25AE%2583%25E5%25B0%25B1%25E6%2598%25AF%25E4%25B8%258D%25E8%2583%25BD%25E5%25AE%259E%25E4%25BE%258B%25E5%258C%2596%25EF%25BC%258C%25E8%25A6%2581%25E8%258E%25B7%25E5%258F%2596%25E6%258A%25BD%25E8%25B1%25A1%25E7%25B1%25BB%25E7%259A%2584%25E5%25AF%25B9%25E8%25B1%25A1%25EF%25BC%258C%2520%25E9%259C%2580%25E8%25A6%2581%25E5%2585%2588%25E7%2594%25A8%25E4%25B8%2580%25E4%25B8%25AA%25E7%25B1%25BB%25E7%25BB%25A7%25E6%2589%25BF%25E6%258A%25BD%25E8%25B1%25A1%25E7%25B1%25BB%25EF%25BC%258C%2520%25E7%2584%25B6%25E5%2590%258E%25E5%258E%25BB%25E5%25AE%259E%25E4%25BE%258B%25E5%258C%2596%25E5%25AD%2590%25E7%25B1%25BB%25E3%2580%2582.%2520%25E4%25B9%259F%25E5%258F%25AF%25E4%25BB%25A5%25E7%2594%25A8%25E5%258C%25BF%25E5%2590%258D%25E5%2586%2585%25E9%2583%25A8%25E7%25B1%25BB%25EF%25BC%258C%25E5%259C%25A8%25E6%258A%25BD%25E8%25B1%25A1%25E7%25B1%25BB%25E4%25B8%25AD%25E5%2588%259B%25E5%25BB%25BA%25E4%25B8%2580%25E4%25B8%25AA%25E5%258C%25BF%25E5%2590%258D%25E7%259A%2584%25E5%25AD%2590%25E7%25B1%25BB%25EF%25BC%258C%25E7%25BB%25A7%25E6%2589%25BF%25E6%258A%25BD%25E8%25B1%25A1%25E7%25B1%25BB%25EF%25BC%258C%25E9%2580%259A%25E8%25BF%2587%25E7%2589%25B9%25E6%25AE%258A%25E7%259A%2584%25E8%25AF%25AD%25E6%25B3%2595%25E5%25AE%259E%25E4%25BE%258B%25E5%258C%2596%25E5%25AD%2590%25E7%25B1%25BB%25E7%259A%2584%25E5%25AF%25B9%25E8%25B1%25A1,%25E3%2580%2582.%2520%25EF%25BC%2588%25E5%2590%258E%25E9%259D%25A2%25E4%25BC%259A%25E7%25BB%2586%25E8%25AF%25B4%25EF%25BC%2589.%2520%25E7%258E%25B0%25E5%259C%25A8%25E9%2587%258D%25E7%2582%25B9%25E6%259D%25A5%25E4%25BA%2586%25EF%25BC%258C%25E8%25A6%2581%25E7%25A0%2594%25E7%25A9%25B6%25E8%25BF%2599%25E4%25B8%25AA%25E9%2597%25AE%25E9%25A2%2598%25EF%25BC%258C%25E5%2589%258D%25E6%258F%2590%25E6%2598%25AF%25E4%25BD%25A0%25E8%25A6%2581%25E4%25BA%2586%25E8%25A7%25A3%25E6%258A%25BD%25E8%25B1%25A1%25E7%25B1%25BB%25EF%25BC%258C%25E4%25B8%2587%25E5%258F%2598%25E4%25B8%258D%25E7%25A6%25BB%25E5%2585%25B6%25E5%25AE%2597%25EF%25BC%258C%25E6%2588%2591%25E4%25BB%25AC%25E4%25BB%258E%25E6%258A%25BD%25E8%25B1%25A1%25E7%25B1%25BB%25E7%259A%2584%25E6%25A0%25B9%25E6%25BA%2590%25E8%25B0%2588%25E8%25B5%25B7%2520%25EF%25BC%258C%2520%25E6%25B7%25B1%25E5%258C%2596%25E5%25AF%25B9%25E6%258A%25BD%25E8%25B1%25A1%25E7%25B1%25BB%25E7%259A%2584%25E7%2590%2586%25E8%25A7%25A3%25E3%2580%2582.
https://blog.csdn.net/qq_38741971/article/details/80099567
使用抽象类
//动物类(抽象类)
public abstract class Animal {
String name;//名字
String color;//颜色
//构造方法
public Animal(String name,String color){
this.name = name;
this.color = color;
}
//非抽象方法
public void eat(){
System.out.println(name+“吃东西!!!”);
}
//抽象方法
public abstract void run();
}
class Dog extends Animal{
public Dog(String name,String color){
super(name,color);
}

@Override
public void run() {
    System.out.println(name+"四条腿跑得快!!");
}

}
class Fish extends Animal{

public Fish(String name, String color) {
    super(name, color);
}

@Override
public void run() {
    System.out.println(name+"摇摇尾巴游啊游!!");
}

}
class Test{
public static void main(String[] args) {
Dog dog = new Dog(“哈巴狗”,“白色”);
dog.run();

    Fish fish = new Fish("锦鲤","红色");
    fish.run();
}

}
抽象类不能实例化的报错示例:
public abstract class Animal {
String name;
String color;

public Animal(String name,String color){
    this.name = name;
    this.color = color;
}
public abstract void run();

}
class Test{
public static void main(String[] args) {
Animal a = new Animal();//Error:(45, 20) java: com.my.animal.Animal是抽象的; 无法实例化
a.run();
}
}
2. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
3. 抽象类中的抽象方法只是声明,不包含方法体{},就是不给出方法的具体实现也就是方法的具体功能。
4. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
抽象类和接口的区别
1. 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是不能有抽象方法的方法体,然而接口中的方法不能有方法体。
2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
3. 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。另外,接口和抽象类在方法上有区别:
1.抽象类可以有构造方法,接口中不能有构造方法。
2.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
3.抽象类中可以有普通成员变量,接口中没有普通成员变量
4. 抽象类中的抽象方法的访问类型可以是public,protected和default类型
5. 抽象类中可以包含静态方法,接口中不能包含静态方法
6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型
7.
一个类可以实现多个接口,但只能继承一个抽象类。二者在应用方面也有一定的区别:接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约。而抽象类在代码实现方面发挥作用,可以实现代码的重用,例如,模板方法设计模式是抽象类的一个典型应用,假设某个项目的所有Servlet类都要用相同的方式进行权限判断、记录访问日志和处理异常,那么就可以定义一个抽象的基类,让所有的Servlet都继承这个抽象基类,在抽象基类的service方法中完成权限判断、记录访问日志和处理异常的代码,在各个子类中只是完成各自的业务逻辑代码。
1.类变量,实例变量,局部变量

1.重载与重写
重载:方法名相同。参数列表不同(参数类型不同、个数不同、顺序不同)。发生在同一个类中,方法名相同,参数列表不同。即重载的方法不能根据返回类型进行区分。
重写:用于在继承里面。父子类,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(public>protected>default>private);如果父类方法访问修饰符为 private 则子类中就不是重写

1.== 和equals的区别
== 的作用:
基本类型:比较值是否相等
引用类型:比较内存地址值是否相等
equals 的作用:
引用类型:默认情况下,比较内存地址值是否相等。可以按照需求逻辑,重写对象的equals方法。例如String重写之后,就是比较内容。String 的equals,如果判断前后类型不一致,那么直接返回false
1.Java中只有值传递
区分是值传递还是引用传递主要是看向方法中传递的是实际参数的副本还是实际参数的地址。
向 printValue 方法中传递的是一个引用的副本,只是这个副本引用和原始的引用指向的同一个对象,所以副本引用修改过对象属性后, 通过原始引用查看对象属性肯定也是被修改过的。换句话说, printValue 方法中修改的是副本引用指向的对象的属性,不是引用本身,如果修改的是引用本身,那么原始引用肯定不受影响。

可以看到将p 传入 printValue 方法后, printValue 方法调用结束后, p 的属性 name 没有改变,这是因为在 printValue 方法中并没有改变副本引用 q 所指向的对象,而是改变了副本引用 q 本身,将副本引用 q 指向了另一个对象并对这个对象的属性进行修改,所以原始引用p 所指向的对象不受影响。所以证明Java中只存在值传递
1.BIO,NIO,AIO的区别

BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并
发处理能力低。
NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通
讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于
事件和回调机制。
1.Java 异常

一共有三种捕获异常的方式:但catch和finally语句不能同时省略
Try- catch
Try-finally
Try-catch -finally.如果进入到catch,那么只执行finally块的内容,不会执行finally块后面的内容
报错error
o虚拟机JVM报错
o线程死锁
异常
o运行时异常: 都是RuntimeException类及其子类异常,包括:1.NullPointerException(空指针异常)、2.IndexOutOfBoundsException(下标越界异常)等,3.(ClassCastException)类型转换异常,4.(算数异常)。这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。 运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
o非运行时异常 (编译异常): 是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
1.字节流与字符流
字节流:继承于InputStream OutputStream
字符流:继承于 InputStreamReader OutputStreamWriter。是对字节流的封装,字符流直接接收字符串,但是要注意字符流转字节流的编码问题
1.序列化的概念
将java对象转化为字节流存储到硬盘中。实现serializable 接口。
1.对象泄露的场景
java 中的内存泄露的情况: 长生命周期的对象持有短生命周期对象的引用就很可能发生内存
泄露, 尽管短生命周期对象已经不再需要, 但是因为长生命周期对象持有它的引用而导致不
能被回收, 这就是 java 中内存泄露的发生场景, 通俗地说, 就是程序员可能创建了一个对
象, 以后一直不再使用这个对象, 这个对象却一直被引用, 即这个对象无用但是却无法被垃
圾回收器回收的, 这就是 java 中可能出现内存泄露的情况。
1.Math的ceil,floor,round。
Math.ceil(),向上取整
Math.floor(),向下取整
Math.round,四舍五入,即将原来的数字加上 0.5 后再向下取整
1.assert的功能
它对一个 boolean 表达式进行检查, 一个正确程序必须保证这个 boolean 表达式的值为 true; 如果该值为 false, 说明程序已经处于不正确的状态下, assert 将给出警告或退出。 一般来说, assertion 用于保证程序最基本、 关键的正确性。 assertion 检查通常在开发和测试时开启。 为了提高性能, 在软件发布后, assertion 检查通常是关闭的。
1.深拷贝和浅拷贝

浅拷贝:只复制指向某个对象的指针,而不复制对象本身。两个引用指向同一个地址,一方改变该数值,那么就会改变。
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
Person p2 = (Person) p1.clone();
深拷贝:是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。指向不同的地址,一方改变该数值,不影响另外一方。序列化的方式实现了对象的深拷贝
@Override
public Object clone() throws CloneNotSupportedException {
// 浅复制时:
// Object object = super.clone();
// return object;

    // 改为深复制:
    Student3 student = (Student3) super.clone();
    // 本来是浅复制,现在将Teacher对象复制一份并重新set进来
    student.setTeacher((Teacher2) student.getTeacher().clone());
    return student;
}

1.JAVA集合

1)collection和Map是两个并列的接口
2)List, Set, Map三者的区别
List

  1. ArrayList :数组
  2. LinkedList :双线链表
    SET
  3. HashSet :底层基于 HashMap 实现, HashSet 存入读取元素的方式和 HashMap 中的 Key 是
    一致的。
  4. TreeSet :红黑树
    Map
  5. HashMap : JDK1.8之前 HashMap 由数组+链表组成的, JDK1.8之后有数组+链表/红黑树组
    成,当链表长度大于8时,链表转化为红黑树,当长度小于6时,从红黑树转化为链表。这样
    做的目的是能提高 HashMap 的性能,因为红黑树的查找元素的时间复杂度远小于链表。
    HashMap 的 key 与 value 类型可以相同也可以不同,可以是字符串(String)类型的 key 和 value,也可以是整型(Integer)的 key 和字符串(String)类型的 value。
    HashMap 中的元素实际上是对象,一些常见的基本类型可以使用它的包装类。

// 引入 HashMap 类
import java.util.HashMap;

public class RunoobTest {
public static void main(String[] args) {
// 创建 HashMap 对象 Sites
HashMap Sites = new HashMap();
// 添加键值对
Sites.put(1, “Google”);
Sites.put(2, “Runoob”);
Sites.put(3, “Taobao”);
Sites.put(4, “Zhihu”);
System.out.println(Sites);
System.out.println(Sites.get(3));
Sites.remove(4);
Sites.clear();//删除所有键值对(key-value)可以使用 clear 方法:
// 输出 key 和 value
for (Integer i : Sites.keySet()) {
System.out.println("key: " + i + " value: " + Sites.get(i));
}
// 返回所有 value 值
for(String value: Sites.values()) {
// 输出每一个value
System.out.print(value + ", ");
}
}
}
//{four=Zhihu, one=Google, two=Runoob, three=Taobao}

无法复制加载中的内容
2. HashTable :数组+链表
3. TreeMap :红黑树
3)线程安全的集合类
Vector;相当于有同步机制的 ArrayList
Stack
HashTable
enum
4)快速失败(fail-fast)和安全失败(fail-safe)
Java的快速失败机制是Java集合框架中的一种错误检测机制。就是最快的时间能把错误抛出而不是让程序执行。当多个线程同时对集合中的内容进行
修改时可能就会抛出 ConcurrentModificationException 异常
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内
容,在拷贝的集合上进行遍历。所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所
以不会抛出 ConcurrentModificationException 异常。缺点是迭代器遍历的是开始遍历那一刻拿
到的集合拷贝,在遍历期间原集合发生了修改,迭代器是无法访问到修改后的内容。
java.util.concurrent 包下的容器都是安全失败,可以在多线程下并发使用。
5)Array和ArrayList的区别
Array 可以包含基本类型和对象类型, ArrayList 只能包含对象类型。
Array 大小是固定的, ArrayList 的大小是动态变化的。 ( ArrayList 的扩容是个常见面试题)
相比于 Array , ArrayList 有着更多的内置方法,如 addAll() , removeAll() 。
对于基本类型数据, ArrayList 使用自动装箱来减少编码工作量;而当处理固定大小的基本数据
类型的时候,这种方式相对比较慢,这时候应该使用 Array 。
6)Colloection和Collections的区别
Java Collections类操作集合详解
Colloection是集合类的上级接口,继承与他有关的接口主要有List和Set
Collections是包装类(用于OJ刷题的常用工具类),服务于Java的collection框架。针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全等操作。然后还有混排(Shuffling)、反转(Reverse)、替换所有的元素(fill)、拷贝(copy)、返回Collections中最小元素(min)、返回Collections中最大元素(max)、返回指定源列表中最后一次出现指定目标列表的起始位置(lastIndexOfSubList)、返回指定源列表中第一次出现指定目标列表的起始位置(IndexOfSubList)、根据指定的距离循环移动指定列表中的元素(Rotate)
7)HashMap和HashTable的区别
相同点:

  1. HashMap和Hashtable都实现了Map接口
  2. 都可以存储key-value数据
    不同点
  3. HashMap可以把null作为key或value,HashTable key和value都不可以为Null
  4. HashMap线程不安全,效率高。HashTable线程安全,效率低。
  5. HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。
    8)Arraylist与Linckedlist的区别
    ArrayList基于动态数组实现的非线程安全的集合,大小可以改变;LinkedList基于链表实现的非线程安全的集合。
    对于随机index访问的get和set方法,一般ArrayList的速度要优于LinkedList。因为ArrayList直接通过数组下标直接找到元素;LinkedList要移动指针遍历每个元素直到找到为止。
    新增和删除元素,一般LinkedList的速度要优于ArrayList。因为ArrayList在新增和删除元素时,可能扩容和复制数组;LinkedList实例化对象需要时间外,只需要修改指针即可。
    LinkedList集合不支持 高效的随机随机访问(RandomAccess)
    ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
    List 线程安全 Vector
    线程不安全 ArrayList
    linkedlist

hashmap,hashset与hashtable
HashMap Hashset Hashtable
HashMap则非线程安全
key和value都可以为null
HashMap是对Map接口的实现
HashMap的初始容量为16, 填充因子是0.75
HashMap是无序的。HashMap是基于哈希算法来确定元素的位置(槽)的,当我们向集合中存入数据时,它会计算传入的Key的哈希值,并利用哈希值取余来确定槽的位置,与存入的先后顺序无关, Hashset仅仅是存储不重复的元素,相当于简化版的HashMap,只是包含HashMap中的key而已。
Hashset也是非线程安全 Hashtable是线程安全
Hashtable不允许key和value为null
HashTable实现了Map接口和Dictionary抽象类
Hashtable初始容量为11,填充因子是0.75
Hashtable与ConcurrentHashMap区别
ConcurrentHashMap融合了hashtable和hashmap二者的优势。hashmap是非线程安全,因此在单线程情况下效率较高;hashtable在的多线程情况下,是线程安全的,同步操作能保证程序执行的正确性。
hashtable每次同步执行的时候都要锁住整个结构:
ConcurrentHashMap锁的方式是稍微细粒度的。
更令人惊讶的是ConcurrentHashMap的读取并发,因为在读取的大多数时候都没有用到锁定,所以读取操作几乎是完全的并发操作,而写操作锁定的粒度又非常细,比起之前又更加快速(这一点在桶更多时表现得更明显些)。只有在求size等操作时才需要锁定整个表。
在迭代时,使用弱一致迭代器,不再是抛出 ConcurrentModificationException,在改变时new新的数据从而不影响原有的数据,iterator完成后再将头指针替换为新的数据。
练习题
1.请设计一个一百亿的计算器
要点:因为int类型的数据上限是21亿,因此不满足百亿,要用BigDecimal类方法
https://www.cnblogs.com/zhangyinhua/p/11545305.html
主要方法与要点:
1)在数据初始化的时候,要用String类型传入到BigDecimal参数中,而不是用浮点型
BigDecimal a =new BigDecimal(0.1);//浮点型,输出结果会有问题
System.out.println(“a values is:”+a);
System.out.println(“=====================”);
BigDecimal b =new BigDecimal(“0.1”);//String类型,可用
System.out.println(“b values is:”+b);
2)常用方法
1.
add(BigDecimal)
2.
BigDecimal对象中的值相加,返回BigDecimal对象
1.
subtract(BigDecimal)
2.
BigDecimal对象中的值相减,返回BigDecimal对象
1.
multiply(BigDecimal)
2.
BigDecimal对象中的值相乘,返回BigDecimal对象
1.
divide(BigDecimal)
2.
BigDecimal对象中的值相除,返回BigDecimal对象
1.
toString()
2.
将BigDecimal对象中的值转换成字符串
1.
doubleValue()
2.
将BigDecimal对象中的值转换成双精度数
1.
floatValue()
2.
将BigDecimal对象中的值转换成单精度数
1.
longValue()
2.
将BigDecimal对象中的值转换成长整数
1.
intValue()
2.
将BigDecimal对象中的值转换成整数
3)数据比较
比较大小一般用的是bigdemical的compareTo方法
new bigdemica(a).compareTo(new bigdemical(b)) >= 0
4)BigDecimal格式化
由于NumberFormat类的format()方法可以使用BigDecimal对象作为其参数,可以利用BigDecimal对超出16位有效数字的货币值,百分值,以及一般数值进行格式化控制。
以利用BigDecimal对货币和百分比格式化为例。首先,创建BigDecimal对象,进行BigDecimal的算术运算后,分别建立对货币和百分比格式化的引用,最后利用BigDecimal对象作为format()方法的参数,输出其格式化的货币值和百分比。

NumberFormat currency = NumberFormat.getCurrencyInstance(); //建立货币格式化引用 
NumberFormat percent = NumberFormat.getPercentInstance();  //建立百分比格式化引用 
percent.setMaximumFractionDigits(3); //百分比小数点最多3位 

BigDecimal loanAmount = new BigDecimal("15000.48"); //贷款金额
BigDecimal interestRate = new BigDecimal("0.008"); //利率   
BigDecimal interest = loanAmount.multiply(interestRate); //相乘

System.out.println("贷款金额:\t" + currency.format(loanAmount)); 
System.out.println("利率:\t" + percent.format(interestRate)); 
System.out.println("利息:\t" + currency.format(interest)); 
//贷款金额: ¥15,000.48 利率: 0.8% 利息: ¥120.00

保留2位小数,位数不足的话补0
public class NumberFormat {

    public static void main(String[] s){
            System.out.println(formatToNumber(new BigDecimal("3.435")));
            System.out.println(formatToNumber(new BigDecimal(0)));
            System.out.println(formatToNumber(new BigDecimal("0.00")));
            System.out.println(formatToNumber(new BigDecimal("0.001")));
            System.out.println(formatToNumber(new BigDecimal("0.006")));
            System.out.println(formatToNumber(new BigDecimal("0.206")));
}
    /**
     * @desc 1.0~1之间的BigDecimal小数,格式化后失去前面的0,则前面直接加上0。
     * 2.传入的参数等于0,则直接返回字符串"0.00"
     * 3.大于1的小数,直接格式化返回字符串
     * @param obj传入的小数
     * @return
     */
    public static String formatToNumber(BigDecimal obj) {
            DecimalFormat df = new DecimalFormat("#.00");
            if(obj.compareTo(BigDecimal.ZERO)==0) {
                    return "0.00";
            }else if(obj.compareTo(BigDecimal.ZERO)>0&&obj.compareTo(new BigDecimal(1))<0){
                    return "0"+df.format(obj).toString();
            }else {
                    return df.format(obj).toString();
            }
    }

}
5)常见问题
通过BigDecimal的divide方法进行除法时当不整除,出现无限循环小数时,就会抛异常。
解决办法:在做除法的时候,要对分母指定保留小数位数,divide(xxxxx,2)
6)总结
1.
在需要精确的小数计算时再使用BigDecimal,BigDecimal的性能比double和float差,在处理庞大,复杂的运算时尤为明显。故一般精度的计算没必要使用BigDecimal。
2.
3.
尽量使用参数类型为String的构造函数。
4.
5.
BigDecimal都是不可变的(immutable)的, 在进行每一次四则运算时,都会产生一个新的对象 ,所以在做加减乘除运算时要记得要保存操作后的值。
6.
7)工具类
package com.vivo.ars.util;
import java.math.BigDecimal;

/**

  • 用于高精确处理常用的数学运算
    */
    public class ArithmeticUtils {
    //默认除法运算精度
    private static final int DEF_DIV_SCALE = 10;

    /**

    • 提供精确的加法运算
    • @param v1 被加数
    • @param v2 加数
    • @return 两个参数的和
      */

    public static double add(double v1, double v2) {
    BigDecimal b1 = new BigDecimal(Double.toString(v1));
    BigDecimal b2 = new BigDecimal(Double.toString(v2));
    return b1.add(b2).doubleValue();
    }

    /**

    • 提供精确的加法运算
    • @param v1 被加数
    • @param v2 加数
    • @return 两个参数的和
      */
      public static BigDecimal add(String v1, String v2) {
      BigDecimal b1 = new BigDecimal(v1);
      BigDecimal b2 = new BigDecimal(v2);
      return b1.add(b2);
      }

    /**

    • 提供精确的加法运算
    • @param v1 被加数
    • @param v2 加数
    • @param scale 保留scale 位小数
    • @return 两个参数的和
      */
      public static String add(String v1, String v2, int scale) {
      if (scale < 0) {
      throw new IllegalArgumentException(
      “The scale must be a positive integer or zero”);
      }
      BigDecimal b1 = new BigDecimal(v1);
      BigDecimal b2 = new BigDecimal(v2);
      return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
      }

    /**

    • 提供精确的减法运算
    • @param v1 被减数
    • @param v2 减数
    • @return 两个参数的差
      */
      public static double sub(double v1, double v2) {
      BigDecimal b1 = new BigDecimal(Double.toString(v1));
      BigDecimal b2 = new BigDecimal(Double.toString(v2));
      return b1.subtract(b2).doubleValue();
      }

    /**

    • 提供精确的减法运算。
    • @param v1 被减数
    • @param v2 减数
    • @return 两个参数的差
      */
      public static BigDecimal sub(String v1, String v2) {
      BigDecimal b1 = new BigDecimal(v1);
      BigDecimal b2 = new BigDecimal(v2);
      return b1.subtract(b2);
      }

    /**

    • 提供精确的减法运算
    • @param v1 被减数
    • @param v2 减数
    • @param scale 保留scale 位小数
    • @return 两个参数的差
      */
      public static String sub(String v1, String v2, int scale) {
      if (scale < 0) {
      throw new IllegalArgumentException(
      “The scale must be a positive integer or zero”);
      }
      BigDecimal b1 = new BigDecimal(v1);
      BigDecimal b2 = new BigDecimal(v2);
      return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
      }

    /**

    • 提供精确的乘法运算
    • @param v1 被乘数
    • @param v2 乘数
    • @return 两个参数的积
      */
      public static double mul(double v1, double v2) {
      BigDecimal b1 = new BigDecimal(Double.toString(v1));
      BigDecimal b2 = new BigDecimal(Double.toString(v2));
      return b1.multiply(b2).doubleValue();
      }

    /**

    • 提供精确的乘法运算
    • @param v1 被乘数
    • @param v2 乘数
    • @return 两个参数的积
      */
      public static BigDecimal mul(String v1, String v2) {
      BigDecimal b1 = new BigDecimal(v1);
      BigDecimal b2 = new BigDecimal(v2);
      return b1.multiply(b2);
      }

    /**

    • 提供精确的乘法运算
    • @param v1 被乘数
    • @param v2 乘数
    • @param scale 保留scale 位小数
    • @return 两个参数的积
      */
      public static double mul(double v1, double v2, int scale) {
      BigDecimal b1 = new BigDecimal(Double.toString(v1));
      BigDecimal b2 = new BigDecimal(Double.toString(v2));
      return round(b1.multiply(b2).doubleValue(), scale);
      }

    /**

    • 提供精确的乘法运算
    • @param v1 被乘数
    • @param v2 乘数
    • @param scale 保留scale 位小数
    • @return 两个参数的积
      */
      public static String mul(String v1, String v2, int scale) {
      if (scale < 0) {
      throw new IllegalArgumentException(
      “The scale must be a positive integer or zero”);
      }
      BigDecimal b1 = new BigDecimal(v1);
      BigDecimal b2 = new BigDecimal(v2);
      return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
      }

    /**

    • 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
    • 小数点以后10位,以后的数字四舍五入
    • @param v1 被除数
    • @param v2 除数
    • @return 两个参数的商
      */

    public static double div(double v1, double v2) {
    return div(v1, v2, DEF_DIV_SCALE);
    }

    /**

    • 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
    • 定精度,以后的数字四舍五入
    • @param v1 被除数
    • @param v2 除数
    • @param scale 表示表示需要精确到小数点以后几位。
    • @return 两个参数的商
      */
      public static double div(double v1, double v2, int scale) {
      if (scale < 0) {
      throw new IllegalArgumentException(“The scale must be a positive integer or zero”);
      }
      BigDecimal b1 = new BigDecimal(Double.toString(v1));
      BigDecimal b2 = new BigDecimal(Double.toString(v2));
      return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
      }

    /**

    • 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
    • 定精度,以后的数字四舍五入
    • @param v1 被除数
    • @param v2 除数
    • @param scale 表示需要精确到小数点以后几位
    • @return 两个参数的商
      */
      public static String div(String v1, String v2, int scale) {
      if (scale < 0) {
      throw new IllegalArgumentException(“The scale must be a positive integer or zero”);
      }
      BigDecimal b1 = new BigDecimal(v1);
      BigDecimal b2 = new BigDecimal(v1);
      return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString();
      }

    /**

    • 提供精确的小数位四舍五入处理
    • @param v 需要四舍五入的数字
    • @param scale 小数点后保留几位
    • @return 四舍五入后的结果
      */
      public static double round(double v, int scale) {
      if (scale < 0) {
      throw new IllegalArgumentException(“The scale must be a positive integer or zero”);
      }
      BigDecimal b = new BigDecimal(Double.toString(v));
      return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
      }

    /**

    • 提供精确的小数位四舍五入处理
    • @param v 需要四舍五入的数字
    • @param scale 小数点后保留几位
    • @return 四舍五入后的结果
      */
      public static String round(String v, int scale) {
      if (scale < 0) {
      throw new IllegalArgumentException(
      “The scale must be a positive integer or zero”);
      }
      BigDecimal b = new BigDecimal(v);
      return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
      }

    /**

    • 取余数
    • @param v1 被除数
    • @param v2 除数
    • @param scale 小数点后保留几位
    • @return 余数
      */
      public static String remainder(String v1, String v2, int scale) {
      if (scale < 0) {
      throw new IllegalArgumentException(
      “The scale must be a positive integer or zero”);
      }
      BigDecimal b1 = new BigDecimal(v1);
      BigDecimal b2 = new BigDecimal(v2);
      return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
      }

    /**

    • 取余数 BigDecimal
    • @param v1 被除数
    • @param v2 除数
    • @param scale 小数点后保留几位
    • @return 余数
      */
      public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) {
      if (scale < 0) {
      throw new IllegalArgumentException(
      “The scale must be a positive integer or zero”);
      }
      return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP);
      }

    /**

    • 比较大小
    • @param v1 被比较数
    • @param v2 比较数
    • @return 如果v1 大于v2 则 返回true 否则false
      */
      public static boolean compare(String v1, String v2) {
      BigDecimal b1 = new BigDecimal(v1);
      BigDecimal b2 = new BigDecimal(v2);
      int bj = b1.compareTo(b2);
      boolean res;
      if (bj > 0)
      res = true;
      else
      res = false;
      return res;
      }
      }
      OJ刷题
      Collection常用方法
      无法复制加载中的内容
      List特有方法
      方法
      void add(int index,Object obj) 在指定位置添加元素
      Object remove(int index) 删除指定元素并返回
      Object set(int index,Object obj) 把指定索引位置的元素更改为指定值并返回修改
      前的值
      int indexOf(Object o) 返回指定元素在集合中第一次出现的索引
      Object get(int index) 返回指定位置的元素
      List subList(int fromIndex,int
      toIndex) 截取集合(左闭右开)
      List和数组相互转换
      List转数组
      List对象.toArray
      Object[] toArray();

List strList = new ArrayList<>();
Object[] strArray = strList.toArray();
数组转List
方法1:用Arrays.asList,更方便一些
String[] strArray = { “array-a”, “array-b” };
List strList = Arrays.asList(strArray);
方法2:使用collections方法
String[] strArray = { “array-a”, “array-b” };
List strList = new ArrayList<>(strArray.length);
Collections.addAll(strList, strArray);
strListNew.add(“array-c”);
LinkedList特有方法
无法复制加载中的内容
MAP
Map适合储存键值对的数据。
containsKey是判断是否有key值,返回true或者false。重点掌握get和put的使用方法

HashSet与TreeSet
Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉。
TreeSet 是二叉树(红黑树的树据结构)实现的,Treeset中的数据是自动排好序的,不允许放入null值
HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束
HashSet要求放入的对象必须实现HashCode()方法,放入的对象,是以hashcode码作为标识的,而具有相同内容的String对象,hashcode是一样,所以放入的内容不能重复。但是同一个类的对象可以放入不同的实例
应用场景分析:
HashSet是基于Hash算法实现的,其性能通常都优于TreeSet。为快速查找而设计的Set,我们通常都应该使用HashSet,在我们需要排序的功能时,我们才使用TreeSet。
Map和Set的五道题
https://blog.csdn.net/weixin_46913665/article/details/123264153?spm=1001.2014.3001.5502
题目一:只出现一次的数字
使用HashMap 的key-value的形式,统计词频
//leetcode 136.https://leetcode.cn/problems/single-number/
写法1:
class Solution {
public static int singleNumber(int[] nums) {
Map map = new HashMap<>();
for (int i:nums){
if(map.containsKey(i)){
map.put(i,map.get(i)+1);//如果已有数值,那么词频+1
}else{
map.put(i,1);//如果没有这个词频,那么设置为1
}

    }
    //遍历map,如果value值是1,那么返回Key
    for (Map.Entryentry : map.entrySet()){
        if (entry.getValue()==1) return entry.getKey();
    }
    return 0;
}

}


写法2
class Solution {
public int singleNumber(int[] nums) {
Map map = new HashMap<>();
for (int i:nums){
map.put(i,map.getOrDefault(i,0)+1);

    }
    for (int i=0;i

}
额外扩展
/*
nowcoder JZ50。https://www.nowcoder.com/practice/1c82e8cf713b4bbeb2a5b31cf5b0417c?tpId=265&tqId=39248&rp=1&ru=/exam/oj/ta&qru=/exam/oj/ta&sourceUrl=%2Fexam%2Foj%2Fta%3FtpId%3D13&difficulty=undefined&judgeStatus=undefined&tags=&title=
在一个长为 字符串中找到第一个只出现一次的字符,
并返回它的位置, 如果没有则返回 -1(需要区分大小写).(从0开始计数)
输入:
“google”
复制第一次只出现
返回值:
4

*/

import java.util.*;
public class Solution {
public int FirstNotRepeatingChar(String str) {
// 先将数据放到字典中,统计字符数量
HashMap mp = new HashMap<>();
//统计每个字符出现的次数
for (int i = 0; i < str.length(); i++) {
// 获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值
mp.put(str.charAt(i),mp.getOrDefault(str.charAt(i),0)+1);
}
for (int i=0;i {
if(mp.get(str.charAt(i))==1)
return i;

    }
    return -1;
}

}

import java.util.*;
public class Solution {
public int FirstNotRepeatingChar(String str) {
// 先将数据放到字典中,统计字符数量
HashMap mp = new HashMap<>();
//统计每个字符出现的次数
for (int i = 0; i < str.length(); i++) {
// 获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值
if(mp.containsKey(str.charAt(i))){
mp.put(str.charAt(i),mp.get(str.charAt(i))+1);//如果已有数值,那么词频+1
}else{
mp.put(str.charAt(i),1);//如果没有这个词频,那么设置为1
}
}
for (int i=0;i {
if(mp.get(str.charAt(i))==1)
return i;

    }
    return -1;
}

}
题目三:宝石与石头
方法1:使用HashSet

public int numJewelsInStones(String jewels, String stones) {
HashSet set = new HashSet<>();
//先把jewels字符串的字母存到哈希集合当中
for(Character ch:jewels.toCharArray()){
set.add(ch);
}
int count = 0;
//遍历stones字符串,看看里面是否包含jewels的字母,如果包含,则利用计数器++。
for(Character ch: stones.toCharArray()){
if(set.contains(ch)){
count++;
}
}
return count;
}
方法2:用暴力的方式做,时间复杂度是O(N^2)
//https://leetcode.cn/problems/jewels-and-stones/
/*
分别遍历stones和jewels的字符,如果jewels包含stones当中,那么count++
/
public int numJewelsInStones(String jewels, String stones) {
int count = 0;
for (int i = 0; i < stones.length(); i++) {
char stone = stones.charAt(i);
for (int j = 0; j < jewels.length(); j++) {
char jewel = jewels.charAt(j);
if (stone == jewel) {
count++;
break;
}
}
}
return count;
}
题目五:前K个高频单词
使用优先队列+HashMap的形式
使用HashMap进行词频统计
模板代码:
// 先将数据放到字典中,统计字符数量
HashMap mp = new HashMap<>();
//统计每个字符出现的次数
for (int i = 0; i < str.length(); i++) {
// 获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值
mp.put(str.charAt(i),mp.getOrDefault(str.charAt(i),0)+1);
}
遍历hashmap
// 遍历hashmap的方法
for (Integer key : map.keySet()) {
System.out.println(key);
System.out.println(map.get(key));
}
按照key或者value排序hashmap.java中的HashMap的储存是没有顺序的,而是按照key的HashCode实现.,所以插入HashMap数据的时候,打印输出的时候是乱序
https://blog.csdn.net/xHibiki/article/details/82938480
import java.util.
;

//Author:Hibiki last modified in 2018.10.04
public class HashMapSort {

public static void main(String[] args) {
    Map phone = new HashMap();
    phone.put("Apple", 7299);
    phone.put("SAMSUNG", 6000);
    phone.put("Meizu", 2698);
    phone.put("Xiaomi", 2400);
    //key-sort
    Set set = phone.keySet();
    Object[] arr = set.toArray();
    Arrays.sort(arr);
    for (Object key : arr) {
        System.out.println(key + ": " + phone.get(key));
    }
    System.out.println();
    //value-sort。
    //step1:将map转换为list,使用collections方法对value进行排序
    //step2:使用for-each进行遍历输出hashmap的结果
    List> list = new ArrayList>(phone.entrySet());
    //方法1:list.sort()
    list.sort(new Comparator>() {
        @Override
        public int compare(Map.Entry o1, Map.Entry o2) {
            return o2.getValue().compareTo(o1.getValue());
        }
    });
    //方法2:collections.sort()。个人喜欢方法2,重写compare方法
    Collections.sort(list, new Comparator>() {
        @Override
        public int compare(Map.Entry o1, Map.Entry o2) {
            return o2.getValue().compareTo(o1.getValue());
        }
    });
    //方法1:for,使用方法1第hashMap进行遍历。
    for (int i = 0; i < list.size(); i++) {
        System.out.println(list.get(i).getKey() + ": " + list.get(i).getValue());
    }
    System.out.println();
    //方法2:for-each
    for (Map.Entry mapping : list) {
        System.out.println(mapping.getKey() + ": " + mapping.getValue());
    }
}

}
//nowcoder 48
import java.util.*;

public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 滑动窗口是指在数组、字符串、链表等线性结构上的一段,类似一个窗口,而这个窗口可以依次在上述线性结构上从头到尾滑动,且窗口的首尾可以收缩。我们在处理滑动窗口的时候,常用双指针来解决,左指针维护窗口左界,右指针维护窗口右界,二者同方向不同速率移动维持窗口。
* @param s string字符串
* @return int整型
*/
public int lengthOfLongestSubstring (String s) {
// write code here,使用双指针法
HashMapmp=new HashMap<>();
int res=0;
for(int left=0,right=0;right {
//如果右指针里面包含之前统计过的字符,那么增加
if(mp.containsKey(s.charAt(right)))
mp.put(s.charAt(right),mp.get(s.charAt(right))+1);
//否则就初始化为1
else
mp.put(s.charAt(right),1);
//出现次数大于1,那么说明窗口内有重复字符,那么向右移动左指针
while(mp.get(s.charAt(right))>1)
{
mp.put(s.charAt(left),mp.get(s.charAt(left++))-1);

        }
        
        res=Math.max(res,right-left+1);

    }
    return res;

}

}
//nowcoder 50

import java.util.*;
public class Solution {
public int FirstNotRepeatingChar(String str) {
// 先将数据放到字典中,统计字符数量
HashMap mp = new HashMap<>();
//统计每个字符出现的次数
for (int i = 0; i < str.length(); i++) {
// 获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值
if(mp.containsKey(str.charAt(i))){
mp.put(str.charAt(i),mp.get(str.charAt(i))+1);//如果已有数值,那么词频+1
}else{
mp.put(str.charAt(i),1);//如果没有这个词频,那么设置为1
}
}
for (int i=0;i {
if(mp.get(str.charAt(i))==1)
return i;

    }
    return -1;
}

}
//acwing490

import java.util.*;
public class acwing490 {

    public static void main(String[] args) {
            // TODO Auto-generated method stub
            Scanner sc=new Scanner(System.in);
            int num=sc.nextInt();
            long []data=new long[num];
            for(int i=0;i

// 总结:HashMap没有按照key或者value进行排序,所以需要引入set额外对key进行排序。引入Collections对value进行排序
// https://blog.csdn.net/xHibiki/article/details/82938480
HashMap mp = new HashMap<>();
//统计每个字符出现的次数
for (int i = 0; i < num; i++) {
// 获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值
mp.put(data[i],mp.getOrDefault(data[i],0)+1);
}

    Set set=mp.keySet();
    Object[]arr=set.toArray();
    Arrays.sort(arr);
    for(Object key:arr)
    {
            System.out.println(key+" "+mp.get(key));
            
    }
    /*      
      
    // 遍历hashmap的方法
    for (Integer key : map.keySet()) {
        System.out.println(key);
        System.out.println(map.get(key));
    }
}

}
*
* */

    }

}
Stack
先进后出
无法复制加载中的内容
import java.util.Arrays;
public class MyStack {
public int [] elem;
public int usedSize;

public MyStack(){
    this.elem = new int[5];
}

//进栈
public void push(int val){
if(isFull()){
//扩容
Arrays.copyOf(this.elem,2*this.elem.length);
}
this.elem[this.usedSize] = val;
this.usedSize++;
}
public boolean isFull(){
return this.usedSize == this.elem.length;
}
//出栈
public int pop(){
if(isEmpty()){
throw new RuntimeException(“栈为空”);
}
int oldVal = this.elem[usedSize - 1];
this.usedSize–;
return oldVal;
}
//获取栈顶元素
public int peek(){
if(isEmpty()){
throw new RuntimeException(“栈为空”);
}
return this.elem[usedSize-1];
}
//是否为空
public boolean isEmpty(){
return this.usedSize == 0;
}
}
import java.util.Stack;
public class TestDemo {
public static void main(String[] args) {
MyStack stack = new MyStack();
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
System.out.println(stack.pop());//弹出栈顶元素,并且删除4
System.out.println(stack.peek());//获取栈顶元素,但不删除3
System.out.println(stack.isEmpty());//false

}

}
QUEUE
无法复制加载中的内容
所以为了防止抛出异常,要多用offer【尾部插数】和poll【头部移除】。Peek是获取头部数据
PriorityQueue(优先队列)
PriorityQueue类是一种队列数据结构实现,其中根据优先级处理对象。它与遵循FIFO(先进先出)算法的标准队列不同。
让我们记下PriorityQueue上的几个要点。
PriorityQueue是一个无限制的队列,并且动态增长。默认初始容量’11’可以使用相应构造函数中的initialCapacity参数覆盖。
它不允许NULL对象。
添加到PriorityQueue的对象必须具有可比性。
默认情况下,优先级队列的对象按自然顺序排序。
比较器可用于队列中对象的自定义排序。
是有序的。优先级队列的头部是基于自然排序或基于比较器的排序的最小元素。当我们轮询队列时,它从队列中返回头对象。
如果存在多个具有相同优先级的对象,则它可以随机轮询其中任何一个。
PriorityQueue 不是线程安全的。PriorityBlockingQueue在并发环境中使用。
它为add和poll方法提供了O(log(n))时间。
PriorityQueue类下面给出了重要的方法,你应该知道。
boolean add(object):将指定的元素插入此优先级队列。
boolean offer(object):将指定的元素插入此优先级队列。
boolean remove(object):从此队列中删除指定元素的单个实例(如果存在)。
Object poll():检索并删除此队列的头部,如果此队列为空,则返回null。
Object element():检索但不删除此队列的头部,如果此队列为空,则返回null。
Object peek():检索但不删除此队列的头部,如果此队列为空,则返回null。
void clear():从此优先级队列中删除所有元素。
Comparator comparator():返回用于对此队列中的元素进行排序的比较器,如果此队列根据其元素的自然顺序排序,则返回null。
boolean contains(Object o):如果此队列包含指定的元素,则返回true。
Iterator iterator():返回此队列中元素的迭代器。
int size():返回此队列中的元素数。
Object [] toArray():返回包含此队列中所有元素的数组。
双端队列的使用
//2530. 执行 K 次操作后的最大分数
class Solution {
public long maxKelements(int[] nums, int k) {

  PriorityQueueq=new PriorityQueue<>((x,y) -> (y-x));//由小顶堆变成大顶堆
    long sum=0L;
    for(int num:nums)
    {
        q.offer(num);//向后插入数据
        
    }
    for(int i=0;i

}
Deque
Deque deque = new LinkedList()
Deque是一个线性collection,支持在两端插入和移除元素。名称 deque 是“double ended queue(双端队列)”的缩写
第一个元素 (头部) 最后一个元素 (尾部)
抛出异常 特殊值 抛出异常 特殊值
插入 addFirst(e) offerFirst(e) addLast(e) offerLast(e)
删除 removeFirst() pollFirst() removeLast() pollLast()
检查 getFirst() peekFirst() getLast() peekLast()
//https://www.acwing.com/problem/content/description/3794/
/*
定义一个单词中位于最中间的字母为中心字母。
如果单词的长度为偶数,则中心字母定义为中间两个字母中靠左的那个。
如果单词的长度为
11,则中心字母是它本身。
一种单词加密方式为:
记录单词的中心字母,并将该字母在单词中删除。
不断重复上述操作,直至单词被完全删除。
将记录下的字母依次连接,得到加密后的单词。
例如,volga 经过加密可以得到 logva。
现在,给定一个加密后的单词,请你求出原单词。
/
/

思路:用双端队列,判断字符串长度。
如果长度是奇数,那么先头插入第一个字符,然后转换为长度为偶数的字符串:
1.头插入第一个字符,2.尾插入第二个字符

*/

package oj_01;
import java.util.*;

/*

  • 使用双端队列,判断字符串奇偶性

  • */
    public class acwing3791 {

     public static void main(String[] args) {
             // TODO Auto-generated method stub
             Scanner sc = new Scanner(System.in);
     int number = sc.nextInt();
     String str = sc.next();
     char[]c=str.toCharArray();
     char[]res=new char[number];
             Deque deque = new LinkedList ();
             
                     
             //如果number是奇数
             if(number%2==1) 
             {
                     deque.offerFirst(c[0]);
                     for(int j=1;j

}
Character 类
Character 类在对象中包装一个基本类型 char 的值
下面是Character类的方法:
无法复制加载中的内容
String
注意:String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了。如果需要对字符串做很多修改,那么应该选择使用StringBuffer和StringBuilder类
字符串转整形
使用Integer.parseInt,字符串转整形,参数是String,默认转换为十进制,如果要转化为其他进制,在后面加上对应的进制数
String c=“4”;
Integer d= Integer.parseInt©;
System.out.println(d);
整形转字符串
使用toString()
Integer a=5;
String b=a.toString();
System.out.println(b);
字符数组转字符串
针对字符数组,使用new String的方式转化为字符串,不能用toString的方式,使用toString的话,会输出String的地址。
char[] chs = {‘h’, ‘e’, ‘l’, ‘l’, ‘o’};

            //方法1:传递一个参数,即字符数组名字
    String str1 = new String(chs);
    System.out.println("通过构造函数,整体转换:");
    System.out.println(str1);

    //传递三个参数,即字符数组名字, 开始的下标,转换的长度
    String str2 = new String(chs, 1, 3);
    System.out.println("通过构造函数,部分转换:");
    System.out.println(str2);

字符串转字符数组
String str = “world”;
char[] chs = str.toCharArray();
System.out.println(Arrays.toString(chs));
CharAt(),获取指定index的字符
参数是index,获取第index个字符
public class Test {
public static void main(String args[]) {
String s = “www.runoob.com”;
char result = s.charAt(6);//获取第6个字符
System.out.println(result);//以上程序执行结果为: n
}
}
compareTo()
返回值是整型,它是先比较对应字符的大小(ASCII码顺序),如果第一个字符和参数的第一个字符不等,结束比较,返回他们之间的长度差值,如果第一个字符和参数的第一个字符相等,则以第二个字符和参数的第二个字符做比较,以此类推,直至比较的字符或被比较的字符有一方结束。
如果参数字符串等于此字符串,则返回值 0;
如果此字符串小于字符串参数,则返回一个小于 0 的值;
如果此字符串大于字符串参数,则返回一个大于 0 的值。
public class Test {

public static void main(String args[]) {
    String str1 = "Strings";
    String str2 = "Strings";
    String str3 = "Strings123";

    int result = str1.compareTo( str2 );
    System.out.println(result);//0
  
    result = str2.compareTo( str3 );
    System.out.println(result);//-3
 
    result = str3.compareTo( str1 );
    System.out.println(result);//3
}

}
int compareToIgnoreCase(String str)
按字典顺序比较两个字符串,不考虑大小写。
String concat(String str)
将指定字符串连接到此字符串的结尾。
public class Test {
public static void main(String args[]) {
String s = “菜鸟教程:”;
s = s.concat(“www.runoob.com”);
System.out.println(s);
}
}
boolean contentEquals(StringBuffer sb)
当且仅当字符串与指定的StringBuffer有相同顺序的字符时候返回真。
static String copyValueOf(char[] data)
返回指定数组中表示该字符序列的 String。
语法
public static String copyValueOf(char[] data)或public static String copyValueOf(char[] data, int offset, int count)
参数
data – 字符数组。
offset – 子数组的初始偏移量。
count – 子数组的长度。
public class Test {
public static void main(String args[]) {
char[] Str1 = {‘h’, ‘e’, ‘l’, ‘l’, ‘o’, ’ ', ‘r’, ‘u’, ‘n’, ‘o’, ‘o’, ‘b’};
String Str2 = “”;

    Str2 = Str2.copyValueOf( Str1 );
    System.out.println("返回结果:" + Str2);//返回结果:hello runoob

    Str2 = Str2.copyValueOf( Str1, 2, 6 );
    System.out.println("返回结果:" + Str2);//返回结果:llo ru。从第二个字符到第6个字符
}

}
boolean endsWith(String suffix)
测试此字符串是否以指定的后缀结束。
如果参数表示的字符序列是此对象表示的字符序列的后缀,则返回 true;否则返回 false。注意,如果参数是空字符串,或者等于此 String 对象(用 equals(Object) 方法确定),则结果为 true。
public class Test {
public static void main(String args[]) {
String Str = new String(“菜鸟教程:www.runoob.com”);
boolean retVal;

    retVal = Str.endsWith( "runoob" );
    System.out.println("返回值 = " + retVal );//true

    retVal = Str.endsWith( "com" );
    System.out.println("返回值 = " + retVal );//false
}

}
Java String indexOf() 方法:返回指定子字符串在此字符串中第一次出现处的索引
indexOf() 方法有以下四种形式:
public int indexOf(int ch): 返回指定字符在字符串中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。
public int indexOf(int ch, int fromIndex): 返回从 fromIndex 位置开始查找指定字符在字符串中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。
int indexOf(String str): 返回指定字符在字符串中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。
int indexOf(String str, int fromIndex): 返回从 fromIndex 位置开始查找指定字符在字符串中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。
public class Test {
public static void main(String args[]) {
String Str = new String(“菜鸟教程:www.runoob.com”);
String SubStr1 = new String(“runoob”);
String SubStr2 = new String(“com”);

    System.out.print("查找字符 o 第一次出现的位置 :" );
    System.out.println(Str.indexOf( 'o' ));
    System.out.print("从第14个位置查找字符 o 第一次出现的位置 :" );
    System.out.println(Str.indexOf( 'o', 14 ));
    System.out.print("子字符串 SubStr1 第一次出现的位置:" );
    System.out.println( Str.indexOf( SubStr1 ));
    System.out.print("从第十五个位置开始搜索子字符串 SubStr1 第一次出现的位置 :" );
    System.out.println( Str.indexOf( SubStr1, 15 ));
    System.out.print("子字符串 SubStr2 第一次出现的位置 :" );
    System.out.println(Str.indexOf( SubStr2 ));
}

}
查找字符 o 第一次出现的位置 :12
从第14个位置查找字符 o 第一次出现的位置 :17
子字符串 SubStr1 第一次出现的位置:9
从第十五个位置开始搜索子字符串 SubStr1 第一次出现的位置 :-1
子字符串 SubStr2 第一次出现的位置 :16
int lastIndexOf(int ch) 返回指定字符在此字符串中最后一次出现处的索引。反向搜索
boolean regionMatches(int toffset, String other, int ooffset, int len)
测试两个字符串区域是否相等。
ignoreCase – 如果为 true,则比较字符时忽略大小写。
toffset – 此字符串中子区域的起始偏移量。
other – 字符串参数。
ooffset – 字符串参数中子区域的起始偏移量。
len – 要比较的字符数。
String replaceAll(String regex, String replacement)
使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
public class Test {
public static void main(String args[]) {
String Str = new String(“www.google.com”);
System.out.print(“匹配成功返回值 :” );
System.out.println(Str.replaceAll(“(.)google(.)”, “runoob” ));//runoob
System.out.print(“匹配失败返回值 :” );
System.out.println(Str.replaceAll(“(.)taobao(.)”, “runoob” ));//匹配失败,所以字符串保持不变。www.google.com
}
}
String replaceAll(String regex, String replacement)
使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
返回值:成功则返回替换的字符串,失败则返回原始字符串。
public class Test {
public static void main(String args[]) {
String Str = new String(“hello runoob,I am from runoob。”);

    System.out.print("返回值 :" );
    System.out.println(Str.replaceFirst("runoob", "google" ));//返回值 :hello google,I am from runoob。
    System.out.print("返回值 :" );
    System.out.println(Str.replaceFirst("(.*)runoob(.*)", "google" ));//返回值 :google
}

}
Java split() 方法
split() 方法根据匹配给定的正则表达式来拆分字符串。
注意: . 、 $、 | 和 * 等转义字符,必须得加 \。
注意:多个分隔符,可以用 | 作为连字符。
public class Test {
public static void main(String args[]) {
String str = new String(“Welcome-to-Runoob”);

    System.out.println("- 分隔符返回值 :" );
    for (String retval: str.split("-")){
        System.out.println(retval);
    }

/*

  • 分隔符返回值 :
    Welcome
    to
    Runoob
    /
    System.out.println(“”);
    System.out.println(“- 分隔符设置分割份数返回值 :” );
    for (String retval: str.split(“-”, 2)){//分割为两个字符串
    System.out.println(retval);
    }
    /
  • 分隔符设置分割份数返回值 :
    Welcome
    to-Runoob

*/

    System.out.println("");
    String str2 = new String("www.runoob.com");
    System.out.println("转义字符返回值 :" );
    for (String retval: str2.split("\\.", 3)){//如果是.这个特殊字符,前面要加上转义
        System.out.println(retval);
    }

/*
转义字符返回值 :
www
runoob
com

/
System.out.println(“”);
String str3 = new String(“acount=? and uu =? or n=?”);
System.out.println(“多个分隔符返回值 :” );
for (String retval: str3.split(“and|or”)){//使用|来分割多个分隔符
System.out.println(retval);
}
/

多个分隔符返回值 :
acount=?
uu =?
n=?

    */
}

}
boolean startsWith(String prefix, int toffset)
测试此字符串从指定索引开始的子字符串是否以指定前缀开始。
subSequence() 方法返回一个新的字符序列,它是此序列的一个子序列。
public class Test {
public static void main(String args[]) {
String Str = new String(“www.runoob.com”);

     System.out.print("返回值 :" );
     System.out.println(Str.subSequence(4, 10) );//返回值 :runoob
}

}
String substring(int beginIndex, int endIndex)
返回一个新字符串,它是此字符串的一个子字符串。
public class RunoobTest {
public static void main(String args[]) {
String Str = new String(“This is text”);

    System.out.print("返回值 :" );
    System.out.println(Str.substring(4) );//返回值 : is text。从第四个字符到最后

    System.out.print("返回值 :" );
    System.out.println(Str.substring(4, 10) );//返回值 : is te。从第四个字符到第10个
}

}
trim() 方法用于删除字符串的头尾空白符。
String.valueOf(),做字符串的强制转换,将参数是Int/double/float转化为string。
Integer.valueOf() 将其他类型转化为int类型
contains() 方法用于判断字符串中是否包含指定的字符或字符串。
public class Main {
public static void main(String[] args) {
String myStr = “Runoob”;
System.out.println(myStr.contains(“Run”));//Runoob包含Run
System.out.println(myStr.contains(“o”));//Runoob包含o
System.out.println(myStr.contains(“s”));//Runoob不包含s
}
}
StringBuffer和StringBuilder
StringBuffer 和 StringBuilder 类的对象能够被多次的修改。 String 类不能修改
StringBuffer,线程安全+速度慢
StringBuilder,线程不安全+速度快
StringBuffer 方法
以下是 StringBuffer 类支持的主要方法:
无法复制加载中的内容
capacity和size的区别:
capacity表示当前最大容量
size表示当前已用容量。
无法复制加载中的内容
collections方法
可以使用Collections对List进行排序
sort方法
这里要求使用 Collections 类中 sort() 方法按从低到高的顺序对其进行排序
import java.util.*;
public class Test1 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
List prices = new ArrayList();
for (int i = 0; i < 5; i++) {
System.out.println(“请输入第 " + (i + 1) + " 个商品的价格:”);
int p = input.nextInt();
prices.add(Integer.valueOf§); // 将录入的价格保存到List集合中
}
Collections.sort(prices); // 调用sort()方法对集合从进行排序,从小到大排序
System.out.println(“价格从低到高的排列为:”);
for (int i = 0; i < prices.size(); i++) {
System.out.print(prices.get(i) + “\t”);
}
}
}
https://blog.csdn.net/zolalad/article/details/30050331
Arrays数组
对数组进行排序。
Integer[] intArray = new Integer[] {4, 1, 3, -23};
// Arrays.sort(intArray);//从小到大的排序
// Arrays.sort(intArray,Comparator.reverseOrder());//从大到小的排序,引入java.utils.*即可,方法1:要用Integer数组的格式
Collections.reverse(Arrays.asList(intArray)); //方法2:反转法,要先从小到大排序之后,再反转,才能得到从大到小的排序

    for(int num:intArray)
            System.out.println(num);

List
可以用collections集合类。参数是List
List intlist=new ArrayList();
intlist.add(1);
intlist.add(21);
intlist.add(13);
intlist.add(221);
Collections.sort(intlist,Collectons.reverseOrder());

    for (int num:intlist)
            System.out.println(num);

对象数组的排序
https://blog.csdn.net/Fly_as_tadpole/article/details/87566011
https://blog.csdn.net/weixin_45654215/article/details/120746263
1.要重写Comparator。其中o1-o2是升序,o2-o1是降序
2.compare函数返回类型是int,如果要比较double类型的数据,那么要用Double.Compare(o2.getMoney(),o1.getMoney())表示从大到小的排序,
从小到大排序:Double.Compare(o1.getMoney(),o2.getMoney())。如果要改为从大到小排序,那么可以在前面加一个负号,即: return -Double.Compare(o1.getMoney(),o2.getMoney())
if(o1.getMoney()!=o2.getMoney())
//方法1:降序排列,对于double类型,不能使用return o1.getId() - o2.getId();而是要使用new double的形式
return new Double(o2.getMoney()).compareTo(new Double(o1.getMoney()));
//方法2:或者return Double.compare(o2.getMoney(),o1.getMoney());
else
// 升序排列
return o1.getCus_no()-o2.getCus_no();
Collections.sort(list, new Comparator() {
@Override
public int compare(CollectionsSort_test o1, CollectionsSort_test o2) {
return o1.getId() - o2.getId();
}
});
package oj_01;
import java.util.*;

//定义一个学生类
public class CollectionsSort_test{
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private int age;
private String name;

public CollectionsSort_test(int id, int age, String name) {
    this.id = id;
    this.age = age;
    this.name = name;
}

// @Override
// public int compareTo(CollectionsSort_test o) {
// //降序。对方-本方
// //return o.age - this.age;
// //升序,本方-对方
// return this.age - o.age;
// }
@Override
public String toString() {
return “CollectionsSort_test{” +
“id=” + id +
“, age=” + age +
“, name='” + name + ‘’’ +
‘}’;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
List list = new ArrayList<>();
list.add(new CollectionsSort_test(1,25,“关羽”));
list.add(new CollectionsSort_test(3,21,“张飞”));
list.add(new CollectionsSort_test(2,18,“刘备”));
list.add(new CollectionsSort_test(4,32,“袁绍”));
list.add(new CollectionsSort_test(5,36,“赵云”));
list.add(new CollectionsSort_test(6,16,“曹操”));
System.out.println(“排序前:”);
for (CollectionsSort_test CollectionsSort_test : list) {
System.out.println(CollectionsSort_test.toString());
}
//使用默认排序
// Collections.sort(list);
//自定义排序1,重点代码段
Collections.sort(list, new Comparator() {
@Override
public int compare(CollectionsSort_test o1, CollectionsSort_test o2) {
return o1.getId() - o2.getId();
}
});
System.out.println(“默认排序后:”);
for (CollectionsSort_test CollectionsSort_test : list) {
System.out.println(CollectionsSort_test.toString());
}
}

}
package oj_01;
import java.util.*;
public class test20230117_2 {

    private int cus_no;
    public test20230117_2(int cus_no, String name, String username, double money) {
            this.cus_no = cus_no;
            this.name = name;
            this.username = username;
            this.money = money;
    }

    @Override
    public   String toString() {
            return "test20230117_2 [cus_no=" + cus_no + ", name=" + name + ", username=" + username + ", money=" + money
                            + "]";
    }

    public int getCus_no() {
            return cus_no;
    }

    public void setCus_no(int cus_no) {
            this.cus_no = cus_no;
    }

    public String getName() {
            return name;
    }
    public void setName(String name) {
            this.name = name;
    }
    public String getUsername() {
            return username;
    }

    public void setUsername(String username) {
            this.username = username;
    }




    public double getMoney() {
            return money;
    }




    public void setMoney(double money) {
            this.money = money;
    }
    private String name;
    private String username;
    private double money;        

    public static void main(String[] args) {
            // TODO Auto-generated method stub
            
             List list = new ArrayList<>();
            list.add(new test20230117_2(1,"关羽","132",20.04));
            list.add(new test20230117_2(3,"张飞","133",20.02));
            list.add(new test20230117_2(2,"刘备","134",20.04));
            list.add(new test20230117_2(4,"袁绍","135",20.03));
            list.add(new test20230117_2(5,"赵云","136",20.05));
            list.add(new test20230117_2(6,"曹操","137",20.02));
            System.out.println("排序前:");
            for (test20230117_2 CollectionsSort_test : list) {
                System.out.println(CollectionsSort_test.toString());
            }
            //使用默认排序

// Collections.sort(list);
//自定义排序1
Collections.sort(list, new Comparator() {
@Override
public int compare(test20230117_2 o1, test20230117_2 o2) {

/*

  • //自定义排序1,比较int类型的数值
    Collections.sort(list, new Comparator() {
    @Override
    public int compare(CollectionsSort_test o1, CollectionsSort_test o2) {
    return o1.getId() - o2.getId();
    }
    });

  • */

                         if(o1.getMoney()!=o2.getMoney())
                                 //降序排列,对于double类型,不能使用return o1.getId() - o2.getId();而是要使用new double的形式
                                 return new Double(o2.getMoney()).compareTo(new Double(o1.getMoney()));
                         else
    

// 升序排列
return o1.getCus_no()-o2.getCus_no();
}
});

            System.out.println("默认排序后:");
            for (test20230117_2 CollectionsSort_test : list) {
                System.out.println(CollectionsSort_test.toString());
            }

    }

}
reverse方法
使用 Collections 类的 reverse() 方法对保存到 List 集合中的 5 个商品名称进行反转排序
import java.util.;
public class Test2 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
List students = new ArrayList();
System.out.println(“******** 商品信息 ********”);
for (int i = 0; i < 5; i++) {
System.out.println(“请输入第 " + (i + 1) + " 个商品的名称:”);
String name = input.next();
students.add(name); // 将录入的商品名称存到List集合中
}
Collections.reverse(students); // 调用reverse()方法对集合元素进行反转排序
System.out.println(“按录入时间的先后顺序进行降序排列为:”);
for (int i = 0; i < 5; i++) {
System.out.print(students.get(i) + “\t”);
}
}
}
String.join(" ",)
import java.util.
;
public class Solution {

public String ReverseSentence(String str) {
    
    String[]ss=str.split(" ");
    List strsToList1=Arrays.asList(ss);//强制转化为Array
    Collections.reverse(strsToList1);
    String ss1=String.join(" ", strsToList1);//将strsToList1中每个字符,后面+" "
    return ss1;

}

}
Collections 还提供了如下常用的用于查找、替换集合元素的方法。
int binarySearch(List list, Object key):使用二分搜索法搜索指定的 List 集合,以获得指定对象在 List 集合中的索引。如果要使该方法可以正常工作,则必须保证 List 中的元素已经处于有序状态。
Object max(Collection coll):根据元素的自然顺序,返回给定集合中的最大元素。
Object max(Collection coll, Comparator comp):根据 Comparator 指定的顺序,返回给定集合中的最大元素。
Object min(Collection coll):根据元素的自然顺序,返回给定集合中的最小元素。
Object min(Collection coll, Comparator comp):根据 Comparator 指定的顺序,返回给定集合中的最小元素。
void fill(List list, Object obj):用元素Obj来填充list列表。
int frequency(Collection c, Object o):返回指定集合中指定元素的出现次数。
int indexOfSubList(List source, List target):返回子 List 对象在父 List 对象中第一次出现的位置索引;如果父 List 中没有出现这样的子 List,则返回 -1。
int lastIndexOfSubList(List source, List target):返回子 List 对象在父 List 对象中最后一次出现的位置索引;如果父 List 中没有岀现这样的子 List,则返回 -1。
boolean replaceAll(List list, Object oldVal, Object newVal):使用一个新值 newVal 替换 List 对象的所有旧值 oldVal。
fill方法
import java.util.;
public class Test3 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
List products = new ArrayList();
System.out.println(“******** 商品信息 ********”);
for (int i = 0; i < 3; i++) {
System.out.println(“请输入第 " + (i + 1) + " 个商品的名称:”);
String name = input.next();
products.add(name); // 将用户录入的商品名称保存到List集合中
}
System.out.println(“重置商品信息,将所有名称都更改为’未填写’”);
Collections.fill(products, “未填写”);//重置List内容
System.out.println(“重置后的商品信息为:”);
for (int i = 0; i < products.size(); i++) {
System.out.print(products.get(i) + “\t”);
}
}
}
replaceAll
import java.util.
;
public class Test4 {
public static void main(String[] args) {
ArrayList nums = new ArrayList();
nums.add(2);
nums.add(-5);
nums.add(3);
nums.add(0);
System.out.println(nums); // 输出:[2, -5, 3, 0]
System.out.println(Collections.max(nums)); // 输出最大元素,将输出 3
System.out.println(Collections.min(nums)); // 输出最小元素,将输出-5
Collections.replaceAll(nums, 0, 1);// 将 nums中的 0 使用 1 来代替
System.out.println(nums); // 输出:[2, -5, 3, 1]
// 判断-5在List集合中出现的次数,返回1
System.out.println(Collections.frequency(nums, -5));
Collections.sort(nums); // 对 nums集合从小到大排序,
System.out.println(nums); // 输出:[-5, 1, 2, 3]
// 只有排序后的List集合才可用二分法查询,输出3
System.out.println(Collections.binarySearch(nums, 3));
}
}
copy
一个集合中保存了 5 个商品名称,现在要使用 Collections 类中的 copy() 方法将前3 个替换掉。
public class Test5 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
List srcList = new ArrayList();
List destList = new ArrayList();
destList.add(“苏打水”);
destList.add(“木糖醇”);
destList.add(“方便面”);
destList.add(“火腿肠”);
destList.add(“冰红茶”);
System.out.println(“原有商品如下:”);
for (int i = 0; i < destList.size(); i++) {
System.out.println(destList.get(i));
}
System.out.println(“输入替换的商品名称:”);
for (int i = 0; i < 3; i++) {
System.out.println(“第 " + (i + 1) + " 个商品:”);
String name = input.next();
srcList.add(name);
}
// 调用copy()方法将当前商品信息复制到原有商品信息集合中
Collections.copy(destList, srcList);
System.out.println(“当前商品有:”);
for (int i = 0; i < destList.size(); i++) {
System.out.print(destList.get(i) + “\t”);
}
}
}
字符串加密
常规套路总结
例1:md5+sha+base64加密
package oj_01;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Scanner;

import javax.xml.bind.DatatypeConverter;

/*

  • 题目大意:输入一串字符串,先大小写相互转换,然后再字符串反转,最后再用md5加密
  • */
    public class kaoshi_2 {

// 字符串大小写转换
public static String reverseCase(String s) {
char[] chars = s.toCharArray();
for (int i = 0; i < chars.length; i++) {
char c = chars[i];
if (c > ‘A’ && c < ‘Z’) {
chars[i] = (char) (c - ‘A’+ ‘a’) ;//大写转小写
} else if (c > ‘a’ && c < ‘z’) {
chars[i] = (char) (c - ‘a’ + ‘A’);//小写转大写
}
}
return new String(chars);
}

// 字符串大小写转换,使用Character函数工具进行字符转换
public static String reverseCase1(String s) {

            //将String 转换为char数组
            char[] chars=s.toCharArray();
            for (int i = 0; i < chars.length; i++) {
                    //判断字符是否是大写,如果是大写,那么小写转大写
                    if(Character.isUpperCase(chars[i]))
                    {
                            //小写转大写
                            chars[i]=Character.toLowerCase(chars[i]);
                            
                    }
                    else if(Character.isLowerCase(chars[i]))
                    {
                            
                            chars[i]=Character.toUpperCase(chars[i]);
                    }
                    
                    
            }
                    return new String(chars);
            
            
            
    }
    //字符串反转,使用Collections库进行转换
    public static String reverseWords(String s) {
            String[] ss = s.split(" ");
            Collections.reverse(Arrays.asList(ss));
            return String.join(" ", ss);
            }
    public static String md5sum(String s) {
            String hash = "";
            try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(s.getBytes());
            byte[] digest = md.digest();
            hash = DatatypeConverter.printHexBinary(digest).toLowerCase();
            } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            } 
            return hash;
            }
    
    public static void main(String[] args) {
            // TODO Auto-generated method stub
            //手动输入

// String str=new Scanner(System.in).nextLine();
String str=“I Love java”;
String res_str1=reverseCase1(str);
System.out.println(res_str1);
String res_str2=reverseWords(res_str1);
String md5_result=md5sum(res_str2);
System.out.println(res_str2);
System.out.println(md5_result);

    }

}
额外扩展:
https://www.cnblogs.com/jinjinqiao/p/13448971.html
//Base64加密算法:采用Base编码具有不可读性,即所编码的数据不会被人用肉眼所直接看到
import sun.misc.BASEDecoder;
import sun.misc.BASEEncoder;
public class BASE {
/**

  • BASE解密
  • @param key
  • @return
  • @throws Exception
    /
    public static byte[] decryptBASE(String key) throws Exception {
    return (new BASEDecoder()).decodeBuffer(key);
    }
    /
    *
  • BASE加密
  • @param key
  • @return
  • @throws Exception
    /
    public static String encryptBASE(byte[] key) throws Exception {
    return (new BASEEncoder()).encodeBuffer(key);
    }
    public static void main(String[] args) {
    String str=“”;
    try {
    String result= BASE.encryptBASE(str.getBytes());
    System.out.println(“result=加密数据======”+result);
    byte result[]= BASE.decryptBASE(result);
    String str=new String(result);
    System.out.println(“str解密数据”+str);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
    import java.math.BigInteger;
    import java.security.MessageDigest;
    import java.util.
    ;
    //md5加密
    public static String md5sum(String s) {
    String hash = “”;
    try {
    MessageDigest md = MessageDigest.getInstance(“MD5”);
    md.update(s.getBytes());
    byte[] digest = md.digest();
    hash = DatatypeConverter.printHexBinary(digest).toLowerCase();
    } catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
    }
    return hash;
    }

import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.*;
//sha加密
public static String sha(String s)
{
String sha=“”;
try{
MessageDigest md = MessageDigest.getInstance(“SHA”);
md.update(s.getBytes());
byte[] digest = md.digest();
sha = DatatypeConverter.printHexBinary(digest).toLowerCase();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return sha;
}

例2:字母循环加密模板

/*
向后移动一个字母
https://www.acwing.com/problem/content/description/769/

向后移动三个字母
https://www.acwing.com/problem/content/3625/

*/

import java.util.*;

public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String a = sc.nextLine();//获取数据源
char[] s = a.toCharArray();//将字符串类型转换为字符数组的形式,以便后面做字符加密
int len = s.length;
int key=1;//数值可变,当key为负数的时候,是向前移动

    for(int i=0;i='A'&&s[i]<='Z') {
                            s[i]+=key;
                            //当key是正数的时候
                            if(s[i]>'Z')
                                    s[i]-=26;
                            //当Key是负数的时候
                            else if(s[i]<'A')
                                    s[i]+=26;
                    
                }
                else if(s[i]>='a'&&s[i]<='z') {
                        s[i]+=key;
                            if(s[i]>'z')
                                    s[i]-=26;
                            else if(s[i]<'a')
                                    s[i]+=26;
                }
    }
    System.out.println(s);//返回字符数组的形式
}

}

并发
1.线程的创建方式
方法1:继承Thread类,重写run函数,作为线程对象存在(继承Thread对象)
public class CreatThreadDemo1 extends Thread{
/**

  • 构造方法: 继承父类方法的Thread(String name);方法
  • @param name
    */
    public CreatThreadDemo1(String name){
    super(name);
    }
    @Override
    public void run() {
    while (!interrupted()){
    System.out.println(getName()+“线程执行了…”);
    try {
    Thread.sleep(200);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    public static void main(String[] args) {
    CreatThreadDemo1 d1 = new CreatThreadDemo1(“first”);
    CreatThreadDemo1 d2 = new CreatThreadDemo1(“second”);
    d1.start();
    d2.start();
    d1.interrupt(); //中断第一个线程
    }
    }
    interrupted方法,是来判断该线程是否被中断。(终止线程不允许用stop方法,该方法不会释放占用的资源。
    让线程等待的方法
    Thread.sleep(200); //线程休息2ms
    Object.wait(); //让线程进入等待,直到调用Object的notify或者notifyAll时,线程停止休眠
    方法2:实现runnable接口,作为线程任务存在
    public class CreatThreadDemo2 implements Runnable {
    @Override
    public void run() {
    while (true){
    System.out.println(“线程执行了…”);
    }
    }

public static void main(String[] args) {
//将线程任务传给线程对象
Thread thread = new Thread(new CreatThreadDemo2());
//启动线程
thread.start();
}
}
Runnable 只是来修饰线程所执行的任务,它不是一个线程对象。想要启动Runnable对象,必须将它放到一个线程对象里。

1.停止正在运行的线程
1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2、使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的
方法。
3、使用interrupt方法中断线程。
1.start和run的区别
1)调用 start() 方法是用来启动线程的,轮到该线程执行时,会自动调用 run();
2)直接调用 run() 方法,无法达到启动多线程的目的,相当于主线程线性执行 Thread 对象的 run() 方法。
3)一个线程对线的 start() 方法只能调用一次,多次调用会抛出 java.lang.IllegalThreadStateException 异常;run() 方法没有限制.
4)只有调用了start()方法,才会表现出多线程的特性,不同线程的run()方法里面的代码交替执行。所以是执行start,而非调用run函数。
5)如果只是调用run()方法,那么代码还是同步执行的(串行执行而非并行),必须等待一个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码。
6)run()方法结束之后,线程才真正结束
1.Sleep 和wait,yield的区别
sleep() 加上参数,导致了程序暂停执行指定的时间,在sleep参数时间段内进入阻塞状态,sleep参数时间结束后进入可运行状态。
yield() 没有参数,会立刻让出线程使用资源,立刻进入可运行状态,给同级别或者更高级别的
wait() 线程会放弃对象锁,进入等待此对象的等待锁定池
join() 是主线程等待子线程结束之后,再继续执行主线程。
例如:当前线程里调用其它线程1的join方法,当前线程阻塞,但不释放对象锁,直到线程1执行完毕或者millis时间到,当前线程进入可运行状态。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。
当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用
用 notify() 方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获得锁标志,他们随时准备争夺锁的拥有权,当调用了某个对象的notifyAll() 方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池
yield()方法和sleep() 方法类似,也不会释放对象锁,它是Thread类的静态方法,区别在于,它没有参数,即yield() 方法只是使当前线程让步,重新回到就绪状态,所以执行yield的线程,有可能在进入到就绪状态后马上又被执行,另外yield方法只能使同优先级或者高优先级的线程得到执行机会
join() 方法底层是调用wait方法,是主线程等待子线程结束之后,再继续执行主线程
区别:
1)所属类
wait()是Object类中的非静态方法;sleep()、yield()是Thread类中的静态方法。
2)作用
wait()用于线程同步或者线程之间进行通信;sleep()用于休眠当前线程,并在指定的时间点被自动唤醒;yield()临时暂停当前正在执行的线程,来让有同样优先级或者更高优先级的正在等待的线程有机会执行(如果等待的线程优先级较低,则当前线程继续执行)。
3)释放资源
wait()会释放线程所占用的锁和管程;sleep()释放线程所占用的锁,但不释放管程;yield()仅释放线程所占用的CPU。
4)应用场景
wait()适用于同步代码块中;sleep()休眠当前线程,应用场景没有限制;yield()暂停当前线程,应用场景也没有限制。
5)被唤醒后的状态
wait()被notify()或者notifyAll()唤醒后,先进入阻塞状态(先获得锁),然后进入就绪状态;sleep()被唤醒后,进入就绪状态;yield()不需要唤醒,一直处于就绪状态,获得CPU后继续运行。
6)线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
7)sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
8)sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。
1.线程的状态转换
1)使用new创建一个线程,使得线程进行初始状态
2)调用start函数进入可运行状态
3)阻塞状态:线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态
阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状
态。(注意,sleep是不会释放持有的锁)
4)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
1.如何保证线程按照顺序执行?
使用join,等待子线程执行完,主线程才能执行。
public class JoinTest2 {
// 1.现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行
public static void main(String[] args) {
/*
先执行t1,然后t2要等t1执行完,因此加上了join
*/
final Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(“t1”);
}
});
final Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
// 引用t1线程,等待t1线程执行完
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“t2”);
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
// 引用t2线程,等待t2线程执行完
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“t3”);
}
});
t3.start();//这里三个线程的启动顺序可以任意,大家可以试下!
t2.start();
t1.start();
}
}
1.CAS的概念
假设有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS一定要volatile变量配合,
1.CAS的问题
问题1:有可能会出现A变成B再变成A的问题,因此要通过AtomicStampedReference,一个带有标记的原子引用类,通过控制变量值的版本来保证CAS的正确性
问题2:循环时间长开销
自旋CAS,如果一直循环执行,一直不成功,会给CPU带来非常大的执行开销。很多时候,CAS思想体现,是有个自旋次数的,就是为了避开这个耗时问题~
问题3:只能保证一个变量的原子操作
CAS 保证的是对一个变量执行操作的原子性,如果对多个变量操作时,CAS 目前无法直接保证操作的原子性的。可以通过这两个方式解决这个问题:
使用互斥锁来保证原子性;
将多个变量封装成对象,通过AtomicReference来保证原子性。
1.线程同步以及线程调度相关的方法
wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
notify():唤醒一个处于等待状态wait()的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞
争,只有获得锁的线程才能进入就绪状态;
1.活锁与死锁
活锁:一个线程通常会有会响应其他线程的活动。如果其他线程也会响应另一个线程的活动,那么就有可能发生活锁。这就相当于两个在走廊相遇的人:甲向他自己的左边靠想让乙过去,而乙向他的右
边靠想让甲过去。可见他们阻塞了对方。甲向他的右边靠,而乙向他的左边靠,他们还是阻塞了对方。
死锁:两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多个线程同时但以不同的顺序请求同一组锁的时候,死锁会让你的程序挂起无法完成任务。死锁会让程序一直卡住,不再程序往下执行。我们只能通过中止并重启的方式来让程序重新执行。
1.乐观锁与悲观锁
1、乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
2、悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,直接上了锁就操作资源了
1.并发编程三个要素
原子性:原子,即一个不可再被分割的颗粒。原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。
可见性:一个线程对共享变量的修改,另一个线程能够立刻看到。(synchronized,volatile)
有序性:程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)
1.关于synchronized和volatile的比较:
volatile synchronized
线程同步的轻量级实现,只能修改变量,性能高
不会出现阻塞
可以保证数据可见性和有序性,但是不能保证原子性(因为性能高,可能会出现线程安全的问题) 可以修饰方法,代码块,性能低
会出现阻塞
可以保证原子性和可见性,有序性
关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且只能修改变量,而synchronized可以修饰方法,以及代码块。
多线程访问volatile不会发生阻塞,而synchronized会出现阻塞
volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步
关键字volatile解决的变量在多线程之间的可见性;而synchronized解决的是多线程之间资源同步问题
出现线程安全问题的原因:
线程切换带来的原子性问题
缓存导致的可见性问题
编译优化带来的有序性问题
解决办法:
JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
synchronized、volatile、LOCK,可以解决可见性问题
Happens-Before 规则可以解决有序性问题
1.上下文切换
多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用。当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。
1.多线程的常用方法

1、Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入阻塞,但不释放对象锁,millis后线程自动苏醒进入可运行状态。作用:给其它线程执行机会的最佳方式。
2、Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的cpu时间片,由运行状态变成可运行状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。
3、t.join()/t.join(long millis),当前线程里调用其它线程1的join方法,当前线程阻塞,但不释放对象锁,直到线程1执行完毕或者millis时间到,当前线程进入可运行状态。
4、obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。
5、obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程
1.集合类中的线程安全与非线程安全
线程安全(加锁),底层代码有同步关键词 Vector
HashTable,
ConcurrentHashMap
StringBuffer,
String
Properties。
ArrayList
Stack
HashTable
enum
非线程安全(没有加锁) ArrayList
HashMap
HashSet
StringBuilder
linkedlist

你可能感兴趣的:(java,jvm,开发语言)