Java复习01-基础知识点
本次的基础知识点复习会有深度的复习,尤其是原理等是重点,做到知其所以然。
先放一张导图:
1.环境变量配置理解、
大家都知道开发一个java程序 打印出hello world 的流程基本有以下步骤:
下载JDK,
安装JDK
配置环境变量
编写代码以.java保存
运行JavaC编译生成xx.class 文件
运行Java xx.class文件,控制台输出
大家想过其中的原因没,为什么要这样做,不这么做会有什么问题。还有都说Java是一次编译到处运行的,原理又是什么类。我们一个一个来解决。
1.下载JDK: 这个没什么好说的,就比如 你要拍照 得先买个照相机。
2.安装JDK: 也很好理解,照相机买了 还得做一些准备工作,比如充电,放内存卡等等。JDK安装后会有2个文件夹
其中JDK就是Java Development Kit的缩写意思是java开发工具。顾名思义 这个文件夹下面包含了我们一切我们开发需要的东西。比如,编译器,一些好用的工具,说明文档,类库等。而JRE是Java Runtime Environment的缩写,顾名思义是java运行时环境,包含了java虚拟机,java基础类库。
环境变量配置:
变量名:JAVA_HOME
变量值:C:\Program Files (x86)\Java\jdk1.8.0_91 // 要根据自己的实际路径配置
变量名:CLASSPATH
变量值:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
变量名:Path
变量值:%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
配置环境变量主要的作用就是 配置路径,让系统可以去这些路径下找东西。
Java_Home是路径。
ClassPath是依赖类库的路径
Path是虚拟机路径和编译器路径
编写代码以.java保存:运行JavaC编译生成xx.class 文件:
.java文件格式是java规定的,这个javaC就是JDK中bin目录下的。
用16进制打开Lear.class文件:
这个一个满足虚拟机的运行规范的文件夹格式。开头4个字节是标识。具体的参考虚拟机规范。
我们还可以反编译回去看看,这个机器在分析一些问题时很有用,如:为什么非静态内部类会导致内存泄漏。
如我们编写的java文件:
先javac 编译,然后反编译:
出现了2个类,其中 一个就是我们的内部类MyTest。打开他们看看。
运行Java xx.class文件,控制台输出:
为什么java Learn可以输出hello world,直接运行Learn就不行。其实很简单,window只能运行.exe结尾的文件,.class是无法运行的。可以看到java.exe 和javac.exe都是可以直接运行的。
总结:我们写java文件 编译的class文件,运行的不是在window 上。而是在虚拟机上,每个平台的虚拟机是不一样的。window版本虚拟机,mac虚拟机。但是虚拟机运行字节码文件的规则是一样的。所以才会一次编译到处理运行的说法。如果你的电脑没装虚拟机那肯定是运行不了。
2.数据类型
java数据类型分为:1.8大基本数据类型:byte short int long float double boolean char
2.引用类型数据:类,接口,数组
大小:1byte=8bit。1short=16bit 1int=32bit 1long=64bit. 1float=32bit 1double=64bit 1boolean=8bit 1char=2bit 单一的 16 位 Unicode 字符.
char与Unicode的关系,以及char字符占多大内存问题。
如果你说的“字符”就是指Java中的char,那好,那它就是16位,2字节。
如果你说的“字符”是指我们用眼睛看到的那些“抽象的字符”,那么,谈论它占几个字节是没有意义的。
具体地讲,脱离具体的编码谈某个字符占几个字节是没有意义的。
就好比有一个抽象的整数“42”,你说它占几个字节?这得具体看你是用byte,short,int,还是long来存它。
字符是同样的道理,如果你想谈“占几个字节”,就要先把编码说清楚。
同一个字符在不同的编码下可能占不同的字节。
就以你举的“字”字为例,“字”在GBK编码下占2字节,在UTF-16编码下也占2字节,在UTF-8编码下占3字节,在UTF-32编码下占4字节。
关于Unicode的理解:Unicode的理解
unicode是编码字符集(将字符编码),而UTF-8、UTF-16、UTF-32是字符集编码
保存到计算机中(硬盘、内存),机器码,取决于用到的字符集编码是哪种。
基本数据类型和引用类型的区别主要在于基本数据类型是分配在栈上的,而引用类型是分配在堆上的。
基本数据类型使用比较比较java基本类型:比较基本类型只能用"",不能用"equals",这里的"=="比较的是两个基本类型的值;
2.String,StringBuilder,StringBuffer
String 是一种特殊的数据类型,本质上还是一个引用类型数据。内部是通过一个char[]数组实现。
String是一个final类,既不能被继承的类。
如果是对字符串进行修改,其本质是new了一个新的String的对象
1:String 内容不可变,StringBuffer。StringBudiler可变
2:StringBuffer:同步的,数据安全,效率低。
StringBuilder:不同步的,数据不安全,效率高。1.5之后才有的
1 String类是引用类型;
2 String类重写Object类的equals()和hashCode(),用于比较内容是否相等,而非引用地址;
3 “==”运算符,对基本数据类型比较的是字面值,对引用类型比较的则是引用地址;
String 字符串常量
StringBuffer 字符串变量
StringBuilder 字符串变量
主要原因是 String 类中字符数组是常量的:
而StringBuilder和StringBuffer 是继承 AbstractStringBuilder 里面的字符数组不是:
而且看到 还有一个最大值。
常量池就类似一个JAVA系统级别提供的缓存,位于虚拟机中
8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种:
直接使用双引号声明出来的String对象会直接存储在常量池中。
如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中
在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。字符串常量由一个一个字符组成,放在了StringTable上。
在JDK6.0中,StringTable的长度是固定的,长度就是1009,因此如果放入String Pool中的String非常多,就会造成hash冲突,导致链表过长,当调用String#intern()时会需要到链表上一个一个找,从而导致性能大幅度下降;
在JDK7.0中,StringTable的长度可以通过参数指定:
-XX:StringTableSize=66666
需要说明的是:字符串常量池中的字符串只存在一份!
字面量包括:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;
最常用的写法:
String aaa=“I am String”;
这个叫做字面量。其实和 int i=5;是一样的。
虽然目前还不知道虚拟机里面是怎么执行这个一句的。
我们反编译看看是什么:
看不出什么,那么到虚拟机看看这句话的字节码指令集到底是什么:
好像也看不出什么
当.java文件编译成.class文件时,其类中的静态String数据是以以下数据结构去存储的:
CONSTANT_Utf8_info {
u1 tag;
u2 length; // 0 ~ 65535
u1 bytes[length];
}
u2是表示一个2个字节的数据类型,这也就意味着允许的最大长度为65535。
是不是到处都找不到这个东西?
其实它是在 虚拟机的规范中 定义的,所以后续需要 好好的看看《深入理解JAVA虚拟机2》
还可以
很乱不过基本上是有点头绪了。具体的还需要《深入理解JAVA虚拟机2》 专研。
3.作用域
这个需要注意的知识点是就是默认修饰符 就是什么都不写
Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
public : 对所有类可见。使用对象:类、接口、变量、方法
protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
4.流程控制和运算符
这个也没什么特别的,基本每个语言通用的,如果跳转,循环,判断 加减等。需要 勤加练习的 有位运算。
这里有个知识点就是 先++还是先返回如:
int a=10;
a=a++; 先返回 再自加 a=10
a=++a; 先自加 再返回 a=11
重点一个就是 使用位运算实现 加减乘除:
/**
* 二进制实现加法-预算的基础 全加器。类似十进制 先来个我们最熟悉的十进制的加法运算:
*
* 13 + 9 = 22
*
* 我们像这样来拆分这个运算过程:
*
* 不考虑进位,分别对各位数进行相加,结果为sum: 个位数3加上9为2;十位数1加上0为1; 最终结果为12;
*
* 只考虑进位,结果为carry: 3 + 9 有进位,进位的值为10;
*
* 如果步骤2所得进位结果carry不为0,对步骤1所得sum,步骤2所得carry重复步骤1、
* 2、3;如果carry为0则结束,最终结果为步骤1所得sum: 这里即是对sum = 12 和carry = 10重复以上三个步骤,(a)
* 不考虑进位,分别对各位数进行相加:sum = 22; (b) 只考虑进位: 上一步没有进位,所以carry = 0; (c) 步骤2carry =
* 0,结束,结果为sum = 22. 我们发现这三板斧行得通!
*/
private static int add(int number1, int number2) {
int sum = number1 ^ number2;
int carry = (number1 & number2) << 1;
while (carry != 0) {
int a = sum;
int b = carry;
sum = a ^ b;
carry = (a & b) << 1;
}
return sum;
}
// 递归写法
// private static int add(int num1, int num2){
// if(num2 == 0)
// return num1;
// int sum = num1 ^ num2;
// int carry = (num1 & num2) << 1;
// return add(sum, carry);
// }
/** 二进制实现减法 */
private static int substract(int number1, int number2) {
int subtractor = add(~number2, 1);// 先求减数的补码(取反加一)
int result = add(number1, subtractor);
return result;
}
/**
* 二进制实现乘法 -类似我们在草稿纸上运算 乘法 (1) 判断乘数是否为0,为0跳转至步骤(4)
* (2)将乘数与1作与运算,确定末尾位为1还是为0,如果为1,则相加数为当前被乘数;如果为0,则相加数为0;将相加数加到最终结果中
* (3)被乘数左移一位,乘数右移一位;回到步骤(1) (4) 确定符号位,输出结果;
*/
private static int multiply(int a, int b) {
// 将乘数和被乘数都取绝对值
int multiplicand = a < 0 ? add(~a, 1) : a;
int multiplier = b < 0 ? add(~b, 1) : b;
// 计算绝对值的乘积
int product = 0;
// 连续累加效率不高
while (multiplier > 0) {
if ((multiplier & 0x1) > 0) {// 每次考察乘数的最后一位
product = add(product, multiplicand);
}
multiplicand = multiplicand << 1;// 每运算一次,被乘数要左移一位
multiplier = multiplier >> 1;// 每运算一次,乘数要右移一位(可对照上图理解)
}
// 计算乘积的符号
if ((a ^ b) < 0) {// 只考虑最高位,如果a,b异号,则异或后最高位为1;如果同号,则异或后最高位为0;
product = add(~product, 1);
}
return product;
}
/**
* 二进制实现除法
*
* @param n
* @return
*/
private static int divide_v2(int a, int b) {
// 先取被除数和除数的绝对值
int dividend = a > 0 ? a : add(~a, 1);// 被除数
int divisor = b > 0 ? b : add(~b, 1); // 除数
int quotient = 0;// 商
int remainder = 0;// 余数
for (int i = 31; i >= 0; i--) {
// 比较dividend是否大于divisor的(1<<i)次方,不要将dividend与(divisor<<i)比较,而是用(dividend>>i)与divisor比较,
// 效果一样,但是可以避免因(divisor<<i)操作可能导致的溢出,如果溢出则会可能dividend本身小于divisor,但是溢出导致dividend大于divisor
if ((dividend >> i) >= divisor) {
quotient = add(quotient, 1 << i);
dividend = substract(dividend, divisor << i);
}
}
// 不断用除数去减被除数,直到被除数小于被除数(即除不尽了)但是效率不高
// while (dividend >= divisor) {// 直到商小于被除数
// quotient = add(quotient, 1);
// dividend = substract(dividend, divisor);
// println(" quotient" + quotient);
// }
// 确定商的符号
if ((a ^ b) < 0) {
// 如果除数和被除数异号,则商为负数
quotient = add(~quotient, 1);
}
// 确定余数符号
remainder = b > 0 ? dividend : add(~dividend, 1);
return quotient;// 返回商
}
篇幅有限 剩余的 数组,输入 输出流,异常等基础内容放到下篇