java入口程序规定的方法必须是静态方法,方法名必须是main,参数必须是String数组
JAVA SE就是标准版,包含标准的JVM和标准库,JAVA EE是企业版,只是在SE版本上加入了大量的API和库,以方便开发WEB应用,数据库,消息服务等。SE和EE的JVM一样。
JAVA ME和SE不同,他是针对嵌入式设备的瘦身版,SE的标准库也无法在ME上使用,ME的虚拟机也是瘦身版。JAVA ME没有真正流行起来,反而Android成为了移动平台标准之一
1.编译语言C,C++,代码是直接编译成机器码执行的,但是不同平台x86,arm等,cpu的指令集不同,因此需要编译出每一种平台的对应机器码
2.解释型语言Ruby,python没有上面的问题,可以由解释器直接加载源码运行,代价是运行效率太低
3.java是将代码编译成字节码,类似抽象cpu指令,然后,针对不同平台编写虚拟机,虚拟机负责加载代码并执行,实现了一次编写到处运行。虚拟机不同平台不同
基本数据类型是CPU可以直接进行运算的类型
以下为一些特殊情况
1.浮点数0.1无法精确表示,因为换算成二进制是一个无限循环小数,也因此浮点数运算会产生误差。
整数可以做移位运算,浮点数不行。
由于浮点数存在运算误差,所以比较两个浮点数是否相等常常会出现错误的结果。正确的比较方法是判断两个浮点数之差的绝对值是否小于一个很小的数(Begdcemal的对比两个数大小就是和1比较)。
2.除数为0时编译不报错
3.两个int相加,超过int最大值溢出时,编译不会报错,运行也不会报错,但是得到的结果不是真正数学上的相加,而且计算机二进制加完之后的其他值
4.int大范围强转为小范围short不会报错会丢失精度
5.整数运算在除数为0时会报错,而浮点数运算在除数为0时,不会报错,但会返回几个特殊值:
NaN表示Not a Number
Infinity表示无穷大
-Infinity表示负无穷大
6.因为字符串使用双引号"…“表示开始和结束,那如果字符串本身恰好包含一个"字符怎么表示?例如,“abc"xyz”,编译器就无法判断中间的引号究竟是字符串的一部分还是表示字符串结束。这个时候,我们需要借助转义字符\:因为\是转义字符,所以,两个\表示一个\字符:常见的转义字符包括:
" 表示字符”
' 表示字符'
\ 表示字符
\n 表示换行符
\r 表示回车符
\t 表示Tab
\u#### 表示一个Unicode编码的字符
api:
// 是否包含子串:
“Hello”.contains(“ll”); // true
注意到contains()方法的参数是CharSequence而不是String,因为CharSequence是String的父类。
搜索子串的更多的例子:
“Hello”.indexOf(“l”); // 2
“Hello”.lastIndexOf(“l”); // 3
“Hello”.startsWith(“He”); // true
“Hello”.endsWith(“lo”); // true
提取子串的例子:
“Hello”.substring(2); // “llo”
“Hello”.substring(2, 4); “ll”
去除首尾空白字符
使用trim()方法可以移除字符串首尾空白字符。空白字符包括空格,\t,\r,\n:
" \tHello\r\n ".trim(); // “Hello”
注意:trim()并没有改变字符串的内容,而是返回了一个新字符串。
另一个strip()方法也可以移除字符串首尾空白字符。它和trim()不同的是,类似中文的空格字符\u3000也会被移除:
“\u3000Hello\u3000”.strip(); // “Hello”
" Hello ".stripLeading(); // "Hello "
" Hello “.stripTrailing(); // " Hello”
String还提供了isEmpty()和isBlank()来判断字符串是否为空和空白字符串:
“”.isEmpty(); // true,因为字符串长度为0
" “.isEmpty(); // false,因为字符串长度不为0
" \n”.isBlank(); // true,因为只包含空白字符
" Hello ".isBlank(); // false,因为包含非空白字符
替换子串
要在字符串中替换子串,有两种方法。一种是根据字符或字符串替换:
String s = “hello”;
s.replace('l', 'w'); // “hewwo”,所有字符'l'被替换为'w'
s.replace(“ll”, “"); // "heo”,所有子串"ll"被替换为"~~"
另一种是通过正则表达式替换:
String s = “A,B;C ,D”;
s.replaceAll(“[\,\;\s]+”, “,”); // “A,B,C,D”
分割字符串
要分割字符串,使用split()方法,并且传入的也是正则表达式:
String s = “A,B,C,D”;
String[] ss = s.split(“\,”); // {“A”, “B”, “C”, “D”}
拼接字符串
拼接字符串使用静态方法join(),它用指定的字符串连接字符串数组:
String[] arr = {“A”, “B”, “C”};
String s = String.join(“", arr); // "AB***C”
格式化字符串
字符串提供了formatted()方法和format()静态方法,可以传入其他参数,替换占位符,然后生成新的字符串:
// String
public class Main {
public static void main(String[] args) {
String s = “Hi %s, your score is %d!”;
System.out.println(s.formatted(“Alice”, 80));
System.out.println(String.format(“Hi %s, your score is %.2f!”, “Bob”, 59.5));
}
}
有几个占位符,后面就传入几个参数。参数类型要和占位符一致。我们经常用这个方法来格式化信息。常用的占位符有:
%s:显示字符串(其实可以显示任何数据类型);
%d:显示整数;
%x:显示十六进制整数;
%f:显示浮点数。
占位符还可以带格式,例如%.2f表示显示两位小数。如果你不确定用啥占位符,那就始终用%s,因为%s可以显示任何数据类型。要查看完整的格式化语法,请参考JDK文档。
字符串转换为int类型:
int n1 = Integer.parseInt(“123”); // 123
要特别注意,Integer有个getInteger(String)方法,它不是将字符串转换为int,而是把该字符串对应的系统变量转换为Integer:
Integer.getInteger(“java.version”); // 版本号,11
字符串的不可变是指字符串内容不可变。创建的字符串内容在常量池中,变的是字符串的引用。
执行String s = “hello”;时,JVM虚拟机先创建字符串"hello",然后,把字符串变量s指向它:
s
│
▼
┌───┬───────────┬───┐
│ │ “hello” │ │
└───┴───────────┴───┘
紧接着,执行s = “world”;时,JVM虚拟机先创建字符串"world",然后,把字符串变量s指向它:
s ──────────────┐
│
▼
┌───┬───────────┬───┬───────────┬───┐
│ │ “hello” │ │ “world” │ │
└───┴───────────┴───┴───────────┴───┘
原来的字符串"hello"还在,只是我们无法通过变量s访问它而已。因此,字符串的不可变是指字符串内容不可变。
浅谈String str = “” 和 new String()的区别
首先明白一个事,java存在一个常量池,可以用来存储字符串常量。
1 创建的字符串变量在内存中的区别
两者看似都是创建了一个字符串对象,但在内存中确是各有各的想法。
String str1= “abc”; 在编译期,JVM会去常量池来查找是否存在“abc”,如果不存在,就在常量池中开辟一个空间来存储“abc”;如果存在,就不用新开辟空间。然后在栈内存中开辟一个名字为str1的空间,来存储“abc”在常量池中的地址值。
String str2 = new String(“abc”) ;在编译阶段JVM先去常量池中查找是否存在“abc”,如果过不存在,则在常量池中开辟一个空间存储“abc”。在运行时期,通过String类的构造器在堆内存中new了一个空间,然后将String池中的“abc”复制一份存放到该堆空间中,在栈中开辟名字为str2的空间,存放堆中new出来的这个String对象的地址值。
也就是说,前者在初始化的时候可能创建了一个对象,也可能一个对象也没有创建;后者因为new关键字,至少在内存中创建了一个对象,也有可能是两个对象。
2 String类的特性
String类 是final修饰的,不可以被继承。
String类的底层是基于char数组的。
3 两个方面
1)性能效率
String类被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。例如:
String str = “hello";
str = str + "world“;
所以当上文str指向了一个String对象(内容为“hello”),然后对str进行“+”操作,str原来指向的对象并没有变,而是str又指向了另外一个对象(“hello world”),原来的对象还在内存中。
由此也可以看出,频繁的对String对象进行修改,会造成很大的内存开销。此时应该用StringBuffer或StringBuilder来代替String。
而new String()更加不适合,因为每一次创建对象都会调用构造器在堆中产生新的对象,性能低下且内存更加浪费。
2)安全性
对象都是只读的,所以多线程并发访问也不会有任何问题。
参考文章:https://www.liaoxuefeng.com/wiki/1252599548343744/1260469698963456
字符编码
在早期的计算机系统中,为了给字符编码,美国国家标准学会(American National Standard Institute:ANSI)制定了一套英文字母、数字和常用符号的编码,它占用一个字节,编码范围从0到127,最高位始终为0,称为ASCII编码。例如,字符'A'的编码是0x41,字符'1'的编码是0x31。
如果要把汉字也纳入计算机编码,很显然一个字节是不够的。GB2312标准使用两个字节表示一个汉字,其中第一个字节的最高位始终为1,以便和ASCII编码区分开。例如,汉字'中'的GB2312编码是0xd6d0。
类似的,日文有Shift_JIS编码,韩文有EUC-KR编码,这些编码因为标准不统一,同时使用,就会产生冲突。
为了统一全球所有语言的编码,全球统一码联盟发布了Unicode编码,它把世界上主要语言都纳入同一个编码,这样,中文、日文、韩文和其他语言就不会冲突。
Unicode编码需要两个或者更多字节表示,我们可以比较中英文字符在ASCII、GB2312和Unicode的编码:
英文字符'A'的ASCII编码和Unicode编码:
┌────┐
ASCII: │ 41 │
└────┘
┌────┬────┐
Unicode: │ 00 │ 41 │
└────┴────┘
英文字符的Unicode编码就是简单地在前面添加一个00字节。
中文字符'中'的GB2312编码和Unicode编码:
┌────┬────┐
GB2312: │ d6 │ d0 │
└────┴────┘
┌────┬────┐
Unicode: │ 4e │ 2d │
└────┴────┘
那我们经常使用的UTF-8又是什么编码呢?因为英文字符的Unicode编码高字节总是00,包含大量英文的文本会浪费空间,所以,出现了UTF-8编码,它是一种变长编码,用来把固定长度的Unicode编码变成1~4字节的变长编码。通过UTF-8编码,英文字符'A'的UTF-8编码变为0x41,正好和ASCII码一致,而中文'中'的UTF-8编码为3字节0xe4b8ad。
UTF-8编码的另一个好处是容错能力强。如果传输过程中某些字符出错,不会影响后续字符,因为UTF-8编码依靠高字节位来确定一个字符究竟是几个字节,它经常用来作为传输编码。
在Java中,char类型实际上就是两个字节的Unicode编码。如果我们要手动把字符串转换成其他编码,可以这样做:
byte[] b1 = “Hello”.getBytes(); // 按系统默认编码转换,不推荐
byte[] b2 = “Hello”.getBytes(“UTF-8”); // 按UTF-8编码转换
byte[] b2 = “Hello”.getBytes(“GBK”); // 按GBK编码转换
byte[] b3 = “Hello”.getBytes(StandardCharsets.UTF_8); // 按UTF-8编码转换
注意:转换编码后,就不再是char类型,而是byte类型表示的数组。
如果要把已知编码的byte[]转换为String,可以这样做:
byte[] b = …
String s1 = new String(b, “GBK”); // 按GBK转换
String s2 = new String(b, StandardCharsets.UTF_8); // 按UTF-8转换
始终牢记:Java的String和char在内存中总是以Unicode编码表示。
Java 中常见编码说明如下:
ISO8859-1:属于单字节编码,最多只能表示 0~255 的字符范围,向下兼容了ASCII,多了写欧洲语。
GBK/GB2312:中文的国标编码,用来表示汉字,属于双字节编码。GBK 可以表示简体中文和繁体中文,而 GB2312 只能表示简体中文。GBK 兼容 GB2312。
Unicode:是一种编码规范,是为解决全球字符通用编码而设计的。UTF-8 和 UTF-16 是这种规范的一种实现,此编码不兼容 ISO8859-1 编码。Java 内部采用此编码。
UTF:UTF 编码兼容了 ISO8859-1 编码,同时也可以用来表示所有的语言字符,不过 UTF 编码是不定长编码,每一个字符的长度为 1~6 个字节不等。一般在中文网页中使用此编码,可以节省空间。
utf-8,1到4个字节,因为可以用ASCII表示的字符用了Unicode表示,由于Unicode基本固定的长度所以在最高位加了00,这样非常占空间,出现了通用转化格式,既UTF,常见的有uft-8,uft-16等。uft-8用来把固定长度的unicode编码变成1-4字节的变长编码。英文一个字节,汉语中字三个字节。UTF-8编码的另一个好处是容错能力强,如果传输过程中某些字符出错,不会影响后续字符,因为UTF-8编码依靠高字节位来确定一个字符究竟是几个字节,它经常用来作为传输编码。
Java中Int、Integer、new Integer()之间的区别:
Java中有八种基本数据类型,Int是其中之一。而Integer是Int的封装,即Integer其实是一个对象。可以通过new来创建一个Integer对象,也可以直接将Int值赋给Integer变量。它们之间的区别如下:
一:Int
Int是Java八种基本数据类型之一,一般大小为4字节32位,取值范围为2-31—231。两个Int类型变量用“==”比较的是内容的大小。
例如:int a = 100;int b = 100;a == b为True。
二:new Integer()
Integer是对Int类型的封装,它是一个对象,可以通过new来常见Integer对象。但是对象通过“==”比较的是对象内存地址。
例如:Integer a = new Integer(100);Integer b = new Integer(100);a == b为False。
三:将Int值赋给Integer变量。
Integer对象除了可以通过new来创建,也可以直接将Int值赋给Integer变量,这是因为系统会自动将这个Int值封装成一个Integer对象。比如:Integer a = 100;实际上的操作是:Integer a = Integer.valueof(100);
这里有一个需要注意的地方:在上面Integer的valueof()方法里面,当Int值范围在-128—127之间时,会通过一个IntegerCache缓存来创建Integer对象;当Int值不在该范围时,直接调用new Integer()来创建对象。因此会产生以下情况:
(1)Integer a = 100;Integer b = 100;a == b为True。因为这两个Integer变量引用的是缓存中的同一个Integer对象。
(2)Integer a = 200; Integer b = 200;a == b为False。这似乎因为这两个Integer变量引用的是通过new创建的两个不同对象。
四:三种变量的相互比较
(1)不管是new创建的Integer对象,还是通过直接赋值Int值创建的Integer对象,它们与Int类型变量通过“==”进行比较时都会自动拆封装变成Int类型,所以Integer对象和Int变量比较的是内容大小。
例如:int a = 100;Integer b = 100;Integer c = new Integer(100);a == b == c为True。
(2)new创建的Integer对象和直接赋Int值创建的Integer对象相互比较
这两种Integer对象通过“==”比较的都是内存地址。
(3)赋Int值创建的Integer对象相互比较
当Int值在-128—127之间时,两个Integer变量引用的是IntegerCache中的同一个对象,比较的内存地址相同。
当Int值不在以上范围时,两个Integer对象都是通过new创建的,比较的内存地址不同。
变量必须先定义后使用,不写初始值就相当于给它指定了默认值,默认值总是0
1.int n =100;JVM在内存中为变量n分配一个储存单元,填入值100;一个变量就分配一个储存单元。
2.计算机最小的储存单元是字节,一个字节就是一个8位二进制数,8bit,十进制0-255,换算成16进制就是00-ff
3.内存单元从0开始编号,称为内存地址。每个内存单元可以看作一间房间,内存地址就是门牌号
4.引用类型的变量类似于C语言的指针,它内部储存一个地址,指向某个对象在内存的位置
注意数组是引用类型,并且数组大小不可变。我们观察下面的代码:
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns;
ns = new int[] { 68, 79, 91, 85, 62 };
System.out.println(ns.length); // 5
ns = new int[] { 1, 2, 3 };
System.out.println(ns.length); // 3
}
}
数组大小变了吗?看上去好像是变了,但其实根本没变。
对于数组ns来说,执行ns = new int[] { 68, 79, 91, 85, 62 };时,它指向一个5个元素的数组:
ns
│
▼
┌───┬───┬───┬───┬───┬───┬───┐
│ │68 │79 │91 │85 │62 │ │
└───┴───┴───┴───┴───┴───┴───┘
执行ns = new int[] { 1, 2, 3 };时,它指向一个新的3个元素的数组:
ns ──────────────────────┐
│
▼
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ │68 │79 │91 │85 │62 │ │ 1 │ 2 │ 3 │ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
但是,原有的5个元素的数组并没有改变,只是无法通过变量ns引用到它们而已。
for each循环无法指定遍历顺序,也无法获取数组的索引
冒泡排序
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] ns = { 28, 12, 89, 73, 65, 18, 96, 50, 8, 36 };
// 排序前:
System.out.println(Arrays.toString(ns));
for (int i = 0; i < ns.length - 1; i++) {
for (int j = 0; j < ns.length - i - 1; j++) {
if (ns[j] > ns[j+1]) {
// 交换ns[j]和ns[j+1]:
int tmp = ns[j];
ns[j] = ns[j+1];
ns[j+1] = tmp;
}
}
}
// 排序后:
System.out.println(Arrays.toString(ns));
}
}
可以用 这个 Arrays.sort(ns);
参考文章:https://www.liaoxuefeng.com/wiki/1252599548343744/1304066130968610
https://www.liaoxuefeng.com/wiki/1252599548343744/1304066080636961
精确匹配:
1.abc精确匹配abc,不能匹配其他的;
2.正则表达式本身有转义字符\,特殊字符的表示需要转移字符,a&c 实际表示 a&c ,注意在java代码中又要加一个转义字符(两个\表示一个\),java里面的正则表达式就是a\&c 实际表示a&c;
3.匹配非ASCII字符,例如中文,需要\u####十六进制表示。例如表示 a和c 需要写成 a\u548cc来匹配,中文字符 和 的unicode编码是548c
模糊匹配:
重复匹配:
https://www.liaoxuefeng.com/wiki/1252599548343744/1306046675025953
范围匹配:
[…]还有一种排除法,即不包含指定范围的字符。假设我们要匹配任意字符,但不包括数字,可以写[^1-9]{3}:
可以匹配"ABC",因为不包含字符1~9;
可以匹配"A00",因为不包含字符1~9;
不能匹配"A01",因为包含字符1;
不能匹配"A05",因为包含字符5。
或规则匹配:
用|连接的两个正则规则是或规则,
例如 AB|CD表示可以匹配AB或CD。
使用括号:
现在我们想要匹配字符串learn java、learn php和learn go怎么办?一个最简单的规则是learn\sjava|learn\sphp|learn\sgo,但是这个规则太复杂了,可以把公共部分提出来,然后用(…)把子规则括起来表示成learn\s(java|php|go)。
参考文章:https://www.liaoxuefeng.com/wiki/1252599548343744/1306046706483233
Pattern p = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");
Matcher m = p.matcher("010-12345678");
p.matcher("010-12345678").matches(); // true
pattern.matcher("021-123456").matches(); // true
if (m.matches()) {//必须匹配成功才能用group
String g0 = m.group(0);// 010-12345678
String g1 = m.group(1);// 010
String g2 = m.group(2);// 12345678
}
String.matches()方法内部调用的就是Pattern和Matcher类的方法。