牛客Java专项练习笔记(1)

本文主要介绍了Java中的基础知识点,包括String的不可变性、字符串常量池的工作原理,抽象类的特点,垃圾回收机制,构造方法的使用,Servlet的生命周期,HashMap的数据结构和冲突解决,以及JDBC的Statement与PreparedStatement的区别。同时,还涵盖了多线程实现方式、线程安全和final关键字的用法,以及基本数据类型的默认值和运算规则。
摘要由CSDN通过智能技术生成

Java小白开始每日刷题,并记录刷题遇到的一些知识点,大佬们多多包涵,内容可能会比较杂乱,如果不太详细尽情谅解!!!希望对一些人有所帮助!!!

本次整理了与String、抽象类、垃圾回收、构造方法、Servlet、Map、JDBC、JUC、final、基本数据类型相关的一些基础知识点。

1. String相关

1.1 String

1)String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。

2)String类底层是char数组来保存字符串的。对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象

字符串常量池

在class文件中有一部分来存储编译期间生成的字面常量以及符号引用,这部分叫做class文件常量池,在运行期间对应着方法区的运行时常量池。

JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池。

工作原理

当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用。

实现前提

字符串常量池实现的前提条件就是Java中String对象是不可变的,这样可以安全保证多个变量共享同一个对象。如果Java中的String对象可变的话,一个引用操作改变了对象的值,那么其他的变量也会受到影响,显然这样是不合理的。

String str1 = "hello";

这里的str1指的是方法区中的字符串常量池中的“hello”,编译时期就知道的;

String str2 = "he" + new String("llo");

这里的str2必须在运行时才知道str2是什么,所以它是指向的是堆里定义的字符串“hello”,所以这两个引用是不一样的。

如果用str1.equal(str2),那么返回的是true;因为String类重写了equals()方法。

编译器没那么智能,它不知道"he" + new String("llo")的内容是什么,所以才不敢贸然把"hello"这个对象的引用赋给str2. 如果语句改为:"he"+"llo"这样就是true了。

new String("zz")实际上创建了2个String对象,就是使用“zz”通过双引号创建的(在字符串常量池),另一个是通过new创建的(在堆里)。只不过他们的创建的时期不同,一个是编译期,一个是运行期。

String s = "a"+"b"+"c"; 语句中,“a”,"b", "c"都是常量,编译时就直接存储他们的字面值,而不是他们的引用,在编译时就直接将它们连接的结果提取出来变成"abc"了。

1.2 StringBuffer

  • length()

public int length()

返回长度(字符数)

  • capacity()

public int capacity()

返回当前容量。容量指可用于最新插入的字符的存储量,超过这一容量就需要再次进行分配。返回: 当前容量。

  • 当设置StringBuffer的容量

1、小于当前容量时,容量不变,默认为16。

2、大于当前容量,并且小于(当前容量+1) * 2,则容量变为(当前容量+1) * 2。

3、大于当前容量,并且大于(当前容量+1) * 2,则容量变为用户所设置的容量。

2. 抽象类相关

2.1 抽象类的特点

  • 抽象类和抽象方法必须使用abstract 关键字修饰

//抽象类的定义
public abstract class 类名 {}

//抽象方法的定义
public abstract void eat();
  • 抽象类可以实现接口

  • 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类

  • 抽象类不能实例化

  • 抽象类可以有构造方法

  • 抽象类的子类

  • 要么重写抽象类中的所有抽象方法

  • 要么是抽象类

3. 垃圾回收相关

3.1 垃圾收集

  • 在Java中,对象的内存在什么时候回收,取决于垃圾回收器什么时候运行。

  • 一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法, 并且在下一次垃圾回收动作发生时,才会真正的回收对象占用的内存

  • 一个对象成为垃圾是因为不再有引用指着它,就可能被垃圾回收器回收,主线程开启一个线程,当主线程运行完成,没有引用指向运行的线程,但是开启的线程还在运行,直到run()方法运行结束退出,这一点和对象的垃圾回收是不一样的。

4. 构造方法相关

4.1 构造方法格式和执行时机

  • 格式注意:

  • 方法名与类名相同,大小写也一致

  • 没有返回值类型,连void也没有

  • 没有具体的返回值(不能有return带回结果数据)

  • 执行时机:

  • 创建对象的时候调用,每创建一次对象,就会执行一次构造方法

  • 不能手动调用构造方法

  • 标准类示例:

  • 类名需要见名知意

  • 成员变量使用private修饰

  • 提供至少两个构造方法

  • 无参构造

  • 带参数的构造方法

  • get和set方法

class Student {
    //成员变量
    private String name;
    private int age;

    //构造方法
    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    //成员方法
    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void show() {
        System.out.println(name + "," + age);
    }
}
/*
    创建对象并为其成员变量赋值的两种方式
        1:无参构造方法创建对象后使用setXxx()赋值
        2:使用带参构造方法直接创建带有属性值的对象
*/
public class StudentDemo {
    public static void main(String[] args) {
        //无参构造方法创建对象后使用setXxx()赋值
        Student s1 = new Student();
        s1.setName("林青霞");
        s1.setAge(30);
        s1.show();

        //使用带参构造方法直接创建带有属性值的对象
        Student s2 = new Student("林青霞",30);
        s2.show();
    }
}

4.2 构造方法注意事项

  • 构造方法可以被重载,一个构造方法可以通过this关键字调用另一个构造方法,this语句必须位于构造方法的第一行

  • 方法重载:方法的名称相同,但参数类型(包括顺序)或参数个数不同,才能构成方法的重载

  • 当一个类中没有定义任何构造方法,Java将自动提供一个无参构造方法

  • 子类通过super关键字调用父类的一个构造方法

  • 当子类的某个构造方法没有通过super关键字调用父类的构造方法,通过这个构造方法创建子类对象时,会自动调用父类的无参构造

  • 构造方法不能被static、final、synchronized、abstract、native修饰,但可以被访问修饰符修饰

  • 构造方法不是类的成员方法

  • 构造方法不能被继承

5. Servlet相关

5.1 Servlet生命周期

生命周期:对象的生命周期指一个对象从被创建到被销毁的整个过程

Servlet运行在Servlet容器(web服务器)中,其生命周期由容器来管理,分为4个阶段:

  1. 加载和实例化:默认情况下,当Servlet第一次被访问时,由容器创建Servlet对象

默认情况,Servlet会在第一次访问被容器创建,但是如果创建Servlet比较耗时的话,那么第一个访问的人等待的时间就比较长,用户的体验就比较差,那么我们能不能把Servlet的创建放到服务器启动的时候来创建,具体如何来配置?

@WebServlet(urlPatterns = "/demo1",loadOnStartup = 1)
loadOnstartup的取值有两类情况
	(1)负整数:第一次访问时创建Servlet对象
	(2)0或正整数:服务器启动时创建Servlet对象,数字越小优先级越高
  1. 初始化:在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象,完成一些如加载配置文件、创建连接等初始化的工作。该方法只会调用一次

  1. 请求处理:每次请求Servlet时,Servlet容器都会调用Servlet的service()方法对请求进行处理。Servlet 容器(即 Web 服务器)调用 service() 方法来处理来自客户端(浏览器)的请求(ServletRequest),并把格式化的响应写回给客户端(ServletResponse)。每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。service() 方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet、doPost、doPut,doDelete 等方法。

  1. 服务终止:当需要释放内存或者关闭容器时,容器就会调用Servlet实例的destory()方法完成资源的释放。在destory()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾回收器回收

案例演示上述生命周期

package com.itheima.web;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
/**
* Servlet生命周期方法
*/
@WebServlet(urlPatterns = "/demo2",loadOnStartup = 1)
public class ServletDemo2 implements Servlet {

    /**
     *  初始化方法
     *  1.调用时机:默认情况下,Servlet被第一次访问时,调用
     *      * loadOnStartup: 默认为-1,修改为0或者正整数,则会在服务器启动的时候,调用
     *  2.调用次数: 1次
     */
    public void init(ServletConfig config) throws ServletException {
        System.out.println("init...");
    }

    /**
     * 提供服务
     * 1.调用时机:每一次Servlet被访问时,调用
     * 2.调用次数: 多次
     */
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        System.out.println("servlet hello world~");
    }

    /**
     * 销毁方法
     * 1.调用时机:内存释放或者服务器关闭的时候,Servlet对象会被销毁,调用
     * 2.调用次数: 1次
     */
    public void destroy() {
        System.out.println("destroy...");
    }
    public ServletConfig getServletConfig() {
        return null;
    }

    public String getServletInfo() {
        return null;
    }
}

5.2 Servlet注意事项

  • Servlet是线程不安全的,在Servlet类中可能会定义共享的类变量,这样在并发的多线程访问的情况下,不同的线程对成员变量的修改会引发错误。

6. Map相关

6.1 HashMap

  • 关于HashMap的一些说法:

  • HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。HashMap的底层结构是一个数组,数组中的每一项是一条链表。

  • HashMap的实例有俩个参数影响其性能: “初始容量” 和 ”装填因子“。

  • HashMap实现不同步,线程不安全。HashTable线程安全

  • HashMap中的key-value都是存储在Entry中的。

  • HashMap可以存null键和null值,不保证元素的顺序恒久不变,它的底层使用的是数组和链表,通过hashCode()方法和equals方法保证键的唯一性

  • 解决冲突主要有三种方法:定址法,拉链法,再散列法。HashMap是采用拉链法解决哈希冲突的。 注:

链表法是将相同hash值的对象组成一个链表放在hash值对应的槽位;用开放定址法解决冲突的做法是:当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。沿此序列逐个单元地查找,直到找到给定的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元);拉链法解决冲突的做法是: 将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于1,但一般均取α≤1。拉链法适合未规定元素的大小。

6.2 HashMap与HashTable区别

  • 继承不同。

public class Hashtable extends Dictionary implements Map
public class HashMap extends AbstractMap implements Map
  • Hashtable中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。

  • Hashtable 中, key 和 value 都不允许出现 null 值。 在 HashMap 中, null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为 null 。当 get() 方法返回 null 值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为 null 。因此,在 HashMap 中不能由 get() 方法来判断 HashMap中是否存在某个键, 而应该用 containsKey() 方法来判断。

  • 两个遍历方式的内部实现上不同。Hashtable、HashMap都使用了Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式。

  • 哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。

  • Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。、

  • 注: HashSet子类依靠hashCode()和equal()方法来区分重复元素。

HashSet内部使用Map保存数据,即将HashSet的数据作为Map的key值保存,这也是HashSet中元素不能重复的原因。而Map中保存key值的,会去判断当前Map中是否含有该Key对象,内部是先通过key的hashCode,确定有相同的hashCode之后,再通过equals方法判断是否相同。

7. JDBC相关

7.1 Statement

  • Statement 每次执行sql语句,数据库都要执行sql语句的编译 ,最好用于仅执行一次查询并返回结果的情形,效率高于PreparedStatement.

  • PreparedStatement是预编译的,使用PreparedStatement有几个好处

  • 在执行可变参数的一条SQL时,PreparedStatement比Statement的效率高,因为DBMS预编译一条SQL当然会比多次编译一条SQL的效率要高。

  • 安全性好,有效防止Sql注入等问题。

  • 对于多次重复执行的语句,使用PreparedStament效率会更高一点,并且在这种情况下也比较适合使用batch

  • 代码的可读性和可维护性。

  • PreparedStatement,用来调用存储过程,它提供了对输出和输入/输出参数的支持。

  • CallableStatement接口扩展,CallableStatement接口还具有对 PreparedStatement 接口提供的输入参数的支持。

  • 三者的继承关系:

8. JUC相关

8.1 多线程实现方式

  • 继承Thread类

  • 实现Runnable接口

  • 实现Callable接口通过FutureTask包装器来创建Thread线程

  • 使用ExecutorService、Callable、Future实现有返回结果的多线程

8.2 线程安全

  • HashTable是线程安全的 Vector是线程安全的

  • ArrayList、TreeSet和LinkedList都不是线程安全的

9. final相关

9.1 final

  • final关键字可以用于成员变量、本地变量、方法以及类

  • final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误

  • 本地变量必须在声明中赋值

  • 在匿名内部类中所有变量都必须是final变量

  • 没有在声明时初始化final变量的称为空白final变量(blank final variable),它们必须在构造器中初始化,或者调用this()初始化。不这么做的话,编译器会报错“final变量(变量名)需要进行初始化”。

  • final修饰方法:表明该方法是最终方法,不能被重写

  • final修饰类:表明该类是最终类,不能被继承

  • final修饰变量:叫做常量,只能被赋值一次

  • final修饰基本数据类型:记录的值不能发生改变

final double PI = 3.14;
  • final修饰引用数据类型:记录的地址值不能发生改变,内部的属性可以改变

//创建对象
final Student S = new Student("zhangsan", 23);

//记录的地址值不能发生改变,内部的属性可以改变
//S = new Student();   //报错
S.setName("lisi");
S.setAge(24);
System.out.println(S.getName() + "," + S.getAge);  //输出结果: 李四,24

10. 基本数据类型相关

10.1 基本数据类型

默认值

存储需求(字节)

取值范围

示例

byte

0

1

-2^7^—2^7^-1

byte b = 10;

char

'\u0000'

2

0—2^16^-1

char c = 'c';

short

0

2

-2^15^—2^15^-1

short s = 10;

int

0

4

-2^31^—2^31^-1

int i =10;

long

0

8

-2^63^—2^63^-1

long l = 10L;

float

0.0f

4

-2^31^—2^31^-1

float f = 10.0F

double

0.0d

8

-2^63^—2^63^-1

double d = 10.0;

boolean

false

1

true/false

boolean flag = true;

10.2 基本运算

  • 所有的byte、short、char型的值将被提升为int型

  • 如果有一个操作数是long型,计算结果是long型

  • 如果有一个操作数是float型,计算结果是float型

  • 如果有一个操作数是double型,计算结果是double型

  • 被fianl修饰的变量不会自动改变类型,当2个final修饰相操作时,结果会根据左边变量的类型而化。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值