Java基础知识学习,一文掌握Java基础知识文集。
🏆作者简介,普修罗双战士,一直追求不断学习和成长,在技术的道路上持续探索和实践。
🏆多年互联网行业从业经验,历任核心研发工程师,项目技术负责人。
🎉欢迎 👍点赞?评论?收藏
文章目录
- 🔎 一、Java基础知识文集(1)
- 🍁🍁 01. JDK 和 JRE 有什么区别?
- 🍁🍁 02. JDK1.7 与 JDK1.8 的区别?
- 🍁🍁 03. 两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?
- 🍁🍁 04. Java 中的 Math. round(-1. 5) 等于多少?
- 🍁🍁 05. String str="i"与 String str=new String("i")一样吗?
- 🍁🍁 06. String 属于基础的数据类型吗?
- 🍁🍁 07. String 类的常用方法都有那些?
- 🍁🍁 08. Java 中操作字符串都有哪些类?它们之间有什么区别?
- 🍁🍁 09. final 在 Java 中有什么作用?
- 🍁🍁 10. == 和 equals 的区别是什么?
- 🍁🍁 11. 如何将字符串反转?
- 🍁🍁 12. 抽象类必须要有抽象方法吗?
- 🍁🍁 13. 普通类和抽象类有哪些区别?
- 🍁🍁 14. 抽象类能使用 final 修饰吗?
- 🍁🍁 15. 接口和抽象类有什么区别?
- 🍁🍁 16. BIO、NIO、AIO 有什么区别?
- 🍁🍁 17. Files的常用方法都有哪些?
- 🍁🍁 18. final 在 java 中有什么作用?
- 🍁🍁 19. 如何判断 List 集合是否为空?
- 🍁🍁 20. Java 中 IO 流分为几种?举例说明?
🔎 一、Java基础知识文集(1)
🍁🍁 01. JDK 和 JRE 有什么区别?
JDK(Java Development Kit)和JRE(Java Runtime Environment)是Java开发中常见的两个术语,它们之间有以下区别:
JDK包含了完整的Java开发工具集,用于开发和编译Java程序。它包括了JRE的所有内容,并且还包含了Java编译器(javac)、调试工具(jdb)、Java文档生成工具(javadoc)等各种开发工具。对于开发者来说,安装JDK是必要的,因为它提供了开发所需的所有工具和库。
JRE是Java应用程序的运行环境,用户在运行Java应用程序时需要安装JRE。它包含了Java虚拟机(JVM)、Java类库和其他运行Java程序所需的基础组件。JRE不包含开发工具,只提供了运行Java程序的功能。
简而言之,JDK面向Java开发者,提供了开发、编译和调试工具,而JRE面向Java应用程序的用户,提供了Java程序的运行环境。
在开发Java应用程序时,开发者需要先安装JDK,并使用其中的编译器(javac)将Java源代码编译为字节码(.class文件)。然后,用户可以在安装了JRE的计算机上运行这些已编译的Java应用程序。
需要注意的是,JRE是JDK的一个子集,也就是说JDK中包含了JRE的所有内容。因此,如果只需要运行Java程序而不进行开发,则只需安装JRE即可。但如果想要进行Java应用程序的开发,则需要安装JDK。
特性 | JDK | JRE |
---|---|---|
包含内容 | 包含完整的Java开发工具集 | 包含Java运行时环境及基础组件 |
面向对象 | 面向Java应用程序的开发者 | 面向Java应用程序的用户 |
包含工具 | 包括编译器、调试工具、文档生成工具等 | 仅包含Java虚拟机和基础类库 |
安装需求 | 需要安装JDK进行Java程序的开发与编译 | 需要安装JRE进行Java程序的运行 |
关系 | JDK包含JRE,JRE是JDK的一个子集 | JRE是JDK的一个子集 |
以上表格总结了JDK和JRE之间的区别,包括其包含内容、面向对象、包含工具、安装需求和它们之间的关系。这些区别表明了JDK主要面向Java的开发者,而JRE主要面向Java应用程序的最终用户。
🍁🍁 02. JDK1.7 与 JDK1.8 的区别?
JDK 1.7和JDK 1.8是Java平台的两个主要版本,它们之间有一些显著的区别:
-
Lambda 表达式和函数式接口:JDK 1.8引入了Lambda表达式和函数式接口的支持,使得编写函数式风格的代码更加简洁和方便。这为并发编程和集合操作带来了很大的改进。
-
Stream API:JDK 1.8引入了Stream API,这是对集合框架的增强,让我们可以通过流式操作来对集合进行处理,例如过滤、映射、归约等,使得集合操作变得更加简洁和灵活。
-
接口的默认方法和静态方法:JDK 1.8允许在接口中定义默认方法和静态方法,这使得接口在evolution过程中更加灵活,可以向已有的接口中添加新的方法,而不会破坏实现该接口的类。
-
类型注解:JDK 1.8引入了类型注解,允许在使用类型时添加注解,这为编写更加安全和动态的代码提供了更多的可能性。
-
新的日期和时间API:JDK 1.8引入了新的日期和时间API,包括java.time包,提供了更好的日期和时间处理支持,使得在处理日期时间上更加方便和安全。
-
PermGen空间移除:JDK 1.8移除了永久代(PermGen),取而代之的是使用元数据区(元空间),这样可以避免了一些常见的性能问题和内存溢出异常。
这些是JDK 1.7和JDK 1.8之间的一些主要区别。总体来说,JDK 1.8引入了许多重要的新特性和改进,使得Java编程更加现代化、灵活和高效。
特性 | JDK 1.7 | JDK 1.8 |
---|---|---|
Lambda 表达式和函数式接口 | 不支持 | 支持 |
Stream API | 不支持 | 支持 |
接口的默认方法和静态方法 | 不支持 | 支持 |
类型注解 | 不支持 | 支持 |
新的日期和时间API | 有限的java.util.Date和java.util.Calendar类 | 引入java.time包,提供更现代的日期和时间API |
PermGen空间移除 | 有PermGen空间 | 移除PermGen空间,使用元数据区(元空间) |
以上表格总结了JDK 1.7和JDK 1.8之间的区别,包括Lambda表达式和函数式接口、Stream API、接口的默认方法和静态方法、类型注解、新的日期和时间API以及PermGen空间移除等方面的差异。这些改进使得JDK 1.8相比JDK 1.7拥有更多的现代化特性和性能优势。
🍁🍁 03. 两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?
在 Java 中,如果两个对象的 hashCode()
返回值相同,equals()
方法不一定为 true。这是因为 hashCode()
方法用于计算对象的哈希码,而 equals()
方法用于判断两个对象是否相等。
举个例子来说明:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
return 1; // 简化起见,假设所有对象的 hashCode 都相同
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Person person = (Person) obj;
return age == person.age && Objects.equals(name, person.name);
}
}
在这个例子中,尽管两个不同的 Person
对象的 hashCode()
都返回相同的值(这里都返回 1),但它们的 equals()
方法会根据它们的实际内容来判断相等性,因此并不一定相等。
因此,可以得出结论:hashCode() 相同,并不代表equals() 一定为 true。
🍁🍁 04. Java 中的 Math. round(-1. 5) 等于多少?
Java 中的 Math.round(-1.5)
结果为 -1。这是因为 Math.round()
方法会对传入的浮点数进行四舍五入操作。
对于负数,Math.round()
方法会遵循以下规则:如果小数部分大于或等于 0.5,向绝对值更大的整数方向进行舍入;如果小数部分小于 0.5,向绝对值更小的整数方向进行舍入。
在这个例子中,-1.5 的小数部分为 0.5,因此根据规则,会向绝对值更大的整数方向进行舍入,即 -1。
所以,Math.round(-1.5)
的结果是 -1。
🍁🍁 05. String str="i"与 String str=new String(“i”)一样吗?
在Java中,String str = "i"
和String str = new String("i")
是不完全相同的。
String str = "i"
是使用字符串字面量直接赋值的方式创建字符串对象。在Java中,字符串字面量的使用会被编译器优化,在内存中创建一个字符串常量(String Pool),如果已经存在相同内容的字符串,则直接引用该常量,而不会创建新的对象。因此,如果之前已经有相同内容的字符串"i"存在于字符串常量池中,那么String str = "i"
会直接指向该常量。
String str = new String("i")
是使用new
关键字显式地创建一个新的字符串对象,无论字符串常量池中是否存在相同内容的字符串。即使字符串常量池中已经存在了字符串"i",new String("i")
仍然会创建一个新的字符串对象。
所以,虽然在某些情况下,String str = "i"
和String str = new String("i")
的结果相同,都表示字符串"i",但实际上它们是两种不同的方式创建字符串对象,并在内存中的位置和使用方式上有所区别。
当使用String str = "i"
时,如果之前已经有相同内容的字符串"i"存在于字符串常量池中,那么str
会直接指向该字符串常量,而不会创建新的对象。这意味着如果其他地方也使用了相同的字符串字面量"i",那么它们实际上都引用的是同一个字符串对象。
而使用String str = new String("i")
时,无论字符串常量池中是否存在相同内容的字符串"i",都会创建一个新的字符串对象,即使内容相同,它们在内存中也是不同的对象。
因此,String str = "i"
和String str = new String("i")
的主要区别在于内存中创建的方式和位置不同,前者优先在字符串常量池中查找并引用已存在的字符串常量,而后者则以显式方式创建新的字符串对象。
🍁🍁 06. String 属于基础的数据类型吗?
在Java中,String
不是基础的数据类型。基础的数据类型包括int
、double
、boolean
等,而String
是一种引用数据类型(也称为类类型)。
引用数据类型是指由用户定义的类或Java提供的类,它们在内存中存储的是对象的引用(地址),而不是对象的实际内容。而基础的数据类型直接在内存中存储值本身。
String
是Java提供的字符串类,它表示一串字符序列。在Java中,字符串是不可变的,即创建后不能被修改。当我们创建一个字符串对象时,实际上是在内存中分配了一块空间来存储该字符串的字符序列,并提供了一系列方法来操作和访问这个字符串。由于String
是一个类,我们可以使用它的方法来进行字符串的操作,如连接、截取、查找等。
因此,String
不是基础的数据类型,而是属于引用数据类型。
🍁🍁 07. String 类的常用方法都有那些?
String
类在Java中有很多常用的方法,以下是其中一些常用的方法和它们的用法示例:
-
length()
: 用于获取字符串的长度。String str = "Hello"; int len = str.length(); // len 的值为 5
-
charAt(int index)
: 用于获取字符串中指定位置的字符。String str = "Hello"; char ch = str.charAt(1); // ch 的值为 'e'
-
substring(int beginIndex) / substring(int beginIndex, int endIndex)
: 用于获取字符串的子串。String str = "Hello"; String sub1 = str.substring(2); // sub1 的值为 "llo" String sub2 = str.substring(1, 3); // sub2 的值为 "el"
-
indexOf(String str) / indexOf(String str, int fromIndex)
: 用于查找指定字符串在原字符串中首次出现的位置索引。String str = "Hello"; int index1 = str.indexOf("l"); // index1 的值为 2 int index2 = str.indexOf("l", 3); // index2 的值为 3
-
toUpperCase() / toLowerCase()
: 用于将字符串转换为全大写或全小写。String str = "Hello"; String upper = str.toUpperCase(); // upper 的值为 "HELLO" String lower = str.toLowerCase(); // lower 的值为 "hello"
-
trim()
: 用于去除字符串两端的空白字符。String str = " Hello "; String trimmed = str.trim(); // trimmed 的值为 "Hello"
-
replace(CharSequence target, CharSequence replacement)
: 用于替换字符串中的指定字符或子串。String str = "Hello, world!"; String replaced = str.replace("o", "0"); // replaced 的值为 "Hell0, w0rld!"
以上是String
类的一些常用方法及其示例,String
类还有很多其他方法可以用来处理字符串,如拼接、判断开头结尾、分割等。
🍁🍁 08. Java 中操作字符串都有哪些类?它们之间有什么区别?
在Java中,常用的用于操作字符串的类有String
、StringBuilder
和StringBuffer
。以下是它们之间的区别的详细比较表格:
类名 | 可变性 | 线程安全性 | 性能 | 举例 |
---|---|---|---|---|
String | 不可变 | 线程安全 | 一般 | String str = “Hello”; |
StringBuilder | 可变 | 非线程安全 | 较高 | StringBuilder sb = new StringBuilder(“Hello”); |
StringBuffer | 可变 | 线程安全 | 较低 | StringBuffer sbf = new StringBuffer(“Hello”); |
可变性:
String
是不可变的。一旦创建了String
对象,就不能修改它的值。StringBuilder
和StringBuffer
是可变的。它们内部的字符序列可以进行修改。
线程安全性:
String
是线程安全的。由于不可变性,String
对象可以被多个线程安全地共享,不需要额外的同步措施。StringBuilder
是非线程安全的。StringBuilder
的方法没有进行同步,如果多个线程同时访问它,需要额外的同步措施。StringBuffer
是线程安全的。StringBuffer
的方法都是同步的,可以在多线程环境下安全使用。
性能:
- 由于
String
对象的不可变性,每次对String
进行修改操作时都会创建一个新的String
对象,会产生大量的临时对象,对性能有一定的影响。 StringBuilder
和StringBuffer
是可变的,可以直接对它们的内部字符序列进行修改,避免了创建新的对象,因此性能较高。StringBuilder
相对于StringBuffer
具有更好的性能,因为不需要同步措施,但它不是线程安全的。
根据需求的不同选择适合的类进行字符串操作:
- 如果字符串是不经常改变的,并且在多线程环境下使用,可以选择使用
String
或StringBuffer
。 - 如果字符串需要频繁进行修改,并且在单线程环境下使用,可以选择使用
StringBuilder
。
举例说明其使用方式:
// 使用String
String str = "Hello";
str = str + " World";
// 使用StringBuilder
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");
// 使用StringBuffer
StringBuffer sbf = new StringBuffer("Hello");
sbf.append(" World");
在上面的示例中,可以看到使用String
和StringBuilder
来拼接字符串的方式有所不同。对于String
来说,每次操作都会创建一个新的String
对象,而StringBuilder
允许我们直接修改其内部的字符序列,避免了额外的对象创建,因此在大量字符串拼接的情况下,使用StringBuilder
会更有效率。
总之,根据具体的需求和情况选择合适的字符串操作类是很重要的。如果需要进行大量的字符串拼接操作,特别是在单线程环境下,推荐使用StringBuilder
;如果涉及多线程环境,则应该选择StringBuffer
。而对于不经常改变的字符串,可以考虑使用String
类。
🍁🍁 09. final 在 Java 中有什么作用?
在Java中,final
关键字有以下几种作用:
- 修饰类:当
final
修饰一个类时,表示该类是最终类,不能被继承。
final class FinalClass {
// 类的内容
}
- 修饰方法:当
final
修饰一个方法时,表示该方法是最终方法,子类不能覆盖重写这个方法。
class BaseClass {
final void finalMethod() {
// 方法的内容
}
}
- 修饰变量:当
final
修饰一个变量时,表示该变量是一个常量,只能被赋值一次,之后不能再改变其值。
final int CONSTANT_VALUE = 100;
final
关键字的作用在于提供了更严格的约束,能够确保类、方法或变量在程序中的使用符合特定的需求。通过使用final
可以有效地防止类被继承、方法被重写和变量被修改,从而增强了程序的可靠性和安全性。
举例说明:
// final修饰类的例子
final class FinalClass {
// 类的内容
}
// final修饰方法的例子
class BaseClass {
final void finalMethod() {
// 方法的内容
}
}
// final修饰变量的例子
class Example {
final int CONSTANT_VALUE = 100;
void modifyConstant() {
// 尝试修改常量值,会导致编译错误
// CONSTANT_VALUE = 200;
}
}
在上面的例子中,可以看到final
关键字的作用。FinalClass
被标记为final
,因此不能被其他类继承;finalMethod
被标记为final
,因此不能被子类重写;CONSTANT_VALUE
被标记为final
,因此不能被再次赋值。
🍁🍁 10. == 和 equals 的区别是什么?
在Java中,==
和equals()
是用于比较对象的操作符,但它们在比较对象时有着不同的行为和区别。
==
比较运算符:
- 在比较基本数据类型时,例如
int
、float
、boolean
等,==
比较的是它们的值是否相等。 - 在比较引用类型时,
==
比较的是对象引用的地址,即比较两个对象是否指向同一个内存地址。
equals()
方法:
equals()
是一个定义在Object
类中的方法,用于比较两个对象的内容是否相等,默认实现是比较两个对象的引用是否相等,与==
操作符的效果相同。- 对于许多类,例如
String
、Integer
等,它们会重写equals()
方法来比较对象的内容是否相等。重写后的equals()
方法可以根据对象内部的属性值来确定对象是否相等。
下面是一个示例来说明==
和equals()
的区别:
String str1 = "Hello";
String str2 = new String("Hello");
System.out.println(str1 == str2); // false
System.out.println(str1.equals(str2)); // true
在上面的示例中,str1
和str2
是两个不同的String
对象,它们的引用地址不同,因此str1 == str2
的结果是false
。但是,它们的内容是相同的,因此str1.equals(str2)
的结果是true
。这是因为String
类重写了equals()
方法,以比较字符串的内容而不是引用地址。
总结来说,==
用于比较基本数据类型的值或者比较对象的引用地址,equals()
用于比较对象的内容是否相等。对于大多数情况下,应该使用equals()
来比较对象的内容。但是需要注意的是,当比较对象时,应该确保该类已经重写了equals()
方法来定义对象相等的判断规则。
🍁🍁 11. 如何将字符串反转?
在Java中,有多种方法可以将字符串进行反转。下面列举了一些常见的方法:
-
使用
StringBuilder
(推荐):String original = "Hello"; StringBuilder reversed = new StringBuilder(original).reverse(); String result = reversed.toString(); System.out.println(result); // 输出 "olleH"
-
使用字符数组:
String original = "Hello"; char[] chars = original.toCharArray(); int left = 0; int right = chars.length - 1; while (left < right) { char temp = chars[left]; chars[left] = chars[right]; chars[right] = temp; left++; right--; } String result = new String(chars); System.out.println(result); // 输出 "olleH"
-
使用递归:
public static String reverseString(String str) { if (str.isEmpty()) { return str; } return reverseString(str.substring(1)) + str.charAt(0); } String original = "Hello"; String result = reverseString(original); System.out.println(result); // 输出 "olleH"
无论使用哪种方法,最终都可以得到字符串的反转形式。选择合适的方法取决于具体的需求和性能要求。其中,使用 StringBuilder
是一种简便且性能高效的方法,特别适用于频繁操作字符串的场景。而使用字符数组或递归则提供了其他一些灵活的思路和方式。
🍁🍁 12. 抽象类必须要有抽象方法吗?
不一定,抽象类并不一定非要包含抽象方法。抽象类可以包含抽象方法,也可以包含非抽象方法,甚至可以不包含任何抽象方法。
下面是一个不包含抽象方法的抽象类的示例:
public abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void makeSound() {
System.out.println("The animal makes a sound");
}
}
在这个示例中,Animal
类被声明为抽象类,但它并不包含任何抽象方法。它包含一个构造函数和一些普通的方法,包括一个普通的方法makeSound()
来模拟动物的叫声。因此,可以看出抽象类并非需要包含抽象方法。
抽象方法的存在使得抽象类具有更灵活的特性,可以将一些方法的实现交给其具体的子类去完成,同时抽象类也可以包含普通的方法和字段。这样的设计让抽象类更具通用性,能够为其子类定义一些通用的行为和属性。
🍁🍁 13. 普通类和抽象类有哪些区别?
普通类(非抽象类)和抽象类之间有几个关键的区别,它们如下:
-
实例化:普通类可以被实例化,而抽象类不能直接被实例化。也就是说,你可以创建普通类的对象,但不能创建抽象类的对象。
-
方法:普通类中的所有方法都有具体的实现,而抽象类中可以包含抽象方法(没有具体实现,只有方法签名),同时也可以包含具体方法。子类必须实现抽象类中的抽象方法,但对于普通类则没有这个要求。
-
继承:一个类只能继承一个普通类,但是可以实现多个接口。而对于抽象类来说,一个类可以继承一个抽象类,并且在Java中,类只能单继承,因此它也遵循这一规则。
-
设计目的:普通类用于具体的对象创建,而抽象类更多用于作为子类的模板,提供一些通用的方法和行为,以便子类继承或实现。
基本上,抽象类是对一组相关的类的抽象,其中可能包含一些通用的方法,而普通类则是用来创建对象和表示具体的实体。因此,选择使用普通类还是抽象类,取决于你的设计需求和模型的抽象程度。
🍁🍁 14. 抽象类能使用 final 修饰吗?
在Java中,抽象类是可以使用 final
修饰的。
使用 final
关键字修饰一个抽象类表示该类不能被继承。也就是说,如果一个抽象类被声明为 final
,则不能再有子类来继承它。这通常用于防止对抽象类的进一步扩展和修改。
示例代码如下:
final abstract class AbstractClass {
// Class members and methods
}
在这个示例中,AbstractClass
被声明为 final abstract
,表示该抽象类是最终的,不能再有子类来继承它。这样其他类就不能继承这个抽象类。
需要注意的是,虽然抽象类可以被声明为 final
,但抽象方法不能被同时声明为 final
和 abstract
,因为 final
表示方法不能被重写,而 abstract
表示方法必须被重写。这两者是冲突的。
总之,抽象类可以使用 final
修饰符来限制它的继承性,但需要注意不能将 final
和 abstract
同时用于同一个方法。
🍁🍁 15. 接口和抽象类有什么区别?
接口和抽象类是面向对象编程中两种不同的机制,它们有一些重要的区别,主要体现在以下几个方面:
-
方法实现:
- 抽象类可以包含具体方法的实现,而接口只能包含方法的声明而没有方法的实现。
- 在 Java 8 之后,接口也可以包含默认方法的实现,但默认方法并非强制要求实现类重写,而抽象类中的方法可以选择性地由子类重写。
-
继承与实现:
- 类可以实现多个接口,但只能继承一个抽象类。
- 接口之间可以通过扩展(extends)来建立继承关系,一个接口可以继承多个其他接口,而抽象类只能继承一个类或抽象类。
-
构造函数:
- 抽象类可以有构造函数,而接口不能拥有构造函数。因为接口是对行为的抽象,而不是对对象的抽象。
-
成员变量:
- 接口中的成员变量默认会被设置为 public static final 类型,即常量;而抽象类中可以包含各种类型的成员变量。
-
设计目的:
- 接口主要用于定义类之间的接口,强调的是对行为的抽象;而抽象类更多地用于作为子类的模板,提供一些通用的方法和行为,强调的是对对象的抽象。
总之,抽象类和接口在使用上有一些细微的差别,具体取决于设计的需求和模型的抽象程度。通常情况下,应该根据具体的情况来选择接口或抽象类,或者两者结合使用,以便更好地实现面向对象的设计原则。
以下是关于接口和抽象类区别,用表格列举如下:
区别 | 接口 | 抽象类 |
---|---|---|
方法实现 | 只能包含方法的声明,无方法实现。 | 可以包含具体方法的实现。 |
多继承 | 支持多继承,可以实现多个接口。 | 只能通过单继承,继承一个抽象类。 |
构造函数 | 无法拥有构造函数。 | 可以拥有构造函数。 |
成员变量 | 默认为 public static final 类型,即常量。 | 可以包含各种类型的成员变量。 |
设计目的 | 主要用于定义类之间的接口,强调对行为的抽象。 | 主要用于作为子类的模板,提供通用方法和行为,强调对对象的抽象。 |
强制实现 | 实现接口时,必须实现接口中的所有方法。 | 可以选择性实现抽象类中的方法。 |
构造函数 | 接口中不能定义构造函数。 | 抽象类中可以定义构造函数。 |
实例化 | 无法直接实例化接口。 | 无法直接实例化抽象类。 |
扩展性 | 可以通过接口的扩展来添加新的方法。 | 可以通过继承抽象类来添加新的方法和属性。 |
这个表格总结了接口和抽象类在各个方面的区别,包括方法实现、多继承、构造函数、成员变量、设计目的、强制实现、构造函数、实例化和扩展性。通过对比表格中的不同,你可以更清晰地了解接口和抽象类之间的区别和适用场景。
🍁🍁 16. BIO、NIO、AIO 有什么区别?
BIO(Blocking I/O)、NIO(Non-blocking I/O)和AIO(Asynchronous I/O)是三种不同的 I/O 模型,它们在处理 I/O 操作时有一些区别。
-
BIO:
- 也称为传统的阻塞式 I/O。
- 在进行 I/O 操作时,线程会被阻塞,直到操作完成。
- 每个连接都需要独立的线程进行处理,因此在高并发环境下会造成线程资源的浪费。
- 适用于连接数较少且对并发性要求不高的场景,如传统的客户端/服务器模型。
-
NIO:
- 也称为非阻塞式 I/O。
- 引入了 Channel(通道)和 Buffer(缓冲区)的概念。
- 可以通过一个线程处理多个连接,不再需要为每个连接创建单独的线程。
- 通过选择器(Selector)进行事件驱动,实现了非阻塞的 I/O 操作。
- 适用于高并发环境下的网络编程,如聊天室、多人在线游戏等。
-
AIO:
- 也称为异步非阻塞式 I/O。
- 在进行 I/O 操作时,不会阻塞线程,而是通过回调机制在操作完成后通知线程。
- 适用于高并发、大量连接的场景,如高性能服务器、实时音视频传输等。
以下是它们的主要区别总结:
模型 | 阻塞与非阻塞 | 并发性 | 可扩展性 | 应用场景 |
---|---|---|---|---|
BIO | 阻塞 | 低 | 低 | 传统的客户端/服务器模型,连接数少 |
NIO | 非阻塞 | 高 | 中 | 高并发环境,如聊天室、多人在线游戏等 |
AIO | 异步非阻塞 | 高 | 高 | 高并发、大量连接的场景 |
综上所述,BIO、NIO 和 AIO 分别适用于不同的 I/O 编程场景,你可以根据具体的需求选择合适的 I/O 模型。
🍁🍁 17. Files的常用方法都有哪些?
Java 中的 Files 类提供了许多用于操作文件系统的常用方法。以下是 Files 类的一些常用方法:
-
文件操作
createFile(Path path, FileAttribute<?>... attrs)
: 创建文件。copy(Path source, Path target, CopyOption... options)
: 复制文件或目录。move(Path source, Path target, CopyOption... options)
: 移动文件或目录。delete(Path path)
: 删除文件或目录。
-
目录操作
createDirectory(Path dir, FileAttribute<?>... attrs)
: 创建目录。walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor)
: 遍历文件树。list(Path dir)
: 列出目录中的文件和子目录。
-
文件属性
isDirectory(Path path, LinkOption... options)
: 判断是否为目录。isRegularFile(Path path, LinkOption... options)
: 判断是否为普通文件。isReadable(Path path)
: 判断文件是否可读。isWritable(Path path)
: 判断文件是否可写。
-
文件读写
readAllLines(Path path, Charset cs)
: 读取文件的所有行。write(Path path, byte[] bytes, OpenOption... options)
: 写入字节数组到文件。readAllBytes(Path path)
: 读取文件的所有字节。
-
文件信息
size(Path path)
: 获取文件大小。getLastModifiedTime(Path path, LinkOption... options)
: 获取文件最后修改时间。
-
其他
exists(Path path, LinkOption... options)
: 判断文件是否存在。isSameFile(Path path, Path path2)
: 判断两个路径是否指向同一文件。
这些方法涵盖了 Java Files 类中一些常用的文件和目录操作。使用这些方法可以进行文件的创建、复制、移动、删除,目录的创建、遍历以及文件属性的获取等操作。根据具体的需求,你可以选择合适的方法来操作文件系统。
🍁🍁 18. final 在 java 中有什么作用?
在 Java 中,final
关键字有多种作用,主要用于修饰类、方法和变量,其作用如下:
-
修饰类:
- 当一个类被声明为
final
时,表示该类不可被继承,即不能有子类。 - 例如:
public final class MyClass { ... }
- 当一个类被声明为
-
修饰方法:
- 当一个方法被声明为
final
时,表示该方法不可被子类重写(覆盖)。 - 例如:
public final void myMethod() { ... }
- 当一个方法被声明为
-
修饰变量:
- 当一个变量被声明为
final
时,表示该变量的值只能被赋值一次,即成为一个常量。一旦被赋值后,就不能再更改。 - 对于基本类型,其数值不能改变;对于对象引用,不能再指向另一个对象,但对象自身的状态是可以改变的。
- 例如:
final int x = 10;
或final MyClass obj = new MyClass();
- 当一个变量被声明为
final
的作用主要是为了实现不可改变性(immutability)和安全性,可以用于确保类、方法或变量的稳定性和安全性。在设计中如果需要避免子类对某个方法或变量的修改,或者确保变量值的稳定性,就可以使用 final
关键字。
🍁🍁 19. 如何判断 List 集合是否为空?
在 Java 中,可以使用以下方法来判断 List 集合是否为空:
-
使用 size() 方法
- 通过调用 List 的
size()
方法,判断 List 的大小是否为 0。 - 如果 List 的大小为 0,即为空集合;否则,不为空集合。
- 例如:
List<String> myList = new ArrayList<>(); if (myList.size() == 0) { System.out.println("List is empty"); } else { System.out.println("List is not empty"); }
- 通过调用 List 的
-
使用 isEmpty() 方法
- List 接口提供了一个
isEmpty()
方法,用于判断集合是否为空。 - 如果 List 为空,则返回 true;否则,返回 false。
- 例如:
List<String> myList = new ArrayList<>(); if (myList.isEmpty()) { System.out.println("List is empty"); } else { System.out.println("List is not empty"); }
- List 接口提供了一个
无论是使用 size()
方法还是 isEmpty()
方法,都可以有效地判断 List 集合是否为空。推荐使用 isEmpty()
方法,因为它更加简洁和语义明确。
🍁🍁 20. Java 中 IO 流分为几种?举例说明?
在Java中,IO流主要分为以下两种类型:
-
字节流(Byte Streams):
- 字节流以字节为单位进行读取和写入操作,主要用于处理二进制数据或者字节流形式的文本数据。
- 字节流类主要位于
java.io
包中,并且以InputStream
和OutputStream
为核心类。 - 示例:
InputStream inputStream = new FileInputStream("example.txt"); int data = inputStream.read(); while (data != -1) { // 处理数据 System.out.print((char) data); data = inputStream.read(); } inputStream.close(); OutputStream outputStream = new FileOutputStream("example.txt"); outputStream.write("Hello, World!".getBytes()); outputStream.close();
-
字符流(Character Streams):
- 字符流以字符为单位进行读取和写入操作,主要用于处理文本数据。
- 字符流类主要位于
java.io
包中,并且以Reader
和Writer
为核心类。 - 示例:
Reader reader = new FileReader("example.txt"); int data = reader.read(); while (data != -1) { // 处理数据 System.out.print((char) data); data = reader.read(); } reader.close(); Writer writer = new FileWriter("example.txt"); writer.write("Hello, World!"); writer.close();
无论是字节流还是字符流,它们均可通过输入流(InputStream、Reader)进行数据读取,通过输出流(OutputStream、Writer)进行数据写入。根据实际的需求,选择适当的流进行操作。在使用 IO 流时,要注意及时关闭流对象,以避免资源泄露。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!