• 01 basic


    下载安装的 JDK 目录

    java 主目录下的: src.zip 是 jdk 的源代码.

    JDK: java development kit 包含java 编译器和JRE

    JRE: java run environment java 运行时环境,JRE 包含 JVM

    JVM: java virtual machine java 的虚拟机, 真正跑 class 文件的.  

    JAVA 内存结构

    java 虚拟机内存分为3个区域, 栈stack, 堆heap, 方法区method area

    底层都类似的,各种语言互相抄袭的.

    栈 stack

    表示方法执行的内存模型, 每个方法被调用都会创建一个栈帧(栈帧是栈内部的一个独立的存储空间)(存储局部变量, 操作数, 方法出口 等)

    JVM 为每个线程创建一个栈, 用于存放该线程执行方法的信息(实际参数,局部变量等)

    栈属于线程私有, 不能实现线程间的共享。

    栈的存储特性是“先进后出”

    栈由系统自动分配,速度快,栈是一个连续的内存空间.

    堆heap

    堆用于存储创建好的对象数组(数组也是对象) {对象的所有东西都存储在这里, 包括实例变量, 比如 new一个对象, 那么全部都在这里}

    JVM 只有一个堆, 被所有线程共享.

    堆是一个不连续的内存空间, 分配灵活, 速度慢.

    方法区 (又叫静态区)

    JVM 只有一个方法区, 被所有线程共享。

    方法区实际上也是堆,只是用于存储类(不是实例, 而是类[模板]), 常量的相关信息.

    用于存储程序中永远不变或唯一的内容(类信息, 静态变量,字符常量 等)

    画一个程序内存走势

    1. java xxx 开始执行程序. 这时候开始启动虚拟机, 分配内存空间(栈和堆)

    2. 将 文件名相同的类(也就是main方法所在的类), 这个类本身加载到内存的方法区中. 也就是将 xxx 加载到方法区.

        然后找到 static 的 main方法(xxx的静态方法刚才都已经在内存中了), 然后可以执行这个 main 方法.

    3. 这时候在栈空间开辟 main 方法的 栈帧, 然后 main 方法里有 stu = new Student(),

        这时: (新建对象的过程)

        a. 首先执行类加载过程(如果此时Student类本身没在方法区), 加载类之后, 这个类的相关代码就在内存中了(也就是方法区中)

        b. JVM 在堆中为新生成的对象Student 开辟内存空间. 对于分配内存的大小, 在Student类本身加载到方法区后, 就可以知道了.

        c. 将构造函数放到栈 stack 中, 开始进行对象初始化工作. 

        d. 将新开辟内存的地址返回给 main 函数中的 stu 局部变量.

    4. 根据main函数的执行, stu.id = 1001, stu.sname = "aa", 这样就会在堆的对象中, 对实例变量进行赋值.

    5. 当调用 stu.play() 时, 找到堆中该对象的 play()方法的内容, 将play方法入栈, 开始执行 play() 方法中的内容.

    Comment: 栈中的函数执行完之后, 就会出栈(根据栈的规则)

    JVM 调优与垃圾回收

    垃圾回收

    引用计数: 判断对象是否要回收, 但是如果是循环互相引用,就会有问题。

    引用可达法: 所有的引用关系做一张图,(貌似可以排除循环互相引用)

    分代垃圾回收机制: 年轻代(新创建的对象),年老代(新创建之后过了一段时间),持久代(不用回收)

    持久代: 用于存储静态文件, 如类方法,静态变量等. (一直在内存中, 也不变化,不会被移出内存) 这个区满了,会调用 FullGC来清理(要注意)

    年轻代: 分为 Eder 和 Suvivor 两个区域.

    Eder, Suvivor {Eder 满了,触发垃圾回收GC(Minor GC) -> 将留下来的对象copy放到 Suvivor1Suvivor2 中, 然后清空Eder,  然后再新增加对象到Eder区}

      依次类推, 这样如果迭代15次, 对象还在Suvivor中, 就把该对象放到 Tenured/Old 区.

    年老代old: 只有一个区, 名字叫 Tenured

    Tenured: 当这个区的占用达到一定比例, 就会触发 Major GC 来清理这个Old区. 如果Old 区满了,就会触发 Full GC.(影响性能)

    GC(垃圾回收) 的分类

    Minor GC: Eder 满了触发,用于清理年轻代区域.

    Major GC: 用于清理年老代区域.

    Full GC: 对所有的区域做 GC 清理, 这个代价很高,我们很多时候优化,都是对这个 Full GC 做优化. (尽量不要使用Full GC)

      System.gc(); 这实际只是发一个请求,掉GC, 这只是一个建议,但是,程序是否真正掉GC, 还是有垃圾回收器决定。

    对象创建与 this

    创建一个对象分为如下四步:

    1. 分配对象空间,并将对象成员变量初始化为0, false 或空
    2. 执行属性值的显式初始化, 比如属性值等于一个静态变量或字符常量等.
    3. 执行构造函数, 在这里都可以使用 this, 因为通过上面1,2两步,实际上对象已经创建好了.
    4. 返回对象的地址给相关的变量, 比如 Student stu = new Student(), 就返回地址给了 stu.

    this 的本质就是创建好的对象的地址this(a,b)  实际上是调用构造函数. 必须位于第一句. this 不能用于static方法中.

    继承的初始化过程从继承树向上, 首先执行爷爷的初始化 -> 爸爸的初始化 -> 自己的初始化. 因为 super() 一层一层往上调用父类的构造器.

    静态导入

    import static java.lang.Math.PI;     // 相当于导入这个类的静态属性, 也可以使用 import static java.lang.Math.*   就是导入这个类的所有的静态属性和方法.

    这样导入之后, 在程序中, 就不用在写 类似 System.out.println(Math.PI), 而是可以直接写System.out.println(PI) , 但是个人觉得还是之前那种看着习惯.

    封装的一般规则

    类属性: private 修饰,暴露方法来修改这个属性, 可以加 validation.

    javaBean: 只提供了属性的get 和 set 方法的简单方类. 没有复杂逻辑功能。

    需要被外部访问的,一般情况下方法都是用 public.

    多肽3个必要条件

    继承, 方法重写, 父类引用指向子类对象(向上转型).

    final 关键字

    final 修饰变量是常量.

    final 修饰方法: 方法不能被子类重写,可以被重载.

    final 修饰类: 此类不能被继承. (这个类就是最终的类, 此类不能再被扩展了) 比如 String 这个类就是一个 final 类.

    数组

    数组是一个对象,数组的每一个元素可以看做是这个对象的成员变量.

    三种初始化

    静态初始化: int[] a = {1, 2, 3};

    动态初始化: int[] a = new int[3]; a[0] = 1; 先分配空间, 之后分别单独初始化

    默认初始化: 默认的, 比如 int[] a = new int[3];    这时就是数组元素默认初始化为 0

    for each 循环 : 只能读取, 而不用于修改, 因为没有下标, 但是适合遍历数组

    for ( int m: a ) {

    }

    抽象类 与 抽象方法

    1. 有抽象方法, 只能定义成抽象类
    2. 抽象类不能实例化,即不能用 new 来实例化抽象类
    3. 抽象类可以包含属性,方法,构造方法,但是构造方法不能用来 new 实例, 只能用来被子类调用.
    4. 抽象类只能用来被继承
    5. 抽象方法必须被子类实现(重写Override)

    抽象类的意义 为子类提供统一的,规范的模板,子类必须实现抽象方法。例如 Number 类(包装类) 就是一个抽象类.

    接口

    只能定义 常量 或 抽象方法. (即便不加 final 或 abstract, 默认的就是常量和抽象方法), 而且全部都是 public 的.

    因为接口需要稳定

    内部类

    非静态内部类: Outer.Inner inner = new Outer().new Inner();    // 定义内部类, 必须要有外部类, 因为这时内部类相当于外部类的成员变量.

      所以也就是说,非静态内部类依托外部对象(实例)。

    静态内部类: 理解为outer 类的一个内部类变量, 这时不再依托外部类实例.  (使用不多)

    匿名内部类: 这个内部类只调用一次. 别的地方不再用. 

    this.addWindowListener(new WindowAdapter() {
    @Override
    public void windowClosing(WindowEvent e) {
    System.exit(0);
    }
    });

    数组

    Arrays 这个类集成了一些数组的方法,可以直接调用, 比如排序等.

    包装类自动装箱拆箱

    Integer a = 234;   对象, 装箱

    int b = a;   int 型, 拆箱

    Collection

    ArrayList, LinkedList: 线性结构

    因为 java 不像 C 语言, 允许动态分配数组, 所以, 实际上可以使用普通数组来构建ArrayList, 比如每当数组长度超过80%时,

    就创建一个新数组(2倍长度于原来数组), 并copy 这个数组到新数组里.

    下边是 key, value 的形式:

    HashSet: set 是无序不可重复的.

    TreeSet: TreeSet 底层是 TreeMap 实现的, 通过 key 来存储 Set 元素,  TreeSet是一维的, 比如 Set<User> = new TreeSet<User>();

    HashMap: Map<Integer, String>m1 = new HashMap<>();

      散列码: 首先将键key 转换为散列码整数, 每个Java对象都有散列码. 散列码是32位整数

      散列码是通过 hash 函数计算得到的数值,一般情况下, 不同的对象, 散列码是不同的. 在数学中, 要将较大的组映射到较小的组上时, 必须使用重复,比如你有6个球, 想放到5个抽屉里, 那必然会有重复出现, 而一般在数学上,我们可以采取取模的方式, 这样抽屉1可能就重复了,对于重复的情况,应该继续查找. 手工解决这种重复问题, 举例:

      首先考虑数据结构, 我们使用了一个对象 HashEntry 来存储, 这个对象有3个实例变量, key,value,link指针, 也就是说, 当有重复发生时, 这个指针变量可以帮助这些重复的hashcode值来继续遍历这个link链表. 还拿放球的例子, 本例中, N_BUCKETS = 5, 而我们实际有6个球, 执行过程是:

    1,2,3,4,5 球来了, 都正常进入相应的 bucketArray 数组中, 当 6 来了时, 把6作为 bucketArray[1] 的第一个元素, 原来的的1是6的后继. 这样前插入链表的好处是, 不用遍历链表, 直接插入在链表头.

    链表头插入:

    package com.hash.duplicate;
    
    public class SimpleStringMap {
        public SimpleStringMap() {
            bucketArray = new HashEntry[N_BUCKETS];
        }
        
        public void put(String key, String value) {
            int bucket = Math.abs(key.hashCode()) % N_BUCKETS;
            HashEntry entry = findEntry(bucketArray[bucket], key);
            if (entry == null) {
                entry = new HashEntry(key, value);
                entry.setLink(bucketArray[bucket]);
                bucketArray[bucket] = entry;
            } else {
                entry.setValue(value);
            }
        }
        
        public String get(String key) {
            int bucket = Math.abs(key.hashCode()) % N_BUCKETS;
            HashEntry entry = findEntry(bucketArray[bucket], key);
            if (entry == null) {
                return null;
            } else {
                return entry.getValue();
            }
        }
        
        public HashEntry findEntry(HashEntry entry, String key) {
            while (entry != null) {
                if (entry.getKey().equals(key)) return entry;
                entry = entry.getLink();
            }
            return null;
        }private static final int N_BUCKETS = 5;
        private HashEntry[] bucketArray;
    }
    
    
    class HashEntry {
        public HashEntry(String key, String value) {
            entryKey = key;
            entryValue = value;
        }
        
        public String getKey() {
            return entryKey;
        }
        
        public String getValue() {
            return entryValue;
        }
        
        public void setValue(String value) {
            entryValue = value;
        }
        
        public HashEntry getLink() {
            return entryLink;
        }
        
        public void setLink(HashEntry nextEntry) {
            entryLink = nextEntry;
        }
        
        private String entryKey;
        private String entryValue;
        private HashEntry entryLink;    // a reference to the next entry in the chain.
    }

    TreeMap: 一般不用, 需要map 中排序时会使用, 是通过红黑二叉树实现的.

    因为是 key,value 存储, 键值是否重复, 根据 equals 方法可以比较. 也就是说, JAVA 对象的散列码通过 hashcode()函数得出来的散列码就等于equals方法,也是判断对象是否相等的依据. 所以尽量不要重写equals方法,因为重写equals方法, 就要重写hashcode方法.

     

  • 相关阅读:
    高级特性(7)- 高级AWT
    洛谷 P1948 [USACO08JAN]电话线Telephone Lines
    洛谷 P2015 二叉苹果树
    洛谷 P2014 选课
    洛谷 P1560 [USACO5.2]蜗牛的旅行Snail Trails(不明原因的scanf错误)
    cogs 10. 信号无错传输
    cogs 9. 中心台站建设。。。
    洛谷 P1731 生日蛋糕
    洛谷 P1092 虫食算
    洛谷 P1034 矩形覆盖
  • 原文地址:https://www.cnblogs.com/moveofgod/p/12316866.html
Copyright © 2020-2023  润新知