二进制,是计算技术中广泛采用的一种数制,由德国数理哲学大师莱布尼茨于 1679 年
发明。二进制数据是用 0 和 1 两个数码来表示的数。它的基数为 2,进位规则是“逢二进
一”。
数字计算机只能识别和处理由‘0’.‘1’符号串组成的代码。其运算模式正是二进制。
二进制对应两种状态,广泛应用于电子科学。比如:可以对应电子器件的开关状态、对
应信号电压状态(+5V 等价于逻辑"1",0V 等价于逻辑"0")、对应卡带是否打孔状态、电磁
存储(磁体状态:南为 0,北为 1)等等
在线进制转化的工具: https://tool.lu/hexconvert/
二进制和十进制数的对应
十进制数 | 二进制 | 十六进制 |
---|---|---|
0 | 0 | 0 |
1 | 1 | 1 |
2 | 10 | 2 |
3 | 11 | 3 |
4 | 100 | 4 |
5 | 101 | 5 |
6 | 110 | 6 |
7 | 111 | 7 |
8 | 1000 | 8 |
9 | 1001 | 9 |
10 | 1010 | a |
11 | 1011 | b |
12 | 1100 | c |
13 | 1101 | d |
14 | 1110 | e |
15 | 1111 | f |
十进制整数转换为二进制整数采用"除 2 取余,逆序排列"法。
单行注释使用“//”
开头。
多行注释以“/*”
开头以“*/”
结尾。注意,多行注释不能嵌套使用。
文档注释以“/**”
开头以“*/”
结尾,注释中包含一些说明性的文字及一些
JavaDoc 标签(后期写项目时,可以生成项目的 API)
标识符是用来给变量、类、方法以及包进行命名的。4 大规则:
标识符的使用规范
abstract | assert | boolean | break | byte | case |
catch | char | class | const | continue | default |
do | double | else | extends | final | finally |
float | for | goto | if | implements | import |
instanceof | int | interface | long | native | new |
null | package | private | protected | public | return |
short | static | strictfp | super | switch | synchronized |
this | throw | throws | transient | try | void |
volatile | while |
Java 数据类型分为两大类:基本数据类型(primitive data type)和引用数据类型(reference data type)。
注意 : 引用数据类型的大小统一为 4 个字节,记录的是其引用对象的地址!
类型 | 占用存储空间 | 表数范围 |
---|---|---|
byte | 1 字节 | -27 ~ 27-1(-128~127) |
short | 2 字节 | -215 ~ 215-1 (-32768~32767) |
int | 4 字节 | -231 ~ 231-1 (-2147483648~2147483647) 约 21 亿 |
long | 8 字节 | -263 ~ 263-1 |
Java 语言整型常量的四种表示形式
Java 语言的整型常数默认为 int 型,声明 long 型常量可以后加‘ l ’或‘ L ’ 。
类型 | 占用存储空间 | 表数范围 |
---|---|---|
float | 4 字节 | -3.403E38~3.403E38 |
double | 8 字节 | -1.798E308~1.798E308 |
float 类型又被称作单精度类型,尾数可以精确到 7 位有效数字。
double 表示这种类型的数值精度约是 float 类型的两倍,又被称作双精度类型,绝大部
分应用程序都采用 double 类型。
Java 浮点类型常量有两种表示形式
(1) 十进制数形式: 例: 3.14 314.0 0.314
(2) 科学记数法形式 例:3.14e0 3.14E2 3.14E-1
浮点型不精确,不要用于比较
浮点数存在舍入误差,数字不能精确表示。浮点数适合普通的科学和工程计算,精度足够;
但不适合精度要求非常高的商业计算,这时候要使用 BigDecimal 进行运算和比较。
浮点常量默认类型是 double,要改成 float 可以后面加 F 或 f
转义符 | 含义 | Unicode 值 |
---|---|---|
\b | 退格(backspace) | \u0008 |
\n | 换行 | \u000a |
\r | 回车 | \u000d |
\t | 制表符(tab) | \u0009 |
\“ | 双引号 | \u0022 |
\‘ | 单引号 | \u0027 |
\ | 反斜杠 | \u005c |
注意事项
以后我们学的 String 类,其实是字符序列(char sequence), 本质是 char 字符组成的数组。
JVM 规范指出 boolean 当做 int 处理,也就是 4 字节,boolean 数组当做 byte 数组处理,这样我
们可以得出 boolean 类型占了单独使用是 4 个字节,在数组中是确定的 1 个字节。
double salary;
long earthPopulation;
int age
变量有三种类型:局部变量、成员变量(也称为实例变量)和静态变量。
类型 | 声明位置 | 从属于 | 生命周期(作用域) |
---|---|---|---|
局部变量 | 方法或语句块内部 | 方法/语句块 | 从声明位置开始,直到方法或语句块执行完毕,局部变量消失 |
成员变量(实例变量) | 类内部,方法外部 | 对象 | 对象创建,成员变量也跟着创建。对象消失,成员变量也跟着消失; |
静态变量(类变量) | 类内部,static 修饰 | 类 | 类被加载,静态变量就有效;类被卸载,静态变量消失。 |
局部变量(local variable)
方法或语句块内部定义的变量。生命周期是从声明位置开始到到方法或语句块执行完毕
为止。局部变量在使用前必须先声明、初始化(赋初值)再使用。
成员变量(也叫实例变量 member variable)
方法外部、类的内部定义的变量。从属于对象,生命周期伴随对象始终。如果不自行初
始化,它会自动初始化成该类型的默认初始值。
静态变量(类变量 static variable)
使用 static 定义。 从属于类,生命周期伴随类始终,从类加载到卸载。
在 Java 语言中,用关键字 final 来定义一个常量。常量一旦被初始化后不能再更改。
计算机的基本用途就是执行数学运算,Java 提供了一套丰富的运算符来操作变量。
运算符 | 用法举例 | 等效的表达式 |
---|---|---|
+= | a += b | a = a+b |
-= | a -= b | a = a-b |
*= | a *= b | a = a*b |
/= | a /= b | a = a/b |
%= | a %= b | a = a%b |
关系运算符用来进行比较运算。关系运算的结果是布尔值:true/false;
运算符 | 含义 | 示例 |
---|---|---|
== | 等于 | a==b |
!= | 不等于 | a!=b |
> | 大于 | a>b |
< | 小于 | a |
>= | 大于或等于 | a>=b |
<= | 小于或等于 | a<=b |
注意事项
=是赋值运算符,而真正的判断两个操作数是否相等的运算符是==。
==、!= 是所有(基本和引用)数据类型都可以使用。
> 、>=、 <、 <= 仅针对数值类型(byte/short/int/long,float/double 以及
char)。
注意
&和|既是逻辑运算符,也是位运算符。如果两侧操作数都是 boolean 类型,就作为逻辑
运算符。如果两侧的操作数是整数类型,就是位运算符。
不要把“^”当做数学运算“乘方”,是“位的异或”操作。
x 为 boolean 类型表达式,先计算 x 的值,若为 true,则整个运算的结果为表达式 y 的
值,否则整个运算结果为表达式 z 的值。
自动类型转换指的是容量小的数据类型可以自动转换为容量大的数据类型。如图 所示,黑色的实线表示无数据丢失的自动类型转换,而虚线表示在转换时可能会有精度的损失。
可以将整型常量直接赋值给 byte、 short、 char 等类型变量,而不需要进行强制类型
转换,只要不超出其表数范围即可。
强制类型转换,又称为造型(cast),用于强制转换数值的类型,可能损失精度。
注意: 不能在布尔类型和任何数值类型之间做强制类型转换
【示例】类型转换常见问题一
int money = 1000000000; //10亿
int years = 20;
//返回的total是负数,超过了int的范围
int total = money*years;
System.out.println("total="+total);
//返回的total仍然是负数。默认是int,因此结果会转成int值,再转成long。但是已经发
生//了数据丢失
long total1 = money*years;
System.out.println("total1="+total1);
//返回的total2正确:先将一个因子变成long,整个表达式发生提升。全部用long来计
算。
long total2 = money*((long)years);
System.out.println("total2="+total2);
顺序结构是一种基本的控制结构,它按照语句出现的顺序执行操作。
代表“先执行 a,再执行 b”的逻辑。
代表“如果…,则…”的逻辑。
条件判断结构有:if 结构和 switch 结构。而 if 结构又可以分为 if 单分支结构、if-else 双分支结构、if-else if-else 多分支结构。
if(布尔表达式){
语句块
}
语法结构:
if(布尔表达式){
语句块 1
}else{
语句块 2
}
当布尔表达式为真时,执行语句块 1,否则,执行语句块 2。也就是 else 部分。
if(布尔表达式 1) {
语句块 1;
} else if(布尔表达式 2) {
语句块 2;
}……
else if(布尔表达式 n){
语句块 n;
} else {
语句块 n+1;
}
当布尔表达式 1 为真时,执行语句块 1;否则,判断布尔表达式 2,当布尔表达式 2 为真时,执行语句块 2;否则,继续判断布尔表达式 3·;如果 1~n 个布尔表达式均判定为假时,则执行语句块 n+1,也就是 else 部分。
switch (表达式) {
case 值 1:
语句块 1;
[break];
case 值 2:
语句块 2;
[break];
… … … … …
[default:
默认语句块;]
}
循环结构分两大类,一类是当型,一类是直到型。
语法结构:
while (布尔表达式) {
循环体;
}
do {
循环体;
} while(布尔表达式) ;
do-while 循环结构会先执行循环体,然后再判断布尔表达式的值,若条件为真,执行
循环体,当条件为假时结束循环。do-while 循环的循环体至少执行一次。
for (初始表达式; 布尔表达式; 迭代因子) {
循环体;
}
初始化部分设置:循环变量的初值
条件判断部分为:布尔表达式
迭代因子:控制循环变量的增减
for 循环在执行条件判定后,先执行的循环体部分,再执行步进。
循环语句内部,再写一个或多个循环,称为嵌套循环。一般工作中多见的就是两层。
【示例】嵌套循环
public class Test14 {
public static void main(String args[ ]) {
for (int i=1; i <=5; i++) {
for(int j=1; j<=5; j++){
System.out.print(i+" ");
}
System.out.println();
}
}
}
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
// 根据用户输入的月份,输出当月的天数,不考虑平年闰年
System.out.println("请输入月份:");
int month = input.nextInt();
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
System.out.println(month + "月共有31天!");
break;
case 4:
case 6:
case 9:
case 11:
System.out.println(month + "月共有30天!");
break;
case 2:
System.out.println(month + "月共有28天!");
break;
default:
System.out.println("您输入的月份有误!");
break;
}
语句块(也叫复合语句)。语句块中定义的变量只能用于自己,外部不能使用。
语句块可以使用外部的变量,而外部不能使用语句块的变量;
方法声明格式:
[修饰符 1 修饰符 2 …] 返回值类型 方法名(形式参数列表){
Java 语句;… … …
}
方法的调用方式:
普通方法 对象名.方法名(实参列表)
静态方法 类名.方法名(实参列表)
方法的详细说明
形式参数:在方法声明时用于接收外界传入的数据。(方法定义时)
实参:调用方法时实际传给方法的数据。 (方法调用时)
返回值:执行完毕后,返还给调用它的环境的数据。
返回值类型:事先约定的返回值的数据类型,如无返回值,则为 void。
重载: 一个类中可以定义多个名称相同,但形式参数列表不同的方法。
注意: 重载的方法,实际是完全不同的方法,只是名称相同而已
构成方法重载的条件:
递归结构包括两个部分:
定义递归头。 解决:什么时候不调用自身方法。如果没有头,将陷入死循环,也就
是递归的结束条件。
递归体。 解决:什么时候需要调用自身方法。
【示例】使用递归求 n!
public class Test22 {
public static void main(String[ ] args) {
long d1 = System.currentTimeMillis();
factorial(10);
long d2 = System.currentTimeMillis();
System.out.printf("递归费时:"+(d2-d1)); //耗时:32ms
}
/** 求阶乘的方法*/
static long factorial(int n){
if(n==1){//递归头
return 1;
}else{//递归体
return n*factorial(n-1);//n! = n * (n-1)!
}
}
}
递归的缺陷
算法简单是递归的优点之一。但是递归调用会占用大量的系统堆栈,内存耗用多,在递归调用层次多时速度要比循环慢的多,所以在使用递归时要慎重。
数组是相同类型数据的有序集合。其中,每一个数据称作一个元素,每个元素可以通过一个
索引(下标)来访问它们。数组的四个基本特点:
type[ ] arr_name; //方式一
type arr_name[ ]; //方式二
注意事项
声明的时候并没有实例化任何对象,只有在实例化数组对象时,JVM 才分配空间,这时才与长度有关。
声明一个数组的时候并没有数组真正被创建。
构造一个数组,必须指定长度。
【示例】创建基本类型一维数组
public class Test {
public static void main(String args[ ]) {
int[ ] s; // 声明数组;
s = new int[10]; // 给数组分配空间;
for (int i = 0; i < 10; i++) {
s[i] = 2 * i + 1;//给数组元素赋值; 数组是对象,数组中的元素就是对象的属性
System.out.println(s[i]);
}
}
}
class Man{
private int age;
private int id;
public Man(int id,int age) {
super();
this.age = age;
this.id = id;
}
}
public class AppMain {
public static void main(String[ ] args) {
Man[ ] mans; //声明引用类型数组;
mans = new Man[10]; //给引用类型数组分配空间;
Man m1 = new Man(1,11);
Man m2 = new Man(2,22);
mans[0]=m1;//给引用类型数组元素赋值;
mans[1]=m2;//给引用类型数组元素赋值;
}
}
数组的初始化方式总共有三种:静态初始化、动态初始化、默认初始化。
除了用 new 关键字来产生数组以外,还可以直接在定义数组的同时就为数组元素分配空间并赋值。
【示例】数组的静态初始化
int [ ] a = { 1, 2, 3 };// 静态初始化基本类型数组;
Man[ ] mans = { new Man(1, 1), new Man(2, 2) };// 静态初始化引用类型数组;
数组定义与为数组元素分配空间并赋值的操作分开进行。
【示例】数组的动态初始化
int[ ] a1 = new int[2];//动态初始化数组,先分配空间;
a1[0]=1;//给数组元素赋值;
a1[1]=2;//给数组元素赋值;
数组是对象,它的元素相当于对象的属性;每个元素也按照属性的方式被默认初始化。
【示例】数组的默认初始化
int a2[ ] = new int[2]; // 默认值:0,0
boolean[ ] b = new boolean[2]; // 默认值:false,false
String[ ] s = new String[2]; // 默认值:null, null
数组元素下标的合法区间:[0, length-1]。我们可以通过下标来遍历数组中的元素,遍历时可以读取元素的值或者修改元素的值。
【示例】使用循环初始化和遍历数组
public class Test {
public static void main(String[ ] args) {
int[ ] a = new int[4];
//初始化数组元素的值
for(int i=0;i<a.length;i++){
a[i] = 100*i;
}
//读取元素的值
for(int i=0;i<a.length;i++){
System.out.println(a[i]);
}
}
}
System.arraycopy(object src,int srcpos,object dest, int destpos,int length)该方法可以将 src 数组里的元素值赋给 dest 数组的元素,其中 srcpos 指定从 src 数组的第几个元素 开始赋值,length 参数指定将 src 数组的多少个元素赋给 dest 数组的元素
【示例】数组的拷贝】
public class Test {
public static void main(String args[ ]) {
String[ ] s = {"阿里","尚学堂","京东","搜狐","网易"};
String[ ] sBak = new String[6];
System.arraycopy(s,0,sBak,0,s.length);
for (int i = 0; i < sBak.length; i++) {
System.out.print(sBak[i]+ "\t");
}
}
}
Arrays 类包含了:排序、查找、填充、打印内容等常见的数组操作。
【示例】使用 Arrays 类输出数组中的元素
import java.util.Arrays;
public class Test {
public static void main(String args[ ]) {
int[ ] a = { 1, 2 };
System.out.println(a); // 打印数组引用的值;
System.out.println(Arrays.toString(a)); // 打印数组元素的值;
}
}
【示例】使用 Arrays 类对数组元素进行排序一
import java.util.Arrays;
public class Test {
public static void main(String args[ ]) {
int[ ] a = {1,2,323,23,543,12,59};
System.out.println(Arrays.toString(a));
Arrays.sort(a);
System.out.println(Arrays.toString(a));
}
}
【示例】使用 Arrays 类实现二分法查找法
import java.util.Arrays;
public class Test {
public static void main(String[ ] args) {
int[ ] a = {1,2,323,23,543,12,59};
System.out.println(Arrays.toString(a));
Arrays.sort(a); //使用二分法查找,必须先对数组进行排序;
System.out.println(Arrays.toString(a));
//返回排序后新的索引位置,若未找到返回负数。
System.out.println("该元素的索引:"+Arrays.binarySearch(a, 12));
}
}
【示例】使用 Arrays 类对数组进行填充
import java.util.Arrays;
public class Test {
public static void main(String[ ] args) {
int[ ] a= {1,2,323,23,543,12,59};
System.out.println(Arrays.toString(a));
Arrays.fill(a, 2, 4, 100); //将2到4索引的元素替换为100;
System.out.println(Arrays.toString(a));
}
}
多维数组可以看成以数组为元素的数组。
【示例】二维数组的声明
public class Test {
public static void main(String[ ] args) {
// Java中多维数组的声明和初始化应按从低维到高维的顺序进行
int[ ][ ] a = new int[3][ ];
a[0] = new int[2];
a[1] = new int[4];
a[2] = new int[3];
// int a1[ ][ ]=new int[ ][4];//非法
}
}
【示例】二维数组的静态初始化
public class Test {
public static void main(String[ ] args) {
int[ ][ ] a = { { 1, 2, 3 }, { 3, 4 }, { 3, 5, 6, 7 } };
System.out.println(a[2][3]);
}
}
多个对象做比较,就要有“比较规则”,然后实现排序。
事实上,java 中排序算法的底层也依赖 Comparable 接口。
Comparable 接口中只有一个方法:
public int compareTo(Object obj) obj
为要比较的对象方法中,将当前对象和 obj 这个对象进行比较,如果大于返回 1,等于返回 0,小于返回-1. (此处的 1 也可以是正整数,-1 也可以是负整数)。 compareTo 方法的代码也比较固定:
public int compareTo(Object o) {
Man man = (Man) o;
if (this.age < man.age) {
return -1;
}
if (this.age > man.age) {
return 1;
}
return 0;
}
【测试 Comparable 接口】使用 Arrays 类对数组元素进行排序二
import java.util.Arrays;
public class Test {
public static void main(String[ ] args) {
Man[ ] msMans = { new Man(3, "a"), new Man(60, "b"), new Man(2, "c") };
Arrays.sort(msMans);
System.out.println(Arrays.toString(msMans));
}
}
class Man implements Comparable {
int age;
int id;
String name;
public Man(int age, String name) {
super();
this.age = age;
this.name = name;
}
public String toString() {
return this.name;
}
public int compareTo(Object o) {
Man man = (Man) o;
if (this.age < man.age) {
return -1;
}
if (this.age > man.age) {
return 1;
}
return 0;
}
}
冒泡排序算法重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就
把他们交换过来,这样越大的元素会经由交换慢慢“浮”到数列的顶端。
冒泡排序算法的运作如下:
import java.util.Arrays;
public class BubbleSort {
public static void main(String[] args) {
int[] array ={58, 45, 48, 89, 7412, 35};
int temp = 0;
for (int i = 0; i < array.length; i++){
boolean swapped = true; // 用于检查是否在一趟遍历中发生了交换的标志
for (int j = 0; j < array.length - 1; j++){
if (array[j] >= array[j+1]){
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
swapped = false; // 设置标志,表示发生了交换
}
}
if (swapped){
break; // 如果在一趟遍历中没有发生交换,说明数组已经有序,退出循环
}
System.out.println(Arrays.toString(array)); // 在每一趟遍历后打印数组
}
}
}
二分法检索(binary search)又称折半检索。
二分法检索的基本思想是设数组中的元素从小到大有序地存放在数组(array)中,首
先将给定值 key 与数组中间位置上元素的关键码(key)比较,如果相等,则检索成功;
否则,若 key 小,则在数组前半部分中继续进行二分法检索;
若 key 大,则在数组后半部分中继续进行二分法检索。
这样,经过一次比较就缩小一半的检索区间,如此进行下去,直到检索成功或检索失败。
【示例】二分法查找法的基本算法
class Solution {
public int search(int[] nums, int target) {
// 如果目标值小于数组的最小值或大于数组的最大值,直接返回-1,不需要进行二分查找
if (target < nums[0] || target > nums[nums.length - 1]) {
return -1;
}
int left = 0; // 左指针,初始指向数组的起始位置
int right = nums.length - 1; // 右指针,初始指向数组的末尾位置
while (left <= right) { // 当左指针小于等于右指针时,进行循环
int mid = left + ((right - left) >> 1); // 计算中间位置,使用位移运算优化除以2的操作
if (nums[mid] == target) { // 如果中间位置的值等于目标值,找到目标值,返回中间位置
return mid;
}
else if (nums[mid] < target) { // 如果中间位置的值小于目标值,则目标值可能在右半部分,更新左指针为mid+1
left = mid + 1;
}
else if (nums[mid] > target) { // 如果中间位置的值大于目标值,则目标值可能在左半部分,更新右指针为mid-1
right = mid - 1;
}
}
return -1; // 没有找到目标值,返回-1
}
}