JAVA学习5-链表、泛型、Map、Vector、二叉树

41.链表LinkedList

链表的基本单元是节点;

单向链表每一个节点都有两个属性:①下一个节点的内存地址,②存储的数据;

单向链表

链表的优点:因为在内存空间上地址不连续,所以链表的增删不会导致大量元素位移,随机增删元素效率较高;缺点:查询效率较低,每一次查找都需要从头部节点开始遍历;

如果在实际业务中随机增删较多的情况下,建议用LinkedList存储数据;

在实际开发中添加元素一般是往末尾添加,所以ArrayList使用较多;

双向链表

LinkedList没有初始容量,内部变量first与last初始值为null;

实际开发中,不管是LinkedList还是ArrayList,写代码的时候不需要关心具体是哪个集合,因为是面向接口编程,调用的方法都是接口中的方法;

表示底层用了数组
//ArrayList l = new ArrayList<>();
//表示底层用了双向链表
LinkedList l = new LinkedList<>();

l.add("aaa");
l.add("aaa1");
l.add("aaa22");

for (int i = 0; i < l.size(); i++) {
    System.out.println(l.get(i));
}

 42.Vector

底层是数组,初始容量是10,扩容后是原容量的2倍;

Vector所以方法都带synchronized修饰,是线程同步的,是线程安全的,效率较低,使用较少了;

Vector l = new Vector();

l.add("aaa");
l.add("aaa1");
l.add("aaa22");

Iterator it = l.iterator();

while (it.hasNext()) {
     System.out.println(it.next());
}

43.泛型初步

使用泛型Movie表示,集合中只能存储Movie类型的数据;

用泛型来指定集合中存储的数据类型,使用泛型之后集合中的数据类型更加统一了;

List<Movie> l = new ArrayList<Movie>();
l.add(new LoveMovie());
l.add(new WarMovie());

// 表示迭代器迭代的是Movie类型
Iterator<Movie> it = l.iterator();

while (it.hasNext()) {
     //使用泛型后,不需要强制类型转换了,每次迭代器返回的都是Movie类型
     it.next().seeMovie();
}

泛型语法机制,只是在程序编译阶段起作用,只是给编译器参考的,运行阶段没用;

泛型的优点:集合中的元素类型统一,如果迭代器使用泛型,就不需要强制类型转换了;

泛型的缺点:集合中存储的元素缺乏多样性;

大多数业务中,集合的类型是统一的,所以泛型特性被认可;

自定义泛型

public class Movie<E> {
    public String name;

    public void leave(E e) {
        System.out.println(e);
    }
}

public static void main(String[] args) {
    Movie<String> m = new Movie<>();
    m.leave("aaa");
}

java源码中常用E(element)和T(type)自定义泛型;

44.自动类型推断

在JDK8之后推出了自动类型推断机制,又称为钻石表达式;

//JDK8之后,后面的ArrayList的泛型内容可以不写
List<Movie> l = new ArrayList<>();

45.Map

Map和Collection没有继承关系;

Map集合以key-value的形式存储数据;

        key和value都是引用数据类型;

        key和value都是存储对象的内存地址;

        key起主导作用,value是key的附属品;

Map的常用方法:

        Map<Integer, String> m = new HashMap<>();
        // 向Map集合添加键值对
        m.put(1, "无间道");
        m.put(2003, "阿凡达");
        m.put(33, "八佰");

        // 通过key获取value值
        String s = m.get(2003);
        System.out.println(s);

        // 获取键值对的数量
        System.out.println(m.size());

        // 通过key删除key-value
        m.remove(1);

        // 判断是否包含某个key
        // contains方法底层调用的是equals,所以自定义类型需要重写equals方法
        System.out.println(m.containsKey(33));

        // 判断是否包含某个value
        System.out.println(m.containsValue(new String("八佰")));

        // 获取所以value
        Collection<String> v = m.values();
        for (String str : v) {
            System.out.println(str);
        }

        // 清空集合
        m.clear();

        // 判断是否为空
        System.out.println(m.isEmpty());

遍历Map

        Map<Integer, String> m = new HashMap<>();
        // 向Map集合添加键值对
        m.put(1, "无间道");
        m.put(2003, "阿凡达");
        m.put(33, "八佰");

        Set<Integer> keys = m.keySet();
        Iterator<Integer> it = keys.iterator();
        //方式一
        while (it.hasNext()) {
            Integer key = it.next();
            System.out.println(m.get(key));
        }
        //方式二
        for (Integer integer : keys) {
            System.out.println(m.get(integer));
        }

方式三

        Set<Map.Entry<Integer, String>> s = m.entrySet();

        Iterator<Map.Entry<Integer, String>> it2 = s.iterator();

        while (it2.hasNext()) {
            Map.Entry<Integer, String> node = it2.next();
            Integer key = node.getKey();
            String val = node.getValue();
            System.out.println(key + "--" + val);
        }

        //或者
        //此方式不必先获取key再通过key获取value,所以效率较高,适合大数据量遍历
        for (Map.Entry<Integer, String> entry : s) {
            System.out.println(entry.getKey() + "---" + entry.getValue());
        }

46.HashMap集合

HashMap集合底层是哈希表/散列表的数据结构;

哈希表是一个数组和单向链表的结合体;

数组:在增删数据的时候效率较低,在查询的时候效率较高;

单向链表:在增删数据的时候效率较高,在查询的时候效率较低;

哈希表将以上两种数据结构融合在一起,充分发挥他们各自的优点;

类似:数组里面是一个个链表;

 源码解析

//哈希表是一维数组,数组的每一个元素是一个单向链表,是数组和单向链表的结合体;
transient Node<K,V>[] table;

static class Node<K,V> implements Map.Entry<K,V> {
        //hash值是key的hashCode()的执行结果,hash值通过hash函数/算法,可以转换存储成数组的下标
        final int hash;    
        final K key;    //存储到map集合的key
        V value;    //存储到map集合的value
        Node<K,V> next;    //下一个节点的内存地址

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
}

hashMap的key部分的特点是无序不可重复的:因为节点不一定挂载到哪一个单向链表上,所以是无序的,equals方法判断两个key如果相同,那么新的value会覆盖旧的value,所以是不可重复的;

由于hashMap集合中的key部分的元素其实就是放到了hashSet集合中了,所以hashSet集合中的元素也需要同时重写hashCode和equals方法;

假设所有的haseCode方法返回一个固定值,那么会导致底层哈希表变成了单向链表,哈希表hashMap使用不当时无法发挥性能;这种情况我们称为散列分布不均匀;

如果有100个元素,分布在10个单向链表,每个单向链表有10个元素,这是最好的,是散列分布均匀的;

如果所有的hashCode方法返回的值都不同,那么就会导致哈希表的底层变成了一维数组,没有链表的概念了,这种情况也是散列分布不均匀的;

散列分布均匀需要重写hashCode方法时有一定的技巧;

hashMap底层数组的容量是16,默认加载因子是0.75,就是当hashMap底层数组的容量达到75%的时候,数组开始扩容,扩容之后的容量是原容量的2倍;

hashMap的初始化容量必须是2的n次方(2,4,8,16...),这是官方推荐的,这是为了达到散列均匀,提高hashMap集合的存储效率,所必须的;

向map集合中存和取都是先调用hashCode方法再调用equals方法,equals有可能调用,有可能不调用;

put(k,v)中,k的hashCode方法返回哈希值,哈希值通过哈希算法转成数组下标,如果数组下标不存在,则直接添加节点,equals方法不执行;

get(k)中,k的hashCode方法返回哈希值,哈希值通过哈希算法转成数组下标,如果数组下标没有元素,则返回null,equals方法不执行;

如果一个类的equals方法重写了,那么它的hashCode方法必须重写,并且如果equals方法返回的是true,那么两个对象hashCode方法返回值必须一样;

equals方法返回true,表示两个对象相同,在同一个单向链表上比较,那么对于同一个单向链表上的节点来说,他们的哈希值是相同的,所以hashCode方法返回的值也应该相同;

终极结论:放在hashMap集合的key部分的元素与hashSet集合的元素,他们的hashCode方法和equals方法必须重写;

JDK8之后,hashMap里面的单向链表中的元素如果超过8个,单向链表这种数据结构会变成红黑树数据结构,当红黑树的元素少于6个,则红黑树会变成单向链表数据结构;

//参考源码
static final int TREEIFY_THRESHOLD = 8;

static final int UNTREEIFY_THRESHOLD = 6;

这种方式也是为了提高检索效率,二叉树的检索会再次缩小检索范围,提高效率;

对于哈希表数据结构来说:

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

47.hashTable

hashTable的key和value都不能是null,否则报空指针异常;

hashTable方法都带有synchronized修饰符,是线程安全的;

现在线程安全有其他的解决方案,这个hashTable对线程安全的处理,导致效率低下,使用较少了;

hashTable与hashMap一样,底层都是哈希表数据结构;

hashTable的初始化容量是11,默认加载因子是0.75;

hashTable的扩容是原容量*2+1;

48.Properties

properties是一个Map集合,继承Hashtable,Properties的key和value都是string类型;

properties被称为属性类对象;

properties是线程安全的;

49.TreeSet

TreeSet底层是TreeMap;

TreeMap集合底层是二叉树;

TreeSet中的元素,等同于放到TreeMap中的key部分;

TreeSet集合中的元素无序不可重复,但是可以按照元素的大小自动排序,称为可排序集合;

        TreeSet<String> ts = new TreeSet<>();
        ts.add("ywxk");
        ts.add("aaa");
        ts.add("kkk");
        ts.add("ccc");
        // 按照字典升序
        for (String str : ts) {
            System.out.print(str);
        }

        TreeSet<Integer> ts2 = new TreeSet<>();
        ts2.add(300);
        ts2.add(100);
        ts2.add(500);
        ts2.add(200);
        for (Integer integer : ts2) {
            System.out.print(integer);
        }

TreeSet无法对自定义类型排序,因为没有指定自定义类型之间的比较规则,导致自定义类型在转换成Compare类型时异常,所以异常名称是java.lang.ClassCastException;

放在TreeSet集合中的元素需要实现java.lang.Comparable接口,并且实现CompareTo方法;

public class Movie implements Comparable<Movie> {
    double price;
    String mName;

    public Movie(double p, String name) {
        this.price = p;
        this.mName = name;
    }

    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return "price=" + price + "--name=" + mName;
    }

    @Override
    public int compareTo(Movie o) {
        // TODO Auto-generated method stub
        //写比较规则
        int p = (int) (this.price - o.price);
        if (p == 0) {
            return this.mName.compareTo(o.mName);
        } else {
            return p;
        }
    }
}

compareTo方法底层是二叉树查找;

除了重写compareTo方法比较TreeSet元素之外,还可以用比较器方式;

比较器实现java.util.comparator接口(Comparable是java.lang包下的,比较器是java.util包下的);

    //new HashSet传入比较器方式
    public static void main(String[] args) throws Exception {
        test1();
    }

    public static void test1() {
        WarMovie wm1 = new WarMovie(1999);
        WarMovie wm2 = new WarMovie(2009);
        WarMovie wm3 = new WarMovie(2003);
        WarMovie wm4 = new WarMovie(1988);
        TreeSet<WarMovie> ts = new TreeSet<>(new Compare());
        ts.add(wm1);
        ts.add(wm2);
        ts.add(wm3);
        ts.add(wm4);

        for (WarMovie warMovie : ts) {
            System.out.println(warMovie);
        }
    }

class Compare implements Comparator<WarMovie> {

    @Override
    public int compare(WarMovie o1, WarMovie o2) {
        // TODO Auto-generated method stub
        return o1.year - o2.year;
    }
}

        //匿名内部类方式
        //类没有名字,new接口
        TreeSet<WarMovie> ts = new TreeSet<>(new Comparator<WarMovie>() {
            @Override
            public int compare(WarMovie o1, WarMovie o2) {
                // TODO Auto-generated method stub
                return o1.year - o2.year;
            }
        });

 放到TreeSet或者TreeMap的key部分的元素如果想要做到排序,可以有两种方式;

①放入集合的元素实现Comparable接口的compareTo方法;

②在new TreeSet的时候传入一个实现Comparator接口的compare方法的比较器;

当比较规则不会发生改变或者比较规则只有一个的时候,选择实现Comparable接口;

当比较规则有多个,或者经常需要切换的时候,选择实现Comparator接口;

Comparator的设计比较符合OCP原则,一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展;

50.自平衡二叉树

自平衡二叉树遵循左小右大原则存放,所以存放的时候需要比较;

遍历二叉树的方式有3种:

        前序遍历:根左右;

        中序遍历:左根右;

        后序遍历:左右根;

前中后指的是跟的位置;

TreeSet/TreeMap集合,Iterator迭代器采用的是中序遍历,即左根右;

存入按照大小存放,取出来的时候就自然按照大小排序了;

        //源码
        do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else {
                    V oldValue = t.value;
                    if (replaceOld || oldValue == null) {
                        t.value = value;
                    }
                    return oldValue;
                }
            } while (t != null);

在TreeSet存入元素的时候,会查找合适的树位置,找到后将元素存入这个位置;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

y_w_x_k

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值