java知识点汇集

学习过程中关于java有些零碎的问题,学习后又常常容易忘记,所以干脆记录下来,放在这里。目前没有进行分类了,只是学了什么就记录什么。但尽可能讲清楚,弄明白,但求甚解。

Java字符串比较

== vs equals(): 在Java中==比较符是同时比较两者的地址和值,都相等时才为true。相当于在现实中一定是同一个东西才会是相同的。而equals() 是比较两个对象的中所表示的内容是否相同。相当于现实中说某些特征相同的物品是否相等,比如说同一个生产线出来的商品,我们从外观形态等一系列特征上判断认为是完全一样的就可以认为是相等的。也可以只比较某些特征,比如人与人从生物的角度说可以都是哺乳动物,这也可以认为是相等的。因此,equals() 是可以在对象中继承object超类后重写的,具体的比较特征是可以自定义的。就String类中的equals() ,需要对象中的字符串副本是长的完全一样的才行。

从上可以判断,两个字符串对象比较是肯定不能用==的,只能用equals()方法,当比较字符串常量的时候是可以用==的。因此需要理解一些字符串对象生成过程。

String a = "str”+1;
String b = "str1";
System.out.println(a.equals(b));
System.out.println(a==b);

此时结果应该都是true,在Java中,初始化的常量字符串被储存在Java的常量池中。而引用变量存在Java的内存栈中保留指向常量字符串的地址引用。因此a和b是栈上不同位置指向了常量池中同一字符串。两种比较方式都能得到true。

String a = "str”+1;
String b = "str1";
b = new String(b);
System.out.println(a.equals(b));
System.out.println(a==b);

此时用==比较符就会得到false的结果了。因为new 出来的String对象是堆中,b 是栈上的变量指向了堆内存。虽然值一样,但地址是不同的。同时每一次new都会在堆内存创建一个变量,因此即使是同样的副本,经过对象包装后地址都是不相同的。

java字符串包装器

String类 vs StringBuffuer类 vs StringBuilder类:

String不可变,对于可变字符串的处理,应考虑后两种方式,但初始化字符串常量的拼接是系统编译时自动采用StringBuffer进行的。通常情况下对几个动态的string对象做拼接用的都是StringBuilder类,它最快,但是非线程安全。StringBuffer是线程安全的,相较于StringBuilder要慢一些。

hashCode()源码分析

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
          char val[] = value;
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

这是String类中的源码,将字符串中的每个字符的ASCII码值做对应处理,映射到int上。为什么采用31,主要考虑几点,要是奇数,散列值通常考虑素数,还有就是31*i==(i<<5)-i,在虚拟机中自动对这个运算进行了优化。还有一种说法是,ASCII码是七位,正常情况希望散列时在每一位出现1或0的概率都是50%,实际第四位和第五位出现1的概率高的多,因为小写字母分布在这个区间,而日常字符串小写字母比例更大,用31就可以把概率分摊到其他几位,减少冲突的可能。具体的原因说不清楚,但思考方式很值得探索,向前辈们致敬!

两数交换

  • 设置临时变量

    利用中间变量完成交换,不赘述

  • 无中间变量的两数交换

    • 用加减法保留信息,当然乘除法也是可以的。但容易出现类型溢出的情况。

      A=A+B;
      B=A-B;
      A=A-B;
      
    • 用异或运算同样可以完成,而且不会溢出,是CPU的低级运算方式,效率很高,推荐使用。

      A=A^B;
      B=A^B;
      A=A^B;
      

jvm内存区

java内存模型图【来自牛客网】

大多数 JVM 将内存区域划分为

  • Method Area(Non-Heap)(方法区) 【共享】
  • Heap(堆) 【共享】
  • Program Counter Register(程序计数器)
  • VM Stack(虚拟机栈,也有翻译成JAVA 方法栈的)
  • Native Method Stack ( 本地方法栈 )

首先我们熟悉一下一个一般性的 Java 程序的工作过程。一个 Java 源程序文件,会被编译为字节码文件(以 class 为扩展名),每个java程序都需要运行在自己的JVM上,然后告知 JVM 程序的运行入口,再被 JVM 通过字节码解释器加载运行。那么程序开始运行后,都是如何涉及到各内存区域的呢?

概括地说来,JVM初始运行的时候都会分配好 Method Area(方法区) 和Heap(堆) ,而JVM 每遇到一个线程,就为其分配一个 Program Counter Register(程序计数器) , VM Stack(虚拟机栈)和Native Method Stack (本地方法栈), 当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因。

jvm内存配置参数

struts

Struts是MVC【逻辑和数据解耦】的一种实现,它将Servlet和JSP标记(属于J2EE规范)用作实现的一部分。Struts继承了MVC的各项特性,并根据J2EE的特点,做了相应的变化与扩展。

  • 控 制

    Struts-config.xml与Controller关联,在Struts中,承担MVC中Controller角色的是一个Servlet,叫ActionServlet【通用控制组件】。它处理所有发送到Struts的HTTP请求,截取和分发这些请求到相应的动作类(这些动作类都是Action类的子类)。另外控制组件也负责用相应的请求参数填充【ActionForm】,并传给动作类【ActionBean】。动作类实现核心商业逻辑,它可以访问javabean或调用EJB。最后动作类把控制权传给后续的JSP,后者生成视图。所有这些控制逻辑利用Struts-config.xml文件来配置。

  • 视图

    主要由JSP生成页面完成视图,Struts提供丰富的JSP标签库:Html,Bean,Logic,Template等,这有利于分开表现逻辑和程序逻辑。

  • 模 型

    以一个或多个javabean的形式存在。这些bean分为三类:ActionForm、Action、JavaBean or EJB。ActionForm通常称之为FormBean,封装了来自于Client的用户请求信息,如表单信息。Action通常称之为ActionBean,获取从 ActionSevlet传来的FormBean,取出FormBean中的相关信息,并做出相关的处理,一般是调用JavaBean或EJB等。

  • 流程

    在Struts中,用户的请求一般以.do作为请求服务名,所有的.do请求均被指向 ActionSevlet,ActionSevlet根据Struts-config.xml中的配置信息,将用户请求封装成一个指定名称的FormBean,并将此FormBean传至指定名称的ActionBean,由ActionBean完成相应的业务操作,如文件操作,数据库操作等。每一个*.do均有对应的FormBean名称和ActionBean名称,这些在Struts-config.xml中配置。

  • 核心

    Struts的核心是ActionSevlet,ActionSevlet的核心是Struts-config.xml。

处理流程图

JVM参数

JVM参数设置、分析

serialVersionUID

java序列化和反序列化

java的序列化和反序列化用于解决持久化问题,用UID判断版本是否兼容。当允许一个对象序列化时实现Serializable接口即可,通过new ObjectOutputStream(new FileOutputStream(file)).writeObject(obj)执行序列化操作。通过new ObjectInputStream(new FileInputStream(“D:/hello.text”)).readObject()执行反序列化操作。

需要注意的时,默认情况UID由编译器将类hash后自动生成的,因此修改类信息后,反序列化就会失败(UID不同),因此最好是自行指定UID=1L,根据实际情况进行UID的变更。

反序列化安全漏洞了解:深入理解 JAVA 反序列化漏洞

长度为0的数组或集合

在看源码时发现有生成一个长度为0的对象数组,深入学习后了解到这样做的好处。

  • 在内容为空的情况下,返回一个0长度的数组或集合类,对于调用接口的人不需要进行null的判断和处理,优化了代码
  • 为了避免每次都返回一个0长度的实例,可以设置静态常量。对于集合类,Collections 提供了EMPTY_LIST | EMPTY_MAP | EMPTY_SET 以及对应的静态常量方法 emptyList() | emptyMap() | emptySet()

这个小技巧在effectiveJava 中也有收录。

String 类为什么设计成final?

为什么String是不可变的?

总结下,主要是考虑到:合理利用线程池,缓存hash,利于其他对象的使用,安全性和线程安全。

String被设计成不可变的,为了不被熊孩子恶意修改,所以就设计成了final,不允许继承和修改。大概是这样吧~

当然,我们是可以通过反射修改String内部的value变量的值的~

集合框架总结

总结1

总结2

继承关系图

对于集合框架有整体了解,逐渐深入剖析,学习源码设计。

Collection接口分析–java1.8

java SE 1.8中Collection接口分析

Collection接口有标准的集合相关的接口方法,后面具体的看怎么实现就好。但jdk1.8新增了几个default方法,分别是removeIf(),spliterator(),stream(),parallelStream(),这里认真学习一下。

  • default关键字

    1.8引入,为了有效扩展接口设计的,在接口中可以默认实现该方法而不必使所有实现子类都被迫实现该方法。default

  • default boolean removeIf(Predicate<? super E> filter)

    Objects.requireNonNull(filter);
    boolean removed = false;
    final Iterator<E> each = iterator();
    while (each.hasNext()) {
        if (filter.test(each.next())) {
            each.remove();
            removed = true;
        }
    }
    return removed;
    

    该方法移除集合中满足filter条件的内容,示例:

    // 删除大于0的数
    new ArrayList<Integer>(){{
                add(-1);
                add(-3);
                add(5);
                add(7);
            }}.removeIf((num) -> num > 0);
    

    关于Predicate,1.8新增,支持函数式编程

  • default Spliterator spliterator()
  • default Stream stream()
  • default Stream parallelStream()

    这部分涉及java1.8新增的流处理和函数式编程,需要专门深入的学习,可以参考Java 8函数式编程 进行学习

Collections集合类分析–

移步 Collections工具类源码学习

Fork me on GitHub