javaSE从入门到精通的二十万字总结(二)

简介: 目录前言5. 集合5.1 集合两大类5.2 Collection前言在看这篇文章之前先预习ava基础可查看我之前写的文章java零基础从入门到精通(全)以及javaSE从入门到精通的十万字总结(一)5. 集合集合实际上就是一个容器。可以来容纳其它类型的数据集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,集合当中存储的都是java对象的内存地址。(或者说集合中存储的是引用。)list.add(100); //自动装箱Integer不同的集合等同于使用了不同的数据结构new

前言

在看这篇文章之前先预习java基础

这部分知识一共有4个文档
第三个是当前这个文档

  1. java零基础从入门到精通(全)
  2. javaSE从入门到精通的二十万字总结(一)
  3. javaSE从入门到精通的二十万字总结(二)
  4. javaSE从入门到精通的二十万字总结(三)

关于这部分的源码如下
javase从入门到精通的学习代码.rar

5. 集合

讲述集合的时候,如果对python感兴趣
也可以对比一下两者的不同
python数据类型详细分析(附代码)

  • 集合实际上就是一个容器。可以来容纳其它类型的数据
  • 集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,集合当中存储的都是java对象的内存地址。(或者说集合中存储的是引用。)list.add(100); //自动装箱Integer
  • 不同的集合等同于使用了不同的数据结构

new ArrayList(); 创建一个集合,底层是数组

    new LinkedList(); 创建一个集合对象,底层是链表
    new TreeSet(); 创建一个集合对象,底层是二叉树
  • 所有的集合类和集合接口都在java.util包下

5.1 集合两大类

==一类是单个方式存储元素:单个方式存储元素,这一类集合中超级父接口:java.util.Collection;==

在这里插入图片描述

==Collection接口继承Iterable接口(此接口有Iterator方法)==

调用此方法得到迭代器对象,执行方法下的三个函数 ==hasNext,next和remove==
list和set集合继承了collection接口,接口是不可以new的

  • list接口主要常用的实现类有
  1. ArrayList集合底层采用了数组这种数据结构,非线程安全。

    1. LinkedList集合底层采用了双向链表的数据结构
    2. Vector集合底层采用了数组这种数据结构,线程安全的,所有的方法都有synchronized关键字修饰,比较安全但效率低,所以使用率低
  • set接口主要常用的实现类有

    1. HashSet集合在new的时候,底层实际上new了一个HashMap集合。向HashSet集合中存储元素,实际上是存储到HashMap集合中,HashMap集合是一个哈希表数据结构
    2. TreeSet集合实际是TreeMap,new TreeSet集合的时候,底层实际上new了一个TreeMap集合,和上面同理。采用了二叉树数据结构
  • Sortset接口特点
  1. 继承了Set结合,本身特点是无序不可重复,但是放在了SortedSet集合中的元素可以自动排序,是自动按照大小进行排序

==一类是以键值对儿的方式存储元素:以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map;==
在这里插入图片描述
Map集合和Collection集合没有关系,存储的方式不同
所有Map集合key元素无序不可重复
Map接口的常用实现类有

  • HashMap集合底层是哈希表数据结构,非线程安全
  • Hashtable集合是哈希表数据结构,线程安全,所有的方法都有synchronized关键字,效率比较低,使用比较少了
  • 还有一个接口SortedMap,本身不可重复无序,但是此处有自动排序,按照大小进行排序。此接口的实现类有TreeMap(二叉树结构)

总结:
所有的实现类:

实现类 底层元素
Arraylist 底层是数组。
LinkedList 底层是双向链表。
Vector 底层是数组,线程安全的,效率较低,使用较少。
HashSet 底层是HashMap,放到 HashSet集合中的元素等同于放到HashMap集合key部分了。
TreeSet 底层是TreeMap,放到 TreeSet集合中的元素等同于放到TreeMap集合key部分了。
HashMap 底层是哈希表。
Hashtable 底层也是哈希表,只不过线程安全的,效率较低,使用较少。
Properties 是线程安全的,并且 key 和value只能存储字符串String。
TreeMap 底层是二叉树。TreeMap集合的 key可以自动按照大小顺序排序。

集合的总体概念如下:


- set集合存储元素的特点:
无序不可重复
无序:存进去的顺序和取出的顺序不一定相同。另外 set集合中元素没有下标。
不可重复:存进去1,不能再存储1了。

- SortedSet ( SortedMap)集合存储元素特点:
无序不可重复的,但是SortedSet集合中的元素是可排序的。
无序:存进去的顺序和取出的顺序不一定相同。另外 set集合中元素没有下标。
不可重复:存进去1,不能再存储1
可排序:可以按照大小顺序排列。

- Map集合的 key,就是一个set集合。
往set集合中放数据,实际上放到了Map集合的 key 部分。

5.2 Collection

==集合中不能直接存储基本数据类型,也不能存java对象,只是存储java对象的内存地址。==
collection是一个接口

  • 没有使用“泛型”之前,Collection中可以存储Object的所有子类型。
  • 使用了“泛型”之后,Collection中只能存储某个具体的类型。

5.2.1 常用方法

常用方法有:

函数 功能
boolean add(Object e) 向集合中添加元素
int size() 获取集合中元素的个数
void clear() 清空集合
boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false
boolean remove(Object o) 删除集合中的某个元素。
boolean isEmpty() 判断该集合中元素的个数是否为0
Object[] toArray() 调用这个方法可以把集合转换成数组。【作为了解,使用不多。】

下面举例常用方法的注意事项

add方法中可以添加常用的数据,也可以==new对象==

Collection c = new ArrayList();

c.add(1200); // 自动装箱(java5的新特性。),实际上是放进去了一个对象的内存地址。Integer x = new Integer(1200);
c.add(3.14); // 自动装箱
c.add(new Object());
c.add(new Student());
c.add(true); // 自动装箱
 c.add("hello"); // "hello"对象的内存地址放到了集合当中

size()返回的是元素的个数
clear可以清空所有元素
contains返回的是boolean类型

5.2.2 迭代器

讲解collection的集合迭代,而且迭代器是通用的
这个迭代器是Collection的上层接口Iterator,而且迭代器一开始没有指向第一个元素

迭代器是对象,迭代器常用的两个方法为

  • boolean hasNext()如果仍有元素可以迭代,则返回 true。
  • Object next() 返回迭代的下一个元素

具体步骤为:
最开始先获取集合对象 Collection c = new ArrayList();
这个对象有iterator()方法。
通过集合对象调用迭代器的接口(接口方法返回接口)Iterator it = c.iterator();
在调用迭代器的方法
这个只能返回object, Object obj = it.next();
而且在输出的时候输出的是obj的内容,因为重写了tosting方法

while(it.hasNext()){
        Object obj = it.next();
        System.out.println(obj);
    }
注意:此时获取的迭代器,指向的是那是集合中没有元素状态下的迭代器。
一定要注意:集合结构只要发生改变,迭代器必须重新获取。
当集合结构发生了改变,迭代器没有重新获取时,调用next()方法时:java.util.ConcurrentModificationException

不可以这样子

// 获取迭代器
//Iterator it = c.iterator();
/*while(it.hasNext()){
    // 编写代码时next()方法返回值类型必须是Object。
    // Integer i = it.next();
    Object obj = it.next();
    System.out.println(obj);
}*/

关于集合中迭代的代码展示

/*
关于集合的迭代/遍历
 */
public class CollectionTest03 {
    public static void main(String[] args) {
        // 创建集合对象
        Collection c1  = new ArrayList(); // ArrayList集合:有序可重复
        // 添加元素
        c1.add(1);
        c1.add(2);
        c1.add(3);
        c1.add(4);
        c1.add(1);

        // 迭代集合
        Iterator it = c1.iterator();
        while(it.hasNext()){
            // 存进去是什么类型,取出来还是什么类型。
            Object obj = it.next();
            /*if(obj instanceof Integer){
                System.out.println("Integer类型");
            }*/
            // 只不过在输出的时候会转换成字符串。因为这里println会调用toString()方法。
            System.out.println(obj);
        }

        // HashSet集合:无序不可重复
        Collection c2 = new HashSet();
        // 无序:存进去和取出的顺序不一定相同。
        // 不可重复:存储100,不能再存储100.
        c2.add(100);
        c2.add(200);
        c2.add(300);
        c2.add(90);
        c2.add(400);
        c2.add(50);
        c2.add(60);
        c2.add(100);
        Iterator it2 = c2.iterator();
        while(it2.hasNext()){
            System.out.println(it2.next());
        }
    }
}

==hashset无序不可重复,而ArrayList有序可重复==
不可重复,就是不能重复add进相同的数字

5.2.3 contains方法

深入contains方法
判断集合中是否包含某个元素
==比较的是内容,是因为内部中调用了equals方法进行比对==
==String类的equals方法比较的是内容不是内存地址==
object类的equals比较的是地址
主要是因为string类的equals重写了,比较的是内容
可以通过代码进行比较
代码展示

public class CollectionTest04 {
    public static void main(String[] args) {
        // 创建集合对象
        Collection c = new ArrayList();

        // 向集合中存储元素
        String s1 = new String("abc"); // s1 = 0x1111
        c.add(s1); // 放进去了一个"abc"

        String s2 = new String("def"); // s2 = 0x2222
        c.add(s2);

        // 集合中元素的个数
        System.out.println("元素的个数是:" + c.size()); // 2
        
        // 新建的对象String
        String x = new String("abc"); // x = 0x5555
        // c集合中是否包含x?结果猜测一下是true还是false?、
        
        System.out.println(c.contains(x)); //判断集合中是否存在"abc" true,结果为true就有
    }
}

在这里插入图片描述
contains主要是因为重写equals方法,所以比较的是内容
查看源码也可看出重写了equals方法

而且通过自个定义函数重写

public class  CollectionTest05 {
    public static void main(String[] args) {
        // 创建集合对象
        Collection c = new ArrayList();
        // 创建用户对象
        User u1 = new User("jack");
        // 加入集合
        c.add(u1);

        // 判断集合中是否包含u2
        User u2 = new User("jack");

        // 没有重写equals之前:这个结果是false
        //System.out.println(c.contains(u2)); // false
        // 重写equals方法之后,比较的时候会比较name。
        System.out.println(c.contains(u2)); // true
         }
}

class User{
    private String name;
    public User(){}
    public User(String name){
        this.name = name;
    }

    // 重写equals方法
    // 将来调用equals方法的时候,一定是调用这个重写的equals方法。
    // 这个equals方法的比较原理是:只要姓名一样就表示同一个用户。
    public boolean equals(Object o) {
        if(o == null || !(o instanceof User)) return false;
        if(o == this) return true;
        User u = (User)o;
        // 如果名字一样表示同一个人。(不再比较对象的内存地址了。比较内容。)
        return u.name.equals(this.name);
    }

}

5.2.4 remove方法

==remove方法也是重写了equals方法==
如果删除某个元素,这个元素有两个一摸一样的
删除这个,这两个元素都会删除

// 创建集合对象
Collection cc = new ArrayList();
// 创建字符串对象
String s1 = new String("hello");
// 加进去。
cc.add(s1);

// 创建了一个新的字符串对象
String s2 = new String("hello");
// 删除s2
cc.remove(s2); // s1.equals(s2) java认为s1和s2是一样的。删除s2就是删除s1。
// 集合中元素个数是?
System.out.println(cc.size()); // 0

前面也讲过迭代器中途不可以改变一点变化
如果改变了会出错
比如下面的代码展示

Collection c2 = new ArrayList();
c2.add("abc");
c2.add("def");
c2.add("xyz");

Iterator it2 = c2.iterator();
while(it2.hasNext()){
    Object o = it2.next();
    // 删除元素
    // 删除元素之后,集合的结构发生了变化,应该重新去获取迭代器
    // 但是,循环下一次的时候并没有重新获取迭代器,所以会出现异常:java.util.ConcurrentModificationException
    // 出异常根本原因是:集合中元素删除了,但是没有更新迭代器(迭代器不知道集合变化了)
    //c2.remove(o); // 直接通过集合去删除元素,没有通知迭代器。(导致迭代器的快照和原集合状态不同。)
    // 使用迭代器来删除可以吗?
    // 迭代器去删除时,会自动更新迭代器,并且更新集合(删除集合中的元素)。
    it2.remove(); // 删除的一定是迭代器指向的当前元素。
    System.out.println(o);
}

System.out.println(c2.size()); //0
  • 使用集合中的删除方法,出异常根本原因是:集合中元素删除了,但是没有更新迭代器(迭代器不知道集合变化了)
  • 使用迭代器自带的删除,迭代器去删除时,会自动更新迭代器,并且更新集合(删除集合中的元素)

主要是集合中删除的方法只是集合快照,如果有更新集合,会出现错误,如果使用的是迭代器的删除方法,如果有更新集合,集合也会随之更新而不影响删除的同步

5.3 List

List集合存储元素特点:有序可重复。有序:List集合中的元素有下标。从0开始,以1递增。重复:存储一个1,还可以再存储1.

List是Collection接口的子接口
以下只列出List接口特有的常用的方法:

常用方法 功能说明
void add(int index, Object element) 某个索引添加元素
Object set(int index, Object element) 某个索引设置元素
Object get(int index) 获取该索引的元素
int indexOf(Object o) 获取对象第一次出现的索引
int lastIndexOf(Object o) 获取对象最后一次出现的索引
Object remove(int index) 删除指定下标的元素
// 创建List类型的集合。
//List myList = new LinkedList();
//List myList = new Vector();
List myList = new ArrayList();

// 添加元素
myList.add("A"); // 默认都是向集合末尾添加元素。
myList.add("B");
myList.add("C");
myList.add("C");
myList.add("D");

//在列表的指定位置插入指定元素(第一个参数是下标)
// 这个方法使用不多,因为对于ArrayList集合来说效率比较低。
myList.add(1, "KING");

// 迭代
Iterator it = myList.iterator();
while(it.hasNext()){
    Object elt = it.next();
    System.out.println(elt);
}

具体遍历元素也可以通过下标值

// 因为有下标,所以List集合有自己比较特殊的遍历方式
// 通过下标遍历。【List集合特有的方式,Set没有。】
for(int i = 0; i < myList.size(); i++){
    Object obj = myList.get(i);
    System.out.println(obj);
}

其他方法比较简单,就不展示出代码

5.3.1 ArrayList

  • 默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10。)
  • 集合底层是一个Object[]数组。

    • 构造方法:
      new ArrayList();
      new ArrayList(20);
  • ArrayList集合的扩容:增长到原容量的1.5倍。

==ArrayList集合底层是数组,尽可能少的扩容。因为数组扩容效率比较低,建议在使用ArrayList集合的时候预估计元素的个数,给定一个初始化容量==

  • 数组优点:检索效率比较高。(每个元素占用空间大小相同,内存地址是连续的,通过数学表达式计算出元素的内存地址,检索效率高。)
  • 数组缺点:随机增删元素效率比较低。另外数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间。)
  • 向数组末尾添加元素,效率很高,不受影响。
  • ==面试官经常问的一个问题?==
    这么多的集合中,你用哪个集合最多?

    答:ArrayList集合。
    因为往数组末尾添加元素,效率不受影响。
    另外,我们检索/查找某个元素的操作比较多。
    
  • ArrayList集合是非线程安全的。(不是线程安全的集合。)

基本的代码实现
注意区分容量与数组的空间大小size

// 默认初始化容量是10
// 数组的长度是10
List list1 = new ArrayList();
// 集合的size()方法是获取当前集合中元素的个数。不是获取集合的容量。
System.out.println(list1.size()); // 0

// 指定初始化容量
// 数组的长度是20
List list2 = new ArrayList(20);
// 集合的size()方法是获取当前集合中元素的个数。不是获取集合的容量。
System.out.println(list2.size()); // 0

构造方法除了以上两种之外 还有第三种转换方式
==将HashSet集合转换成List集合==

// 默认初始化容量10
List myList1 = new ArrayList();

// 指定初始化容量100
List myList2 = new ArrayList(100);

// 创建一个HashSet集合
Collection c = new HashSet();
// 添加元素到Set集合
c.add(100);
c.add(200);
c.add(900);
c.add(50);

// 通过这个构造方法就可以将HashSet集合转换成List集合。
List myList3 = new ArrayList(c);
for(int i = 0; i < myList3.size(); i++){
    System.out.println(myList3.get(i));
}

5.3.2 LinkedList

再讲这部分知识的时候,涉及单链表和双向链表以及对比线性表的区别
可查看我之前写过的文章
【数据结构】顺序表及链表详细分析(全)

具体涉及单链表和双链表的创建可查看这篇代码
[【leetcode】链表-设计链表(单双链表全)
](https://blog.csdn.net/weixin_47872288/article/details/118217006)

具体单链表的创建和功能
简单阐述基本功能

创建链表

public class Link {
    public static void main(String[] args) {

    // 头节点
    Node header;

    int size = 0;

    public int size(){
        return size;
    }

    // 向链表中添加元素的方法(向末尾添加)
    public void add(E data){
    //public void add(Object data){
        // 创建一个新的节点对象
        // 让之前单链表的末尾节点next指向新节点对象。
        // 有可能这个元素是第一个,也可能是第二个,也可能是第三个。
        if(header == null){
            // 说明还没有节点。
            // new一个新的节点对象,作为头节点对象。
            // 这个时候的头节点既是一个头节点,又是一个末尾节点。
            header = new Node(data, null);
        }else {
            // 说明头不是空!
            // 头节点已经存在了!
            // 找出当前末尾节点,让当前末尾节点的next是新节点。
            Node currentLastNode = findLast(header);
            currentLastNode.next = new Node(data, null);
        }
        size++;
    }

    /**
     * 专门查找末尾节点的方法。
     */
    private Node findLast(Node node) {
        if(node.next == null) {
            // 如果一个节点的next是null
            // 说明这个节点就是末尾节点。
            return node;
        }
        // 程序能够到这里说明:node不是末尾节点。
        return findLast(node.next); // 递归算法!
    }
}

创建单链表功能表述

/*
单链表中的节点。
节点是单向链表中基本的单元。
每一个节点Node都有两个属性:
    一个属性:是存储的数据。
    另一个属性:是下一个节点的内存地址。
 */
public class Node {

    // 存储的数据
    Object data;

    // 下一个节点的内存地址
    Node next;

    public Node(){

    }

    public Node(Object data, Node next){
        this.data = data;
        this.next = next;
    }
}

==通过以上代码可看出链表的优缺点:==

链表的优点:
由于链表上的元素在空间存储上内存地址不连续。
所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。
在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用LinkedList。

链表的缺点:
不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止。所以LinkedList集合检索/查找的效率较低。

LinkedList集合底层也是有下标的。
注意:ArrayList之所以检索效率比较高,不是单纯因为下标的原因。是因为底层数组发挥的作用。
LinkedList集合照样有下标,但是检索/查找某个元素的时候效率比较低,因为只能从头节点开始一个一个遍历。

List list = new LinkedList();
list.add("a");
list.add("b");
list.add("c");

for(int i = 0; i <list.size(); i++){
    Object obj = list.get(i);
    System.out.println(obj);
}
 List list2 = new ArrayList(); // 这样写表示底层用了数组
 List list2 = new LinkedList(); // 这样写表示底层用了双向链表

5.3.3 Vector

  • 底层也是一个数组
  • 初始化容量:10
  • 怎么扩容的?

扩容之后是原容量的2倍。
10--> 20 --> 40 --> 80

  • ArrayList集合扩容特点:ArrayList集合扩容是原容量1.5倍。
  • Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的。效率比较低,使用较少了。

==创建一个集合==

// 创建一个Vector集合
List vector = new Vector();
//Vector vector = new Vector();

遍历集合数组

Iterator it = vector.iterator();
while(it.hasNext()){
    Object obj = it.next();
    System.out.println(obj);
}

使用非线程安全转换为线程安全

  • 怎么将一个线程不安全的ArrayList集合转换成线程安全的呢?

使用集合工具类:
java.util.Collections;

这里区别一下:
java.util.Collection 是集合接口。
java.util.Collections 是集合工具类。

// 这个可能以后要使用!!!!
List myList = new ArrayList(); // 非线程安全的。

// 变成线程安全的
Collections.synchronizedList(myList); // 这里没有办法看效果,因为多线程没学,你记住先!

// myList集合就是线程安全的了。
myList.add("111");
myList.add("222");
myList.add("333");

5.4 补充泛型机制

只在程序编译阶段起作用,只是给编译器参考的。(运行阶段泛型没用!)

泛型优点:

  1. 集合中存储的元素类型统一了。
  2. 从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”!

缺点:

  1. 导致集合中存储的元素缺乏多样性!

5.4.1 不使用泛型机制

在这之前必须掌握几个点

  1. 迭代器返回的最后类型是Object
  2. 向下转如果是它的子类型的话,就会执行,通过这个关键字obj instanceof Animal,本身所有类都有继承object的

具体可看我这篇文章
java之instanceof用法详细分析(全)

public class GenericTest01 {
    public static void main(String[] args) {

        List myList = new ArrayList();

        // 准备对象
        Cat c = new Cat();
        Bird b = new Bird();

        // 将对象添加到集合当中
        myList.add(c);
        myList.add(b);

        // 遍历集合,取出每个Animal,让它move
        Iterator it = myList.iterator();
        while(it.hasNext()) {
            // 没有这个语法,通过迭代器取出的就是Object
            //Animal a = it.next();

            Object obj = it.next();
            //obj中没有move方法,无法调用,需要向下转型!
            if(obj instanceof Animal){
                Animal a = (Animal)obj;
                a.move();
            }
        }
          
    }
}

class Animal {
    // 父类自带方法
    public void move(){
        System.out.println("动物在移动!");
    }
}

class Cat extends Animal {
    // 特有方法
    public void catchMouse(){
        System.out.println("猫抓老鼠!");
    }
}

class Bird extends Animal {
    // 特有方法
    public void fly(){
        System.out.println("鸟儿在飞翔!");
    }
}

5.4.2 使用泛型机制

区别在于 如果使用了泛型机制的代码
可以不用强转化
但是调用子类的方法还是要进行强转换

// 使用JDK5之后的泛型机制
// 使用泛型List<Animal>之后,表示List集合中只允许存储Animal类型的数据。
// 用泛型来指定集合中存储的数据类型。
List<Animal> myList = new ArrayList<Animal>();

// 指定List集合中只能存储Animal,那么存储String就编译报错了。
// 这样用了泛型之后,集合中元素的数据类型更加统一了。
//myList.add("abc");

Cat c = new Cat();
Bird b = new Bird();

myList.add(c);
myList.add(b);

// 获取迭代器
// 这个表示迭代器迭代的是Animal类型。
Iterator<Animal> it = myList.iterator();
while(it.hasNext()){
    // 使用泛型之后,每一次迭代返回的数据都是Animal类型。
    //Animal a = it.next();
    // 这里不需要进行强制类型转换了。直接调用。
    //a.move();

    // 调用子类型特有的方法还是需要向下转换的!
    Animal a = it.next();
    if(a instanceof Cat) {
        Cat x = (Cat)a;
        x.catchMouse();
    }
    if(a instanceof Bird) {
        Bird y = (Bird)a;
        y.fly();
    }

5.4.3 自动类型推断机制

ArrayList<这里的类型会自动推断>(),前提是JDK8之后才允许

 List<Animal> myList = new ArrayList<>();

添加对应的类型

myList.add(new Animal());
myList.add(new Cat());
myList.add(new Bird());

// 遍历
Iterator<Animal> it = myList.iterator();
while(it.hasNext()){
    Animal a = it.next();
    a.move();
}

如果类型是string类型
则为

List<String> strList = new ArrayList<>();

// 类型不匹配。
//strList.add(new Cat());
strList.add("http://www.126.com");
strList.add("http://www.baidu.com");
strList.add("http://www.bjpowernode.com");

// 类型不匹配。
//strList.add(123);

如果使用了泛型机制则可以不用强转换,返回的类型是对应的

// 遍历
Iterator<String> it2 = strList.iterator();
while(it2.hasNext()){
    // 如果没有使用泛型
    /*
    Object obj = it2.next();
    if(obj instanceof String){
        String ss = (String)obj;
        ss.substring(7);
    }
     */
    // 直接通过迭代器获取了String类型的数据
    String s = it2.next();
    // 直接调用String类的substring方法截取字符串。
    String newString = s.substring(7);
    System.out.println(newString);

5.4.4 自定义泛型

自定义泛型的时候,<> 尖括号中的是一个标识符,随便写。
java源代码中经常出现的是:<E>和<T>。E是Element单词首字母。T是Type单词首字母。

根据自已定义的泛型,泛型可以随便写,但常用的就那两个字母

public class GenericTest03<标识符随便写> {

    public void doSome(标识符随便写 o){
        System.out.println(o);
    }

    public static void main(String[] args) {

        // new对象的时候指定了泛型是:String类型
        GenericTest03<String> gt = new GenericTest03<>();

        // 类型不匹配
        //gt.doSome(100);

        gt.doSome("abc");

        // =============================================================
        GenericTest03<Integer> gt2 = new GenericTest03<>();
        gt2.doSome(100);

        // 类型不匹配
        //gt2.doSome("abc");

        MyIterator<String> mi = new MyIterator<>();
        String s1 = mi.get();

        MyIterator<Animal> mi2 = new MyIterator<>();
        Animal a = mi2.get();

        // 不用泛型就是Object类型。
        /*GenericTest03 gt3 = new GenericTest03();
        gt3.doSome(new Object());*/
    }
}

class MyIterator<T> {
    public T get(){
        return null;
    }
}

5.4.5 补充foreach结构

foreach有一个缺点:没有下标。
在需要使用下标的循环中,不建议使用增强for循环
测试例子

public class ForEachTest01 {
    public static void main(String[] args) {

        // int类型数组
        int[] arr = {432,4,65,46,54,76,54};

        // 遍历数组(普通for循环)
        for(int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }

        // 增强for(foreach)
        // 以下是语法
        /*for(元素类型 变量名 : 数组或集合){
            System.out.println(变量名);
        }*/

        System.out.println("======================================");
        // foreach有一个缺点:没有下标。在需要使用下标的循环中,不建议使用增强for循环。
        for(int data : arr) {
            // data就是数组中的元素(数组中的每一个元素。)
            System.out.println(data);
        }
    }
}

==结合泛型的foreach结构类型例子==

public class ForEachTest02 {
    public static void main(String[] args) {
        // 创建List集合
        List<String> strList = new ArrayList<>();

        // 添加元素
        strList.add("hello");
        strList.add("world!");
        strList.add("kitty!");

        // 遍历,使用迭代器方式
        Iterator<String> it = strList.iterator();
        while(it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }

        // 使用下标方式(只针对于有下标的集合)
        for(int i = 0; i < strList.size(); i++){
            System.out.println(strList.get(i));
        }

        // 使用foreach
        for(String s : strList){ // 因为泛型使用的是String类型,所以是:String s
            System.out.println(s);
        }

        List<Integer> list = new ArrayList<>();
        list.add(100);
        list.add(200);
        list.add(300);
        for(Integer i : list){ // i代表集合中的元素
            System.out.println(i);
        }
    }
}

5.5 Set

无序不可重复

5.5.1 HashSet

  • 存储时顺序和取出的顺序不同
  • 不可重复
  • 放到HashSet集合中的元素实际上是放到HashMap集合的key部分了
// 演示一下HashSet集合特点
Set<String> strs = new HashSet<>();

// 添加元素
strs.add("hello3");
strs.add("hello4");
strs.add("hello1");
strs.add("hello2");
strs.add("hello3");
strs.add("hello3");
strs.add("hello3");
strs.add("hello3");

// 遍历
/*
hello1
hello4
hello2
hello3
 */
for(String s : strs){
    System.out.println(s);

5.5.2 TreeSet

无序不可重复的,但是存储的元素可以自动按照大小顺序排序
这里的无序指的是存进去的顺序和取出来的顺序不同。并且没有下标

// 创建集合对象
Set<String> strs = new TreeSet<>();
// 添加元素
strs.add("A");
strs.add("B");
strs.add("Z");
strs.add("Y");
strs.add("Z");
strs.add("K");
strs.add("M");
// 遍历
/*
    A
    B
    K
    M
    Y
    Z
从小到大自动排序!
 */
for(String s : strs){
    System.out.println(s);

5.6 Map

  • Map和Collection没有继承关系。
  • Map集合以key和value的方式存储数据:键值对

key和value都是引用数据类型
key和value都是存储对象的内存地址
key起到主导的地位,value是key的一个附属品

5.6.1 常用方法

方法 表述
V put(K key, V value) 向Map集合中添加键值对
V get(Object key) 通过key获取value
void clear() 清空Map集合
boolean containsKey(Object key) 判断Map中是否包含某个key
boolean containsValue(Object value) 判断Map中是否包含某个value
boolean isEmpty() 判断Map集合中元素个数是否为0
V remove(Object key) 通过key删除键值对
int size() 获取Map集合中键值对的个数。
Collection values() 获取Map集合中所有的value,返回一个Collection
Set keySet() 获取Map集合所有的key(所有的键是一个set集合)
Set<Map.Entry<K,V>> entrySet() 将Map集合转换成Set集合

主要的代码展示

public class MapTest01 {
    public static void main(String[] args) {
        // 创建Map集合对象
        Map<Integer, String> map = new HashMap<>();
        // 向Map集合中添加键值对
        map.put(1, "zhangsan"); // 1在这里进行了自动装箱。
        map.put(2, "lisi");
        map.put(3, "wangwu");
        map.put(4, "zhaoliu");
        // 通过key获取value
        String value = map.get(2);
        System.out.println(value);
        // 获取键值对的数量
        System.out.println("键值对的数量:" + map.size());
        // 通过key删除key-value
        map.remove(2);
        System.out.println("键值对的数量:" + map.size());
        // 判断是否包含某个key
        // contains方法底层调用的都是equals进行比对的,所以自定义的类型需要重写equals方法。
        System.out.println(map.containsKey(new Integer(4))); // true
        // 判断是否包含某个value
        System.out.println(map.containsValue(new String("wangwu"))); // true

        // 获取所有的value
        Collection<String> values = map.values();
        // foreach
        for(String s : values){
            System.out.println(s);
        }

        // 清空map集合
        map.clear();
        System.out.println("键值对的数量:" + map.size());
        // 判断是否为空
        System.out.println(map.isEmpty()); // true
    }
}

最后一个方法函数重点说明:

假设现在有一个Map集合,如下所示:
    map1集合对象
    key             value
    ----------------------------
    1               zhangsan
    2               lisi
    3               wangwu
    4               zhaoliu

    Set set = map1.entrySet();
    set集合对象
    1=zhangsan 【注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中元素的类型是 Map.Entry<K,V>】
    2=lisi     【Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类,是Map中的静态内部类】
    3=wangwu
    4=zhaoliu ---> 这个东西是个什么?Map.Entry

所谓的静态内部类可以通过下面这个代码进行理解

public class MyClass {

    // 声明一个静态内部类
    private static class InnerClass {

        // 静态方法
        public static void m1(){
            System.out.println("静态内部类的m1方法执行");
        }

        // 实例方法
        public void m2(){
            System.out.println("静态内部类中的实例方法执行!");
        }

    }

    public static void main(String[] args) {

        // 类名叫做:MyClass.InnerClass
        MyClass.InnerClass.m1();

        // 创建静态内部类对象
        MyClass.InnerClass mi =  new MyClass.InnerClass();
        mi.m2();

        // 给一个Set集合
        // 该Set集合中存储的对象是:MyClass.InnerClass类型
        Set<MyClass.InnerClass> set = new HashSet<>();

        // 这个Set集合中存储的是字符串对象。
        Set<String> set2 = new HashSet<>();

        Set<MyMap.MyEntry<Integer, String>> set3 = new HashSet<>();
    }
}


class MyMap {
    public static class MyEntry<K,V> {

    }
}

5.6.2 集合遍历

==第一种方式:获取所有的key,通过遍历key,来遍历value==

Map<Integer, String> map = new HashMap<>();
map.put(1, "zhangsan");
map.put(2, "lisi");
map.put(3, "wangwu");
map.put(4, "zhaoliu");
// 遍历Map集合
// 获取所有的key,所有的key是一个Set集合
Set<Integer> keys = map.keySet();

遍历key,通过key获取value

  1. 迭代器可以
Iterator<Integer> it = keys.iterator();
while(it.hasNext()){
    // 取出其中一个key
    Integer key = it.next();
    // 通过key获取value
    String value = map.get(key);
    System.out.println(key + "=" + value);
}
  1. foreach也可以
for(Integer key : keys){
    System.out.println(key + "=" + map.get(key));
}

==第二种方式:Set<Map.Entry<K,V>> entrySet()==
以上这个方法是把Map集合直接全部转换成Set集合。

// Set集合中元素的类型是:Map.Entry
Set<Map.Entry<Integer,String>> set = map.entrySet();
// 遍历Set集合,每一次取出一个Node
// 迭代器
/*Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
while(it2.hasNext()){
    Map.Entry<Integer,String> node = it2.next();
    Integer key = node.getKey();
    String value = node.getValue();
    System.out.println(key + "=" + value);
}*/

// foreach
// 这种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值。
// 这种方式比较适合于大数据量。
for(Map.Entry<Integer,String> node : set){
    System.out.println(node.getKey() + "--->" + node.getValue());
}

5.6.3 HashMap

  • 底层是哈希表/散列表的数据结构。
  • 哈希表是一个数组和单向链表的结合体。
    数组:在查询方面效率很高,随机增删方面效率很低。
    单向链表:在随机增删方面效率较高,在查询方面效率很低。
    哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点
  • HashMap集合底层的源代码:
public class HashMap{
   // HashMap底层实际上就是一个数组。(一维数组)
Node<K,V>[] table;
    // 静态的内部类HashMap.Node
static class Node<K,V> {
final int hash; // 哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以转换存储成数组的下标。)
final K key; // 存储到Map集合中的那个key
V value; // 存储到Map集合中的那个value
Node<K,V> next; // 下一个节点的内存地址。
    }
}

哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。)

  • HashMap集合的key部分特点:无序,不可重复。
    为什么无序? 因为不一定挂到哪个单向链表上。
    不可重复是怎么保证的? equals方法来保证HashMap集合的key不可重复。如果key重复了,value会覆盖。
  • 哈希表HashMap使用不当时无法发挥性能!

1.假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。这种情况我们成为:散列分布不均匀。 什么是散列分布均匀?假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的, 是散列分布均匀的。
2.假设将所有的hashCode()方法返回值都设定为不一样的值,这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。也是散列分布不均匀。
散列分布均匀需要你重写hashCode()方法时有一定的技巧。

  • HashMap集合的默认初始化容量是16,默认加载因子是0.75
    这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容。==HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。==

==最主要掌握的是:==

 map.put(k,v)
 v = map.get(k)
 以上这两个方法的实现原理

在这里插入图片描述

测试HashMap集合key部分的元素特点

// Integer是key,它的hashCode和equals都重写了。
Map<Integer,String> map = new HashMap<>();
map.put(1111, "zhangsan");
map.put(6666, "lisi");
map.put(7777, "wangwu");
map.put(2222, "zhaoliu");
map.put(2222, "king"); //key重复的时候value会自动覆盖。

System.out.println(map.size()); // 4

// 遍历Map集合
Set<Map.Entry<Integer,String>> set = map.entrySet();
for(Map.Entry<Integer,String> entry : set){
    // 验证结果:HashMap集合key部分元素:无序不可重复。
    System.out.println(entry.getKey() + "=" + entry.getValue());
  • 放在HashMap集合key部分的元素其实就是放到HashSet集合中了。所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法。

==此处重点讲解一下==
向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法!
equals方法有可能调用,也有可能不调用。
put(k,v)和get(k)举例,什么时候equals不会调用? k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。数组下标位置上如果是null,equals不需要执行。

注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写。
并且equals方法返回如果是true,hashCode()方法返回的值必须一样。
equals方法返回true表示两个对象相同,在同一个单向链表上比较。
那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。
所以hashCode()方法的返回值也应该相同。

对于哈希表数据结构来说:
    如果o1和o2的hash值相同,一定是放到同一个单向链表上。
    当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。

重写equals与hashcode两者进行比较的测试代码

 Student s1 = new Student("zhangsan");
        Student s2 = new Student("zhangsan");

        // 重写equals方法之前是false
        //System.out.println(s1.equals(s2)); // false

        // 重写equals方法之后是true
        System.out.println(s1.equals(s2)); //true (s1和s2表示相等)

        System.out.println("s1的hashCode=" + s1.hashCode()); //284720968 (重写hashCode之后-1432604525)
        System.out.println("s2的hashCode=" + s2.hashCode()); //122883338 (重写hashCode之后-1432604525)

        // s1.equals(s2)结果已经是true了,表示s1和s2是一样的,相同的,那么往HashSet集合中放的话,
        // 按说只能放进去1个。(HashSet集合特点:无序不可重复)
        Set<Student> students = new HashSet<>();
        students.add(s1);
        students.add(s2);
        System.out.println(students.size()); // 这个结果按说应该是1. 但是结果是2.显然不符合HashSet集合存储特点。怎么办?

重写的类主要代码

public class Student {
    private String name;

    public Student() {
    }

    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    // hashCode

    // equals(如果学生名字一样,表示同一个学生。)
    /*public boolean equals(Object obj){
        if(obj == null || !(obj instanceof Student)) return false;
        if(obj == this) return true;
        Student s = (Student)obj;
        return this.name.equals(s.name);
    }*/

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

5.6.4 Hashtable

  • Hashtable的key和value都是不能为null的。

HashMap集合的key和value都是可以为null的。

  • Hashtable方法都带有synchronized:线程安全的。

线程安全有其它的方案,这个Hashtable对线程的处理
导致效率较低,使用较少了。

  • Hashtable和HashMap一样,底层都是哈希表数据结构。

Hashtable的初始化容量是11,默认加载因子是:0.75f
Hashtable的扩容是:原容量 * 2 + 1

这个会出错

 Map map = new Hashtable();
//map.put(null, "123");
map.put(100, null);

这个不会出错

Map map = new HashMap();

// HashMap集合允许key为null
map.put(null, null);
System.out.println(map.size()); // 1

// key重复的话value是覆盖!
map.put(null, 100);
System.out.println(map.size()); //1

// 通过key获取value
System.out.println(map.get(null)); // 100

5.6.5 properties

Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。
Properties被称为属性类对象。
Properties是线程安全的。

// 创建一个Properties对象
Properties pro = new Properties();

// 需要掌握Properties的两个方法,一个存,一个取。
pro.setProperty("url", "jdbc:mysql://localhost:3306/bjpowernode");
pro.setProperty("driver","com.mysql.jdbc.Driver");
pro.setProperty("username", "root");
pro.setProperty("password", "123");

// 通过key获取value
String url = pro.getProperty("url");
String driver = pro.getProperty("driver");
String username = pro.getProperty("username");
String password = pro.getProperty("password");

System.out.println(url);
System.out.println(driver);
System.out.println(username);
System.out.println(password);

5.6.6 TreeMap

  • TreeSet集合底层实际上是一个TreeMap

TreeMap集合底层是一个二叉树。
放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了。

  • TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。称为:可排序集合。
// 创建一个TreeSet集合
TreeSet<String> ts = new TreeSet<>();
// 添加String
ts.add("zhangsan");
ts.add("lisi");
ts.add("wangwu");
ts.add("zhangsi");
ts.add("wangliu");
// 遍历
for(String s : ts){
    // 按照字典顺序,升序!
    System.out.println(s);
}

TreeSet<Integer> ts2 = new TreeSet<>();
ts2.add(100);
ts2.add(200);
ts2.add(900);
ts2.add(800);
ts2.add(600);
ts2.add(10);
for(Integer elt : ts2){
    // 升序!
    System.out.println(elt);

==对自定义的类型来说,TreeSet可以排序吗?

以下程序中对于Person类型来说,无法排序。因为没有指定Person对象之间的比较规则==
以下程序运行的时候出现了这个异常:
java.lang.ClassCastException:
    class com.bjpowernode.javase.collection.Person
    cannot be cast to class java.lang.Comparable

出现这个异常的原因是:

Person类没有实现java.lang.Comparable接口。

程序如下

public class TreeSetTest03 {
    public static void main(String[] args) {
        person p1 = new person(32);
        //System.out.println(p1);
        person p2 = new person(20);
        person p3 = new person(30);
        person p4 = new person(25);

        // 创建TreeSet集合
        TreeSet<person> persons = new TreeSet<>();
        // 添加元素
        persons.add(p1);
        persons.add(p2);
        persons.add(p3);
        persons.add(p4);

        // 遍历
        for (person p : persons){
            System.out.println(p);
        }
    }
}

class Person {
    int age;
    public Person(int age){
        this.age = age;
    }

    // 重写toString()方法
    public String toString(){
        return "Person[age="+age+"]";
    }
}

==第一种改写==
放在TreeSet集合中的元素需要实现java.lang.Comparable接口
需要在这个方法中编写比较的逻辑,或者说比较的规则,按照什么进行比较!

class Customer implements Comparable<Customer>{

    int age;
    public Customer(int age){
        this.age = age;
    }

    // k.compareTo(t.key)
    // 拿着参数k和集合中的每一个k进行比较,返回值可能是>0 <0 =0
    // 比较规则最终还是由程序员指定的:例如按照年龄升序。或者按照年龄降序。
    @Override
    public int compareTo(Customer c) { // c1.compareTo(c2);
       
        //return this.age - c.age; // =0 >0 <0
        return c.age - this.age;
    }

    public String toString(){
        return "Customer[age="+age+"]";
    }
}


//具体改写的接口也可以这样定义
         // this是c1
        // c是c2
        // c1和c2比较的时候,就是this和c比较。
        /*int age1 = this.age;
        int age2 = c.age;
        if(age1 == age2){
            return 0;
        } else if(age1 > age2) {
            return 1;
        } else {
            return -1;
        }*/

实现类如下

Customer c1 = new Customer(32);
Customer c2 = new Customer(20);
Customer c3 = new Customer(30);
Customer c4 = new Customer(25);

// 创建TreeSet集合
TreeSet<Customer> customers = new TreeSet<>();
// 添加元素
customers.add(c1);
customers.add(c2);
customers.add(c3);
customers.add(c4);

// 遍历
for (Customer c : customers){
    System.out.println(c);

另一个完整的案例可以参照如下程序
先按照年龄升序,如果年龄一样的再按照姓名升序

public class TreeSetTest05 {
    public static void main(String[] args) {
        TreeSet<Vip> vips = new TreeSet<>();
        vips.add(new Vip("zhangsi", 20));
        vips.add(new Vip("zhangsan", 20));
        vips.add(new Vip("king", 18));
        vips.add(new Vip("soft", 17));
        for(Vip vip : vips){
            System.out.println(vip);
        }
    }
}

class Vip implements Comparable<Vip>{
    String name;
    int age;

    public Vip(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Vip{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    /*
    compareTo方法的返回值很重要:
        返回0表示相同,value会覆盖。
        返回>0,会继续在右子树上找。【10 - 9 = 1 ,1 > 0的说明左边这个数字比较大。所以在右子树上找。】
        返回<0,会继续在左子树上找。
     */
    @Override
    public int compareTo(Vip v) {
        // 写排序规则,按照什么进行比较。
        if(this.age == v.age){
            // 年龄相同时按照名字排序。
            // 姓名是String类型,可以直接比。调用compareTo来完成比较。
            return this.name.compareTo(v.name);
        } else {
            // 年龄不一样
            return this.age - v.age;
        }
    }
}

==第二种改写的方式==
使用比较器的方式
比较器要传入参数进去

TreeSet<WuGui> wuGuis = new TreeSet<>();//这样不行,没有通过构造方法传递一个比较器进去

完整代码案例如下

public class TreeSetTest06 {
    public static void main(String[] args) {
        // 创建TreeSet集合的时候,需要使用这个比较器。
        // TreeSet<WuGui> wuGuis = new TreeSet<>();//这样不行,没有通过构造方法传递一个比较器进去。

        // 给构造方法传递一个比较器。
        //TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());

        // 大家可以使用匿名内部类的方式(这个类没有名字。直接new接口。)
        TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() {
            @Override
            public int compare(WuGui o1, WuGui o2) {
                return o1.age - o2.age;
            }
        });

        wuGuis.add(new WuGui(1000));
        wuGuis.add(new WuGui(800));
        wuGuis.add(new WuGui(810));

        for(WuGui wuGui : wuGuis){
            System.out.println(wuGui);
        }
    }
}

// 乌龟
class WuGui{

    int age;

    public WuGui(int age){
        this.age = age;
    }

    @Override
    public String toString() {
        return "小乌龟[" +
                "age=" + age +
                ']';
    }
}

// 单独在这里编写一个比较器
// 比较器实现java.util.Comparator接口。(Comparable是java.lang包下的。Comparator是java.util包下的。)
/*
class WuGuiComparator implements Comparator<WuGui> {

    @Override
    public int compare(WuGui o1, WuGui o2) {
        // 指定比较规则
        // 按照年龄排序
        return o1.age - o2.age;
    }
}
 */

最终的结论:

放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
    第一种:放在集合中的元素实现java.lang.Comparable接口。
    第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。
    

Comparable和Comparator怎么选择呢?

当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。

Comparator接口的设计符合OCP原则。

5.7 collections工具类

java.util.Collection 集合接口
java.util.Collections 集合工具类,方便集合的操作。

==线程安全的集合转换为非线程安全==

// ArrayList集合不是线程安全的。
List<String> list = new ArrayList<>();

// 变成线程安全的
Collections.synchronizedList(list);

==运用排序的函数==

// 排序
list.add("abf");
list.add("abx");
list.add("abc");
list.add("abe");

Collections.sort(list);
for(String s : list){
    System.out.println(s);
}

==自定义的要重写Comparable接口==

List<WuGui2> wuGuis = new ArrayList<>();
wuGuis.add(new WuGui2(1000));
wuGuis.add(new WuGui2(8000));
wuGuis.add(new WuGui2(500));
// 注意:对List集合中元素排序,需要保证List集合中的元素实现了:Comparable接口。
Collections.sort(wuGuis);
for(WuGui2 wg : wuGuis){
    System.out.println(wg);
}

class WuGui2 implements Comparable<WuGui2>{
    int age;
    public WuGui2(int age){
        this.age = age;
    }

    @Override
    public int compareTo(WuGui2 o) {
        return this.age - o.age;
    }

    @Override
    public String toString() {
        return "WuGui2{" +
                "age=" + age +
                '}';
    }
}

==set集合如何进行排序==

// 对Set集合怎么排序呢?
Set<String> set = new HashSet<>();
set.add("king");
set.add("kingsoft");
set.add("king2");
set.add("king1");
// 将Set集合转换成List集合
List<String> myList = new ArrayList<>(set);
Collections.sort(myList);
for(String s : myList) {
    System.out.println(s);
}

// 这种方式也可以排序。
//Collections.sort(list集合, 比较器对象);

5.8 总结

一共有以上的集合元素
注意考察代码功能是

  • 每个集合对象的创建(new)
  • 向集合中添加元素
  • 从集合中取出某个元素
  • 遍历集合

5.8.1 概念总结

关于概念的总结,map这一块比较多,因为面试实在是太常考了

HashSet 如何检查重复
当你把对象加⼊ HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加⼊的位置,同时也会与其他加⼊的对象的 hashcode 值作⽐较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调⽤ equals() ⽅法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让加⼊操作成功。

关于map的比较:

HashMap 和 Hashtable 的区别

  1. 线程是否安全: HashMap 是⾮线程安全的,HashTable 是线程安全的,因为 HashTable 内部的⽅法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使⽤ConcurrentHashMap 吧!);
  2. 效率: 因为线程安全的问题,HashMap 要⽐ HashTable 效率⾼⼀点。另外,HashTable 基本被淘汰,不要在代码中使⽤它
  3. 对 Null key 和 Null value 的⽀持: HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有⼀个,null 作为值可以有多个;HashTable 不允许有 null 键和 null 值,否则会抛出 NullPointerException
  4. 初始容量⼤⼩和每次扩充容量⼤⼩的不同 : 创建时如果不指定容量初始值,Hashtable 默

认的初始⼤⼩为 11,之后每次扩充,容量变为原来的 2n+1。HashMap 默认的初始化⼤⼩为
16。之后每次扩充,容量变为原来的 2 倍。

HashMap 的底层实现
JDK1.8 之前 HashMap 底层是 数组和链表 结合在⼀起使⽤也就是 链表散列

JDK1.8 之后在解决哈希冲突时有了较⼤的变化,当链表⻓度⼤于阈值(默认为8)(将链表转换成红⿊树前会判断,如果当前数组的⻓度⼩于 64,那么会选择先进⾏数组扩容,⽽不是转换为红⿊树)时,将链表转化为红⿊树,以减少搜索时间

TreeMap、TreeSet 以及 JDK1.8 之后的 HashMap 底层都⽤到了红⿊树。红⿊树就是为了解决⼆叉
查找树的缺陷,因为⼆叉查找树在某些情况下会退化成⼀个线性结构

HashMap 的⻓度为什么是 2 的幂次⽅

为了能让 HashMap 存取⾼效,尽量较少碰撞,也就是要尽量把数据分配均匀
⽤之前还要先做对数组的⻓度取模运算,得到的余数才能⽤来要存放的位置也就是对应的数组下标。这个数组下标的计算⽅法是“ (n - 1) & hash ”。

hash%length==hash&(length-1)的前提是 length 是 2的 幂次⽅

ConcurrentHashMap 和 Hashtable 的区别

  • 底层数据结构: JDK1.7 的 ConcurrentHashMap 底层采⽤ 分段的数组+链表 实现,JDK1.8 采⽤的数据结构跟 HashMap1.8 的结构⼀样,数组+链表/红⿊⼆叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采⽤ 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突⽽存在的
  • 实现线程安全的⽅式(重要): ① 在 JDK1.7 的时候,ConcurrentHashMap(分段锁) 对整个桶数组进⾏了分割分段(Segment),每⼀把锁只锁容器其中⼀部分数据,多线程访问容器⾥不同数据段的数据,就不会存在锁竞争,提⾼并发访问率。 到了 JDK1.8 的时候已经摒弃了Segment 的概念,⽽是直接⽤ Node 数组+链表+红⿊树的数据结构来实现,并发控制使⽤synchronized 和 CAS 来操作。(JDK1.6 以后 对 synchronized 锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② Hashtable(同⼀把锁) :使⽤ synchronized 来保证线程安全,效率⾮常低下。当⼀个线程访问同步⽅法时,其他线程也访问同步⽅法,可能会进⼊阻塞或轮询状态,如使⽤ put 添加元素,另⼀个线程不能使⽤ put 添加元素,也不能使⽤get,竞争会越来越激烈效率越低

5.8.2 代码总结

==ArrayList和linkedlist==

// 创建集合对象
//ArrayList<String> list = new ArrayList<>();
LinkedList<String> list = new LinkedList<>();
// 添加元素
list.add("zhangsan");
list.add("lisi");
list.add("wangwu");
// 从集合中取出某个元素
// List集合有下标
String firstElt = list.get(0);
System.out.println(firstElt);
// 遍历(下标方式)
for(int i = 0; i < list.size(); i++){
    String elt = list.get(i);
    System.out.println(elt);
}
// 遍历(迭代器方式,这个是通用的,所有Collection都能用)
Iterator<String> it = list.iterator();
while(it.hasNext()){
    System.out.println(it.next());
}

// while循环修改为for循环
/*for(Iterator<String> it2 = list.iterator(); it2.hasNext(); ){
    System.out.println("====>" + it2.next());
}*/

// 遍历(foreach方式)
for(String s : list){
    System.out.println(s);

==HashSet==

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

        // 添加元素
        set.add("abc");
        set.add("def");
        set.add("king");

        // set集合中的元素不能通过下标取了。没有下标
        // 遍历集合(迭代器)
        Iterator<String> it = set.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }

        // 遍历集合(foreach)
        for(String s : set){
            System.out.println(s);
        }

        set.add("king");
        set.add("king");
        set.add("king");
        System.out.println(set.size()); //3 (后面3个king都没有加进去。)

        set.add("1");
        set.add("10");
        set.add("2");

        for(String s : set){
            System.out.println("--->" + s);
        }

        // 创建Set集合,存储Student数据
        Set<Student> students = new HashSet<>();

        Student s1 = new Student(111, "zhangsan");
        Student s2 = new Student(222, "lisi");
        Student s3 = new Student(111, "zhangsan");

        students.add(s1);
        students.add(s2);
        students.add(s3);

        System.out.println(students.size()); // 2

        // 遍历
        for(Student stu : students){
            System.out.println(stu);
        }

    }
}

class Student {
    int no;
    String name;

    public Student() {
    }

    public Student(int no, String name) {
        this.no = no;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return no == student.no &&
                Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(no, name);
    }
}

==hashmap==

// 创建Map集合
Map<Integer, String> map = new HashMap<>();
// 添加元素
map.put(1, "zhangsan");
map.put(9, "lisi");
map.put(10, "wangwu");
map.put(2, "king");
map.put(2, "simth"); // key重复value会覆盖。
// 获取元素个数
System.out.println(map.size());
// 取key是2的元素
System.out.println(map.get(2)); // smith
// 遍历Map集合很重要,几种方式都要会。
// 第一种方式:先获取所有的key,遍历key的时候,通过key获取value
Set<Integer> keys = map.keySet();
for(Integer key : keys){
    System.out.println(key + "=" + map.get(key));
}

// 第二种方式:是将Map集合转换成Set集合,Set集合中每一个元素是Node
// 这个Node节点中有key和value
Set<Map.Entry<Integer,String>> nodes = map.entrySet();
for(Map.Entry<Integer,String> node : nodes){
    System.out.println(node.getKey() + "=" + node.getValue());
}

==properties==

// 创建对象
Properties pro = new Properties();
// 存
pro.setProperty("username", "test");
pro.setProperty("password", "test123");
// 取
String username = pro.getProperty("username");
String password = pro.getProperty("password");
System.out.println(username);
System.out.println(password);

==TreeSet==
这个比较特殊

public class TreeSetTest {
    public static void main(String[] args) {
        // 集合的创建(可以测试以下TreeSet集合中存储String、Integer的。这些类都是SUN写好的。)
        //TreeSet<Integer> ts = new TreeSet<>();

        // 编写比较器可以改变规则。
        TreeSet<Integer> ts = new TreeSet<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1; // 自动拆箱
            }
        });

        // 添加元素
        ts.add(1);
        ts.add(100);
        ts.add(10);
        ts.add(10);
        ts.add(10);
        ts.add(10);
        ts.add(0);

        // 遍历(迭代器方式)
        Iterator<Integer> it = ts.iterator();
        while(it.hasNext()) {
            Integer i = it.next();
            System.out.println(i);
        }

        // 遍历(foreach)
        for(Integer x : ts){
            System.out.println(x);
        }

        // TreeSet集合中存储自定义类型
        TreeSet<A> atree = new TreeSet<>();

        atree.add(new A(100));
        atree.add(new A(200));
        atree.add(new A(500));
        atree.add(new A(300));
        atree.add(new A(400));
        atree.add(new A(1000));

        // 遍历
        for(A a : atree){
            System.out.println(a);
        }

        //TreeSet<B> btree = new TreeSet<>(new BComparator());
        // 匿名内部类方式。
        TreeSet<B> btree = new TreeSet<>(new Comparator<B>() {
            @Override
            public int compare(B o1, B o2) {
                return o1.i - o2.i;
            }
        });

        btree.add(new B(500));
        btree.add(new B(100));
        btree.add(new B(200));
        btree.add(new B(600));
        btree.add(new B(300));
        btree.add(new B(50));

        for(B b : btree){
            System.out.println(b);
        }
    }
}

// 第一种方式:实现Comparable接口
class A implements Comparable<A>{
    int i;

    public A(int i){
        this.i = i;
    }

    @Override
    public String toString() {
        return "A{" +
                "i=" + i +
                '}';
    }

    @Override
    public int compareTo(A o) {
        //return this.i - o.i;
        return o.i - this.i;
    }
}

class B {
    int i;
    public B(int i){
        this.i = i;
    }

    @Override
    public String toString() {
        return "B{" +
                "i=" + i +
                '}';
    }
}

// 比较器
class BComparator implements Comparator<B> {

    @Override
    public int compare(B o1, B o2) {
        return o1.i - o2.i;
    }
}

6. IO

I : Input
O : Output
通过IO可以完成硬盘文件的读和写

==流的分类:==
一种方式是按照流的方向进行分类:以内存作为参照物,往内存中去,叫做输入(Input)。或者叫做读(Read)。从内存中出来,叫做输出(Output)。或者叫做写(Write)。
另一种方式是按照读取数据方式不同进行分类:按照字节的方式读取数据,按照字符的方式读取数据的

  • java.io.InputStream 字节输入流
  • java.io.OutputStream 字节输出流
  • java.io.Reader 字符输入流
  • java.io.Writer 字符输出流

以上都是都是抽象类。(abstract class),所有的流都实现java.io.Closeable接口,都是可关闭的,都有close()方法。所有的输出流都实现java.io.Flushable接口,都是可刷新的,都有flush()方法,这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)刷新的作用就是清空管道。注意:如果没有flush()可能会导致丢失数据

    文件专属:
        java.io.FileInputStream(掌握)
        java.io.FileOutputStream(掌握)
        java.io.FileReader
        java.io.FileWriter

    转换流:(将字节流转换成字符流)
        java.io.InputStreamReader
        java.io.OutputStreamWriter

    缓冲流专属:
        java.io.BufferedReader
        java.io.BufferedWriter
        java.io.BufferedInputStream
        java.io.BufferedOutputStream

    数据流专属:
        java.io.DataInputStream
        java.io.DataOutputStream

    标准输出流:
        java.io.PrintWriter
        java.io.PrintStream(掌握)

    对象专属流:
        java.io.ObjectInputStream(掌握)
        java.io.ObjectOutputStream(掌握)

==只要“类名”以Stream结尾的都是字节流。以“Reader/Writer”结尾的都是字符流==

6.1 FileInputStream

采用这种int readData = fis.read(); 一次读取一个字节byte,这样内存和硬盘交互太频繁,基本上时间/资源都耗费,读取文件路径的时候使用绝对路径,(IDEA会自动把\编程\,因为java中\表示转义,也可以使用/)
案例如下:

public class FileInputStreamTest02 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("D:\\temp");

            /*while(true) {
                int readData = fis.read();
                if(readData == -1) {
                    break;
                }
                System.out.println(readData);
            }*/

            // 改造while循环
            int readData = 0;
            while((readData = fis.read()) != -1){
                System.out.println(readData);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

为了改进一次只读取一个数据
可以使用一次最多读取 b.length 个字节。减少硬盘和内存的交互,提高程序的执行效率。往byte[]数组当中读。
相对路径在工程Project的根就是IDEA的默认当前路径

// 开始读,采用byte数组,一次读取多个字节。最多读取“数组.length”个字节。
byte[] bytes = new byte[4]; // 准备一个4个长度的byte数组,一次最多读取4个字节。
// 这个方法的返回值是:读取到的字节数量。(不是字节本身)
int readCount = fis.read(bytes);
System.out.println(readCount); // 第一次读到了4个字节。
// 将字节数组全部转换成字符串
//System.out.println(new String(bytes)); // abcd
// 不应该全部都转换,应该是读取了多少个字节,转换多少个。
System.out.println(new String(bytes,0, readCount));

readCount = fis.read(bytes); // 第二次只能读取到2个字节。
System.out.println(readCount); // 2
// 将字节数组全部转换成字符串
//System.out.println(new String(bytes)); // efcd
// 不应该全部都转换,应该是读取了多少个字节,转换多少个。
System.out.println(new String(bytes,0, readCount));

readCount = fis.read(bytes); // 1个字节都没有读取到返回-1
System.out.println(readCount); // -1

为了改进上面这种算法采用有多少读取多少

FileInputStream fis = null;
try {
    fis = new FileInputStream("chapter23/src/tempfile3");
    // 准备一个byte数组
    byte[] bytes = new byte[4];
    /*while(true){
        int readCount = fis.read(bytes);
        if(readCount == -1){
            break;
        }
        // 把byte数组转换成字符串,读到多少个转换多少个。
        System.out.print(new String(bytes, 0, readCount));
    }*/

    int readCount = 0;
    while((readCount = fis.read(bytes)) != -1) {
        System.out.print(new String(bytes, 0, readCount));
    }

==模板==

byte[] bytes = new byte[4];
int readCount = 0;
while((readCount = fis.read(bytes)) != -1) {
    System.out.print(new String(bytes, 0, readCount));
}

==其他常用方法==

方法 功能
int available() 返回流当中剩余的没有读到的字节数量
long skip(long n) 跳过几个字节不读

这些方法的具体使用如下
主要是一次性获取
不太适合太大的文件,因为byte[]数组不能太大。

byte[] bytes = new byte[fis.available()];

int readCount = fis.read(bytes); // 6
System.out.println(new String(bytes)); // abcdef

// skip跳过几个字节不读取,这个方法也可能以后会用!
fis.skip(3);
System.out.println(fis.read()); //100

6.2 FileOutputStream

myfile文件不存在的时候会自动新建fos = new FileOutputStream("myfile");

如果没有true追加的形式,则会直接覆盖掉原来的东西

// 以追加的方式在文件末尾写入。不会清空原文件内容。
fos = new FileOutputStream("chapter23/src/tempfile3", true);
// 开始写。
byte[] bytes = {97, 98, 99, 100};
// 将byte数组全部写出!
fos.write(bytes); // abcd
// 将byte数组的一部分写出!
fos.write(bytes, 0, 2); // 再写出ab

将字符串转换为byte数组,进行写入

// 字符串
String s = "我是一个中国人,我骄傲!!!";
// 将字符串转换成byte数组。
byte[] bs = s.getBytes();
// 写
fos.write(bs);

每一次的写入都要记得刷新fos.flush();
而且在finally之后要关闭

if (fos != null) {
    try {
        fos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

6.3 FileReader

文件字符输入流,只能读取普通文本。
读取文本内容时,比较方便,快捷。
主要的核心代码如下
==读取指定的单一字符遍历输出:==

//准备一个char数组
char[] chars = new char[4];
// 往char数组中读
reader.read(chars); // 按照字符的方式读取:第一次e,第二次f,第三次 风....
for(char c : chars) {
    System.out.println(c);
}

或者是根据文件获取的字符数一直while,比较方便,当为-1的时候结束输出

// 开始读
char[] chars = new char[4]; // 一次读取4个字符
int readCount = 0;
while((readCount = reader.read(chars)) != -1) {
    System.out.print(new String(chars,0,readCount));
}

6.4 FileReader

FileWriter:
文件字符输出流。写。只能输出普通文本。
核心代码如下:
追加与不追加,覆盖的区别

// 创建文件字符输出流对象
//out = new FileWriter("file");
out = new FileWriter("file", true);

书写的核心文件如下

// 开始写。
char[] chars = {'我','是','中','国','人'};
out.write(chars);
out.write(chars, 2, 3);

也可以单独输出一个字符串

out.write("我是一名java软件工程师!");
// 写出一个换行符。
out.write("\n");
out.write("hello world!");

每一次的写入都要记得刷新fos.flush();
而且在finally之后要关闭

if (out != null) {
    try {
        out.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

6.5 文件复制

==字节流的文件复制==
使用FileInputStream + FileOutputStream完成文件的拷贝。
拷贝的过程应该是一边读,一边写。
使用以上的字节流拷贝文件的时候,文件类型随意,万能的。什么样的文件都能拷贝。
完整代码如下,==模板==

public class Copy01 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            // 创建一个输入流对象
            fis = new FileInputStream("");
            // 创建一个输出流对象
            fos = new FileOutputStream("");

            // 最核心的:一边读,一边写
            byte[] bytes = new byte[1024 * 1024]; // 1MB(一次最多拷贝1MB。)
            int readCount = 0;
            while((readCount = fis.read(bytes)) != -1) {
                fos.write(bytes, 0, readCount);
            }

            // 刷新,输出流最后要刷新
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 分开try,不要一起try。
            // 一起try的时候,其中一个出现异常,可能会影响到另一个流的关闭。
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

注意区分两者的不同
==字符流的文件复制==
使用FileReader FileWriter进行拷贝的话,只能拷贝“普通文本”文件。
完整代码如下

public class Copy02 {
    public static void main(String[] args) {
        FileReader in = null;
        FileWriter out = null;
        try {
            // 读
            in = new FileReader("chapter23/src/com/bjpowernode/java/io/Copy02.java");
            // 写
            out = new FileWriter("Copy02.java");

            // 一边读一边写:
            char[] chars = new char[1024 * 512]; // 1MB
            int readCount = 0;
            while((readCount = in.read(chars)) != -1){
                out.write(chars, 0, readCount);
            }

            // 刷新
            out.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

6.6 带缓冲区字符流

6.6.1 BufferedReader

==带有缓冲区的字符输入流==
通过这个 BufferedReader br = new BufferedReader(reader);本身这个函数内部是不可以使用reader的,因为是抽象类,必须要找抽象类的实现类,所以在这之前要 FileReader reader = new FileReader("Copy02.java");

当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。
像当前这个程序来说:FileReader就是一个节点流。BufferedReader就是包装流/处理流。

关闭的时候只需要关闭外层流就可,因为内部流in会自动关闭

具体的测试文件如下

FileReader reader = new FileReader("Copy02.java");

BufferedReader br = new BufferedReader(reader);

// 读一行
String firstLine = br.readLine();
System.out.println(firstLine);

// br.readLine()方法读取一个文本行,但不带换行符。
String s = null;
while((s = br.readLine()) != null){
    System.out.println(s);
}

// 关闭流
// 对于包装流来说,只需要关闭最外层流就行,里面的节点流会自动关闭。(可以看源代码。)
br.close();

如果传输的是字节流,需要将字节流转换为字符流使用InputStreamReader
具体测试代码如下


FileInputStream in = new FileInputStream("Copy02.java");

// 通过转换流转换(InputStreamReader将字节流转换成字符流。)
// in是节点流。reader是包装流。
InputStreamReader reader = new InputStreamReader(in);

// 这个构造方法只能传一个字符流。不能传字节流。

BufferedReader br = new BufferedReader(reader);*/

// 合并
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("Copy02.java")));

String line = null;
while((line = br.readLine()) != null){
    System.out.println(line);
}

// 关闭最外层
br.close();

6.6.1 BufferedWriter

只要是写的,都要在后面记得刷新

带有缓冲区的字符输出流
//BufferedWriter out = new BufferedWriter(new FileWriter("copy"));


// 开始写。
out.write("hello world!");
out.write("\n");
out.write("hello kitty!");
// 刷新
out.flush();
// 关闭最外层
out.close();

如果是带有字节流的,可以将其转换为字符流
true为追加文件末尾而不是覆盖

BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("copy", true)));

6.7 数据流

==字节数据输出流==
java.io.DataOutputStream:数据专属的流。
这个流可以将数据连同数据的类型一并写入文件。
注意:这个文件不是普通文本文档。(这个文件使用记事本打不开。)

// 创建数据专属的字节输出流
本身是接口类,不可以直接new,必须接一个接口实现类

 DataOutputStream dos = new DataOutputStream(new FileOutputStream("data"));

比如测试代码如下

// 写数据
byte b = 100;
short s = 200;
int i = 300;
long l = 400L;
float f = 3.0F;
double d = 3.14;
boolean sex = false;
char c = 'a';
// 写
dos.writeByte(b); // 把数据以及数据的类型一并写入到文件当中。
dos.writeShort(s);
dos.writeInt(i);
dos.writeLong(l);
dos.writeFloat(f);
dos.writeDouble(d);
dos.writeBoolean(sex);
dos.writeChar(c);

// 刷新
dos.flush();
// 关闭最外层
dos.close();

==字节数据输入流==
DataInputStream:数据字节输入流。
DataOutputStream写的文件,只能使用DataInputStream去读。并且读的时候你需要提前知道写入的顺序。
读的顺序需要和写的顺序一致。才可以正常取出数据。
测试文件同理如下

DataInputStream dis = new DataInputStream(new FileInputStream("data"));
// 开始读
byte b = dis.readByte();
short s = dis.readShort();
int i = dis.readInt();
long l = dis.readLong();
float f = dis.readFloat();
double d = dis.readDouble();
boolean sex = dis.readBoolean();
char c = dis.readChar();

System.out.println(b);
System.out.println(s);
System.out.println(i + 1000);
System.out.println(l);
System.out.println(f);
System.out.println(d);
System.out.println(sex);
System.out.println(c);

dis.close();

6.8 File

  • File类不能完成文件的读和写。
  • 一个File对象有可能对应的是目录,也可能是文件。
    File只是一个路径名的抽象表示形式。

    创建一个File对象

File f1 = new File("D:\\file");

判断是否存在!

System.out.println(f1.exists());
// 如果D:\file不存在,则以文件的形式创建出来
if(!f1.exists()) {
    // 以文件形式新建
    f1.createNewFile();
}

// 如果D:\file不存在,则以目录的形式创建出来
if(!f1.exists()) {
    // 以目录的形式新建。
    f1.mkdir();
}

// 可以创建多重目录吗?
File f2 = new File("D:/a/b/c/d/e/f");
if(!f2.exists()) {
    // 多重目录的形式新建。
    f2.mkdirs();
}


File f3 = new File("D:\\course\\01-开课\\学习方法.txt");
// 获取文件的父路径
String parentPath = f3.getParent();
System.out.println(parentPath); //D:\course\01-开课
File parentFile = f3.getParentFile();
System.out.println("获取绝对路径:" + parentFile.getAbsolutePath());

File f4 = new File("copy");
System.out.println("绝对路径:" + f4.getAbsolutePath()); // C:\Users\Administrator\IdeaProjects\javase\copy

==常用方法==
示例代码

File f1 = new File("D:\\course\\01-开课\\开学典礼.ppt");
// 获取文件名
System.out.println("文件名:" + f1.getName());

// 判断是否是一个目录
System.out.println(f1.isDirectory()); // false

// 判断是否是一个文件
System.out.println(f1.isFile()); // true

// 获取文件最后一次修改时间
long haoMiao = f1.lastModified(); // 这个毫秒是从1970年到现在的总毫秒数。
// 将总毫秒数转换成日期?????
Date time = new Date(haoMiao);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String strTime = sdf.format(time);
System.out.println(strTime);

// 获取文件大小
System.out.println(f1.length()); //216064字节。

==获取所有子文件==
使用File中的listFiles方法
使用 File[] 数组来接收

// File[] listFiles()
// 获取当前目录下所有的子文件。
File f = new File("D:\\course\\01-开课");
File[] files = f.listFiles();
// foreach
for(File file : files){
    //System.out.println(file.getAbsolutePath());
    System.out.println(file.getName());

6.9 拷贝目录文件

  • 判断是否为其文件,如果是文件则一边读一边写传输数据
  • 判断是否为其目录,如果是目录,再根据其子目录进行传输遍历
/*
拷贝目录
 */
public class CopyAll {
    public static void main(String[] args) {
        // 拷贝源
        File srcFile = new File("D:\\course\\02-JavaSE\\document");
        // 拷贝目标
        File destFile = new File("C:\\a\\b\\c");
        // 调用方法拷贝
        copyDir(srcFile, destFile);

    }

    /**
     * 拷贝目录
     * @param srcFile 拷贝源
     * @param destFile 拷贝目标
     */
    private static void copyDir(File srcFile, File destFile) {
        if(srcFile.isFile()) {
            // srcFile如果是一个文件的话,递归结束。
            // 是文件的时候需要拷贝。
            // ....一边读一边写。
            FileInputStream in = null;
            FileOutputStream out = null;
            try {
                // 读这个文件
                // D:\course\02-JavaSE\document\JavaSE进阶讲义\JavaSE进阶-01-面向对象.pdf
                in = new FileInputStream(srcFile);
                // 写到这个文件中
                // C:\course\02-JavaSE\document\JavaSE进阶讲义\JavaSE进阶-01-面向对象.pdf
                String path = (destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\")  + srcFile.getAbsolutePath().substring(3);
                out = new FileOutputStream(path);
                // 一边读一边写
                byte[] bytes = new byte[1024 * 1024]; // 一次复制1MB
                int readCount = 0;
                while((readCount = in.read(bytes)) != -1){
                    out.write(bytes, 0, readCount);
                }
                out.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (out != null) {
                    try {
                        out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return;
        }
        // 获取源下面的子目录
        File[] files = srcFile.listFiles();
        for(File file : files){
            // 获取所有文件的(包括目录和文件)绝对路径
            //System.out.println(file.getAbsolutePath());
            if(file.isDirectory()){
                // 新建对应的目录
                //System.out.println(file.getAbsolutePath());
                //D:\course\02-JavaSE\document\JavaSE进阶讲义       源目录
                //C:\course\02-JavaSE\document\JavaSE进阶讲义       目标目录
                String srcDir = file.getAbsolutePath();
                String destDir = (destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\")  + srcDir.substring(3);
                File newFile = new File(destDir);
                if(!newFile.exists()){
                    newFile.mkdirs();
                }
            }
            // 递归调用
            copyDir(file, destFile);
        }
    }
}

6.10 IO+properties

经常改变的数据,可以单独写到一个文件中,使用程序动态读取
将来只需要修改这个文件的内容,java代码不需要改动,不需要重新编译,服务器也不需要重启。就可以拿到动态的信息

配置文件中的内容格式是:
key1=value
key2=value

属性配置文件建议以.properties结尾,但这不是必须的。
这种以.properties结尾的文件在java中被称为:属性配置文件。
其中Properties是专门存放属性配置文件内容的一个类

主要的代码思路是
新建一个输入流对象
创建一个集合,运用结合中的load方法将其输入流对象加载到集合中
之后运用集合的key与value进行获取

public class IoPropertiesTest01 {
    public static void main(String[] args) throws Exception{
        /*
        Properties是一个Map集合,key和value都是String类型。
        想将userinfo文件中的数据加载到Properties对象当中。
         */
        // 新建一个输入流对象
        FileReader reader = new FileReader("chapter23/userinfo.properties");

        // 新建一个Map集合
        Properties pro = new Properties();

        // 调用Properties对象的load方法将文件中的数据加载到Map集合中。
        pro.load(reader); // 文件中的数据顺着管道加载到Map集合中,其中等号=左边做key,右边做value

        // 通过key来获取value呢?
        String username = pro.getProperty("username");
        System.out.println(username);

        String password = pro.getProperty("password");
        System.out.println(password);

     
    }
}

配置文件

username=admin
password=456456

7. 序列化与反序列化

具体这部分知识可看我之前的文章
java之序列化与反序列化的详细解析(全)

相关文章
|
8月前
|
存储 安全 Java
【javaSE】 万字带你了解String类
【javaSE】 万字带你了解String类
|
14天前
|
安全 Java 程序员
一文让你深入了解JavaSE的知识点(下)
一文让你深入了解JavaSE的知识点(下)
|
14天前
|
Java 编译器 数据安全/隐私保护
一文让你深入了解JavaSE的知识点(上)
一文让你深入了解JavaSE的知识点
|
2月前
|
安全 算法 Java
JavaSE 进阶-javase进阶(三)
JavaSE 进阶-javase进阶
42 0
|
安全 Java API
JavaSE 进阶-javase进阶(二)
JavaSE 进阶-javase进阶
37 0
|
2月前
|
存储 人工智能 算法
JavaSE 进阶-javase进阶(一)
JavaSE 进阶-javase进阶
47 0
|
10月前
|
算法 Java 开发者
【JavaSE专栏9】Java 注释知多少
【JavaSE专栏9】Java 注释知多少
|
10月前
|
Java
【JavaSE】Java基础语法(十七)
1. final fianl关键字的作用 final代表最终的意思,可以修饰成员方法,成员变量,类 final修饰类、方法、变量的效果 fianl修饰类:该类不能被继承(不能有子类,但是可以有父类) final修饰方法:该方法不能被重写 final修饰变量:表明该变量是一个常量,不能再次赋值
|
10月前
|
存储 算法 Java
【JavaSE】Java基础语法(十四):Static
概述 Java中的static是一个修饰符(也可称关键字),可以用于修饰变量、方法和代码块。 特点与应用 static修饰的成员具有以下特点: 被类的所有对象共享 是我们判断是否使用静态关键字的条件 随着类的加载而加载,优先于对象存在 对象需要类被加载后,才能创建 可以通过类名调用 也可以通过对象名调用
|
12月前
|
存储 Java 索引
【JavaSE】Java基础语法(五):数组详解
【JavaSE】Java基础语法(五):数组详解