理论篇-Java中一些零碎的知识点

理论篇-Java中一些零碎的知识点

1. Java中length,length方法,size方法区别

length属性:用于获取数组长度。
length方法:用于获取字符串长度。
size方法:用于获取泛型集合有多少个元素。

2. isEmpty方法

isEmpty方法用来判断是否为空,很多类都有,比如String、Queue、Stack类。

3.  Queue中 add/offer,element/peek,remove/poll方法

add         增加一个元索                         如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove   移除并返回队列头部的元素     如果队列为空,则抛出一个NoSuchElementException异常
element  返回队列头部的元素               如果队列为空,则抛出一个NoSuchElementException异常
offer       添加一个元素并返回true         如果队列已满,则返回false
poll         移除并返问队列头部的元素     如果队列为空,则返回null
peek       返回队列头部的元素               如果队列为空,则返回null

4. Set用

Set的特点:不能存储相同的元素。

5. static代码块、构造代码块和构造方法

  • static代码块:类加载时就会调用,仅执行一次,没有名字、参数和返回值。
  • 构造代码块:在对象初始化时执行,有几个对象执行几次,晚于static代码块,早于构造函数,没有名字、参数和返回值。
  • 构造方法:在对象初始化时执行,有几个对象执行几次,没有返回值。
 public class Constructor {

     public static void main(String[] args) {
System.out.println("创建第一个对象:");
Test test1 = new Test();
}
} class Test {
// 静态代码块1
static {
System.out.println("静态代码块1");
}
// 构造代码块1:
{
System.out.println("构造代码块1");
} // 构造函数1
public Test() {
System.out.println("无参构造函数");
} }

运行结果:

 创建第一个对象:
静态代码块1
构造代码块1
无参构造函数

6. 重写equals和hashcode方法

        重写equals()方法就必须重写hashCode()方法主要是针对HashSet和Map集合类型。集合框架只能存入对象(对象的引用)。在向HashSet集合中存入一个元素时,HashSet会调用该对象(存入对象)的hashCode()方法来得到该对象的hashCode()值,然后根据该hashCode值决定该对象在HashSet中存储的位置。简单的说:HashSet集合判断两个元素相等的标准是:两个对象通过equals()方法比较相等,并且两个对象的HashCode()方法返回值也相等。如果两个元素通过equals()方法比较返回true,但是它们的hashCode()方法返回值不同,HashSet会把它们存储在不同的位置,依然可以添加成功。同样:在Map集合中,例如其子类Hashtable(jdk1.0错误的命名规范),HashMap,存储的数据是对,key,value都是对象,被封装在Map.Entry,即:每个集合元素都是Map.Entry对象。在Map集合中,判断key相等标准也是:两个key通过equals()方法比较返回true,两个key的hashCode的值也必须相等。判断valude是否相等equal()相等即可。
        集合类都重写了toString方法。String类重写了equal和hashCode方法,比较的是值。

7. 序列化与反序列化

        Java中对象的序列化是指将内存中的对象转换成以字节序列的形式表示,这些字节序列包含了对象的数据和信息,序列化后的对象可以被写到数据库、文件中或者网络传输,一般当我们使用缓存cache或远程调用rpc的时候,经常要让实体类实现Serializable接口。
        反序列化就是将字节序列恢复成Java对象的过程 。
                                                                                具体参考《对Java Serializable(序列化)的理解和总结》 

8. transient关键字

        transient作用是让某些被修饰的成员属性变量不被序列化,比如类中的字段值可以根据其它字段推导出来 ,可以考虑使用关键字transient修饰,主要是为了节省存储空间。

9. 枚举enum

  • 是什么:枚举是一个特殊的类,有实例字段、构造器和方法, 因此它是可拓展的。
  • 何时用:定义固定常量集合的时候,等价于public static final 变量名,如下所示:
 public enum Index {
ZERO(0),
ONE(1),
TWO(2); private int index; Index(int index) {
this.index = index;
} public int getIndex() {
return index;
}
}

10. ThreadLocal局部变量实现线程同步 

如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
ThreadLocal 的使用:java并发编程 ThreadLocal使用

11. 原子类Automic

赋值一个变量,编译后会产生多个JVM语言指令,如果处于多线程情况下对于这个变量进行加减操作会导致数据不一致。为避免此问题, Java 引入了原子变量 Atomic。当一个线程正在操作一个原子变量时,即使其他线程也想要操作这个变量,类的实现中含有一个检查那步骤操作是否完成的机制。 基本上,操作获取变量的值,改变本地变量值,然后尝试以新值代替旧值。如果旧值还是一样,那么就改变它。如果不一样,方法再次开始操作。这个操作称为  Compare and Set (校对注:简称 CAS ,比较并交换的意思)。具体原理参见java并发包之原子类Automic

12. 线程池

New Thread的弊端如下:
  • 每次新建对象性能差。
  • 线程缺乏统一的管理,可能无限制的新建线程,相互竞争,可能占用过多的资源导致OOM。
  • 缺乏更多功能,如定时执行、定期执行、线程中断。
Java提供的四种线程池的好处在于:
  • 重用存在的线程,减少对象创建、消亡的开销,提高响应速度。
  • 可有效控制最大并发线程数,降低资源消耗,同时避免过多资源竞争。
  • 提高线程的可管理性,可以进行统一的分配,调优和监控。
  • 提供定时执行、定期执行、单线程、并发数控制等功能。
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool 创建一个可缓存线程池,可灵活回收空闲线程,适合大量、耗时少的任务,若无可回收,则新建线程;
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待;
newScheduledThreadPool 创建一个定时线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建单个工作线程执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
线程池不建议使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样可以让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
Executors各个方法的弊端:
  • newFixedThreadPool和newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存。
  • newCachedThreadPool和newScheduledThreadPool:
主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程。
以ScheduledThreadPoolExecutor为例,其中,当daemon(true)时表示设置一个新的守护线程,注意当用户线程结束后,守护线程会自动退出。如果任务的执行时间超过任务调度周期,比如任务执行需要10s,而给定执行时间间隔是5s的话,任务的调度是在任务10s执行完之后立即重新执行,而不是5s的周期:
 public class ScheduledThreadPoolTest {
public static void main(String[] args) throws InterruptedException {
// 创建大小为5的线程池
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5,new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(false).build()); for (int i = 0; i < 3; i++) {
Task worker = new Task("task-" + i);
// 只执行一次
// scheduledThreadPool.schedule(worker, 5, TimeUnit.SECONDS);
// 周期性执行,每5秒执行一次
scheduledThreadPool.scheduleAtFixedRate(worker, 0,5, TimeUnit.SECONDS);
}
Thread.sleep(10000);
System.out.println("Shutting down executor...");
// 关闭线程池
scheduledThreadPool.shutdown();
boolean isDone;
// 等待线程池终止
do {
isDone = scheduledThreadPool.awaitTermination(1, TimeUnit.DAYS);
System.out.println("awaitTermination...");
}
while(!isDone)
System.out.println("Finished all threads");
}
}
 class Task implements Runnable {
private String name; public Task(String name) {
this.name = name;
} @Override
public void run() {
System.out.println("name = " + name + ", startTime = " + new Date());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("name = " + name + ", endTime = " + new Date());
}
}

13. Java中自动装箱与拆箱(autoboxing and unboxing)

自动装箱就是自动将基本数据类型转换为包装器类型;
自动拆箱就是自动将包装器类型转化为基本数据类型。
 Integer total = 99;//自动装箱
int totalprim = total;//自动拆箱

自动装箱拆箱的类型为八种基本类型: 

理论篇-Java中一些零碎的知识点基本类型和包装器类型有许多不同点

14. Java中的隐式转换和强制转换

  • 由低到高

 byte a = 1;
short b = a;
int c = b;
long d = c; float f = 2f;
double g = f;
  • 由高到低

默认强制转换

15. Java中String、StringBuffer、StringBuilder

String:不可变长的字符序列;

StringBuffer:可变的字符序列,线程安全,效率低;

StringBuilder:可变的字符序列,线程不安全,效率高。

16. 向下转型和向上转型

为了共用一套代码:

 public class PetFactory {
private PetFactory() {
}
public static Pet getPet(String type) throws Exception {
Pet pet = null;
switch (type) {
case "dog":
pet = new Dog();
break;
case "cat":
pet = new Cat();
break;
default:
throw new Exception();
}
return pet;
}
}
public void example(String type){
Pet pet = PetFactory.getPet(type); //向上转型
playWithPet(pet);//公共的
if(pet instanceOf Dog){
Dog snoopy = (Dog) pet; //向下转型
snoopy.sitDown();
} else {
Cat white = (Cat) pet; //向下转型
white.climb();
}
}

17. String hashCode 方法选择数字31作为乘子

  • 31是一个不大不小的质数,质数的特性(只有1和自己是因子)能够使得它和其他数相乘后得到的结果比其他方式更容易产成唯一性,降低哈希算法的冲突率;
  • 31可以被 JVM 优化,31 * i = (i << 5) - i。

18. Java内部类访问外部类局部变量必须声明为final

编译后的内部类和外部类各一个class文件,内部类访问外部类局部变量实际是复制了一份,为了避免数据的不一致性,设置局部变量为final。

19. 类的加载

Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息,通过该元信息可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户通过元信息间接调用Class对象的功能。
在Java中,类装载器把一个类装入JVM中,要经过以下步骤:
  • 装载:查找和导入Class文件;
  • 链接:把类的二进制数据合并到JRE中; (a)校验:检查载入Class文件数据的正确性; (b)准备:给类的静态变量分配存储空间; (c)解析:将符号引用转成直接引用;
  • 初始化:对类的静态变量,静态代码块执行初始化操作

20. 双亲委派模式

  • 原理

类加载器有加载类的需求时,先请求父加载器帮忙加载,直到传到顶层启动类加载器,父加载不了再由子加载器加载;

  • 使用原因

为了避免重复加载,父加载器加载过了子加载器就没必要再加载了,否则我们可以随时使用自定义的类代替Java核心API中的类型,好阔怕

21. volatile关键字

  • 使用volatile关键字修饰变量,会强制将修改值立即写入主存,当一个线程修改时,会导致其他线程工作内存中的变量缓存无效,其他线程再读取变量时会去主存读取;
  • volatile 修饰的变量会禁止指令重排序。

与synchronized对比:

  • volatile只能修饰变量。synchronized还可修饰方法;
  • volatile只能保证数据的可见性,不能用来同步,多个线程并发访问volatile修饰的变量不会阻塞。synchronized不仅保证可见性,而且还保证原子性,多个线程争夺synchronized锁对象的时候,会出现阻塞。

22. synchronized与Lock的区别

  • synchronized是java内置关键字,在JVM层面,而Lock是个java类;
  • synchronized无法判断是否获取到锁,Lock可以判断是否获取到锁;
  • synchronized会自动释放锁(执行完同步代码会释放锁 ;执行过程中发生异常会释放锁),Lock须在finally中使用unlock()方法手工释放锁,否则容易造成线程死锁;
  • 用synchronized关键字的线程,如果一个线程获得了锁,其他线程等待。如果线程阻塞,其他线程会一直等待,而Lock锁就不一定会等待下去,如果尝试获取不到就结束了。

23. 深拷贝和浅拷贝

这个问题纠结了好久,在看原型模式之前终于要解决一下子了。上定义:

  • 浅拷贝:浅拷贝是指在拷贝对象时,对于八大基本类型(byte,short,int,long,char,double,float,boolean)的变量重新复制一份,而对于引用类型的变量只是对直接引用进行拷贝,没有对直接引用指向的对象进行拷贝;
  • 深拷贝:深拷贝是指在拷贝对象时,不仅把基本数据类型的变量会重新复制一份,同时会对引用指向的对象进行拷贝,注意拷贝的时候值也拷贝过来。

在 Java 中,所有的 Class 都继承自 Object ,而在 Object 中有一个clone()方法,它被声明为了 protected ,所以我们可以在其子类中使用它。它限制所有调用clone()方法的对象,都必须实现Cloneable接口,否者将抛出 CloneNotSupportedException 这个异常。先看浅拷贝,

 private class Father implements Cloneable {
public String name;
public int age;
public Child child; @Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException ignore) {
e.printStace();
}
return null;
}
}

如果使用该方法去clone,克隆出的Father对象的hashcode跟被克隆的对象的hashcode不同,但成员变量child的hashcode是一样的,说明引用对象使用的是同一个,再看深拷贝,

 private class Father implements Cloneable {
public String name;
public int age;
public Child child; @Override
public Object clone() {
try {
Father cloneFather =(Father)super.clone();
cloneFather.child = (Child)super.clone();
return cloneFather;
} catch (CloneNotSupportedException ignore) {
e.printStace();
}
return null;
}
}
private class Child implements Cloneable {
public String name;
public int age; @Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException ignore) {
e.printStace();
}
return null;
}
}

在克隆Father对象时,又对成员对象Child(里边的成员变量是基本类型)进行了克隆,这就是深拷贝,层级拷贝,直到基本类型,这样代码量不可估计,所以有了序列化拷贝的方式,就是把对象放到流里,再拿出来,前提是没有transient对象,对象以及对象内部所有引用到的对象都是可串行化的,简单地讲,序列化就是将对象写到流中,写到字节流里就等于复制了对象,原来的对象并没有动,然后写到另个内存地址中去。。

 public Object deepClone()
{
//写入对象
ByteArrayOutoutStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);
//读取对象
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
}

24. Java为什么取消多继承

子类继承多个父类有两个问题:

  • 多个父类中有相同名字的变量,子类在调用的时候不知道调用哪一个;
  • 多个父类中有相同名字的方法,子类未重写,子类在调用的时候不知道调用哪一个。

25. Java IO和装饰类模式

Java IO这块流比较多,以前也没看过,最近在看装饰者模式(https://www.cnblogs.com/lcmichelle/p/10854871.html),正好梳理了一下。Java应用程序通过输入流(InputStream)的Read方法从源地址处读取字节,然后通过输出流(OutputStream)的Write方法将流写入到目的地址。

首先,流的来源主要有五种:文件(File)、字节数组、StringBuffer、其他线程和序列化的对象,因此,Java中有FileInputStream处理文件,ByteArrayInputStream处理字节数组,StringBufferInputStream处理StringBuffer,PipedInput-Stream处理线程间的输入流,ObjectInputStream处理被序列化的对象。还有一个SequenceInputStre-am处理包裹有多种数据来源的业务。

后来,这些流在使用过程中出现了一些问题:FileInputStream读磁盘速度慢;读出的数据都是byte[]类型,需要用户自己转换成基本类型;从Stream读出来的数据无法推回去。针对这些问题,Java需要增加拥有缓存的BufferedInputStream,把byte转换成JAVA基本类型的DataInput-Stream和回写数据到stream的Push-backInput-Stream。直接增加类的话,排列组合形成的子类太多,因此,Java采用了装饰者模式。

如下图所示,最里边的实心是处理文件读取的FileInputStream,外面套一个BufferedInputStream的壳,那么这部分就是带buffer的FileInputStream。如果再套一个DataInputStream,那么就成了能输出int这样java 基本类型并且带buffer的FileInputStream。搭配由客户去决定,我们只需要提供套壳和最里面的实心InputStream(InputStream的6个孩子)。客户在搭配的时候必须有一个实心,否则就没有数据来源。 BufferedInputStream,DataInputStream,PushbackInputStream都继承自InputStream类,这样才能实现嵌套。这3个套娃壳有着共同的特点都是用来装饰,在他们上层在抽象一个FilterInputStream,让FilterInputStream继承自InputStream,以后所有的装饰类都从FilterInputStream继承。

理论篇-Java中一些零碎的知识点

理论篇-Java中一些零碎的知识点

26. Comparable和Comparator接口

相同点:

  • 都是Java util下的两个接口,都是对自定义的数据结构比较大小,比如Person类。

不同点:

  • Comparable是内部比较器,定义在类的内部,而Comparator是外部比较器,定义在类的外边,不修改实体类;
  • Comparable需要实现compareTo()方法,传一个外部参数进行比对;Comparator接口需要实现compare()方法,对外部传入的两个类进行比较;
  • 实现Comparator接口代码采用策略模式,可以定义某个类的多个比较器,在排序时根据实际场景*调用,而Comparable接口实现后不方便改动,扩展性不好。

举例:

comparable

 class Person implements Comparable<Person>{//该接口强制让人具有比较性
private String name;
private int age;
public Person(String name,int age) {
this.name = name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Person o) {
if(this.age>o.age){
return 1;
}else if(this.age<o.age){
return -1;
}else{
return this.name.compareTo(o.name);
}
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
 import java.util.Iterator;
import java.util.TreeSet; public class Demo {
public static void main(String[] args) {
Person p0 = new Person("张三",3);
Person p = new Person("张三",1);
Person p1 = new Person("张三",2);
Person p2 = new Person("张四",2);
Person p3 = new Person("张四",2); TreeSet<Person> treeSet=new TreeSet<Person>();
treeSet.add(p0);
treeSet.add(p);
treeSet.add(p1);
treeSet.add(p2);
treeSet.add(p3); Iterator<Person> iterator = treeSet.iterator();
while(iterator.hasNext()){
Person next = iterator.next();
System.out.println(next);
}
}
}

comparator

 class Com implements Comparator<Person>{//该接口强制让集合具有比较性
@Override
public int compare(Person o1, Person o2) {
if(o1.getAge()>o2.getAge()){
return 1;
}else if(o1.getAge()<o2.getAge()){
return -1;
}else{
return o1.getName().compareTo(o2.getName());
}
}
} class Person{
private String name;
private int age;
public Person(String name,int age) {
this.name = name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
 import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet; public class Demo {
public static void main(String[] args) {
Person p0 = new Person("张三",3);
Person p = new Person("张三",1);
Person p1 = new Person("张三",2);
Person p2 = new Person("张四",2);
Person p3 = new Person("张四",2); TreeSet<Person> treeSet=new TreeSet<>(new Com());
treeSet.add(p0);
treeSet.add(p);
treeSet.add(p1);
treeSet.add(p2);
treeSet.add(p3); Iterator<Person> iterator = treeSet.iterator();
while(iterator.hasNext()){
Person next = iterator.next();
System.out.println(next);
}
}
}

可以看到comparator方法中是将Com对象作为参数传给treeset等需要进行排序的集合容器的构造函数中了。

最后,如果实现类没有实现Comparable接口,又想对两个类进行比较(或者实现类实现了Comparable接口,但是对compareTo方法内的比较算法不满意),那可以实现Comparator接口,自定义一个比较器。

27. JVM调优总结 -Xms -Xmx -Xss -XX

Xms是指设定程序启动时占用内存大小,就是JVM默认堆的大小。一般来讲,大点,程序会启动的快一点,但是也可能会导致机器暂时间变慢。

Xmx是指设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多内存,超出了这个值,就会抛出OutOfMemory异常。

Xss是指设定每个线程的堆栈大小。这个就要依据你的程序,看一个线程大约需要占用多少内存,可能会有多少线程同时运行等。

-XX:PermSize用于设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。

上面四个参数的设置都是默认以Byte为单位的,也可以在数字后面添加[k/K]或者[m/M]来表示KB或者MB。而且,超过机器本身的内存大小也是不可以的,否则就等着机器变慢而不是程序变慢了。

28. 同步和异步

  • 同步:发送一个请求,等待返回,然后再发送下一个请求,比如电话,需要接通对方
  • 同步好处:按顺序修改可以避免出现死锁,读脏数据的发生
  • 异步:发送一个请求,不等待返回,随时可以再发送下一个请求,比如广播
  • 异步好处:多线程并发可以提高效率

29. 大端和小端

计算机的内存最小单位是是BYTE(字节)。
一个大于BYTE的数据类型在内存中存放的时候要有先后顺序。

高内存地址放整数的高位,低内存地址放整数的低位,这种方式叫倒着放,术语叫小端对齐。电脑X86和手机ARM都是小端对齐的。

高内存地址放整数的低位,低内存地址放整数的高位,这种方式叫正着放,术语叫大端对齐。很多Unix服务器的cpu都是大端对齐的。

理论篇-Java中一些零碎的知识点

30. Lambda表达式的意义

上一篇:JS闭包的理解


下一篇:辅助模块:udp_sweep