欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java基础若干问题

程序员文章站 2022-04-16 21:29:32
...

1.JDK 和 JRE 有什么区别?

JDK

​ JDK是Java Development Kit的缩写,是Java的开发工具包,主要包含了各种类库和工具,当然也包含了另外一个JRE.。那么为什么要包含另外一个JRE呢?而且<JDK安装目录>/JRE/bin目录下,包含有server一个文件夹~包含一个jvm.dll,这说明JDK提供了一个虚拟机。

​ 另外,JDK的bin目录下有各种Java程序需要用到的命令,与JRE的bin目录最明显的区别就是JDK文件下才有javac,这一点很好理解,因为JRE只是一个运行环境而已,与开发无关。正因为如此,具备开发功能的JDK所包含的JRE下才会同时有server的JVM,而仅仅作为运行环境的JRE下,只需要server的jvm.dll就够了。

注意:JDK所提供的运行环境和工具度需要进行环境变量的配置以后,才能使用,最主要的配置就是把<JDK安装目录>/bin目录设置为Path环境变量值的一部分。

JRE

​ JRE是Java Runtime Environment的缩写,是Java程序的运行环境。既然是运行,当然要包含JVM,也就是所谓的Java虚拟机,还有所以的Java类库的class文件,都在lib目录下,并且都打包成了jar。

JDK与JRE的区别

​ JDK是Java的开发工具,它不仅提供了Java程序运行所需的JRE,还提供了一系列的编译,运行等工具,如javac,java,javaw等。JRE只是Java程序的运行环境,它最核心的内容就是JVM(Java虚拟机)及核心类库。

2.== 和 equals 的区别是什么?

==是判断两个变量或实例是不是指向同一个内存空间,equals是判断两个变量或实例所指向的内存空间的值是不是相同

==是指对内存地址进行比较 , equals()是对字符串的内容进行比较

==指引用是否相同, equals()指的是值是否相同

3.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?

\1. 如果两个对象相等,则hashcode一定也是相同的

\2. 两个对象相等,对两个对象分别调用equals方法都返回true

\3. 两个对象有相同的hashcode值,它们也不一定是相等的

4.final, finally 和 finalize 的区别?

final 用于声明属性,方法和类,表示属性不可变,方法不可被重写,类不可被继承。

final成员变量表示常量,只能被赋值一次,赋值后值不再改变。

当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。

final修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。

当函数的参数类型声明为final时,说明该参数是只读型的。即你可以读取使用该参数,但是无法改变该参数的值。

finally 是异常处理语句结构的一部分,表示总是执行。

finalize 是 object 类的一个方法,在垃圾收集器执行的时候会调用这个对象回收的方法,工垃圾收集时其他资源的回收,比如关闭文件。

5 JDK 中的 java.lang.Math 类

  • round() :返回四舍五入,负 .5 小数返回较大整数,如 -1.5 返回 -1。

  • ceil() :返回小数所在两整数间的较大值,如 -1.5 返回 -1。

  • tail() :返回小数所在两整数间的较小值,如 -1.5 返回 -2。

6.String 属于基础的数据类型吗?

答:不是。String是一个final对象,是java等编程语言的字符串。

7.java 中操作字符串都有哪些类?它们之间有什么区别?

答:String、StringBuffer、StringBuilder

String : final修饰,String类的方法都是返回new String。即对String对象的任何改变都不影响到原对象,对字符串的修改操作都会生成新的对象。
StringBuffer : 对字符串的操作的方法都加了synchronized,保证线程安全。
StringBuilder : 不保证线程安全,在方法体内需要进行字符串的修改操作,可以new StringBuilder对象,调用StringBuilder对象的append、replace、delete等方法修改字符串。

区别:String是不可变的对象,对每次对String类型的改变时都会生成一个新的对象,StringBuffer和StringBuilder是可以改变对象的。

对于操作效率:StringBuilder > StringBuffer > String

对于线程安全:StringBuffer 是线程安全,可用于多线程;StringBuilder 是非线程安全,用于单线程

不频繁的字符串操作使用 String。反之,StringBuffer 和 StringBuilder 都优于String

8.String str="i"与 String str=new String(“i”)一样吗?

首先,通过main()方法进栈。

然后再栈中定义一个对象s1,去堆中开辟一个内存空间,将内存空间的引用赋值给s1,“i”是常量,然后去字符串常量池 查看是否有i字符串对象,没有的话分配一个空间存放i,并且将其空间地址存入堆中new出来的空间中。

字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价。JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化。为 了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池。如果字符串已经存在池中, 就返回池中的实例引用。如果字符串不在池中,就会实例化一个字符串并放到池中。

在栈中定义一个对象s2,然后去字符串常量池中查看是否有”i”字符串对象,有,直接把”i”的地址赋值给s2.

即s1中存的是堆中分配的空间,堆中分配的空间中存的是字符串常量池中分配空间存放”i”的空间的地址值。而s2中之间存的是字符串常量池中分配空间存放”i”的空间的地址值

9.如何将字符串反转?

方法1

public static string ReverseByRecursive(string original){
if (original.Length == 1)
return original;
else
return original.Substring(1).ReverseByRecursive() + original[0];}

方法2 charAt() 方法用于返回指定索引处的字符。索引范围为从 0 到 length() - 1。

​ public static String reverse2(String s) {

​ int length = s.length();

​ String reverse = “”;

​ for(int i = length-1; i >=0; i–)

 reverse = s.charAt(i) + reverse;

​ return reverse; }

方法3 toCharArray() 方法将字符串转换为字符数组。

​ public static String reverse3(String s) {

char[] array = s.toCharArray();

String reverse = “”;

for(int i = array.length - 1; i >= 0; i–)

reverse += array[i];

return reverse;}

public static string ReverseByCharBuffer2(string original)
{
char[] c = original.ToCharArray();
int l = original.Length;
for (int i = 0; i < l / 2; i++)
{
char t = c[i];
c[i] = c[l - i - 1];
c[l - i - 1] = t;
}
return new string©;
}

方法4

对于字符串反转,我们可以使用.NET类库自带的Array.Reverse方法

public static string ReverseByArray(string original)
{
char[] c = original.ToCharArray();
Array.Reverse©;
return new string©;
}

方法5

public static String reverse4(String s) {

return new StringBuffer(s).reverse().toString();

}

方法6

ublic static string ReverseByStack(this string original)
{
Stack stack = new Stack();
foreach (char ch in original)
{
stack.Push(ch);
}
char[] c = new char[original.Length];
for (int i = 0; i < original.Length; i++)
{
c[i] = stack.Pop();
}
return new string©;
}

10.String 类的常用方法都有那些?

答:下面列举了20个常用方法。格式:返回类型 方法名 作用。

1、和长度有关:

  • int length() 得到一个字符串的字符个数

2、和数组有关:

  • byte[] getByte() ) 将一个字符串转换成字节数组
  • char[] toCharArray() 将一个字符串转换成字符数组
  • String split(String) 将一个字符串按照指定内容劈开

3、和判断有关:

  • boolean equals() 判断两个字符串的内容是否一样
  • boolean equalsIsIgnoreCase(String) 忽略太小写的比较两个字符串的内容是否一样
  • boolean contains(String) 判断一个字符串里面是否包含指定的内容
  • boolean startsWith(String) 判断一个字符串是否以指定的内容开头
  • boolean endsWith(String) 判断一个字符串是否以指定的内容结尾

4、和改变内容有关:

  • String toUpperCase() 将一个字符串全部转换成大写
  • String toLowerCase() 将一个字符串全部转换成小写
  • String replace(String,String) 将某个内容全部替换成指定内容
  • String replaceAll(String,String) 将某个内容全部替换成指定内容,支持正则
  • String repalceFirst(String,String) 将第一次出现的某个内容替换成指定的内容
  • String substring(int) 从指定下标开始一直截取到字符串的最后
  • String substring(int,int) 从下标x截取到下标y-1对应的元素
  • String trim() 去除一个字符串的前后空格

5、和位置有关:

  • char charAt(int) 得到指定下标位置对应的字符
  • int indexOf(String) 得到指定内容第一次出现的下标
  • int lastIndexOf(String) 得到指定内容最后一次出现的下标

11.抽象类必须要有抽象方法吗?(abstract)

答:抽象类中不一定要包含抽象(abstrace)方法。也就是了,抽象中可以没有抽象(abstract)方法。反之,类中含有抽象方法,那么类必须声明为抽象类。

12.普通类和抽象类有哪些区别?

答:

  1. 抽象类不能被实例

  2. 抽象类不能有构造函数,抽象方法也不能被声明为静态必须使用abstract关键字修饰

  3. 抽象类可以有抽象方法

  4. 抽象类的抽象方法必须被非抽象子类继承

    接口:1、接口使用interface修饰;2、接口不能被实例化;3、一个类只能继承一个类,但是可以实现多个接口;4、接口中方法均为抽象方法;5、接口中不能包含实例域或静态方法(静态方法必须实现,接口中方法是抽象方法,不能实现)

    15.java 中 IO 流分为几种?

    答:可以分4种。

    1. 字节输入流(InputStream

    2. 字节输出流(OutputStream

    3. 字符输入流(Reader

    4. 字符输出流(Writer

      16.BIO、NIO、AIO 有什么区别

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

      BIO是一个连接一个线程。 NIO是一个请求一个线程。 AIO是一个有效请求一个线程。

      BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

      NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。

      AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

      https://blog.csdn.net/m0_38109046/article/details/89449305

      IO流是阻塞的,NIO流是不阻塞的。

      Java NIO使我们可以进行非阻塞IO操作。比如说,单线程中从通道读取数据到buffer,同时可以继续做别的事情,当数据读取到buffer中后,线程再继续处理数据。写数据也是一样的。另外,非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。

      Java IO的各种流是阻塞的。这意味着,当一个线程调用 read()write() 时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了

      2)Buffer(缓冲区)

      IO 面向流(Stream oriented),而 NIO 面向缓冲区(Buffer oriented)。

      Buffer是一个对象,它包含一些要写入或者要读出的数据。在NIO类库中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中·可以将数据直接写入或者将数据直接读到 Stream 对象中。虽然 Stream 中也有 Buffer 开头的扩展类,但只是流的包装类,还是从流读到缓冲区,而 NIO 却是直接读到 Buffer 中进行操作。

      在NIO厍中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。

      最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了ByteBuffer,还有其他的一些缓冲区,事实上,每一种Java基本类型(除了Boolean类型)都对应有一种缓冲区。

      3)Channel (通道)

      NIO 通过Channel(通道) 进行读写。

      通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。

      4)Selectors(选择器)

      NIO有选择器,而IO没有。

      选择器用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。

17.Files的常用方法都有哪些?
createNewFile()在指定位置创建一个空文件,成功就返回true,如果已存在就不创建,然后返回false。
mkdir() 在指定位置创建一个单级文件夹。
mkdirs() 在指定位置创建一个多级文件夹。
renameTo(File dest)如果目标文件与源文件是在同一个路径下,那么renameTo的作用是重命名, 如果目标文件与源文件不是在同一个路径下,那么renameTo的作用就是剪切,而且还不能操作文件夹。

删除:
delete() 删除文件或者一个空文件夹,不能删除非空文件夹,马上删除文件,返回一个布尔值。
deleteOnExit()jvm退出时删除文件或者文件夹,用于删除临时文件,无返回值。
判断:
exists() 文件或文件夹是否存在。
isFile() 是否是一个文件,如果不存在,则始终为false。
isDirectory() 是否是一个目录,如果不存在,则始终为false。
isHidden() 是否是一个隐藏的文件或是否是隐藏的目录。
isAbsolute() 测试此抽象路径名是否为绝对路径名。
获取:
getName() 获取文件或文件夹的名称,不包含上级路径。
getAbsolutePath()获取文件的绝对路径,与文件是否存在没关系
length() 获取文件的大小(字节数),如果文件不存在则返回0L,如果是文件夹也返回0L。
getParent() 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回null。
lastModified()获取最后一次被修改的时间。

文件夹相关:
static File[] listRoots()列出所有的根目录(Window中就是所有系统的盘符)
list() 返回目录下的文件或者目录名,包含隐藏文件。对于文件这样操作会返回null。
listFiles() 返回目录下的文件或者目录对象(File类实例),包含隐藏文件。对于文件这样操作会返回null。
list(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。
listFiles(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。

18.java 容器都有哪些?

答:List、Set、Map

19.Collection 和 Collections 有什么区别?

Collcetion是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法。实现该接口的类主要有List和Set,该接口的设计目标是为各种具体的集合提供最大化的统一操作方式。

Collections是针对集合类的一个包装类,它提供了一系列的静态方法以实现对各种集合的搜索、排序、线程安全化等操作,其中大多数方法都是用来处理线性表。 Collections类不能实例化,如同一个工具类,服务于Collection。若是在使用Collections类的方法时候,对应的collection的对象为null,则这些方法都会抛出 NullPointerException 。之前我们见到的包装类还有Arrays,它是为数组提供服务的。

20.List、Set、Map 之间的区别是什么?

ArrayList是有序的,ArrayList长度可变
底层实现实际上就是一个数组.ArrayList就是对数组进行了一系列的包装,
线程不安全,优点,特性,简单的说就是增删速度比较慢,但是查找速度快
LinkedList底层是双向链表的结构LinkedList是有顺序的 长度也是可变的 也是可以重复的 底层实现是双向列表 线程不安全增删快,查询慢.

Hashset

HashSet是Set接口的典型实现, 大多数时候使用Set集合时就是使用这个实现类.HashSet按 Hash算法(实际上HsahSet没有自己实现存储,而是调用的HashMap的实现) 来存储集合中的元素.
HashSet无序 HashSet不能保证元素的排列顺序,顺序可能与添加的顺序不同,顺序也有可能发生变化.
线程不安全 HashSet不是线程同步的,如果多个线程同时修改一个HashSet,则必须通过代码来保证其同步.HashSet长度可变 HashSet通过计算存储对象的Hash码来保存数据,保存在HashSet中的数据元素在内存中的位置是不连续的,HashSet的长度也是不固定的.元素不可重复 HashSet容器中不允许包含相同的元素.后面我们会一起看下如何判断是否是相同元素的.用hashmap实现

HashMap

https://www.jianshu.com/p/ee0de4c99f87

  • List:

    • 可以允许重复对象
    • 可以插入多个null元素
    • 是一个有序容器
  • Set:

    • 不允许重复对象
    • 只允许一个null元素
    • 无序容器
  • Map:

    • Map不是Collection的子接口或实现类。Map是一个接口
    • Map 的每个Entry都特有两个对象,也就是一个键一个值,Map可能会持有相同的值对象但键对象必须是唯一的
    • Map里可以拥有随意个niull值但最多只能有一个null键

    21.HashMap 和 Hashtable 有什么区别?

    1、继承的父类不同

      Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。
    

    2、线程安全性不同

      javadoc中关于hashmap的一段描述如下:此实现不是同步的。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。
    
      Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省情况下是非Synchronize的。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步,但使用HashMap时就必须要自己增加同步处理。(结构上的修改是指添加或删除一个或多个映射关系的任何操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的非同步访问,如下所示:
    
      Map m = Collections.synchronizedMap(new HashMap(...));
    
      Hashtable 线程安全很好理解,因为它每个方法中都加入了Synchronize。这里我们分析一下HashMap为什么是线程不安全的:
    
      HashMap底层是一个Entry数组,当发生hash冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。
    

    3、是否提供contains方法

      HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因为contains方法容易让人引起误解。
    
      Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。
    

    4、key和value是否允许null值

      其中key和value都是对象,并且不能包含重复key,但可以包含重复的value。
    
      通过上面的ContainsKey方法和ContainsValue的源码我们可以很明显的看出:
    
      Hashtable中,key和value都不允许出现null值。但是如果在Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常,这是JDK的规范规定的。
    

    HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
    5、两个遍历方式的内部实现上不同

      Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
    

    6、hash值不同

      哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。
    
      hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值。
    
      Hashtable计算hash值,直接用key的hashCode(),而HashMap重新计算了key的hash值,Hashtable在求hash值对应的位置索引时,用取模运算,而HashMap在求位置索引时,则用与运算,且这里一般先用hash&0x7FFFFFFF后,再对length取模,&0x7FFFFFFF的目的是为了将负的hash值转化为正值,因为hash值有可能为负数,而&0x7FFFFFFF后,只有符号外改变,而后面的位都不变。
    

    7、内部实现使用的数组初始化和扩容方式不同

      HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。
      Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。
    
      Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是 old*2+1。
    

    22.如何决定使用 HashMap 还是 TreeMap?

    答:对于在 Map 中插入、删除、定位一个元素这类操作,HashMap 是最好的选择,因为相对而言 HashMap 的插入会更快,但如果你要对一个 key 集合进行有序的遍历,那 TreeMap 是更好的选择。

23.hashmap的实现

而是在执行put操作的时候才真正构建table数组

java基础若干问题java基础若干问题

https://blog.csdn.net/woshimaxiao1/article/details/83661464

24.HashSet的实现原理:

①是基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap。封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。

②当我们试图把某个类的对象当成 HashMap的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的equals(Object obj)方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。

③HashSet的其他操作都是基于HashMap的。
add添加的元素存放在HashMap中,其他方法结合源码分析,参考HashMap
HashMap数据结构分析链接:
特性

HashSet为什么不能存放相同元素:
在HashMap的put API中,在存入一个元素时,会调用其hashcode方法计算hashcode值,然后在计算出存放entry数组的index,当index相同时调用equals方法判断是否是同一个元素,是则不能存放。

25.ArrayList 和 LinkedList 的区别是什么?

答:

  • ArrayList 与 LinkedList都实现了List接口
  • ArrayList 是线性表,底层是使用数组实现的,它在尾端插入和访问数据时效率较高
  • LinkedList 是双向链表,它在中间插入或者插入时效率较高,在访问数据时效率较低
相关标签: Java