java基础练习题【夯实基础】
- why
- How
- Practice
- Date - 10.30:tada:
- T1. String的理解【pool和堆】
- T2. 核心类Math三角函数理解
- T3. 线程执行的理解
- T4. Java初始化顺序【装载顺序】
- T5.静态成员变量的调用
- T6. Servlet程序读取HTTP头
- T7. java异常
- Date - 10.31:tada:
- T1.源码HashMap处理哈希冲突
- T2.字节流编码转换
- T3. 服务器通信
- T4.包裹体的初始化
- T5.URL创建对象的异常
- T6.runtime Exception 和检查异常
- T7.Spring框架理解
在长期的学习练习中,做了大量的编程练习,但是很少做理论的练习卷,所以造成了一种尴尬的情况-----题感极低,题感就是看到一道理论题目,却很少想到编程实践中或者以前的知识,所以我现在就开始每天刷题,更新这个刷题专栏
How
每天在客上进行刷题,培养题感,主要是还可以给未知的知识建立一种感觉
Practice Date - 10.30
今天开始的第一次练习,理论和实践要结合起来才有效果,以前总想着编程,可是有的东西理解不深刻容易引起巨大的bug
T1. String的理解【pool和堆】针对下面的代码块,哪个equal为true:()
String s1 = "xiaopeng"; String s2 = "xiaopeng"; String s3 =new String (s1);
== 比较的是两个变量所指对象的地址是否相同,equals比较的是两个变量所指对象的内容是否相同
这里考察的是对于创建字符串对象的深刻理解
正确的答案是 s1 == s2 ,因为这里s1和s2都是指代的字符串常量池中"xiaopeng"对象的地址,所以两变量相等【地址相等】,s3以s2的字符串内容在堆中开辟内存,所以s3的地址和s1以及s2不一样,所以不相等 ==
这里为了防止下一次再犯错误,深入分析一下String的创建对象的问题
首先明确几个概念 : JVM的堆,栈,方法区,这是几个不同的内存部分
- 字符串常量池是一个缓冲区,存在于方法区,用来存放字符串常量,其余的常量池比如int的常量池【都位于方法区】----- 在Java6和6之前,常量池一般是存放在方法区中的,到了Java7,常量池就被存放到了堆中,Java8之后,就取消了整个永久代区域,取而代之的是元空间。在运行时常量池与静态常量池都会存放在元空间中,但字符串常量池依然存放在堆中。
- new为创建新的对象,java中所有创建出的对象都位于堆中? – 想一想对不对
- 对象位于堆中不能直接使用,需要引用来指代它,也就是变量,所有的变量【引用】都存在于栈中
说白了,这就是java为了减少消耗,提高内存而做出的高效管理方式,常量池的意义就是避免对此那个的重复创建,提高效率,常量池存储的是什么呢?
- 常量池存储的是什么呢?
直接进入String的intern方法看一下源码解释
When the intern method is invoked, if the pool already contains a
string equal to this {@code String} object as determined by
the {@link #equals(Object)} method, then the string from the pool is
returned. Otherwise, this {@code String} object is added to the
pool and a reference to this {@code String} object is returned.
再看一下intern方法的解释
It follows that for any two strings {@code s} and {@code t},
{@code s.intern() == t.intern()} is {@code true}
if and only if {@code s.equals(t)} is {@code true}.@return a string that has the same contents as this string, but is
guaranteed to be from a pool of unique strings.
- 现在再重新来看这道题目,并且思考之前那片String博客内容【当时的理解很片面,抱歉】
现在我们知道字符串的字面量对象是存储在堆中的方法区的,创建对象有两种方式【存储的位置不同】
- 直接用字面量创建 : String s1 = “xiaopeng”; 这里创建时发现“xiaopeng”这个content不存在,那就在pool中创建一个content为xioapeng的对象,把这个对象的地址引用赋给s1,当String s2 = “xiaopeng”; 发现pool中已经有xiaopeng,不再继续创建,直接将pool中的content为xiaopeng的对象的地址赋给s1,所以s1 == s2 ✌️
- 使用new关键字创建对象 : String s3 =new String (s1); 这里就是以s1字符串的content在堆中新创建一个和其内容相等的字符串,所以s3的地址和前两个是不一样的;若给的是字面量,那么首先检查字面量在pool中是否存在,如果不存在,就在pool中创建一个字面量对象,并且以该对象的content再在堆中创建一个新的对象
所以之前那篇博客中String s = “hello” + “C” + “风”;就是创建了一个对象,都是字符串常量,所以直接拼接,使用的第一种方式,那么是在pool种创建一个对象,将这个对象的引用返回给s1
-
- 拓展 : java中的对象都创建在堆中吗?裸
T2. 核心类Math三角函数理解既然我都这样子问了,那肯定就不是了,JVM会进行逃逸分析,如果不逃逸,为局部对象那么就会直接在栈上分配,如果分配失败,则尝试TLAB分配----- 这些知识到JVM分析时会详细分析
下列哪个选项是正确计算42度(角度)的余弦值?
double d = Math.cos(42) double d = Math.cosine(42) double d = Math.cos(Math.toRadians(42)) double d = Math.cos(Math.toDegrees(42))
这里考察的纯属就是用法了,因为之前每怎么用,所以有些不清
Math中的三角函数都使用的是弧度制,所以要将角度转换为弧度来使用,toRadians是转换为弧度,而toDegree就是转换为角度
这些核心类平时还是要多使用才行,特别是Math类进行 数学计算,下次编一个计算机的小程序来使用这个类
所以想计算角度的余弦值,那么就是用Math.cos(Math.toRadians(42)) 来完成计算
T3. 线程执行的理解Which method you define as the starting point of new thread in a class from which n thread can be execution?下列哪一个方法你认为是新线程开始执行的点,也就是从该点开始线程n被执行。
public void start() public void run() public void int() public static void main(String args[]) public void runnable()
题目的意思是问调用哪个方法线程被执行,线程有三个状态,执行,就绪,等待
- 当我们调用start方法时,只是让该线程启动,进入就绪状态,就绪是不等同于执行的,所以不是题目的解答,需要等待CPU的调度
- run方法调用,那就说明该线程被执行,所以开始执行的点就是run, 该方法就是获得CPU时间
线程同步,精灵线程的作用,以及synchronized使用等等详见之前的博客,这道题弥补之前对于run方法的理解
T4. Java初始化顺序【装载顺序】下面代码的输出是什么?
public class base { private String baseName = "base"; public base() { callName(); } public void callName() { System. out. println(baseName); } static class Sub extends base { private String baseName = "sub"; public void callName() { System. out. println (baseName) ; } } public static void main(String[] args) { base b = new Sub(); } }
这里就要明确加载顺序,还有就是静态和非静态的成员变量了
当执行创建对象的语句时
三个原则
- 静态方法和变量随类加载而加载
- 非静态方法和变量随对象的创建而加载
- 成员变量先于构造器加载
所以从左到右,先识别base,进行类加载
首先要进行类加载 ,并且先加载父类,再加载子类 ,并且static的就在此时加载
1. 类加载就是JVM找到相应的类,首先查找其基类,之后加载基类,加载超类的static成员变量和static方法,以及静态方法块
2 .再加载本类的static成员变量和块
之后识别new,进行对象创建
3.加载父类的非静态成员变量
4.加载父类的构造器
5.加载本类的非静态成员变量
6.加载本类的构造器
相比这下就明白了吧,对于加载顺序和static有明确的认识
来分析一下这里的过程,发现base类,首先进行类加载,类加载先加载的是Object再加载Baase的静态的方法和变量,这里没有,之后分析的是new 对象,这个过程是创建Sub对象,所以还是父类优先,先加载父类的变量,那么这里就将父类的basename设置为base,加载了父类的方法,之后就是加载父类的构造器,进入构造器
其实这里调试一下就知道顺序了
- 首先进入了子类的构造器,之后进入了父类构造器,之后进入Object的空构造器
- 执行了父类的basename语句,再次进入了构造器,执行callname方法,但是是子类的callname方法
- 之后输出null,进入子类,执行baseName的赋值,之后结束
所以我现在对于加载的理解就是,实现进行类加载,这是随之加载的就是静态的方法和变量,类加载就是加载识别的类以及其父类,之后进入new环节,首先跳转到最顶级的超类Object,按照成员变量优于构造器的原则进行加载,但是一旦父类的构造器所调用的方法在子类中被重写,那么就要考虑如果用到子类变量,因为还未进入子类,所以可能会异常输出。
对于static就是因为staic修饰部分随类的加载而加载,如果不是创建了对象,那么非静态的方法和变量就不能加载,不符合类加载的含义,那么所以静态环境只能使用静态的变量和方法。而创建对象的时候就会加载所有的对象方法,所以就可以直接随意的调用,比如上面系统识别callName重写,直接跳到子类的方法中执行,但是整体还是在父类的构造器中。只是由于变量的作用域有影响
T5.静态成员变量的调用下面代码在main()方法中第八行后可以正常使用的是( )
public class Test
{
private int a=10;
int b=20;
static int c=1;
public static void main(String arg[])
{
Test t = new Test();
}
}
给的选项有下面几个
t.a this.c Test.b t.c
这里考察的就是对于静态变量的理解,上面提到多,静态变量在类加载的时候就加载了,那么执行了Test t = new Test();之后,所有的变量都加载了,首先看这里是main方法,那么就是静态环境,所以
this关键字不能使用【静态环境】,我认为this就是一个预定义,在没有对象时,this是要指代对象的,在进行类加载的时候,会加载static的方法和变量,那么,如果有this存在,但是这是并没有任何对象,那么this就什么也不是,非静态环境因为必须要对象来调用,这个时候this就指代这个对象,所以静态环境都不能使用this和super
这里的a和b都是实例变量,c为类变量,类变量使用类名直接调用,但是也可以直接用对象调用,只是编译器会提醒,但是不会报错。而b是不能用类名直接调用的,所以选择第一个和第四个
T6. Servlet程序读取HTTP头以下哪些方法可以取到http请求中的cookie值()?
request.getAttribute //以对象的形式返回已经命名属性的值,如果没有就返回null request.getHeader //以字符串的形式返回指定的请求头的值, cookie也是一种头,所以可以获取到 request.getParameter //以字符串的形式返回请求参数的值,如果没有就null request.getcookies //返回包含客户端发送该请求的所有的cookie对象,以数组的形式
这里考察的是对于cookies的使用,上面的方法都是通过HttpServletRequest对象来调用的,getHeader和getcookies都可以
两个Web间为转发关系,转发的target Web可以用getAttribute方法和源Web进行request范围的属性共享
T7. java异常关于Java的一些概念,下面哪些描述是正确的:( )
所有的Java异常和错误的基类都是java.lang.Exception, 包括java.lang.RuntimeException 通过try … catch … finally语句,finally中的语句部分无论发生什么异常都会得到执行 java中所有的数据都是对象 Java通过垃圾回收回收不再引用的变量,垃圾回收时对象的finallize方法一定会得到执行 Java是跨平台的语言,无论通过哪个版本的Java编写的程序都能在所有的Java运行平台中运行 Java通过synchronized进行访问的同步,synchronized作用非静态成员方法和静态成员方法上同步的目标是不同的
这里就一个一个分析,考察的是对于java异常的理解
-
所有的java异常的基类都是Exception,错误的基类为Error,Exception和Error的共同基类为Throwable【稍微思考一下就大概明白错误和异常时不一样的,平时debug很多次】
-
之前提到过finnaly是强制执行语句,无论发生什么异常都会执行,连return都会阻止不了,只有JVM停掉。但是区区Exception都是阻止不了的,JVM停掉可不是异常
-
数据类型分为基本数据类型比如int,long·····,之后才是对象类型,所以不是所有的数据都是对象
-
而finallize 的执行是需要判断的 【但是不管怎么说finallize只能被执行一次】
- 如果该对象的finallize没有被执行过,那么就会被执行,同时将对象放到等待清理队列F-Queue,此时,如果在下次GC前,该对象于GC-ROOTS建立引用连接,那么就会复活,下次GC时如果再被GC,就直接回收了,因为已经执行过一次了;
- 如果被执行过一次,那么GC时就不执行finallize,直接回收,也就是F-Queue中的都不会执行了
-
java是跨平台的语言,其强调的是因为其为半编译语言,而每种系统上都有JVM,那么就可以各平台运行,但是版本迭代之后,高版本产生的新的东西,原有的JRE是运行高版本的JRE的,因为不知道编译的规则
-
synchronized就是加上同步锁,互斥,上锁的环境只能由一个线程进行执行,上锁的是对象,只有对象的锁打开后才能下一个线程进入,所以sleep方法有时就会进入到死锁状态,这个时候就使用wait和notify方法替代。作用在静态方法上和非静态方法上的目标是不一样的。
今天的题目就先这样~每日更新
Date - 10.31 T1.源码HashMap处理哈希冲突在Java中,HashMap中是用哪些方法来解决哈希冲突的?
我们学习数据结构就知道hash表就是一种的数组,只是让位置于所存储的值产生了关系,实现随意存取,解决哈希冲突的方式有如下几种
- 开放地址法
- 二次hash法
- 链地址法
- 建立一个公共溢出区
而HashMap如何解决呢,进入HashMap源码就可以发现
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node[] tab; Node p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode)p).putTreeval(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
//通过这段源码就可以发现使用的是链地址法来解决的Hash冲突
对于其他的解决Hash冲突的方法,等到数据结构专栏的文章再详细探讨
T2.字节流编码转换下面哪段程序能够正确的实现了GBK编码字节流到UTF-8编码字节流的转换:
byte[] src,dst;dst=String.fromBytes(src,"GBK").getBytes("UTF-8") dst=new String(src,"GBK").getBytes("UTF-8") dst=new String("GBK",src).getBytes() dst=String.encode(String.decode(src,"GBK")),"UTF-8" )
- 首先明确一点,String类没有encode和decode方法:happy:
- 同时也没有fromByte类方法
看一下几个方法
public byte[] getBytes(String charsetName)
public byte[] getBytes() //该方法时和平台(编码)相关的,中文系统返回的时GBK或者GBC32,英文系统是ISO-8859-1
public String(byte bytes[], Charset charset) //解码指定编码名称的字节码数组为字符串
-
也就是通过getByte方法可以将字符串转化为指定编码的字节数组并返回该数组
-
从指定的编码的字节数组获取数据成字符串使用构造器
所以这里首先A和D是常识错误,C的错误首先是参数列表的顺序不对,还有要指定编码为UTF-8
String s1 = new String("hello Cfeng"); //这里创建了2个对象【在字符串常量池和堆中】
byte[] orignal = new byte[100];
orignal = s1.getBytes("UTF-8");
for(byte b:orignal)
{
System.out.print(b + " ");
}
byte[] current = new byte[100];
current = new String(orignal, "UTF-8").getBytes("GBK");
System.out.print("n");
for(byte b:current)
{
System.out.print(b + " ");
}
这里就实现了转码操作,所以就是先利用原来的字节数组解码变成字符串,再将字符串转码成目标格式的字节码
T3. 服务器通信在一个基于分布式的游戏服务器系统中,不同的服务器之间,哪种通信方式是不可行的()?
管道
消息队列
高速缓存数据库
套接字
这里考察的是服务器的通信
-
套接字 :之前计算机网络中两台服务器通信使用的就是传输层的service,而套接字就是一个整数,包含通信双方的信息,但是只是本地可见,TCP套接字包含信息更多。
-
高速缓存数据库就是通信双方可以共享内存,实现快速通信
数据库高速缓冲区的主要功能是用来暂时存放最近读取自数据库中的数据,也就是数据文件 (Data File)内的数据,而数据文件是以数据块 (Block)为单位,因此,数据库高速缓冲区中的大小是以块为基数。
-
消息队列 :MQ
消息队列(Message Queue),是分布式系统中重要的组件,其通用的使用场景可以简单地描述为:. 当不需要立即获得结果,但是并发量又需要进行控制的时候,差不多就是需要使用消息队列的时候。. 消息队列主要解决了应用耦合、异步处理、流量削锋等问题。. 当前使用较多的消息队列有RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、metaMq等,而部分数据库如 Redis 、Mysql以及phxsql也可实现消息队列的功能。
-
管道 pipe
管道有如下几种类型
普通的管道(PIPE):通常有两种限制,一是单工,只能单向传输;二是血缘,通常用于父子进程之间【或有血缘关系】
流管道(S_pipe) : 去除了上述第一种限制,实现双向传输【第二种限制还在:只能具有血缘关系的才能相互通信】
命名管道(name_pipe) : 去除第二种限制,实现了无血缘关系的不同进程间通信,实现了不同进程之间通信【第一种限制还在: 只能单向】
而不同服务器之间的线程怎么可能具有亲缘关系?并且通信一定是相互的。
所以服务器之间是不能使用管道的方式来进行通信的
T4.包裹体的初始化给出以下代码,请给出结果.
class Two{ Byte x; } class PassO{ public static void main(String[] args){ PassO p=new PassO(); p.start(); } void start(){ Two t=new Two(); System.out.print(t.x+””); Two t2=fix(t); System.out.print(t.x+” ” +t2.x); } Two fix(Two tt){ tt.x=42; return tt; } }
这段代码是很简单的,分析就知道最开始输出的是默认初始值,之后两者都是42,
但是这里的知识点是默认初始化
首先是基本数据类型:数值型都初始化为0 ;布尔型初始化为false,char型为’ ’
对象类型初始值都是null
而这里是Byte x,而不是byte x,使用的是包裹体,所以初始化的值为null
比如Integer,Double,Byte,Boolean的初始值都是null — 需要注意,不能犯错
T5.URL创建对象的异常URL u =new URL(“http://www.123.com”);。如果www.123.com不存在,则返回______。
- http://www.123.com
- “”
- null
- 抛出异常
执行该语句确实会抛出异常,但是是IOException,不管网址存不存在,最后都会返回一个该网址的衔接,打印出来就是该网址
检查的异常是由于字符串格式和URL格式不符导致的,与网址是否存在无关。URL的toString方法返回字符串,无论网址是否存在
这里的URL知识在java SE中分享漏调了,抽个时间补上,在小欢聊天室中导入程序图标的时候用到了。
T6.runtime Exception 和检查异常以下说法哪个正确
- IOException在编译时会被发现
- NullPointerEception在编译时不被发现
- SQLException在编译时会被发现
- FileNotFoundException在编译时会被发现
昨天的练习题中就已经知道了异常和错误的基类都是Throwable,并且finally会强制执行,编译时可以发现的异常叫做Checked Exception,这种异常也叫做非运行时异常,主要就是文件异常,SOL异常和用户自定义异常,主要就是看存不存在之类的。
而其他的运行时异常在编译过程中是不能发现的,错误包括ThreadDeath,也就是死锁;还有虚拟机错误。
这道题目中,后两者都是存在与否的异常,可以检查出来,还有第一个也是可以发现的就是IOException
T7.Spring框架理解下面关于Spring的说法中错误的是()
- Spring是一个支持快速开发Java EE框架的框架
- Spring中包含一个“依赖注入”模式的实现
- 使用Spring可以实现声明式事务
- Spring提供了AOP方式的日志系统
目前还没有分享Spring框架,这个框架就是支持快速开发Java EE的,包含了“依赖注入”模式的实现,可以用它实现声明式事务
但是没有提供AOP方式的日志系统, AOP 为 Aspect Oriented Programming , 面向切面编程,通过预编译的方式和运行期的动态实现程序功能。
Spring是通过AOP的支持,借助log4i等Apache开源组件实现了日志系统
Spring框架和网络一样,是一个分层结构,由7个良好的模块组成
核心容器有:Spring上下文, Spring AOP,Spring DAO,Spring ORM,Spring Web,Spring MVC
之后会和大家详细分析这个框架
今天的练习就到这里,每日更新



