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平台的两个主要版本,它们之间有一些显著的区别:

  1. Lambda 表达式和函数式接口:JDK 1.8引入了Lambda表达式和函数式接口的支持,使得编写函数式风格的代码更加简洁和方便。这为并发编程和集合操作带来了很大的改进。

  2. Stream API:JDK 1.8引入了Stream API,这是对集合框架的增强,让我们可以通过流式操作来对集合进行处理,例如过滤、映射、归约等,使得集合操作变得更加简洁和灵活。

  3. 接口的默认方法和静态方法:JDK 1.8允许在接口中定义默认方法和静态方法,这使得接口在evolution过程中更加灵活,可以向已有的接口中添加新的方法,而不会破坏实现该接口的类。

  4. 类型注解:JDK 1.8引入了类型注解,允许在使用类型时添加注解,这为编写更加安全和动态的代码提供了更多的可能性。

  5. 新的日期和时间API:JDK 1.8引入了新的日期和时间API,包括java.time包,提供了更好的日期和时间处理支持,使得在处理日期时间上更加方便和安全。

  6. 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不是基础的数据类型。基础的数据类型包括intdoubleboolean等,而String是一种引用数据类型(也称为类类型)。

引用数据类型是指由用户定义的类或Java提供的类,它们在内存中存储的是对象的引用(地址),而不是对象的实际内容。而基础的数据类型直接在内存中存储值本身。

String是Java提供的字符串类,它表示一串字符序列。在Java中,字符串是不可变的,即创建后不能被修改。当我们创建一个字符串对象时,实际上是在内存中分配了一块空间来存储该字符串的字符序列,并提供了一系列方法来操作和访问这个字符串。由于String是一个类,我们可以使用它的方法来进行字符串的操作,如连接、截取、查找等。

因此,String不是基础的数据类型,而是属于引用数据类型。

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

String类在Java中有很多常用的方法,以下是其中一些常用的方法和它们的用法示例:

  1. length(): 用于获取字符串的长度。

    String str = "Hello";
    int len = str.length(); // len 的值为 5
    
  2. charAt(int index): 用于获取字符串中指定位置的字符。

    String str = "Hello";
    char ch = str.charAt(1); // ch 的值为 'e'
    
  3. 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"
    
  4. 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
    
  5. toUpperCase() / toLowerCase(): 用于将字符串转换为全大写或全小写。

    String str = "Hello";
    String upper = str.toUpperCase(); // upper 的值为 "HELLO"
    String lower = str.toLowerCase(); // lower 的值为 "hello"
    
  6. trim(): 用于去除字符串两端的空白字符。

    String str = "  Hello  ";
    String trimmed = str.trim(); // trimmed 的值为 "Hello"
    
  7. replace(CharSequence target, CharSequence replacement): 用于替换字符串中的指定字符或子串。

    String str = "Hello, world!";
    String replaced = str.replace("o", "0"); // replaced 的值为 "Hell0, w0rld!"
    

以上是String类的一些常用方法及其示例,String类还有很多其他方法可以用来处理字符串,如拼接、判断开头结尾、分割等。

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

在Java中,常用的用于操作字符串的类有StringStringBuilderStringBuffer。以下是它们之间的区别的详细比较表格:

类名 可变性 线程安全性 性能 举例
String 不可变 线程安全 一般 String str = “Hello”;
StringBuilder 可变 非线程安全 较高 StringBuilder sb = new StringBuilder(“Hello”);
StringBuffer 可变 线程安全 较低 StringBuffer sbf = new StringBuffer(“Hello”);

可变性

  • String是不可变的。一旦创建了String对象,就不能修改它的值。
  • StringBuilderStringBuffer是可变的。它们内部的字符序列可以进行修改。

线程安全性

  • String是线程安全的。由于不可变性,String对象可以被多个线程安全地共享,不需要额外的同步措施。
  • StringBuilder是非线程安全的。StringBuilder的方法没有进行同步,如果多个线程同时访问它,需要额外的同步措施。
  • StringBuffer是线程安全的。StringBuffer的方法都是同步的,可以在多线程环境下安全使用。

性能

  • 由于String对象的不可变性,每次对String进行修改操作时都会创建一个新的String对象,会产生大量的临时对象,对性能有一定的影响。
  • StringBuilderStringBuffer是可变的,可以直接对它们的内部字符序列进行修改,避免了创建新的对象,因此性能较高。
  • StringBuilder相对于StringBuffer具有更好的性能,因为不需要同步措施,但它不是线程安全的。

根据需求的不同选择适合的类进行字符串操作:

  • 如果字符串是不经常改变的,并且在多线程环境下使用,可以选择使用StringStringBuffer
  • 如果字符串需要频繁进行修改,并且在单线程环境下使用,可以选择使用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");

在上面的示例中,可以看到使用StringStringBuilder来拼接字符串的方式有所不同。对于String来说,每次操作都会创建一个新的String对象,而StringBuilder允许我们直接修改其内部的字符序列,避免了额外的对象创建,因此在大量字符串拼接的情况下,使用StringBuilder会更有效率。

总之,根据具体的需求和情况选择合适的字符串操作类是很重要的。如果需要进行大量的字符串拼接操作,特别是在单线程环境下,推荐使用StringBuilder;如果涉及多线程环境,则应该选择StringBuffer。而对于不经常改变的字符串,可以考虑使用String类。

09. final 在 Java 中有什么作用?

在Java中,final关键字有以下几种作用:

  1. 修饰类:当final修饰一个类时,表示该类是最终类,不能被继承。
final class FinalClass {
    // 类的内容
}
  1. 修饰方法:当final修饰一个方法时,表示该方法是最终方法,子类不能覆盖重写这个方法。
class BaseClass {
    final void finalMethod() {
        // 方法的内容
    }
}
  1. 修饰变量:当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()是用于比较对象的操作符,但它们在比较对象时有着不同的行为和区别。

==比较运算符

  • 在比较基本数据类型时,例如intfloatboolean等,==比较的是它们的值是否相等。
  • 在比较引用类型时,==比较的是对象引用的地址,即比较两个对象是否指向同一个内存地址。

equals()方法

  • equals()是一个定义在Object类中的方法,用于比较两个对象的内容是否相等,默认实现是比较两个对象的引用是否相等,与==操作符的效果相同。
  • 对于许多类,例如StringInteger等,它们会重写equals()方法来比较对象的内容是否相等。重写后的equals()方法可以根据对象内部的属性值来确定对象是否相等。

下面是一个示例来说明==equals()的区别:

String str1 = "Hello";
String str2 = new String("Hello");

System.out.println(str1 == str2);           // false
System.out.println(str1.equals(str2));      // true

在上面的示例中,str1str2是两个不同的String对象,它们的引用地址不同,因此str1 == str2的结果是false。但是,它们的内容是相同的,因此str1.equals(str2)的结果是true。这是因为String类重写了equals()方法,以比较字符串的内容而不是引用地址。

总结来说,==用于比较基本数据类型的值或者比较对象的引用地址,equals()用于比较对象的内容是否相等。对于大多数情况下,应该使用equals()来比较对象的内容。但是需要注意的是,当比较对象时,应该确保该类已经重写了equals()方法来定义对象相等的判断规则。

11. 如何将字符串反转?

在Java中,有多种方法可以将字符串进行反转。下面列举了一些常见的方法:

  1. 使用 StringBuilder(推荐):

    String original = "Hello";
    StringBuilder reversed = new StringBuilder(original).reverse();
    String result = reversed.toString();
    System.out.println(result);  // 输出 "olleH"
    
  2. 使用字符数组:

    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"
    
  3. 使用递归:

    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. 普通类和抽象类有哪些区别?

普通类(非抽象类)和抽象类之间有几个关键的区别,它们如下:

  1. 实例化:普通类可以被实例化,而抽象类不能直接被实例化。也就是说,你可以创建普通类的对象,但不能创建抽象类的对象。

  2. 方法:普通类中的所有方法都有具体的实现,而抽象类中可以包含抽象方法(没有具体实现,只有方法签名),同时也可以包含具体方法。子类必须实现抽象类中的抽象方法,但对于普通类则没有这个要求。

  3. 继承:一个类只能继承一个普通类,但是可以实现多个接口。而对于抽象类来说,一个类可以继承一个抽象类,并且在Java中,类只能单继承,因此它也遵循这一规则。

  4. 设计目的:普通类用于具体的对象创建,而抽象类更多用于作为子类的模板,提供一些通用的方法和行为,以便子类继承或实现。

基本上,抽象类是对一组相关的类的抽象,其中可能包含一些通用的方法,而普通类则是用来创建对象和表示具体的实体。因此,选择使用普通类还是抽象类,取决于你的设计需求和模型的抽象程度。

14. 抽象类能使用 final 修饰吗?

在Java中,抽象类是可以使用 final 修饰的。

使用 final 关键字修饰一个抽象类表示该类不能被继承。也就是说,如果一个抽象类被声明为 final,则不能再有子类来继承它。这通常用于防止对抽象类的进一步扩展和修改。

示例代码如下:

final abstract class AbstractClass {
    // Class members and methods
}

在这个示例中,AbstractClass 被声明为 final abstract,表示该抽象类是最终的,不能再有子类来继承它。这样其他类就不能继承这个抽象类。

需要注意的是,虽然抽象类可以被声明为 final,但抽象方法不能被同时声明为 finalabstract,因为 final 表示方法不能被重写,而 abstract 表示方法必须被重写。这两者是冲突的。

总之,抽象类可以使用 final 修饰符来限制它的继承性,但需要注意不能将 finalabstract 同时用于同一个方法。

15. 接口和抽象类有什么区别?

接口和抽象类是面向对象编程中两种不同的机制,它们有一些重要的区别,主要体现在以下几个方面:

  1. 方法实现

    • 抽象类可以包含具体方法的实现,而接口只能包含方法的声明而没有方法的实现。
    • 在 Java 8 之后,接口也可以包含默认方法的实现,但默认方法并非强制要求实现类重写,而抽象类中的方法可以选择性地由子类重写。
  2. 继承与实现

    • 类可以实现多个接口,但只能继承一个抽象类。
    • 接口之间可以通过扩展(extends)来建立继承关系,一个接口可以继承多个其他接口,而抽象类只能继承一个类或抽象类。
  3. 构造函数

    • 抽象类可以有构造函数,而接口不能拥有构造函数。因为接口是对行为的抽象,而不是对对象的抽象。
  4. 成员变量

    • 接口中的成员变量默认会被设置为 public static final 类型,即常量;而抽象类中可以包含各种类型的成员变量。
  5. 设计目的

    • 接口主要用于定义类之间的接口,强调的是对行为的抽象;而抽象类更多地用于作为子类的模板,提供一些通用的方法和行为,强调的是对对象的抽象。

总之,抽象类和接口在使用上有一些细微的差别,具体取决于设计的需求和模型的抽象程度。通常情况下,应该根据具体的情况来选择接口或抽象类,或者两者结合使用,以便更好地实现面向对象的设计原则。

以下是关于接口和抽象类区别,用表格列举如下:

区别 接口 抽象类
方法实现 只能包含方法的声明,无方法实现。 可以包含具体方法的实现。
多继承 支持多继承,可以实现多个接口。 只能通过单继承,继承一个抽象类。
构造函数 无法拥有构造函数。 可以拥有构造函数。
成员变量 默认为 public static final 类型,即常量。 可以包含各种类型的成员变量。
设计目的 主要用于定义类之间的接口,强调对行为的抽象。 主要用于作为子类的模板,提供通用方法和行为,强调对对象的抽象。
强制实现 实现接口时,必须实现接口中的所有方法。 可以选择性实现抽象类中的方法。
构造函数 接口中不能定义构造函数。 抽象类中可以定义构造函数。
实例化 无法直接实例化接口。 无法直接实例化抽象类。
扩展性 可以通过接口的扩展来添加新的方法。 可以通过继承抽象类来添加新的方法和属性。

这个表格总结了接口和抽象类在各个方面的区别,包括方法实现、多继承、构造函数、成员变量、设计目的、强制实现、构造函数、实例化和扩展性。通过对比表格中的不同,你可以更清晰地了解接口和抽象类之间的区别和适用场景。

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

BIO(Blocking I/O)、NIO(Non-blocking I/O)和AIO(Asynchronous I/O)是三种不同的 I/O 模型,它们在处理 I/O 操作时有一些区别。

  1. BIO

    • 也称为传统的阻塞式 I/O。
    • 在进行 I/O 操作时,线程会被阻塞,直到操作完成。
    • 每个连接都需要独立的线程进行处理,因此在高并发环境下会造成线程资源的浪费。
    • 适用于连接数较少且对并发性要求不高的场景,如传统的客户端/服务器模型。
  2. NIO

    • 也称为非阻塞式 I/O。
    • 引入了 Channel(通道)和 Buffer(缓冲区)的概念。
    • 可以通过一个线程处理多个连接,不再需要为每个连接创建单独的线程。
    • 通过选择器(Selector)进行事件驱动,实现了非阻塞的 I/O 操作。
    • 适用于高并发环境下的网络编程,如聊天室、多人在线游戏等。
  3. AIO

    • 也称为异步非阻塞式 I/O。
    • 在进行 I/O 操作时,不会阻塞线程,而是通过回调机制在操作完成后通知线程。
    • 适用于高并发、大量连接的场景,如高性能服务器、实时音视频传输等。

以下是它们的主要区别总结:

模型 阻塞与非阻塞 并发性 可扩展性 应用场景
BIO 阻塞 传统的客户端/服务器模型,连接数少
NIO 非阻塞 高并发环境,如聊天室、多人在线游戏等
AIO 异步非阻塞 高并发、大量连接的场景

综上所述,BIO、NIO 和 AIO 分别适用于不同的 I/O 编程场景,你可以根据具体的需求选择合适的 I/O 模型。

17. Files的常用方法都有哪些?

Java 中的 Files 类提供了许多用于操作文件系统的常用方法。以下是 Files 类的一些常用方法:

  1. 文件操作

    • createFile(Path path, FileAttribute... attrs): 创建文件。
    • copy(Path source, Path target, CopyOption... options): 复制文件或目录。
    • move(Path source, Path target, CopyOption... options): 移动文件或目录。
    • delete(Path path): 删除文件或目录。
  2. 目录操作

    • createDirectory(Path dir, FileAttribute... attrs): 创建目录。
    • walkFileTree(Path start, Set options, int maxDepth, FileVisitor visitor): 遍历文件树。
    • list(Path dir): 列出目录中的文件和子目录。
  3. 文件属性

    • isDirectory(Path path, LinkOption... options): 判断是否为目录。
    • isRegularFile(Path path, LinkOption... options): 判断是否为普通文件。
    • isReadable(Path path): 判断文件是否可读。
    • isWritable(Path path): 判断文件是否可写。
  4. 文件读写

    • readAllLines(Path path, Charset cs): 读取文件的所有行。
    • write(Path path, byte[] bytes, OpenOption... options): 写入字节数组到文件。
    • readAllBytes(Path path): 读取文件的所有字节。
  5. 文件信息

    • size(Path path): 获取文件大小。
    • getLastModifiedTime(Path path, LinkOption... options): 获取文件最后修改时间。
  6. 其他

    • exists(Path path, LinkOption... options): 判断文件是否存在。
    • isSameFile(Path path, Path path2): 判断两个路径是否指向同一文件。

这些方法涵盖了 Java Files 类中一些常用的文件和目录操作。使用这些方法可以进行文件的创建、复制、移动、删除,目录的创建、遍历以及文件属性的获取等操作。根据具体的需求,你可以选择合适的方法来操作文件系统。

18. final 在 java 中有什么作用?

在 Java 中,final 关键字有多种作用,主要用于修饰类、方法和变量,其作用如下:

  1. 修饰类

    • 当一个类被声明为 final 时,表示该类不可被继承,即不能有子类。
    • 例如:public final class MyClass { ... }
  2. 修饰方法

    • 当一个方法被声明为 final 时,表示该方法不可被子类重写(覆盖)。
    • 例如:public final void myMethod() { ... }
  3. 修饰变量

    • 当一个变量被声明为 final 时,表示该变量的值只能被赋值一次,即成为一个常量。一旦被赋值后,就不能再更改。
    • 对于基本类型,其数值不能改变;对于对象引用,不能再指向另一个对象,但对象自身的状态是可以改变的。
    • 例如:final int x = 10;final MyClass obj = new MyClass();

final 的作用主要是为了实现不可改变性(immutability)和安全性,可以用于确保类、方法或变量的稳定性和安全性。在设计中如果需要避免子类对某个方法或变量的修改,或者确保变量值的稳定性,就可以使用 final 关键字。

19. 如何判断 List 集合是否为空?

在 Java 中,可以使用以下方法来判断 List 集合是否为空:

  1. 使用 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");
      }
      
  2. 使用 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");
      }
      

无论是使用 size() 方法还是 isEmpty() 方法,都可以有效地判断 List 集合是否为空。推荐使用 isEmpty() 方法,因为它更加简洁和语义明确。

20. Java 中 IO 流分为几种?举例说明?

在Java中,IO流主要分为以下两种类型:

  1. 字节流(Byte Streams)

    • 字节流以字节为单位进行读取和写入操作,主要用于处理二进制数据或者字节流形式的文本数据。
    • 字节流类主要位于 java.io 包中,并且以 InputStreamOutputStream 为核心类。
    • 示例:
      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();
      
  2. 字符流(Character Streams)

    • 字符流以字符为单位进行读取和写入操作,主要用于处理文本数据。
    • 字符流类主要位于 java.io 包中,并且以 ReaderWriter 为核心类。
    • 示例:
      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 流时,要注意及时关闭流对象,以避免资源泄露。

Java基础知识学习,一文掌握Java基础知识文集。_第1张图片

你可能感兴趣的:(Java专栏,Java基础学习,多线程专栏,java,分布式,jdk,人工智能,算法,面试)