三万字带你了解那些年面过的Java八股文【建议收藏】

本文详述了Java面试中的常见知识点,包括基础语法、集合框架、多线程、Java虚拟机、MySQL、Spring框架、计算机网络、MQ消息队列、Redis和Nginx等内容,旨在帮助读者理解和掌握面试中的核心概念和技术要点。
摘要由CSDN通过智能技术生成

前言

国内的互联网面试,恐怕是现存的、最接近科举考试的制度。而且,我国的八股文(基础知识、集合框架、多线程、线程的五种状态、虚拟机、MySQL、Spring相关、计算机网络、MQ系列等)确实是独树一帜。以美国为例,北美工程师面试比较重视算法(Coding),近几年也会加入Design轮(系统设计和面向对象设计OOD)和BQ轮(Behavioral question,行为面试问题),今天博主为大家熬断半头青丝捋一捋这现代八股文。

Java基础知识

基础知识导图

需要完整图的小伙伴可联系博主

IO流

详情见博主此文:IO流知识体系详解

    按照流的方向:输入流(inputStream)和输出流(outputStream).

    按照实现功能分:节点流(可以从或向一个特定的地方(节点)读写数据。如 FileReader)和处理流(是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如 BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接)

    按照处理数据的单位: 字节流和字符流。字节流继承于 InputStream 和 OutputStream,字符流继承于InputStreamReader 和 OutputStreamWriter。

字节流如何转为字符流

字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象。

字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象

如何将一个 java 对象序列化到文件里

在 java 中能够被序列化的类必须先实现 Serializable 接口,该接口没有任何抽象方法只是起到一个标记作用

Lambda

详情见博主此文:Lambda表达式详细讲解

&和&&的区别?

&:逻辑与(and),运算符两边的表达式均为true时,整个结果才为true
&&:短路与,如果第一个表达式为false时,第二个表达式就不会计算了

在java中如何跳出当前的多重循环?

在循环语句外前面定义一个标号,然后在里层循环体的代码中使用带有标号的break语句,即可跳出循环。
比如:

ok:
for (int j = 0; j < 10; j++) {
	break ok;
}

面向对象的三大特征

1.封装

作用:提高代码的安全性
1、将属性私有化,并提供对外界的接口(get/set方法)。
2、用private修饰的属性和方法,只能在本类中使用。

2.继承

作用:提高代码的复用性,减少重复代码
1、子类可以继承父类非私有的属性和方法,不能继承构造方法和私有的属性和方法。
2、可以综合子类的共同特征来去提炼父亲的类。
3、子类在继承父类的各种属性和方法时,也可以有自己的属性和方法。
4、一个子类只能有一个父类,java只能单继承,不能多继承,因为多个类中的方法名相同,方法体不同,不知使用哪个。
5、一个类继承最顶端叫做基类或者超类,所有的超类叫做object 。
6、在继承关系中,如果父类没有无参数的构造方法,如何解决?
1.子类中添加一个和父类构造方法参数列表相同的构造方法,通过super参数传递给父类的构造方法
2.如果父类允许修改的时候,可以在父类中创建一个无参的构造方法
7、在继承关系中,代码块的执行顺序:父静>子静>父构造代码块>父构造方法>子构造代码块>子构造方法

3.多态

1.分类
编译时多态:在编译过程中察觉的多态,重载,向上转型。
运行时多态:在运行过程中察觉的多态,向下转型。
2.向上转型、向下转型是在继承关系中,向下转型必须在向上转型的基之上。
3.在继承关系中,父类的对象可以指向子类的实例,父类引用实体方法的时候,是调用子类重写以后的方法。
4.向上转型
父类的引用指向子类的实体
父类类名 对象名=new 子类类();
优点:减少重复代码,提高代码的复用性
缺点:父类的引用无法调用子类特有的属性和方法
解决方案:向下转型
5.向下转型:
子类对象的父类引用赋给子类
子类类名 对象名=(子类类名)父类对象;
6. instanceof 判断左边的对象是否属于右边的类 对象名 instanceof 类名(子类类名)
7.匿名对象
new 类名()只有堆空间,没有栈空间,只能属于一次,为了节省代码。

"=="和equals方法究竟有什么区别?

==:表示两个变量的值是否相等,比较两个基本数据类型的数据或者引用变量,用 ==
equals:用于比较两个独立对象的内容是否相同,字符串的比较也用equals

三个与取整有关的方法

Math.ceil():表示向上取整;Math.ceil(11.3)=12;Math.ceil(-11.3)=-12。

Math.floor():表示向下取整;Math.floor(11.6)=11;Math.floor(-11.6)=-12。

Math.round():表示四舍五入;Math.round(11.5)=12;Math.round(-11.5)=-11;

Math.round(11.3)=11;Math.round(-11.3)=-11;

Java中运算符

算术运算符:+ 、 - 、 * 、 / 、 % 、 ++ 、 --
赋值运算符:= 、 += 、 -= 、 *= 、 /= 、 %=
关系运算符:> 、 < 、 >= 、 <= 、 == 、 !=
逻辑运算符:! 、 & (只要有一个false  最终结果就是false) 、

         | (但凡有一个true   最终结果就是true) 、

         ^ (如果两边一样     最终结果为false   如果两边不同   最终结果为true)、

         &&(如果第一个是false 那第二个不执行  最终结果是false)、

         ||(如果第一个表达式的结果是true 那第二个表达式 就不去计算了 ,最终结果是true)
位运算符: ~ 、 >> 、 << 、 >>>
字符串连接运算符:+
三目运算符:X ? Y : Z
            X为boolean类型表达式,先计算x的值,若为true,整个三目运算的结果为表达式Y的值,否则整个运算结果为表达式Z的值。

✨重载和重写的区别?

重载(Overload):函数名相同,参数不同。可以改变返回值类型,参数的个数和类型。
重写(Override):和父类的的方法名称、参数完全相同

String和StringBuffuer、StringBuilder的区别?

String:字符串数值不可变;
StringBuffer:字符串可修改,可以动态构造字符数据。StringBuffer类是可以通过Append()来修改值。线程安全。
StringBuilder:线程不安全。
三者在执行速度方面的比较:StringBuilder > StringBuffer > String
对于三者使用的总结:
1.如果要操作少量的数据用 = String  
2.单线程操作字符串缓冲区下操作大量数据 = StringBuilder
3.多线程操作字符串缓冲区下操作大量数据 = StringBuffer

java中有几种方法实现一个线程?用什么关键字修饰同步方法?stop()和suspend()方法为何不推荐使用?

第一种:继承Thread类。New Thread(){}.start():表示调用子类对象的run方法
第二种:实现Runable接口
第三种:线程池创建多线程
第四种:实现Callable接口,重写call函数
继承Thread类实现多线程,重写run方法时没有返回值也不能抛出异常,使用Callable接口就可以解决这个问题

Callable接口和Runnable接口的不同之处:
1.Callable规定的方法是call,而Runnable是run
2.call方法可以抛出异常,但是run方法不行
3.Callable对象执行后可以有返回值,运行Callable任务可以得到一个Future对象,通过Future对象可以了解任务执行情况,可以取消任务的执行,而Runnable不可有返回值
用synchronized 关键字修饰同步方法。
反对使用stop(),是因为它不安全。
suspend() 方法容易发生死锁。调用 suspend() 的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被" 挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用 suspend() ,而应在自己的 Thread 类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait() 命其进入等待状态。若标志指出线程应当恢复,则用一个 notify()重新启动线程。

sleep()和wait()有什么区别?

sleep是线程被调用时,占着cpu休眠,其他线程不能占用cpu,os认为该线程正在工作,不会让出系统资源,wait是进入等待池等待,让出系统资源,其他线程可以占用cpu 

sleep()和wait()方法的区别可从两个角度阐述:
1.cpu的抢占权;2.锁旗标是否释放
两者都会释放cpu的抢占权;
wait()方法执行完即可释放锁旗标,进入线程的等待队列;
sleep()执行完,不会释放,进入等待队列;

基本语句(for、if else、switch、while、break和continue)

1.for循环语句

for ([循环变量初始值设定]; [循环条件判断]; [改变循环变量的值]){
     循环体
}
注意:1、表达式2一般不可以省略,否则死循环
      2、表达式3可以省略,但是在循环体中必须有语句修改变量,以使表达式2在某一时刻为false结束循环。
      3、若同时省略表达式1,表表达式3,则相当于while(表达式2)语句
      4、三个表达式均省略 即for(;;)语句,此时相当于while(true)语句
      5、表达式1、表达式3可以是逗号表达式,以使循环变量值在修改时可以对其它变量赋值

2.if…else… 判断语句

1、if(){}

2、if(){}else{}

3、if(){}else if(){}

4、if(){if(){}else()}    

5、if()执行语句 esle   执行语句 注意:执行语句只有一条语句的时候.可以将if esle 的大括号省略

注意:()内是boolean类型表达式,{}是语句块
    比较字符串用equals,比较内容。比较数值用==,比较地址。
    基本数据类型:变量名、变量值在栈中。
    引用数据类型:变量名在栈中,变量值在常量池中。

3.while 循环语句

while( 条件表达式语句){

     循环体语句;

     }

  //初始条件


  do{

    //循环体;
	//迭代
}while( 循环条件判断); 
  注意:1、当第一次执行时,若表达式=false时,则while语句不执行,而do/while语句执行一次后面的语句
  2、一定要切记在switch循环中,如果没有break跳出语句,每一个case都要执行一遍,在计算最终结果。

4.switch 语句

switch(表达式expr){
    case const1:

        statement1;

        break;

    … …

    case constN:

        statementN;

        break;

    [default:

        statement_dafault;

        break;]

}
注意:1、表达式必须是int、byte、char、short、enmu、String类型
      2、constN必须是常量或者finall变量,不能是范围
      3、所有的case语句的值不能相同,否则编译会报错
      4、default可要可不要
      5、break用来执行完一个分支后使程序跳出switch语句块,否则会一直会执行下去。

5.if和switch的区别是什么?

1、if可以判断范围,也可以判断一个值
switch只能判断指定的值
2、若只判断指定的值,则使用switch语句,效率快
if判断范围,对数据判断灵活,自身的格式也比较灵活

6.break和continue区别是什么?

break跳出某个循环
continue跳过某个循环
注意:if外有循环可以用break、continue,单纯if不可以用。

关键字

1、static调用格式:

    1、同一个类中:
        静态的:

            方法名     属性名

            类名.方法名  类名.属性名

            对象名.方法名 对象名.属性名

        非静态的:

            对象名.属性名 对象名.方法名

    2、不同类中:

        静态的:

            对象名.方法名 对象名.属性名

            类名.方法名  类名.属性名

        非静态的:

            对象名.属性名 类名.方法名

    注意:1、static可以修饰属性、方法、代码块,不可以修饰类和构造方法。

          2、静态方法随着类的加载而加载。

          3、在静态方法区内的东西只有一份,所有的对象共享这一份空间,只要有一个对象对属性进行修改,所有的对象调用都是修改后的数据。

          4、代码块的执行顺序:静态代码块(只被调用一次)>构造代码块{}>构造方法>普通方法(需调用)

2、this关键字

    1、可以调用属性和方法。

        this.属性名(全局变量)

        this.方法名();

    2、在构造方法中:

        1、this();括号内的参数个数、顺序、类型根据调用的方法来决定。

        2、必须放在第一行,只能调用一次。

        3、调用构造方法时只能在构造方法中调用,调用属性和方法时可以在构造方法中可以在普通方法中。

        4、当全局变量和局部变量有重名字的时候,用this来区分。

3、super关键字

    1、super指代父类对象。

    2、super可以调用属性、方法、构造方法。

    3、super调用父类的构造方法。

    4、super调用构造方法时候必须放在第一行。

4、final最终的

    1、可以修饰全局变量,声明的时候必须赋值,只能赋值一次。

    2、可以修饰局部变量,声明时候可以不赋值,但也只能赋值一次。

    3、可以修饰方法,可以正常使用,不能被重写。

    4、可以修饰类,可以正常使用,不能被继承。

    5、用final修饰的属性通常叫常量。

    6、static final 全局变量。每个字母都要大写。

5、this和super的区别

    1、this指的是本类创建的对象。                           super指代的是父类的对象

    2、this可以调用属性、方法、构造方法。                   super也可以调用属性、方法、构造方法。

    3、this调用属性和方法的时候,调用自己本类的属性和方法。 如果本类没有,那就用super去父类中找

    4、this调用构造方法调用,调用本类的其他构造方法。       super调用父类的构造方法。

    5、this和super在调用构造方法的时候必须放在第一行。

    6、this和super不能同时存在

6、最小作用域最强原则:

    局域变量在此方法中,比全局变量在此方法中的作用强。

作用域public、private、protected 以及不写时的区别?

private修饰的成员变量和函数只能在类本身和内部类中被访问
protected 修饰的成员变量和函数能被类本身、子类及同一个包中的类访问
public修饰的成员变量和函数可以被类、子类、同一个包中的类以及任意其他类访问
默认情况(不写)下,属于一种包访问,即能被类本身以及同一个包中的类访问

forward和redirect两种跳转方式的区别?

1.从地址栏显示来说
forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址.
redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL
2.从数据共享来说
forward:转发页面和转发到的页面可以共享request里面的数据
redirect:不能共享数据.
3.从运用地方来说
forward:一般用于用户登陆的时候,根据角色转发到相应的模块
redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等
4.从效率来说
forward:高
redirect:低

HashMap和Hashtable的区别?

HashMap:实现了Map接口,允许空(null)键值(key),由于非线程安全,在只有一个线程访问的情况下,效率高于Hashtable。
Hashtable:不能将null作为key或者value。方法是同步的,线程安全。

List、Set和Map的区别?

List:是存储单列数据的集合,存储有顺序,允许重复。继承Collection接口。
Set: 是存储单列数据的集合。继承Collection接口。不允许重复。
Map:存储键和值这样的双列数据的集合,存储数据无顺序,键(key)不能重复,值(value)。可以重复。

hashCode与equals的区别与联系?

一、equals方法的作用

1、默认情况(没有覆盖equals方法)下equals方法都是调用Object类的equals方法,而Object的equals方法主要用于判断对象的内存地址引用是不是同一个地址(是不是同一个对象)。
2 、要是类中覆盖了equals方法,那么就要根据具体的代码来确定equals方法的作用了,覆盖后一般都是通过对象的内容是否相等来判断对象是否相等。

二、Hashcode()方法:

1、我们并没有覆盖equals方法只覆盖了hashCode方法,两个对象虽然hashCode一样,但在将stu1和stu2放入set集合时由于equals方法比较的两个对象是false,所以就没有在比较两个对象的hashcode值。
2、覆盖一下equals方法和hashCode方法,stu1和stu2通过equals方法比较相等,而且返回的hashCode值一样,所以放入set集合中时只放入了一个对象。
3、我们让两个对象equals方法比较相等,但hashCode值不相等试试,虽然stu1和stu2通过equals方法比较相等,但两个对象的hashcode的值并不相等,所以在将stu1和stu2放入set集合中时认为是两个不同的对象。

总结:

1、equals方法用于比较对象的内容是否相等(覆盖以后)
2、hashcode方法只有在集合中用到
3、当覆盖了equals方法时,比较对象是否相等将通过覆盖后的equals方法进行比较(判断对象的内容是否相等)。
4、将对象放入到集合中时,首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等,如果不相等直接将该对象放入集合中。如果hashcode值相等,然后再通过equals方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入。

模式

1、单例模式

分类:懒汉式、饿汉式
1、构造方法私有化
2、在本类中创建本类对象
3、保证对象的唯一性final
4、给外界提供得到对象的方法 static
5、在多线程中,饿汉式安全,懒汉式不安全

2、简单工厂模式

批量创建对象
1 创建工厂类 : 创建对象的方法
2 果汁类 是所有种类果汁的父类
3 在工厂类的方法中返回果汁类
4 根据测试类中传递的字符串判断到底返回哪种果汁
5 测试类通过工厂类返回果汁对象

3、建造者模式

内部类使用场景
目的:静态内部类创建外部类对象
1、 创建外部类,在其中创建一个静态内部类
2、静态内部类中写属性,构造方法和set get方法
3、静态内部类中写一个方法,必须返回外部类对象
4、 给外部类创建对象,传递参数。

4、装饰者模式

1、在处理流中使用
2、子类重写父类的方法,提高父类方法的功能及效率
3、为了尽可能减少重复代码,在重写的方法中用父类的对象调用父类原来的方法
4、得到父类对象可以通过将父类对象作为子类属性,通过子类构造方法传递父类对象

Java常用类

1.装箱拆箱

1、装箱:把基本数据类型转成包装类类型
2、拆箱:把包装类类型转成基本数据类型
3、为什么要包装类
    八种基本数据类型不满足面向对象的思想,不包括属性和方法。如果给基本数据类型添加功能,只能创建其包装类,将方法和属性封装进去(jdk5.0以后出现了自动拆箱,装箱)
4、Integer支持字符串,但字符串必须是数字。Integer integer3=new Integer("2");
   compareTo();     比较大小,大返回整数,小于返回负数,相等返回0

   toBinaryString();    将十进制数转成二进制,返回String字符串的表现形式

   toHexString();   将十进制转成十六进制

   toOctalString(); 将十进制转成八进制

   toString();      将int类型数据转成String字符串

   Integer.valueOf();   将int转成integer类型对象

   new Integer();   将int转成integer类型对象

   parseInt();      将Integer转成int

2.String字符串

==                  比较地址
.equals()               比较内容
.equalsIgnoreCase()         忽略大小写比较是否相同
.charAt();              字符串截取出指定的下标开始
.compareTo()                比较大小
.compareToIgnore()          忽略大小比较
.concat()               将参数字符串连接到指定字符串后面
.contains()             是否包含参数字符串
.startsWith()               以指定前缀开头
.endsWith()             以指定后缀结尾
.indexOf("/")               第一次出现
.indexOf("/", 3)            指定位置开始索引
.lastIndexOf("/")           最后一次出现
.substring(string11.lastIndexOf("/")+1);截取指定位置
.substring(string11.lastIndexOf("/")+1, string11.lastIndexOf("."));//截取字符串,指定开始位置和结束位置
.replace('a', 'b')          替换指定字符串,替换所有的
.toUpperCase()              全部转为大写
.toLowerCase()              全部转成小写
.trim()                 去掉字符串前后的空格,中间的去不掉

3.Boolean

Boolean boolean=new Boolean("false");
System.out.println(boolean);

4.正则表达式

字符类
[abc]       a、b、c其中任意一个
[^abc]      除了a、b、c中的任意一个
[a-zA-Z]     a-z或A-Z范围中的任意一个
[a-zA-Z0-9]  a-z A-Z 0-9 其中任意一个
[……]         可以自己定义范围
预定字符类
\d   数字0-9
\D   非数字0-9
\s   空白字符:[ \t\n\x0B\f\r]
\S   非空白字符:\s
\w   单词字符:[a-zA-Z_0-9]
\W   非单词字符\w
数量词
?     一次或者一次也没有
*      0次到多次
+      一次或者多次
{n}    恰好n次
{n,}   至少n次
{n,m}  至少n次但不超过m次
.matches();    匹配是否适合
.spil();   拆分

5.时间相关类

1、Date类
    .getTime();计算毫秒

2、SimpleDateFormat类  格式化时间
    .format();返回的是String字符串

3、Calendar接口  日历字段之间的转换提供了一些方法
    .get(Calendar.YEAR);
    .get(Calendar.MONTH);// 默认是当前月份减一    从0开始的  
    .get(Calendar.DAY_OF_MONTH);
    .get(Calendar.DAY_OF_WEEK);
    Calendar calendar = Calendar.getInstance();
    Date date = calendar.getTime();

4、Runtime运行时时间
    .freeMemory(); 当前的系统剩余空间

5、System.exit(0);退出程序,参数是0 是正常退出
   System.gc();调用垃圾回收器 ,不一定能够起来 ,只是起到一个促进的作用

Java集合框架

常用的三种集合接口:

1.List:继承自Collection。可以存在相同的对象,有序的。具体实现类有ArrayList,LinkedList,Vector等(已经被抛弃,很少使用)
2.Set:继承自Collection。不能存在相同的对象,无序的,就是数学意义上的集合。具体实现类有HashSet,LinkedHashSet,TreeSet等。
3.Map:以键值对的形式存放对象。key-value。一般是key为String类型,value为Object的类型。具体实现类有HashMap,LinkedHashMap,TreeMap等。

List(有序,可以重复的集合)

public interface List<E> extends Collection<E> {}
1
由于 List 接口是继承于 Collection 接口,所以基本的方法如上所示。

1、List 接口的三个典型实现:
①、List list1 = new ArrayList();
底层数据结构是数组,查询快,增删慢;线程不安全,效率高
②、List list2 = new Vector();
底层数据结构是数组,查询快,增删慢;线程安全,效率低,几乎已经淘汰了这个集合
③、List list3 = new LinkedList();
底层数据结构是链表,查询慢,增删快;线程不安全,效率高

代码示例:

//产生一个 List 集合,典型实现为 ArrayList
        List list = new ArrayList();
        //添加三个元素
        list.add("Tom");
        list.add("Bob");
        list.add("Marry");
        //构造 List 的迭代器
        Iterator it = list.iterator();
        //通过迭代器遍历元素
        while(it.hasNext()){
            Object obj = it.next();
            //System.out.println(obj);
        }

        //在指定地方添加元素
        list.add(2, 0);

        //在指定地方替换元素
        list.set(2, 1);

        //获得指定对象的索引
        int i=list.indexOf(1);
        System.out.println("索引为:"+i);

        //遍历:普通for循环
        for(int j=0;j<list.size();j++){
             System.out.println(list.get(j));
        }

Set(典型实现 HashSet()是一个无序,不可重复的集合)

HashSet 基于 HashMap 实现,使用了 HashMap 的 K 作为元素存储,V 为 new Object() ,在 add() 方法中如果两个元素的 Hash 值相同,则通过 equals 方法比较是否相等。
LinkedHashSet LinkedHashSet 继承于 HashSet,并且其内部是通过 LinkedHashMap 来实现的。
TreeSet 红黑树实现有序唯一。

1、Set hashSet = new HashSet();

 ①、HashSet:不能保证元素的顺序;不可重复;不是线程安全的;集合元素可以为 NULL;
②、其底层其实是一个数组,存在的意义是加快查询速度。我们知道在一般的数组中,元素在数组中的索引位置是随机的,元素的取值和元素的位置之间不存在确定的关系,因此,在数组中查找特定的值时,需要把查找值和一系列的元素进行比较,此时的查询效率依赖于查找过程中比较的次数。而 HashSet 集合底层数组的索引和值有一个确定的关系:index=hash(value),那么只需要调用这个公式,就能快速的找到元素或者索引。
③、对于 HashSet: 如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。
1、当向HashSet集合中存入一个元素时,HashSet会先调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值决定该对象在HashSet中的存储位置
1.1、如果 hashCode 值不同,直接把该元素存储到 hashCode() 指定的位置
1.2、如果 hashCode 值相同,那么会继续判断该元素和集合对象的 equals() 作比较
1.2.1、hashCode 相同,equals 为 true,则视为同一个对象,不保存在 hashSet()中
1.2.2、hashCode 相同,equals 为 false,则存储在之前对象同槽位的链表上,这非常麻烦,我们应该约束这种情况,即保证:如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。
注意:每一个存储到 哈希 表中的对象,都得提供 hashCode() 和 equals() 方法的实现,用来判断是否是同一个对象
   对于 HashSet 集合,我们要保证如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。
————————————————
版权声明:本文为CSDN博主「白大锅」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45735355/article/details/120081530

常见的 hashCode()算法:

2、Set linkedHashSet = new LinkedHashSet();

 不可以重复,有序,因为底层采用 链表 和 哈希表的算法。链表保证元素的添加顺序,哈希表保证元素的唯一性

3、Set treeSet = new TreeSet();

TreeSet:有序;不可重复,底层使用 红黑树算法,擅长于范围查询。
如果使用 TreeSet() 无参数的构造器创建一个 TreeSet 对象, 则要求放入其中的元素的类必须实现 Comparable 接口所以, 在其中不能放入 null 元素
必须放入同样类的对象(默认会进行排序) 否则可能会发生类型转换异常.我们可以使用泛型来进行限制

Map(key-value 的键值对,key 不允许重复,value可以)

1、严格来说 Map 并不是一个集合,而是两个集合之间 的映射关系。
2、这两个集合没每一条数据通过映射关系,我们可以看成是一条数据。即 Entry(key,value)。Map 可以看成是由多个 Entry 组成。
3、因为 Map 集合即没有实现于 Collection 接口,也没有实现 Iterable 接口,所以不能对 Map 集合进行 for-each 遍历。

代码示例:

Map<String,Object> hashMap = new HashMap<>();
        //添加元素到 Map 中
        hashMap.put("key1", "value1");
        hashMap.put("key2", "value2");
        hashMap.put("key3", "value3");
        hashMap.put("key4", "value4");
        hashMap.put("key5", "value5");
         
        //删除 Map 中的元素,通过 key 的值
        hashMap.remove("key1");
         
        //通过 get(key) 得到 Map 中的value
        Object str1 = hashMap.get("key1");
         
        //可以通过 添加 方法来修改 Map 中的元素
        hashMap.put("key2", "修改 key2 的 Value");
         
        //通过 map.values() 方法得到 Map 中的 value 集合
        Collection<Object> value = hashMap.values();
        for(Object obj : value){
            //System.out.println(obj);
        }
         
        //通过 map.keySet() 得到 Map 的key 的集合,然后 通过 get(key) 得到 Value
        Set<String> set = hashMap.keySet();
        for(String str : set){
            Object obj = hashMap.get(str);
            //System.out.println(str+"="+obj);
        }
         
        //通过 Map.entrySet() 得到 Map 的 Entry集合,然后遍历
        Set<Map.Entry<String, Object>> entrys = hashMap.entrySet();
        for(Map.Entry<String, Object> entry: entrys){
            String key = entry.getKey();
            Object value2 = entry.getValue();
            System.out.println(key+"="+value2);
        }
         
        System.out.println(hashMap);

Map常用方法及实现类

    1.添加:
        map.put(key,value) //在key位置上存储value值,key存在则覆盖原有值;
        map.putAll(Map m);//将Map集合m放在map中
    2.删除:
        map.clear(); //清空map中的数据
        map.remvoe(key); //删除key及其位置上的元素,返回其值。
    3.判断:
        map.containsValue(value); //判断集合是否包含value值
        map.containsKey(key); //判断集合是否包含key键
    4.获取:
        map.get(key); //获取key键上的value值
        map.size(); //获取map集合的大小
        Collection c = map.values(); 
        // 返回map集合中的value值 的Collection集合;
        Set< K > set = keySet(); //取出key的所有值的Set集合
        Set< Map.Entry< K , V > >set =entrySet();

实现类:

1.Hashtable : 底层用哈希表实现,不允许存在null键和值,集合线程安全(线程同步) jdk1.0以前常用
2.HashMap : 底层用哈希表实现,运行存在null的键和值,集合线程不同步,用法与Hastable相同
3.TreeMap : 底层用二叉树实现,用于需要排序的Map集合中
4.Properties:继承Hastbale,主要用于流中文件固化
5.ConcurrentHashMap:线程安全的 HashMap。
1.7 采用分段锁的形式加锁;1.8 使用 Synchronized 和 CAS 实现同步,若数组的 Node 为空,则通过 CAS 的方式设置值,不为空则加在链表的第一个节点。获取第一个元素是否为空使用 Unsafe 类提供的 getObjectVolatile 保证可见性。
对于读操作,数组由 volatile 修饰,同时数组的元素为 Node,Node 的 K 使用 final 修饰,V 使用 volatile 修饰,下一个节点也用 volatile 修饰,保证多线程的可见性。
6.LinkedHashMap :继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。

Map与Set关系

1、都有几个类型的集合。HashMap 和 HashSet ,都采 哈希表算法;TreeMap 和 TreeSet 都采用 红-黑树算法;LinkedHashMap 和 LinkedHashSet 都采用 哈希表算法和红-黑树算法。
2、分析 Set 的底层源码,我们可以看到,Set 集合 就是 由 Map 集合的 Key 组成

Java多线程

创建线程和启动

(1)继承Thread类创建线程类
通过继承Thread类创建线程类的具体步骤和具体代码如下:
• 定义一个继承Thread类的子类,并重写该类的run()方法;
• 创建Thread子类的实例,即创建了线程对象;
• 调用该线程对象的start()方法启动线程。

class SomeThead extends Thraad   { 
    public void run()   { 
     //do something here  
    }  
 } 
 
public static void main(String[] args){
 SomeThread oneThread = new SomeThread();   
  步骤3:启动线程:   
 oneThread.start(); 
}
(2)实现Runnable接口创建线程类
通过实现Runnable接口创建线程类的具体步骤和具体代码如下:
• 定义Runnable接口的实现类,并重写该接口的run()方法;
• 创建Runnable实现类的实例,并以此实例作为Thread的target对象,即该Thread对象才是真正的线程对象。
class SomeRunnable implements Runnable   { `在这里插入代码片`
  public void run()   { 
  //do something here  
  }  
} 
Runnable oneRunnable = new SomeRunnable();   
Thread oneThread = new Thread(oneRunnable);   
oneThread.start(); 
(3)通过Callable和Future创建线程
通过Callable和Future创建线程的具体步骤和具体代码如下:
• 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
• 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
• 使用FutureTask对象作为Thread对象的target创建并启动新线程。
• 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值其中,Callable接口(也只有一个方法)定义如下:
public interface Callable   { 
  V call() throws Exception;  
 } 
  步骤1:创建实现Callable接口的类SomeCallable(略);   
  步骤2:创建一个类对象: 
      Callable oneCallable = new SomeCallable(); 
  步骤3:由Callable创建一个FutureTask对象:   
    FutureTask oneTask = new FutureTask(oneCallable); 
  注释: FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了 Future和Runnable接口。 
  步骤4:由FutureTask创建一个Thread对象:   
    Thread oneThread = new Thread(oneTask);   
  步骤5:启动线程:  
    oneThread.start();

线程生命周期

1、新建状态
用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)。
注意:不能对已经启动的线程再次调用start()方法,否则会出现Java.lang.IllegalThreadStateException异常。
2、就绪状态
处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列(尽管是采用队列形式,事实上,把它称为可运行池而不是可运行队列。因为cpu的调度不一定是按照先进先出的顺序来调度的),等待系统为其分配CPU。等待状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态,系统挑选的动作称之为“cpu调度”。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。
提示:如果希望子线程调用start()方法后立即执行,可以使用Thread.sleep()方式使主线程睡眠一伙儿,转去执行子线程。
3、运行状态
处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
处于就绪状态的线程,如果获得了cpu的调度,就会从就绪状态变为运行状态,执行run()方法中的任务。如果该线程失去了cpu资源,就会又从运行状态变为就绪状态。重新等待系统分配资源。也可以对在运行状态的线程调用yield()方法,它就会让出cpu资源,再次变为就绪状态。
注: 当发生如下情况是,线程会从运行状态变为阻塞状态:
①、线程调用sleep方法主动放弃所占用的系统资源
②、线程调用一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
③、线程试图获得一个同步监视器,但更改同步监视器正被其他线程所持有
④、线程在等待某个通知(notify)
⑤、程序调用了线程的suspend方法将线程挂起。不过该方法容易导致死锁,所以程序应该尽量避免使用该方法。
当线程的run()方法执行完,或者被强制性地终止,例如出现异常,或者调用了stop()、desyory()方法等等,就会从运行状态转变为死亡状态。
4、阻塞状态
处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。
在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。
5、死亡状态
当线程的run()方法执行完,或者被强制性地终止,就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

线程管理

线程睡眠——sleep

Java提供了一些便捷的方法用于会线程状态的控制。具体如下:

1、线程睡眠——sleep
如果我们需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread的sleep方法。
注:

(1)sleep是静态方法,最好不要用Thread的实例对象调用它,因为它睡眠的始终是当前正在运行的线程,而不是调用它的线程对象,它只对正在运行状态的线程对象有效。如下面的例子:
public class Test1 {  
    public static void main(String[] args) throws InterruptedException {  
        System.out.println(Thread.currentThread().getName());  
        MyThread myThread=new MyThread();  
        myThread.start();  
        myThread.sleep(1000);//这里sleep的就是main线程,而非myThread线程  
        Thread.sleep(10);  
        for(int i=0;i<100;i++){  
            System.out.println("main"+i);  
        }  
    }  
}
2)Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。但是不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。因为使用sleep方法之后,线程是进入阻塞状态的,只有当睡眠的时间结束,才会重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不可能精准的去干涉它,所以如果调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒。

线程让步——yield

yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态的方法,它也可以让当前正在执行的线程暂停,让出cpu资源给其他的线程。但是和sleep()方法不同的是,它不会进入到阻塞状态,而是进入到就绪状态。yield()方法只是让当前线程暂停一下,重新进入就绪的线程池中,让系统的线程调度器重新调度器重新调度一次,完全可能出现这样的情况:当某个线程调用yield()方法之后,线程调度器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值