Java学习笔记-JavaSE高级

1. java中的集合

在Java中,集合(Collection)是一组对象的容器,用于存储和操作一组相关的元素。Java提供了丰富的集合框架(Collection Framework),包括接口和实现类,用于处理不同类型的集合数据。

Java的集合框架位于java.util包中,其中最常用的集合接口和类包括:

  1. List(列表):

    • List是有序的集合,允许元素重复
    • 常见的实现类有ArrayList、LinkedList、Vector等。
    • List提供了按索引访问、添加、删除和修改元素的方法。
  2. Set(集合):

    • Set是不允许重复元素的集合,不保证元素的顺序。
    • 常见的实现类有HashSet、TreeSet、LinkedHashSet等。
    • Set提供了高效的元素查找和去重的功能。
  3. Map(映射):

    • Map是键值对的集合,每个元素都包含一个键和对应的值。
    • 常见的实现类有HashMap、TreeMap、LinkedHashMap等。
    • Map提供了通过键快速查找值的功能。
  4. Queue(队列):

    • Queue是一种先进先出(FIFO)的集合,用于处理元素的排队和顺序操作。
    • 常见的实现类有LinkedList、PriorityQueue等。
    • Queue提供了添加、删除和检索元素的方法。

Java的集合框架还提供了其他接口和类,如Deque(双端队列)、Iterator(迭代器)、Collections(集合工具类)等,用于处理不同的集合需求。

2. 详细说说ArrayList

ArrayList是Java集合框架中的一个实现类,实现了List接口,用于存储一组有序的元素。ArrayList基于数组实现,具有动态大小的特性,可以根据需要自动调整容量。

以下是ArrayList的一些重要特点和常用方法:

  1. 动态大小:

    • ArrayList可以根据需要自动调整容量,可以动态添加和删除元素。
    • 在添加元素时,如果当前容量不足,ArrayList会自动增加其内部数组的大小。
  2. 有序集合:

    • ArrayList是一个有序集合,可以按照元素的插入顺序访问元素。
    • 可以通过索引访问和操作集合中的元素。
  3. 允许重复元素:

    • ArrayList允许存储重复的元素,每个元素都有唯一的索引值。
  4. 高效的随机访问:

    • 由于ArrayList基于数组实现,所以它提供了高效的随机访问能力。
    • 可以通过索引直接访问集合中的元素,时间复杂度为O(1)。
  5. 支持动态修改:

    • ArrayList提供了一系列的方法用于添加、删除、修改和查找元素。
    • 例如,add()方法用于在指定位置插入元素,remove()方法用于删除指定位置的元素,set()方法用于替换指定位置的元素。
  6. 迭代和遍历:

    • ArrayList实现了Iterable接口,可以使用增强的for循环或迭代器来遍历集合中的元素。

需要注意的是,由于ArrayList基于数组实现,在频繁的插入和删除操作时,可能会导致数组的扩容和元素的移动,从而影响性能。在需要频繁插入和删除操作的场景中,可以考虑使用LinkedList等其他集合类。

以下是一个示例代码,展示了ArrayList的基本用法:

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        // 创建一个ArrayList对象
        ArrayList<String> list = new ArrayList<>();

        // 添加元素
        list.add("Apple");
        list.add("Banana");
        list.add("Orange");

        // 获取元素
        System.out.println("第一个元素:" + list.get(0));

        // 修改元素
        list.set(2, "Grape");

        // 遍历元素
        System.out.println("遍历元素:");
        for (String fruit : list) {
            System.out.println(fruit);
        }

        // 删除元素
        list.remove(1);

        // 判断元素是否存在
        System.out.println("是否包含Grape:" + list.contains("Grape"));

        // 获取元素数量
        System.out.println("元素数量:" + list.size());
    }
}

3. ArrayList常用方法

  1. 添加元素:

    • add(E element):将指定元素添加到列表的末尾。
    • add(int index, E element):将指定元素插入到列表的指定位置。
  2. 获取元素:

    • get(int index):返回列表中指定位置的元素。
    • indexOf(Object element):返回元素在列表中首次出现的索引。
    • lastIndexOf(Object element):返回元素在列表中最后一次出现的索引。
  3. 删除元素:

    • remove(int index):移除列表中指定位置的元素。
    • remove(Object element):从列表中移除首次出现的指定元素。
  4. 修改元素:

    • set(int index, E element):将指定位置的元素替换为新元素。
  5. 列表信息:

    • size():返回列表中的元素数量。
    • isEmpty():检查列表是否为空。
    • contains(Object element):检查列表是否包含指定元素。
  6. 迭代和遍历:

    • iterator():返回一个迭代器,用于遍历列表中的元素。
    • 增强的for循环:通过迭代器或ArrayListforEach()方法遍历列表。
  7. 数组操作:

    • toArray():将列表转换为数组。
    • addAll(Collection<? extends E> c):将指定集合中的所有元素添加到列表的末尾。
  8. 清空列表:

    • clear():移除列表中的所有元素,使其为空。

4.  详细说说LinkedList

LinkedList是Java集合框架中的一个实现类,实现了List接口和Deque接口,用于存储一组有序的元素。与ArrayList不同,LinkedList基于双向链表实现,具有动态大小和高效的插入和删除操作。

以下是LinkedList的一些重要特点和常用方法:

  1. 双向链表结构:

    • LinkedList内部使用双向链表实现,每个节点包含元素和前后两个指针。
    • 双向链表的结构使得插入和删除操作更高效。
  2. 动态大小:

    • LinkedList可以根据需要自动调整容量,可以动态添加和删除元素。
    • 在添加或删除元素时,不需要像ArrayList那样进行数组的扩容或移动。
  3. 有序集合:

    • LinkedList是一个有序集合,可以按照元素的插入顺序访问元素。
    • 可以通过索引访问和操作集合中的元素。
  4. 高效的插入和删除操作:

    • 由于基于双向链表实现,LinkedList在插入和删除元素时具有较好的性能。
    • 在列表的开头或结尾进行插入和删除操作的时间复杂度为O(1),在中间位置的操作需要遍历链表,时间复杂度为O(n)。
  5. 迭代和遍历:

    • LinkedList实现了Iterable接口,可以使用增强的for循环或迭代器来遍历集合中的元素。
  6. 支持双端队列操作:

    • LinkedList实现了Deque接口,支持在队列的两端进行添加和删除操作。
    • 可以作为队列(先进先出)或栈(后进先出)使用。

需要注意的是,由于LinkedList是基于链表实现的,随机访问的性能较差,因为访问元素需要从头节点开始遍历链表。因此,如果需要频繁进行随机访问操作,ArrayList可能更适合。

以下是一个示例代码,展示了LinkedList的基本用法:

import java.util.LinkedList;

public class Main {
    public static void main(String[] args) {
        // 创建一个LinkedList对象
        LinkedList<String> list = new LinkedList<>();

        // 添加元素
        list.add("Apple");
        list.add("Banana");
        list.add("Orange");

        // 获取元素
        System.out.println("第一个元素:" + list.get(0));

        // 修改元素
        list.set(2, "Grape");

        // 遍历元素
        System.out.println("遍历元素:");
        for (String fruit : list) {
            System.out.println(fruit);
        }

        // 删除元素
        list.remove(1);

        // 判断元素是否存在
        System.out.println("是否包含Grape:" + list.contains("Grape"));

        // 获取元素数量
        System.out.println("元素数量:" + list.size());
    }
}

5. LinkedList常用方法

  1. 添加元素:

    • add(E element):将指定元素添加到列表的末尾。
    • addFirst(E element):将指定元素添加到列表的开头。
    • addLast(E element):将指定元素添加到列表的末尾。
  2. 获取元素:

    • getFirst():返回列表中的第一个元素。
    • getLast():返回列表中的最后一个元素。
  3. 删除元素:

    • remove():移除并返回列表的第一个元素。
    • remove(int index):移除并返回列表中指定位置的元素。
    • removeFirst():移除并返回列表的第一个元素。
    • removeLast():移除并返回列表的最后一个元素。
  4. 修改元素:

    • set(int index, E element):将指定位置的元素替换为新元素。
  5. 列表信息:

    • size():返回列表中的元素数量。
    • isEmpty():检查列表是否为空。
    • contains(Object element):检查列表是否包含指定元素。
  6. 队列操作:

    • offer(E element):将指定元素添加到队列的末尾。
    • poll():移除并返回队列的第一个元素。
  7. 栈操作:

    • push(E element):将指定元素推入栈顶。
    • pop():弹出并返回栈顶的元素。
  8. 迭代和遍历:

    • iterator():返回一个迭代器,用于遍历列表中的元素。
    • descendingIterator():返回一个逆向迭代器,用于逆向遍历列表中的元素。
  9. 清空列表:

    • clear():移除列表中的所有元素,使其为空。

6. 集合的遍历方式

      1. 增强的 for 循环:

  • 适用于所有实现了Iterable接口的集合类,如ArrayListLinkedListHashSet等。
  • 语法:for (元素类型 变量名 : 集合)
  • 示例:
    ArrayList<String> list = new ArrayList<>();
    list.add("Apple");
    list.add("Banana");
    list.add("Orange");
    
    for (String fruit : list) {
        System.out.println(fruit);
    }
    

    2. 迭代器(Iterator):

  • 适用于所有实现了Iterable接口的集合类,如ArrayListLinkedListHashSet等。
  • 通过调用集合的iterator()方法获取迭代器对象,使用hasNext()next()方法遍历集合元素。
  • 示例:
    ArrayList<String> list = new ArrayList<>();
    list.add("Apple");
    list.add("Banana");
    list.add("Orange");
    
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
        String fruit = iterator.next();
        System.out.println(fruit);
    }
    

    3. 普通 for 循环:

  • 适用于实现了List接口的有序集合类,如ArrayListLinkedList等。
  • 使用索引进行遍历,通过调用size()方法获取集合大小。
  • 示例:
    ArrayList<String> list = new ArrayList<>();
    list.add("Apple");
    list.add("Banana");
    list.add("Orange");
    
    for (int i = 0; i < list.size(); i++) {
        String fruit = list.get(i);
        System.out.println(fruit);
    }
    

    4. Java 8+ 的流式遍历(Stream API):

  • 适用于所有实现了Iterable接口的集合类,如ArrayListLinkedListHashSet等。
  • 使用流式编程风格,可以进行过滤、映射、排序等操作。

7. 说说泛型

泛型(Generics)是Java中的一个重要特性,它允许在定义类、接口和方法时使用参数化类型,从而提供类型安全和代码重用性。

泛型的主要目的是在编译时期强化类型检查,并提供更好的代码可读性和可维护性。使用泛型可以将类型作为参数传递给类、接口和方法,从而实现参数化的数据类型。

Java中的泛型有以下几个关键概念:

  1. 类型参数(Type Parameter):

    • 类型参数是在定义类、接口或方法时指定的占位符类型。
    • 使用尖括号<T>来声明一个类型参数,T可以是任意合法的标识符。
  2. 泛型类(Generic Class):

    • 泛型类是使用类型参数的类。
    • 类的定义中使用类型参数来代表实际的类型,可以在类的属性、方法、构造函数等地方使用类型参数。
  3. 泛型接口(Generic Interface):

    • 泛型接口是使用类型参数的接口。
    • 接口的定义中使用类型参数来代表实际的类型,可以在接口的方法定义中使用类型参数。
  4. 泛型方法(Generic Method):

    • 泛型方法是在方法定义中使用类型参数的方法。
    • 方法的返回类型、参数类型或局部变量类型可以使用类型参数。
  5. 类型通配符(Wildcard):

    • 类型通配符用于表示未知类型。
    • 使用通配符?来表示未知类型,可以用于泛型类的实例化、泛型方法的调用以及通配符上限和下限的限定。

通过使用泛型,可以实现以下几个重要的好处:

  1. 类型安全:

    • 泛型在编译时期进行类型检查,可以在编译时捕获类型错误,提供更好的类型安全性。
  2. 代码重用:

    • 泛型可以使代码更通用,可以在不同类型之间实现代码的重用性。
  3. 减少类型转换:

    • 泛型可以避免手动进行类型转换,提高代码的简洁性和可读性。

使用泛型时,需要注意以下几点:

  1. 泛型类型参数不能是基本数据类型,只能是类类型或接口类型。
  2. 泛型在运行时会进行类型擦除,即泛型的类型参数在运行时被擦除为其上界类型。
  3. 泛型数组的创建是非法的,但可以使用通配符或强制类型转换来解决。

以下是一个使用泛型的示例代码,展示了泛型类和泛型方法的用法:

public class MyGenericClass<T> {
    private T value;

    public MyGenericClass(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public <E> void genericMethod(E element) {
        System.out.println("Generic method: " + element);
    }

    public static void main(String[] args) {
        MyGenericClass<Integer> myInteger = new MyGenericClass<>(10);
        System.out.println("Value: " + myInteger.getValue());

        MyGenericClass<String> myString = new MyGenericClass<>("Hello");
        System.out.println("Value: " + myString.getValue());

        myInteger.genericMethod("Generic");
    }
}

8. 说说HashSet

HashSet是Java集合框架中的一个实现类,实现了Set接口,用于存储一组不重复的元素。它基于哈希表(Hash Table)实现,具有快速的插入和查找操作。

以下是HashSet的一些重要特点和常用方法:

  1. 无序集合:

    • HashSet是一个无序集合,不保证元素的插入顺序。
    • LinkedHashSet不同,HashSet不维护元素的插入顺序,因此迭代顺序是不确定的。
  2. 唯一性:

    • HashSet中不允许存储重复的元素,每个元素都是唯一的。
    • HashSet通过对象的hashCode()equals()方法来判断元素的唯一性。
  3. 基于哈希表实现:

    • HashSet内部使用哈希表(Hash Table)实现,通过哈希函数将元素存储在数组中。
    • 哈希表提供了高效的插入和查找操作,使得HashSet具有较快的性能。
  4. 插入和查找操作的性能:

    • HashSet的插入和查找操作的时间复杂度都是近似常数时间O(1)。
    • 这是通过哈希表的哈希函数和桶(Bucket)来实现的,可以快速定位和访问元素。
  5. 支持 null 元素:

    • HashSet可以存储null元素,但只能有一个null元素。
  6. 不保证迭代顺序:

    • HashSet的迭代顺序是不确定的,与元素的插入顺序无关。
    • 如果需要有序遍历,可以使用LinkedHashSet

下面是一些常用的HashSet方法:

  • add(E element):将指定元素添加到集合中。
  • remove(Object element):从集合中移除指定元素。
  • contains(Object element):检查集合中是否包含指定元素。
  • isEmpty():检查集合是否为空。
  • size():返回集合中的元素数量。
  • clear():清空集合中的所有元素。

需要注意的是,向HashSet中存储自定义对象时,需要正确重写对象的hashCode()equals()方法,以确保唯一性的正确判断。

以下是一个示例代码,展示了HashSet的基本用法:

import java.util.HashSet;

public class Main {
    public static void main(String[] args) {
        // 创建一个HashSet对象
        HashSet<String> set = new HashSet<>();

        // 添加元素
        set.add("Apple");
        set.add("Banana");
        set.add("Orange");

        // 遍历元素
        System.out.println("遍历元素:");
        for (String fruit : set) {
            System.out.println(fruit);
        }

        // 判断元素是否存在
        System.out.println("是否包含Banana:" + set.contains("Banana"));

        // 移除元素
        set.remove("Orange");

        // 获取元素数量
        System.out.println("元素数量:" + set.size());
    }
}

9. 说说queue

Queue(队列)是Java集合框架中的一个接口,用于存储一组元素,并提供了一组操作来管理这些元素。队列遵循先进先出(FIFO)的原则,即最先进入队列的元素将最先被移除。

Queue接口继承自Collection接口,并在其基础上添加了用于管理队列的方法。以下是Queue接口的一些常用方法:

  1. 添加元素:

    • boolean add(E element):将指定元素添加到队列的尾部,如果队列已满,则抛出异常。
    • boolean offer(E element):将指定元素添加到队列的尾部,如果队列已满,则返回false。
  2. 获取和删除元素:

    • E remove():获取并删除队列的头部元素,如果队列为空,则抛出异常。
    • E poll():获取并删除队列的头部元素,如果队列为空,则返回null。
    • E element():获取但不删除队列的头部元素,如果队列为空,则抛出异常。
    • E peek():获取但不删除队列的头部元素,如果队列为空,则返回null。
  3. 其他方法:

    • boolean isEmpty():检查队列是否为空。
    • int size():返回队列中的元素数量。
    • void clear():清空队列中的所有元素。

Queue接口有多个实现类,其中常用的包括:

  • LinkedList:使用双向链表实现的队列。
  • ArrayDeque:使用循环数组实现的双端队列。
  • PriorityQueue:基于堆结构实现的优先级队列。

除了Queue接口定义的方法外,实现类可能还提供了其他特定的方法和功能。例如,PriorityQueue支持根据元素的优先级进行自动排序。

以下是一个示例代码,展示了Queue的基本用法:

import java.util.LinkedList;
import java.util.Queue;

public class Main {
    public static void main(String[] args) {
        // 创建一个Queue对象
        Queue<String> queue = new LinkedList<>();

        // 添加元素
        queue.add("Apple");
        queue.add("Banana");
        queue.add("Orange");

        // 获取和删除元素
        String head = queue.remove();
        System.out.println("Removed element: " + head);

        // 获取但不删除元素
        String peeked = queue.peek();
        System.out.println("Peeked element: " + peeked);

        // 遍历元素
        System.out.println("Elements in the queue:");
        for (String element : queue) {
            System.out.println(element);
        }

        // 检查队列是否为空
        boolean isEmpty = queue.isEmpty();
        System.out.println("Is the queue empty? " + isEmpty);

        // 获取队列的大小
        int size = queue.size();
        System.out.println("Size of the queue: " + size);

        // 清空队列
        queue.clear();
    }
}

10. 说说HashMap

HashMap是Java集合框架中的一个实现类,实现了Map接口,用于存储键值对(key-value)的数据。它基于哈希表(Hash Table)实现,提供了快速的插入、查找和删除操作。

以下是HashMap的一些重要特点和常用方法:

  1. 键值对存储:

    • HashMap以键值对的形式存储数据,每个键关联一个值。
    • 键和值可以是任意非空对象,但键不能重复,值可以重复。
  2. 基于哈希表实现:

    • HashMap内部使用哈希表(Hash Table)实现,通过哈希函数将键映射到桶(Bucket)的索引位置。
    • 哈希表提供了快速的插入、查找和删除操作,使得HashMap具有较快的性能。
  3. 哈希冲突解决:

    • 当两个或多个键被映射到同一个桶时,发生了哈希冲突。
    • HashMap使用链表或红黑树来解决哈希冲突,称为拉链法(Separate Chaining)。
    • 当链表长度较长时,链表会被转换为红黑树,以提高插入、删除和查找的效率。
  4. 动态调整大小:

    • HashMap可以根据需要自动调整容量,以便存储更多的键值对。
    • 在插入元素时,如果超过了负载因子(Load Factor)阈值,HashMap会进行扩容操作,重新分配桶的数量,以保持性能。
  5. 支持 null 键和 null 值:

    • HashMap允许键和值为null,但只能有一个null键。
  6. 不保证迭代顺序:

    • HashMap的迭代顺序是不确定的,与元素的插入顺序无关。
    • 如果需要有序遍历,可以使用LinkedHashMap

下面是一些常用的HashMap方法:

  • put(K key, V value):将指定的键值对存储到HashMap中。
  • get(Object key):获取与指定键关联的值。
  • remove(Object key):从HashMap中移除指定键对应的键值对。
  • containsKey(Object key):检查HashMap中是否包含指定的键。
  • isEmpty():检查HashMap是否为空。
  • size():返回HashMap中键值对的数量。
  • clear():清空HashMap中的所有键值对。

需要注意的是,HashMap在使用自定义对象作为键时,需要正确实现对象的hashCode()equals()方法,以确保正确的键唯一性和查找操作的准确性。

以下是一个示例代码,展示了HashMap的基本用法:

import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        // 创建一个HashMap对象
        HashMap<String, Integer> map = new HashMap<>();

        // 添加键值对
        map.put("Apple", 10);
        map.put("Banana", 5);
        map.put("Orange", 8);

        // 获取值
        int appleQuantity = map.get("Apple");
        System.out.println("Apple quantity: " + appleQuantity);

        // 检查键是否存在
        boolean containsBanana = map.containsKey("Banana");
        System.out.println("Contains Banana: " + containsBanana);

        // 移除键值对
        map.remove("Orange");

        // 获取键值对数量
        int size = map.size();
        System.out.println("Size: " + size);

        // 遍历键值对
        System.out.println("Entries:");
        for (HashMap.Entry<String, Integer> entry : map.entrySet()) {
            String fruit = entry.getKey();
            int quantity = entry.getValue();
            System.out.println(fruit + ": " + quantity);
        }
    }
}

11. HashMap和Hashtable的区别

  1. 线程安全性:

    • Hashtable是线程安全的,即多个线程可以同时访问和修改Hashtable的实例,而不会导致数据不一致或其他并发问题。
    • HashMap是非线程安全的,即在多线程环境下,如果多个线程同时访问和修改同一个HashMap实例,可能导致数据不一致或其他并发问题。需要在多线程环境下使用时,可以通过外部同步控制(如使用Collections.synchronizedMap()方法)来实现线程安全。
  2. 继承关系:

    • Hashtable是早期Java集合框架中的类,在Java 1.0中引入,它实现了Dictionary接口,并继承了Hashtable类。
    • HashMap是在Java 1.2中引入的新类,它实现了Map接口,并继承了AbstractMap类。
  3. 允许 null 键和 null 值:

    • HashMap允许使用null作为键和值,可以存储一个null键和多个null值。
    • Hashtable不允许使用null作为键或值,如果尝试存储null键或值,会抛出NullPointerException
  4. 性能:

    • HashMap相对于Hashtable来说,具有更好的性能。
    • HashMap是非线程安全的,因此不需要同步开销,可以更快地执行插入、查找和删除操作。
    • Hashtable是线程安全的,使用同步来保证线程安全性,可能导致一些性能损失。

12.  说说迭代器

迭代器(Iterator)是Java集合框架中用于遍历集合元素的一种接口。它提供了一种统一的方式来访问集合中的元素,而不需要了解集合内部的实现细节。通过迭代器,我们可以按照指定的顺序逐个访问集合中的元素,进行遍历、查找、删除等操作。

迭代器接口(Iterator)定义了一些常用的方法,可以帮助我们在集合中进行迭代操作,如下所示:

  1. boolean hasNext()

    • 判断集合中是否还有下一个元素。
    • 如果还有下一个元素,则返回true,否则返回false
  2. E next()

    • 返回集合中的下一个元素。
    • 如果集合中还有下一个元素,则返回该元素,并将迭代器的指针向前移动。
    • 如果集合已经没有元素可供返回,则抛出NoSuchElementException异常。
  3. void remove()

    • 从集合中移除当前元素。
    • 此方法可选,在使用之前需要先调用next()方法。
    • 一般情况下,它会移除最后一次通过next()方法返回的元素。
    • 如果在调用next()方法之前或者已经调用了remove()方法之后再次调用remove(),则会抛出IllegalStateException异常。

迭代器的使用方式如下:

Iterator<E> iterator = collection.iterator();
while (iterator.hasNext()) {
    E element = iterator.next();
    // 进行操作,例如打印元素或者进行其他处理
}

注意,迭代器遍历集合时,是通过调用hasNext()方法判断是否还有下一个元素,然后通过next()方法获取元素,并将迭代器指针向前移动。

迭代器的优点包括:

  • 支持从前往后的遍历操作,无需关心集合内部的数据结构。
  • 在遍历过程中可以进行元素的删除操作,而不会抛出ConcurrentModificationException异常。

大多数集合类都实现了Iterator接口,并提供了iterator()方法来获取对应的迭代器实例。通过迭代器,我们可以灵活地遍历集合中的元素,无需直接操作底层数据结构,提高了代码的可读性和可维护性。

需要注意的是,当使用迭代器遍历集合时,应避免直接使用集合的方法来进行元素的增删操作,而是应该使用迭代器的remove()方法来进行元素的删除,以避免可能的并发修改异常或遍历错误。

13. IO

(一)输入/输出流

输入输出流(I/O Stream)是Java中用于处理数据输入和输出的抽象概念。它提供了统一的接口和模型,用于读取和写入不同类型的数据。

Java的输入输出流可以分为两种类型:字节流(Byte Stream)和字符流(Character Stream)。

  1. 字节流(Byte Stream):

    • 字节流以字节为单位进行数据读取和写入。它们处理的是字节数据,适用于二进制文件(如图片、视频等)或者与底层系统交互时需要以字节形式进行数据传输的场景。
    • 字节流的顶层父类是InputStreamOutputStream。常用的字节流类包括FileInputStreamFileOutputStreamByteArrayInputStreamByteArrayOutputStream等。
  2. 字符流(Character Stream):

    • 字符流以字符为单位进行数据读取和写入。它们处理的是字符数据,适用于处理文本文件(如文本文档、配置文件等)或者与用户进行交互的场景。
    • 字符流的顶层父类是ReaderWriter。常用的字符流类包括FileReaderFileWriterBufferedReaderBufferedWriter等。

Java的输入输出流按照数据流向可以分为输入流(Input Stream)和输出流(Output Stream):

  • 输入流用于从数据源(如文件、网络连接、输入设备等)读取数据。
  • 输出流用于向数据目标(如文件、网络连接、输出设备等)写入数据。

在使用输入输出流时,通常需要注意以下几点:

  1. 打开和关闭流:

    • 在使用流之前,需要先打开流来建立与数据源或数据目标的连接。
    • 使用完流后,应及时关闭流来释放资源。可以使用close()方法关闭流。
  2. 缓冲流:

    • 为了提高I/O操作的效率,可以使用缓冲流(Buffered Stream)来减少实际的物理读写次数。
    • 缓冲流通过内部缓冲区来进行数据的读取和写入,可以减少对底层I/O设备的访问次数,提高性能。
    • 常用的缓冲流包括BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter等。
  3. 异常处理:

    • I/O操作可能会抛出IOException及其子类的异常,需要适当地进行异常处理。
    • 通常使用try-catch语句来捕获异常,并进行相应的处理或者资源释放。

以下是一个使用字节流和字符流进行文件读写的示例代码:

import java.io.*;

public class Main {
    public static void main(String[] args) {
        try {
            // 使用字节流读取文件
            FileInputStream fis = new FileInputStream("input.txt");
            int data;
            while ((data = fis.read()) != -1) {
                // 处理读取到的字节数据
                System.out.print((char) data);
            }
            fis.close();

            // 使用字符流写入文件
            FileWriter writer = new FileWriter("output.txt");
            writer.write("Hello, World!");
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

(二)文件输入/输出流

文件输入输出流是Java I/O中用于读取和写入文件的流。它们是字节流的一种,主要用于处理二进制文件或以字节为单位的数据

Java提供了两个主要的文件输入输出流类:FileInputStreamFileOutputStream。它们分别用于从文件读取数据和向文件写入数据。

  1. FileInputStream(文件输入流):

    • FileInputStream用于从文件中读取数据。
    • 它继承自InputStream类,是字节流的一种。
    • 可以通过创建一个FileInputStream对象来打开一个文件,并使用read()方法从文件中读取字节数据。
    • 示例代码:
      try {
          FileInputStream fis = new FileInputStream("input.txt");
          int data;
          while ((data = fis.read()) != -1) {
              // 处理读取到的字节数据
              System.out.print((char) data);
          }
          fis.close();
      } catch (IOException e) {
          e.printStackTrace();
      }
      
  2. FileOutputStream(文件输出流):

    • FileOutputStream用于向文件写入数据。
    • 它继承自OutputStream类,是字节流的一种。
    • 可以通过创建一个FileOutputStream对象来打开一个文件,并使用write()方法向文件中写入字节数据。
    • 示例代码:
      try {
          FileOutputStream fos = new FileOutputStream("output.txt");
          String text = "Hello, World!";
          fos.write(text.getBytes());
          fos.close();
      } catch (IOException e) {
          e.printStackTrace();
      }
      

在使用文件输入输出流时,需要注意以下几点:

  • 在打开文件之前,需要确保文件存在或者可以创建。
  • 使用完文件输入输出流后,需要调用close()方法关闭流,以释放相关的资源。
  • 可以使用read()write()方法进行字节级别的读取和写入操作。
  • 可以使用缓冲流(如BufferedInputStreamBufferedOutputStream)来提高读写的性能。

需要注意的是,文件输入输出流处理的是字节数据,适用于处理二进制文件或以字节为单位的数据。如果需要处理文本文件或字符数据,可以使用字符流(如FileReaderFileWriter)来读写文本内容。

(三)带缓存的输入/输出流

带缓存的输入输出流是在Java I/O中用于提高读取和写入操作性能的一种机制。它们通过在内存中引入缓冲区来减少实际的物理读写次数,从而提高读写效率。

Java提供了多个带缓存的输入输出流类,其中包括:

  1. BufferedInputStream(带缓存的输入流):

    • BufferedInputStreamInputStream的子类,用于在内部维护一个缓冲区,从而提供高效的输入操作。
    • 它通过一次性从底层输入流中读取一定数量的字节到缓冲区中,然后从缓冲区中逐个字节地提供数据。
    • 示例代码:
    try {
        FileInputStream fis = new FileInputStream("input.txt");
        BufferedInputStream bis = new BufferedInputStream(fis);
        int data;
        while ((data = bis.read()) != -1) {
            // 处理读取到的字节数据
            System.out.print((char) data);
        }
        bis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    
  2. BufferedOutputStream(带缓存的输出流):

    • BufferedOutputStreamOutputStream的子类,用于在内部维护一个缓冲区,从而提供高效的输出操作。
    • 它将数据写入缓冲区,当缓冲区满时,将缓冲区中的数据一次性写入底层输出流中。
    • 示例代码:
    try {
        FileOutputStream fos = new FileOutputStream("output.txt");
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        String text = "Hello, World!";
        bos.write(text.getBytes());
        bos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    

使用带缓存的输入输出流时,需要注意以下几点:

  • 缓存的大小可以通过构造函数进行指定,如果未指定,默认大小为8KB。
  • 当缓存区满时,会触发一次物理读写操作。
  • 在使用完带缓存的流后,需要调用close()方法关闭流,以确保缓冲区中的数据被刷新到底层流中。
  • 可以通过调用flush()方法手动将缓冲区中的数据刷新到底层流中。

使用带缓存的输入输出流可以显著提高读取和写入操作的效率,特别是在频繁进行小数据块的读写时。然而,在某些情况下(例如网络传输),由于缓存需要一定的时间来填充或刷新,因此在适当的时机调用flush()方法以确保数据及时到达是很重要的。

14. 反射

反射(Reflection)是指在运行时动态地获取和操作类的信息的能力。通过反射,可以在程序运行时检查类、获取类的成员(字段、方法、构造函数等),并调用类的方法或创建对象。在Java中,反射机制提供了一组类和接口,使得我们可以在运行时分析和操作类的结构、属性和行为。

Java反射机制的核心是java.lang.reflect包,它提供了一些类和接口,用于描述和操作类的结构和成员。

以下是反射机制的一些常见用途和功能:

  1. 获取类的信息:

    • 可以使用Class类来获取一个类的信息,如类的名称、包名、父类、实现的接口等。
    • 可以通过Class.forName()方法根据类名获取类的Class对象。
  2. 获取类的成员(字段、方法、构造函数):

    • 可以使用getFields()getDeclaredFields()方法获取类的字段。
    • 可以使用getMethods()getDeclaredMethods()方法获取类的方法。
    • 可以使用getConstructors()getDeclaredConstructors()方法获取类的构造函数。
  3. 创建对象:

    • 可以使用newInstance()方法通过类的Class对象来创建类的实例。
  4. 调用方法:

    • 可以使用Method类的invoke()方法来调用类的方法。
  5. 访问和修改字段:

    • 可以使用Field类的get()set()方法来读取和修改类的字段值。
  6. 动态代理:

    • 反射机制可以用于动态生成代理类,实现对对象的动态代理操作。

反射机制的使用场景包括但不限于以下几种情况:

  • 当我们需要在运行时动态加载类、创建对象或调用方法时,可以使用反射机制。
  • 当我们需要通过字符串来指定类名、方法名或字段名时,可以使用反射机制。
  • 当我们需要在运行时获取类的信息、检查类的结构或动态修改类的行为时,可以使用反射机制。

需要注意的是,由于反射是在运行时动态地进行的,因此会带来一些性能上的损耗。在性能要求较高的场景中,应慎重使用反射,避免频繁的反射操作。

15. 线程

线程是计算机程序执行的最小单元,它是进程中的一个独立执行序列。在Java中,线程用于实现多任务处理,使程序能够同时执行多个操作。

以下是关于线程的一些重要概念和特性:

  1. 并发执行:线程允许程序的不同部分同时执行,从而提高系统的并发性和响应能力。

  2. 轻量级实体:相对于进程而言,线程是一种轻量级的执行单元。创建和销毁线程的开销较小,线程切换的成本也较低。

  3. 线程状态:线程可以处于不同的状态,如创建、就绪、运行、阻塞和终止等。

  4. 线程调度:线程调度器负责按照特定算法和策略将CPU时间片分配给不同的线程,使其能够并发地执行。

  5. 线程同步:多个线程访问共享资源时可能会引发竞态条件(Race Condition)和数据不一致的问题。线程同步机制,如使用锁、互斥量和信号量等,用于协调和控制线程的访问,保证数据的一致性和线程的安全性。

在Java中,线程是通过Thread类来实现的。创建线程的常见方式包括:

  • 继承Thread类并重写run()方法,然后创建线程对象并调用start()方法启动线程。
  • 实现Runnable接口,并将实现了Runnable接口的对象作为参数传递给Thread类的构造函数,然后创建线程对象并调用start()方法启动线程。

以下是一个使用继承Thread类的示例代码:

class MyThread extends Thread {
    public void run() {
        // 线程执行的代码
        System.out.println("Hello from MyThread!");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

Java还提供了其他线程相关的类和接口,如:

  • Runnable接口:定义了线程执行的任务,可以通过实现Runnable接口来创建线程。
  • Callable接口:类似于Runnable接口,但可以返回执行结果,并能抛出受检异常。
  • Executor接口:用于执行提交的任务,并管理线程的生命周期。
  • ThreadPoolExecutor类:用于创建和管理线程池,提供了对线程池中线程的调度和管理。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值