==既可以比较基本数据类型,也可以进行比较引用数据类型。
如果比较的是基本数据类型,那么比较的就是数值的大小。如果比较的是引用数据类型,那么比较的就是地址是否相同。即是判断是否是同一个对象
equals是Object的子类,只可以比较引用数据类型;默认比较的是地址是否相同,但是子类往往重写这个equals方法来比较内容是否相同
比如说String类,Integer类等重写equals方法;
String类:通过源码分析得这个重写equals之后的规则:
如果比较的两个字符串地址相同,那么之间返回true;若地址不相同继续比较两个字符串的长度。如果比较的两个字符串长度相同,那么继续比较两个字符串的每一个字符;若长度不相同则返回false。如果两个字符串的每一个字符都相同,那么返回true,若有一个字符不相同则返回false
Integer重写equals方法:
如果比较的地址相同,那么之间返回true,若地址不相同,那么进行比较两个值的大小,若相等返回true,不相等返回false。
1.ArrayList基于动态数组的,是连续的内存存储。适合通过下标进行访问,针对这一点的查询下标,由于LinkedList是基于链表的,它是存储在分散的内存中的,不适合查询。并且ArrayList是随机进行访问下标的,但是LinkedLIst必须进行逐一进行遍历,并且只可以使用迭代器,不推荐使用for循环【原因:因为每一次for循环都会通过get(i)去取得某一元素,都需要对list进行重新遍历,性能消耗很大】
附加:不要想着使用indexOf等返回元素的索引,并且利用这个去进行遍历输出。因为当使用indexOf进行访问时会进行遍历整个list表,即使集合中没有这个元素,我们也会遍历整个列表
2. ArrayList:
如果使用ArrayList去添加一个元素但是不指定位置的,源码可知:默认是添加到最后一个位置的
但是. ArrayList:添加元素的时候存在一定的缺点,如图如果说把6添加到0和1之间的话,在ArrayList底层机制中是通过:把1-5所有的元素依次向后移,0往前移动一位。之后再进行添加即可。【当然这里移动是元素以拷贝的方式进行移动】,源码如下:
LinkedList:
但是LinkedList这种情况之下【指定把这个元素插入到哪一个索引位置的时候】是更适合进行做数据的插入和删除操作
其实LinkedList添加新元素[不指定索引下标]的时候,底层源码可查出:每一次就是添加到最后一个位置。哈哈哈,是不是很想笑。。这样的话,效率就会十分的高
重点转折:如果说ArrayList使用得当的话,指定适当大的初始化的数组大小并且结合尾插法进行插入添加新元素,这样的话ArrayList就不会涉及到元素的移动,甚至性能要超过LinkedList。因为LinkedList底层无论添加什么元素,在底层都会创建大量的node对象,并且交给intern去维护。
3.ArrayList的扩容机制:ArrayList拥有一个构造器,可以指定对数组大小进行创建。当添加元素超过这个初始化的数组大小的时候,底层就会触发扩容机制。按照扩容因子进行创建新的数组大小,把旧数组的元素对应下标进行拷贝到新数组中,并且把新添加的元素添加到新数组中
大部分的框架或日常一般使用ArrayList较多
4.LinkedList和ArrayList都实现了List接口,但是LinkedList多实现了一个Deque接口,这使得它可以被当作一个双端队列去使用
final意为最后的,最终的。final可以修饰类,方法,局部变量,属性
当被final修饰初始化之后,就不可以再发生改变了。
当不希望类被继承时,我们使用final修饰类
当不希望类的方法被子类覆盖或重写时,我们可以使用final修饰。
当不希望属性(即是成员变量)或局部变量被修改时,我们可以使用final修饰
分析:
1.final修饰成员变量(即是属性):
如果final修饰的是类变量(即是静态变量),我们只能在静态初始化块中指定初始值或者声明的时候就初始化。原因:因为类变量(即是静态变量)是当类加载时就应该进行完初始化操作,如果我们放在构造器中进行初始化的话,就会报错,因为只有创建对象时才会进行构造器初始化。
我们new 一个对象分为两大步:1.进行类加载 2.才是进行new对象(即是创建对象)
3.当修饰基本类型数据和引用类型数据
如果是基本类型的变量,那么final修饰之后,值是不可以进行改变的。如final int i=1;那么这个i是不可以再改变值的大小了
如果是引用类型的变量,那么final修饰之后,则对其初始化之后便不能再让其指向另外一个对象。但是引用指向的值是可变的
首先我们要明白一点,不论什么内部类,它和外部类是处于同一个级别的,内部类不会因为定义在外部类的方法中,当外部类的方法执行完时就随着而销毁。
那么就会产生问题,当外部类的方法执行完时,局部变量就会销毁了,但是如果内部类也可能在访问这个局部变量【我们知道内部类是独立存在的,不会随着外部类方法的销毁而销毁】,所以这时候内部类可能访问了一个已经销毁不存在的局部变量。为了解决这个问题,JVM会在一开始就复制一份局部变量作为内部类的局部变量,这样就可以当外部类的方法销毁时,内部类还可以进行访问JVM机给它复制的局部变量
但是问题来了,JVM将局部变量复制为内部类的成员变量时,必须保证两个成员变量是一样的,所以说当内部类改变了这个局部变量的值,外部类的方法处的局部变量也会跟着改变
所以把局部变量设置为final,对它初始化之后就不可以进行修改这个变量,保证了内部类的成员变量和方法的局部变量的一致性,使得外部类方法中的局部变量与内部类建立的拷贝保持一致
hashCode方法的作用是获取哈希码,也称为散列码。这个码其实就是一个int类型的整数值,他并不是对象的地址码值。这个哈希码的作用只是为了确定该对象在哈希表【即是散列表】中的索引位置。hashCode方法定义在JDK的Object.java中,Java中的任何类都包含hashCode方法。散列表(即是哈希表)中存储的是键值对(key-value),它的特点是:能根据 键 快速检查出对应的 值。这其中就利用到了散列码(即是哈希码),通过哈希码快速的找出是哈希表中的哪一个对象。哈希码好比就是键,位于哈希表中的对象就好比是值。通过键找到对应的值
为什么要有hashCode方法?
以"HashSet的put方法中如何检查对象是否重复"为例来说明为什么要有hashCode:
对象加入HashSet时,HashSet会先计算对象的hashCode返回的整数值来判断对象加入的位置,看位置是否已经有值,如果没有值,那么HashSet会假设对象没有重复出现。但是如果发现这个位置有值,那么就要使用equals方法再进一步进行比较,
这个时候就要分情况了,
1.如果是链条结构:
如果得出的结果为true,也就是说两个对象相同,那么HashSet就不会让其加入操作成功,具体put方法中指向的就是把这个对象对应的值value进行替换原来的对象的value,但是不可以新对象不可以加到这个HashSet中。
如果一直比较到链条的最后还是没有找出相同的对象的话,那么这个新待加入的对象就会加在链条的尾部。注意:当链条上的节点数达到8个时,就会进行树化了。。
2.如果是红黑树结构:
那么就要进行具体的树化添加了
3.如果只有一个节点时,添加的方式和1差不多了。
这样下来就大大减少了equals方法的使用量,注意equals方法对系统的消耗是很大的,得比较地址等等,使用hashCode结合equals方法更加节省了性能,同时也提升了效率
如果两个对象相等,则hashcode一定也是相同的。
如果两个对象相等,调用equals方法都返回true
两个对象hashcode值相等,它们也不一定是相等的
因此,只要equals被覆盖(即是重写),那么equals方法也应该进行覆盖(即是重写)【正如上面HashSet检验重复时,equals和hashCode需要结合着用】
hashCode方法的默认行为是对堆上的对象产生的独特值。如果没有重写hashCode方法,那么class类的两个对象无论如何都不会相等(即使这两个对象是指向相同的数据)
equals方法默认是比较两个对象的地址,如果重写的话可以如String,Integer等比较内容
JDK1.7版本:HashMap底层是链表+数组
通过JDK1.7源码我们可以知道,首先扩容机制是扩容到原来的两倍大小,扩容到原来的两倍之后再进行双层遍历循环,第一层循环是遍历原数组,第二个while循环是为了看是否是链条,通过每一个元素的hash值通过indexFor方法,再把把原数组的每一个元素节点复制到新扩容之后的数组
JDK1.8:链表+红黑树+数组
源码在之前笔记记过,这里直接写思路了。方法和上面JDK1.7差不多,还是进行扩容到原来的2倍大小,扩容之后通过遍历之后计算每一个节点元素应该对应到新扩容之后的数组的哪一个索引位置。有可能这个旧数组的红黑树到新数组就会变化为两条链表或三条链表或者一颗红黑树和一条链表。
区别:
HashMap方法没有sychronized修饰,线程非安全,HashTable线程安全。但是HashTable效率不高,因为它的锁太多了,把所有方法都锁着了,自然就效率低了。因此我们现在多使用CurrentHashTable
HashMap允许key和value为null,HashTable则不允许
底层实现:数组+链表实现:
JDK1.8开始链表长度达到8并且数组的长度要达到64时,链表才能转变为红黑树,元素以内部类Node节点存在。
1.计算key的hash值,二次hash之后然后对数组长度取模,求得对应的数组下标
【记住,每一个元素都有对应的hash值【原因就是每一个元素都有对应的hashCode方法得到的值,这个方法是以堆空间上的内存行为而定】,然而这个hash方法是通过底层的哈希算法得到的,为了就是一定程度上避免哈希冲突。】
什么是哈希冲突?不同的对象通过计算得到的hash值相等。所以说每当我们计算两个对象得到相同的hash值时,我们还要再进行equals方法的比较
言归正传:
2.如果没有产生哈希冲突,那么进行equals比较看是否相等,如果相等,那么直接覆盖这个节点处的value值。如果不相等,判断是什么形状:
(1)是链表形状,那么就遍历整个链表然后找找看是否存在和新插入的元素比较hash值相等并且equals比较也返回true的,当然key相等那么equals这个条件就不用看了【详细看JavaSE学习笔记-11,源码分析】。在添加的过程中要注意看是否满足树化的条件,链表元素达到8个且数组的元素达到64个。ok,当红黑树的长度低于6时,则会退化为链表
(2)如果是红黑树形式,那么进行红黑树形式的添加操作即可
3.如果key为null,那么直接规定存在下标为0的位置
总而言之:只要HashMap底层的putVal方法源码吃透,这些都好办
JDK是Java的开发工具,Java开发程序员都需要安装JDK
JRE是Java运行环境,如果一般人不做Java开发,只是想运行Java程序的话,那么直接安装一个JRE这个Java运行环境即可
JVM是把Java代码解释成class文件,即是解释成机器码。让操作系统能够运行
总结:JDK包含了JRE,JRE包含着JVM
流程:.java文件通过javac编译成.class文件,由于JVM有许多版本,它适配了很多操作系统,那么.class文件可以在不同的版本的JVM上运行,
这就是Java流传的一句话:一处编译,到处运行【就是.class文件到处运行,并不是JVM到处运行】。
JVM拿到.class文件之后,通过lib目录下的类库进行解释这个class文件,最后翻译成机器码,最终可以映射到不同的操作系统下进行运行。
整理不易,希望对大家有帮助。也希望看到的兄弟能给一个点赞,谢谢啦哈哈哈