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个阶段:
加载和实例化:默认情况下,当Servlet第一次被访问时,由容器创建Servlet对象
默认情况,Servlet会在第一次访问被容器创建,但是如果创建Servlet比较耗时的话,那么第一个访问的人等待的时间就比较长,用户的体验就比较差,那么我们能不能把Servlet的创建放到服务器启动的时候来创建,具体如何来配置?
@WebServlet(urlPatterns = "/demo1",loadOnStartup = 1)
loadOnstartup的取值有两类情况
(1)负整数:第一次访问时创建Servlet对象
(2)0或正整数:服务器启动时创建Servlet对象,数字越小优先级越高
初始化:在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象,完成一些如加载配置文件、创建连接等初始化的工作。该方法只会调用一次
请求处理:每次请求Servlet时,Servlet容器都会调用Servlet的service()方法对请求进行处理。Servlet 容器(即 Web 服务器)调用 service() 方法来处理来自客户端(浏览器)的请求(ServletRequest),并把格式化的响应写回给客户端(ServletResponse)。每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。service() 方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet、doPost、doPut,doDelete 等方法。
服务终止:当需要释放内存或者关闭容器时,容器就会调用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修饰相操作时,结果会根据左边变量的类型而化。