Java基础
Posted miraclemaker
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java基础相关的知识,希望对你有一定的参考价值。
1.面向对象三大特征:
- 面向对象比面向过程易拓展,易更新,易维护,相对来说性能略低
- 封装:对数据进行封装保护,保证外部能获得数据的同时不能随意修改数据
- 继承:类去获得其他类的属性和方法,并拓展自己的特征和行为
- 多态:要在继承,重写,父类引用指向子类的基础上,降低耦合度。很多子类在同一个父类方法的基础上拓展自己不同的行为。多态的重要概念:程序中的引用变量在编译期间不知道具体调用谁,只有在运行期间才知道调用的是谁的方法(动态绑定)。
2.final和static:
static:多用来修饰方法和变量,表示变量和方法只有一个。
- 修饰后可以直接通过类名访问。不能修饰局部变量。
final:修饰属性,方法,变量,类,表示修饰的值不变。
- 修饰类:类不可被继承。
- 修饰方法:方法不能被重写。
- 修饰属性:属性的值不可改变。
3.接口和抽象类的区别:
相似点:
- 接口和抽象类都不能被实例化。
- 接口和抽象类的抽象方法都要被实现。
不同点:
- 接口只有抽象方法,静态方法(子类不可重写,只能通过接口调用),默认方法(子类可以选择重写或者直接调用),抽象类可以有构造方法和普通方法。
- 接口的变量是能用public static final修饰表示不变且可直接访问。抽象类成员变量可以随意定义。
4.重写和重载:
- 重载发生在同一个类中,方法名相同,而参数列表、返回类型、权限修饰符可以不同。
- 重写发生在子类中,方法名、参数列表、返回类型都相同,权限修饰符要大于父类方法,声明异常范围要小于父类方法,并且final和private修饰的方法不可重写。
5.基本类型和引用类型:
基本类型:
- byte(1字节),short(2字节),int(4字节),long(8字节),float(4字节),double(8字节),char(2字节),boolean(1字节)。
- 保存值,赋值时按照值传递。
- 直接将数值保存在栈中,栈被放在缓存中,取值很快,而引用类型占用空间大,cache miss概率也高。
引用类型:
- 分为类,接口,数组三种。例如:Integer , Object , String , Date , List , int[]........
- 保存引用对象的地址,赋值时是按引用传递。(未被指向的对象会被垃圾回收掉)
- 栈中保存地址,堆中保存对象,地址指向堆中对象。
小细节:
- 向ArrayList,HashMap装值时,基本数据类型不行,因为他们装的都是Object。
- 函数传参时,传的是栈中的内容,所以基本类型传的是值,函数操作对原值无影响,而引用类型传递的是地址,所以函数操作对原值有影响。
- Integer和String都有享元模式,Integer保存的是-128-127,范围内不会新生成一个对象。
6.==和equals的区别
- ==比较基本类型,比较的是值,==比较引用类型,比较的是内存地址。
- equlas判断内存地址,不能用来判断基本类型。但是有些类重写了equals方法,比如String的equals被重写后,比较的是字符值。(重写了equlas后,也必须重写hashcode()方法)
7.HashMap:
- 实现:Jdk8以后是基于数组+链表+红黑树来实现的,特点是,key不能重复(可以为null,key为null的放在数组的头部)。线程不安全。
- 扩容:HashMap的默认容量为16,默认的负载因子为0.75,当HashMap中元素个数超过容量乘以负载因子的个数时,就创建一个大小为前一次两倍的新数组,再将原来数组中的数据rehash操作到新数组中。当数组长度到达64且链表长度大于8时,链表转为红黑树。
- put:先进行自定义的hash运算(扰动函数)之后判断数组桶有没有被初始化,没有先初始化。之后进行index的计算(新hash与length-1的与操作),如果已经有值了进行equals判断,如果为true证明相同就直接覆盖,如果为false就进行尾插法插入数据(jdk7之前的头插法会造成环形链表死循环问题),最后判断是否需要扩容进行rehash运算。需要注意的是同一链表上的数据hash值基本都不相同,只是和length-1做与运算后相同。
- 默认容量16,length-1:因为当容量为2的n次幂时,length-1的二进制后面全为1,这样可以保证计算index的时候进行最全面的分布。
- 负载因子:临界值=容量*负载因子;要考虑时间和空间上的平衡,如果过大,那么查找的时间长,如果过小,对空间不友好。有一个算法算出来0.7的时候比较好,又结合上最好使得临界值为整数,容量为2的次幂,所以设置为0.75保证临界值恒为整数。
- 扰动函数:我们发现算出的哈希码转为2进制后千米那很大部分都是零,这样在进行异或运算的时候大的哈希码前面有1也没用,因此将每个数的哈希码的高十六位和第十六位先异或后得出新的哈希码再进行与底层数组长度做异或运算。
- Rehash:数组扩容后再hash一遍岂不是很浪费时间,hashmap其实并没有再做一遍hash。我们发现数组扩容后,等于n的二进制位最高位由0变成了1,这样做hash的时候其实新位置=老位置/老位置+老容量,要看老位置在n扩容后新增的哪个位置是1还是0,如果老位置是1&n扩容后那个位置新增的1,最后就是老位置+老容量,否则就是老位置。需要注意的是链表上的数据rehash后可能会跑到不同的数组桶中,因为length-1发生变化,新的index也会发生变化,这也是头插环形链表的形成原因。
- 多线程存在的问题:1.多线程下同时put如果发生hash碰撞,会导致数据丢失,因为线程执行无法保证是按照原来的put顺序执行。2.并发下进行扩容时,如果是头插,有几率导致环形链表,get无限循环
- 快速根据key找到索引:
。传入key后会进行哈希算法,能够根据key直接算出索引。
- HashCode和equals:
。HashMap同时重写了这两个方法,我们的目的是内容相同的key都只有一份。而hashcode用来生成hash码,equals用来判断是否是同一对象。
。hashcode重写,equals未重写:两个内容对象进来判断出相同hash码后,但equals判断出地址不同就会被重复放入同一个桶,有两个相同key了。
。hashcode未重写,equals重写:两个内容相同对象进来判断出不同,就会存放在不同桶的位置有两个相同key了。
- 想要线程安全:用ConcurrentHashMap,用HashTable,用Collections.synchronizedHashMap()。
- ConcurrentHashMap结构:
。JDK7:结构是数组+链表;使用分段锁,将一个Map分为了16个段,每个段都是一个小的hashmap,每次操作只对其中一个段加锁
。JDK8:结构是数组链表红黑树,采用CAS+Synchronized保证线程安全。
。put数据的时候:
。先判断table是否为null或者0,如果是就进行初始化。
。再计算key的哈希码到指定位置判断当前节点是否为空,是就通过CAS方式插入;不是就加synchronized尾插到链表上。
。最后判断哈希值是否为-1,如果是,那么其他线程正在进行扩容,线程就会协助扩容。
- ConcurrentHashMap中key和value为什么不能为null:
。如果我们get(key)==null,那么可能是不存在key或者value刚好为null,那么在hashmap单线程中我们再用containskey判断一下有无key就好,不存在歧义。但是如果在多线程中,我们调用get后得到null,想要调用containskey去判断,结果有可能是不存在key,也有可能有其他线程刚好添加了一下null的key,结果就为存在key,也就是说我们完全无法判断key为null时,到底是哪一种情况。
- HashTable:
。HashTable的每个方法都用synchronized修饰,线程安全,但同时读写效率很低
。HashTable的Key不允许为null
。HashTable只对key进行一次hash,HashMap进行了两次Hash
。HashTable底层使用的数组加链表
- LinkedHashMap:
。HashMap是无序的,key插入进来之后要执行hash,执行hash后的插入位置就是随机的了,想要有序要用到LinkedHashMap;LinkedHashMap维护了一个双向链表(有三个指针,before,after,next)before指针指向上个插入的元素,after指向下一个插入的元素(有一个空的header节点作为循环队列的入口),next指向数组桶中的next。
8.ArrayList和LinkedList:
- ArratList的底层使用动态数组,默认容量为10,当元素数量到达容量时,生成一个新的数组,大小为前一次的1.5倍,然后将原来的数组copy过来;因为数组在内存中是连续的地址,所以ArrayList查找数据更快,由于扩容机制添加数据效率更低,线程不安全。
- LinkedList的底层使用链表,在内存中是离散的,没有扩容机制;LinkedList在查找数据时需要从头遍历,所以查找慢,但是添加数据效率更高,线程不安全。
- ArrayDeque:底层也是动态数组,有两个指针head和tail,分别指向头元素和尾元素的下一个位置;tail指针到末尾了会指向下标0,当head==tail时会发生扩容。当ArrayDeque删除不能删除指定索引的数据,只能按照值删(匹配到的第一个)。不能存储空值。
- 想线程安全:
。使用collentions.synchronizedList()方法为ArrayList加锁
。使用Vector,Vector底层与Arraylist相同,但是每个方法都由synchronized修饰,速度很慢
。使用juc下的CopyOnWriterArrayList,该类实现了读操作不加锁,写操作时为list创建一个副本,期间其它线程读取的都是原本list,写操作都在副本中进行,写入完成后,再将指针指向副本。
9.String、StringBuilder,StringBuffer的区别:
- String不可变,为char[]数组,并由final修饰。改变字符串实际上是生成了一个新的字符串,将引用指过去,因为不可变,所以线程安全。
- StringBuilder:可变单线程操作大量数据,修改改的是值,引用不变,可变,线程不安全。
- StringBuffer:可变多线程操作大量数据,修改改的是值,引用不变,关键方法都加了synchronized,线程安全。
10.HashCode和equals:
- HashCode方法和equals方法原本都是通过地址进行判断hash码或者是否相等,但是可以被重写成通过值判断。
11.Java创建对象的五种方式:
- 通过 new 关键字 ,通过 new 关键字调用类的有参或无参构造方法来创建对象。
- 通过 Class 类的 newInstance() 方法 这种默认是调用类的无参构造方法创建对象。
。Person p2 = (Person) Class. forName("com.ys.test. Person"). newInstance();
- 通过 Constructor 类的 newInstance 方法,法2其实内部用的还是本方法。
。Person p3 = (Person) Person.class.getConstructors()[0].newInstance();
- 通过 Clone 方法,默认浅拷贝。
。Person p4 = (Person) p3.clone();
- 序列化 序列化是把堆内存中的 Java 对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络节点。而反序列化则是把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。
12.浅拷贝和深拷贝:
- 浅拷贝:浅拷贝只复制某个对象的引用,而不复制对象本身,新旧对象还是共享同一块内存
- 深拷贝:深拷贝会创造一个值相同的对象,新对象和原对象不共享内存,修改新对象与原对象无关。
- 实现拷贝都要实现cloneable接口,重写clone方法。java默认是浅拷贝,想要深拷贝手动复制一个副本。
13.什么是反射:
- 获取类的class对象,动态的操作类的内部结构,获取到属性和方法。
- 应用场景:要操作权限不够的类属性和方法时、动态代理,实现自定义注解时、动态加载第三方jar包时、按需加载类,节省编译和初始化时间。
- 如何获取class对象:class.forName(类路径),类.class(),对象的getClass()。
- 优缺点:
。优点:可以在运行时调用一个类的方法属性,提高了程序的灵活;提高代码复用率,比如动态代理;
。缺点:jvm无法对反射出来的代码做优化性能低;代码的可读性下降;反射破坏了代码本身的封装,造成安全性问题。
14.JavaIO流:
- io分为字节流和字符流
- 字节流:InputStream/OutputStream为抽象类,派生若干子类。按 8 位传输,以字节为单位输入输出数据。
- 字符流:Reader/Writer 是字符的抽象类,派生若干子类。按 16 位传输,以字符为单位输入输出数据。
- 为什么要有字符流:字符是字节在虚拟机转换得到的,转换过程是很耗时的,而且不同的编码可能会有乱码问题,所以就提出了字符流。
- BIO、NIO、AIO:
。BIO:同步阻塞IO。
。NIO:同步非阻塞IO,支持阻塞和非阻塞两种模式。对于低负载、低并发的应用程序,使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的应用,应使用 NIO 的非阻塞模式来开发。
。AIO:异步阻塞IO。
15.Java集合:
- 分为Collection(list,set,queue)接口和Map(map)接口
- list,set,queue,map四者区别:
。list:存放有序可重复的元素。ArrayList,Vector,LinkedList;
。set:存放无序,不可重复的元素。HashSet,TreeSet;
。queue:存放有序可重复的元素。Deque;
。map:存放键值对,key不可重复,key与value一对一。HashMap,Hashtable,TreeMap;
16.为什么说java是半编译半解释型语言
- java代码的执行流程:
17.JVM,JRE,JDK:
- JVM:将编译后的class文件转化成可以在各种系统上运行的的二进制机器码。
- JRE:JVM+java类库+java命令。只能运行java代码。
- JDK:JRE+编译器和工具,可以创建或编译java代码。
18.父类 xx = new 子类()与子类 xx = new 子类()的区别:
- 区别:
。1中对象只能调用从父类继承的方法
。1和2调用方法默认都是调用子类重写之后的方法
- 原因:子类需要加载父类存在的属性和方法,所以初始化前会先加载父类的构造器,而父类构造器会去加载子类重写后的方法。
- 空构造器:因为子类构造器没写super时会默认调用父类的无参构造器,如果父类没有写出无参构造器就会报错
19.单例模式:
- 构造方法为什么是私有:避免外界通过构造方法创建对象,因此将构造方法隐藏起来。
- get方法为什么加static:调用方法要不new对象后对象.方法;要不类.静态方法,因为构造方法被隐藏,所以需要将get方法设置为static。
- get方法为什么要二次判空:如果多个线程同时判断对象为空后进行锁争抢,一个线程创建完单例对象后释放锁,其他线程进来不二次判空的话就又会创建一次单例对象。
- 单例对象为什么加static:静态方法无法访问非静态变量,而get方法时静态,所以单例对象也需要加static;并且加static的对象在类加载的时候就被初始化,也更合理。
- 单例对象为什么加volatile:防止指令重排,避免对象未被初始化就被其他线程返回。
以上是关于Java基础的主要内容,如果未能解决你的问题,请参考以下文章