Java基础知识
1. 基础
==、equals和hashCode的区别
1、==:对于基本类型,比较的是它们的值;对于引用类型,比较的是引用的值,也就是对象实例的地址
2、equals()
方法是Object类中的方法,默认实现的是public boolean equals(Object obj) {return (this == obj);}
这说明如果一个类没有重写equals()方法,它默认就使用==操作符,也就是比较两个变量指向的对象是否是同一对象, 这时候使用equals和使用==会得到同样的结果
3、hashCode()方法和equals()方法的作用其实一样,在Java里都是用来对比两个对象是否相等一致。重写equals()方法一般比较全面比较复杂, 这样效率比较低,而利用hashCode()方法进行对比,则只要生成一个hash值进行对比就行了。那么hashCode既然效率这么高为什么还有equals呢呢? 因为hashCode并不是完全可靠,有时候不同的对象他们生成的hashCode也会一样(hash冲突),所以hashCode只能说是大部分时候可靠,并不是绝对可靠。equals()相等的两个对象他们的hashCode()肯定相等
hashCode()相等的两个对象他们equals()不一样相等
重载(Overload)和重写(Override)的区别
重写(Override)
重写发生在运行期,针对父类和子类来说的,是在子类中重写父类的方法
1、要求方法名,参数个数和类型必须相同
2、返回的数据类型必须与父类相同或者是其子类
3、访问修饰符的限制一定要大于父类中该方法的访问修饰符(public > protected > default > private)
4、重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常
重载(Overload)
重载发生在编译期,针对一个类说的,是JAVA中多态性的一种表现
1、要求方法名相同
2、必须有不同的参数列表
3、可以有不同的返回类型
4、可以有不同的修饰符
5、可以抛出不同的异常
StringBuilder、StringBuffer、+、String.concat
- StringBuilder是线程不安全;
- StringBuffer是线程安全;
- +实际上内部是用的是StringBuilder来实现的,所以非循环体可以直接使用+,循环体不行,因为会频繁创建StringBuilder;
- String.concat实质是new String,效率也低
耗时排序:
StringBuilder < StringBuffer < String.concat < +
2. 容器
List、Map、Set
- Set(集合) 是无序、不可以重复的
- List(列表) 是有序、可以重复的
- Map(映射) 是键值对:Map<Key, Value>
ArrayList、Vector、LinkedList、CopyOnWriteArrayList
ArrayList
非线程安全,基于数据实现,查找快:o(1),增删慢o(n)
初始容量为10,扩展通过System.arrayCopy方法。LinkedList
非线程安全,基于双向链表实现,查找慢o(n),增删快o(1)
封装了队列和栈的调用Vector
方法是同步的,加了synchronized关键字,线程安全的。当元素超过它的初始化大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样ArrayList就有利于节约内存空间。ArrayList与Vector都可以设置初始化的空间大小,Vector还可以设置增加的空间大小,而ArrayList没有提供设置增长空间的方法CopyOnWriteArrayList
这是一个ArrayList的线程安全的变体,其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的,在CopyOnWriteArrayList里面增加了一个数据,这个时候CopyOnWriteArrayList底层实现添加的原理是先copy出一个容器(可以简称副本),再往新的容器里添加这个新的数据,最后把新的容器的引用地址赋值给了之前那个旧的容器地址,但是在添加这个数据的期间,其他线程如果要去读取数据,仍然是读取到旧的容器里的数据。这对于读操作远远多于写操作的应用非常适合。CopyOnWriteArrayList在兼顾了线程安全的同时,又提高了并发性,性能比Vector有不少提高(CopyOnWriteArrayList方法中使用了lock锁,Vector只是方法上加synchronized)
HashMap、TreeMap、LinkedHashMap、HashTable、ConcurrentHashMap
HashMap
非线程安全,HashMap由数组+链表组成,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。允许null键和null值HashTable
基于哈希表实现,特点和 hashMap是一样的,但是线程安全,不允许null键或null值TreeMap
基于红黑树实现,非线程安全,不允许null键但运行null值,存储在它里面的key是从小到大排好序的。由于内部要排序,所以存入的key必须是相同对象LinkedHashMap
LinkedHashMap也是一个HashMap,但是内部维持了一个双向链表,可以保持顺序ConcurrentHashMap
基于哈希表实现。线程安全,不允许null键或null值。
不同于HashTable实现线程安全的策略 —— 简单粗暴,get/put所有相关操作加入synchronized,这就导致所有操作需要竞争同一把锁;ConcurrentHashMap采用了非常精妙的“分段锁”策略,其主干是个Segment数组。一个Segment就是一个子哈希表,Segment里维护了一个HashEntry数组,所以,并发环境下,对于同一个Segment的操作才需考虑线程同步,不同的Segment则无需考虑
总结
当你想要强行给插入的元素维护一个想要的顺序时,应该使用TreeMap;当你想要元素顺序与插入顺序一致时,应该使用LinkedHashMap;不考虑线程安全的其它情况应该使用HashMap,因为它的性能最优;考虑线程安全的话,应尽量使用ConcurrentHashMap而不是HashTable,因为其效率实在太过低下。
HashSet、LinkedHashSet、TreeSet与Map类似
HashMap和ArrayMap对比
1. 查找效率
HashMap因为其根据hashCode的值直接算出index,所以其查找效率是随着数组长度增大而增加的。
ArrayMap使用的是二分法查找,所以当数组长度每增加一倍时,旧需要多进行一次判断,效率下降。
所以对于Map数量比较大的情况下,推荐使用HashMap
2. 扩容数量
HashMap初始值16个长度,每次扩容的时候,直接申请双倍的数组空间
ArrayMap每次扩容的时候,如果size长度大于8时申请size*1.5个长度,大于4小于8时申请8个,小于4时申请4个
这样比较ArrayMap其实时申请了更少的空间,但是扩容的频率会更高。因此,如果当数量比较大的时候,还是使用HashMap更合适,因为其扩容的次数要比ArrayMap少很多。
3. 查找效率
HashMap每次扩容的时候时重新计算每个数组成员的位置,然后放到新的位置。
ArrayMap则是直接使用System.arraycopy。
所以效率上肯定是ArrayMap更占优势。
4. 内存耗费
以ArrayMap采用了一种独特的方式,能够重复的利用因为数据扩容而遗留下来的数组空间,方便下一个ArrayMap的使用。而HashMap没有这种设计。
由于ArrayMap只缓存了长度是4和8的时候,所以如果频繁的使用到Map,而且数据量都比较小的时候,ArrayMap无疑是相当的节省内存的。总结
数据量比较小(1000内),并且需要频繁的使用Map存储数据的时候,推荐使用ArrayMap。 而数据量比较大的时候,则推荐使用HashMap。
ArrayMap、SparseArray
ArrayMap
- 基于两个数组实现,一个存放 hash;一个存放键值对。扩容的时候只需要数组拷贝,不需要重建哈希表
- 内存利用率高
- 不适合存大量数据,因为会对 key 进行二分法查找(1000以下)
SparseArray
- 基于两个数组实现,int 做 key,避免了对key的自动装箱(int转为Integer类型)
- 内存利用率高
- 不适合存大量数据,因为会对 key 进行二分法查找(1000以下)
总结
SparseArray和ArrayMap都差不多,假设数据量在1000以内:
- 如果key的类型已经确定为int类型,那么使用SparseArray,因为它避免了自动装箱的过程,如果key为long类型,它还提供了一个LongSparseArray来确保key为long类型时的使用
- 如果key类型为其它的类型,则使用ArrayMap
3. 网络
网络协议模型
应用层: 负责处理特定的应用程序细节(HTTP、FTP、DNS)
传输层: 为两台主机提供端到端的基础通信(TCP、UDP)
网络层: 控制分组传输、路由选择等(IP)
链路层: 操作系统设备驱动程序、网卡相关接口
TCP、UDP区别
TCP连接,可靠,有序,面向字节流,速度慢
UDP无连接,不可靠,无序,面向报文,速度快
TCP三次握手、四次挥手
TCP三次握手
1、A:你能听到吗?
2、B:我能听到,你能听到吗?
3、A:我能听到,开始吧
A和B两方都要能确保:我说的话,你能听到;你说的话,我能听到。所以需要三次握手
TCP四次挥手
1、A:我说完了
2、B:我知道了,等一下,我可能还没有说完
3、B:我也说完了
4、A:我知道了,结束吧
B收到A结束的消息后B可能还没有说完,没法立即回复结束标识,只能等说完再告诉A我说完了
4. 线程
并发编程的3个基本概念
原子性(即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。)
可见性(指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。)
有序性(即程序执行的顺序按照代码的先后顺序执行。)
volatile 关键字
只能用来修饰变量,适用修饰可能被多线程同时访问的变量
相当于轻量级的 synchronized,volatitle 能保证有序性(禁用指令重排序)、可见性;后者还能保证原子性
变量位于主内存中,每个线程还有自己的工作内存,变量在自己线程的工作内存中有份拷贝,线程直接操作的是这个拷贝
被 volatile 修饰的变量改变后会立即同步到主内存,保持变量的可见性.
单例模式,为什么需要volatile
volatile想要解决的问题是,在另一个线程中想要使用instance,发现instance!=null,但是实际上instance还未初始化完毕这个问题
instance = new Instance();
可以分解为3行伪代码
- 分配内存
- 初始化对象
- 设置instance指向刚分配的地址
上面的代码在编译器运行时,可能会出现重排序 从1-2-3 排序为1-3-2线程A在执行
instance = new Instance();
代码时,B线程进来,而此时A执行了 1和3,没有执行2,此时B线程判断instance不为null 直接返回一个未初始化的对象,就会出现问题
lock 和 synchronized
synchronized 是 Java 关键字,内置特性;Lock 是一个接口
synchronized 会自动释放锁;lock 需要手动释放,所以需要写到 try catch 块中并在 finally 中释放锁
synchronized 无法中断等待锁;lock 可以中断
Lock 可以提高多个线程进行读/写操作的效率
竞争资源激烈时,lock 的性能会明显的优于 synchronized
wait 和 sleep
sleep 是 Thread 的静态方法,可以在任何地方调用
wait 是 Object 的成员方法,只能在 synchronized 代码块中调用,否则会报 IllegalMonitorStateException 非法监控状态异常
sleep 不会释放共享资源锁,wait 会释放共享资源锁
四种引用
强引用: 不会被回收
软引用: 内存不足时会被回收
弱引用: gc 时会被回收
虚引用: 无法通过虚引用得到对象,可以监听对象的回收
网友评论