Java基础之扩展特性
一、泛型
Java 泛型(generics)是 JDK5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。一般用来限制类操作的类型,一旦类型与前期设定不匹配,则直接编译报错。
ArrayList<Dog> al = new ArrayList<>();//<Dog>即泛型的指定参数,提高安全性
泛型分两种,一种放在方法上,一种放在类上
1.方法泛型案例:传入一个类型,返回该类型的集合。
import java.util.ArrayList;
import java.util.List;
public class MethodGeneric{
public static void main(String[] args) {
MethodGeneric generic = new MethodGeneric();
List<Dog> query1 = generic.query(new Dog());//query(T t)
List<Dog> query2 = generic.query(Dog.class);//query(Class<T> t)
}
public <T> List<T> query(T t) {
List<T> list = new ArrayList<>();
System.out.println(t);
return list;
}
public <T> List<T> query(Class<T> t) {
List<T> list = new ArrayList<>();
System.out.println(t);
return list;
}
}
class Dog {
}
注意:里面的 T 也可以写为 E、K、V。它们只是用于彼此区分,因为一个方法或类中可能传入多个类型。
2.类泛型:限制类里面所有方法都只能操作该类型(语法和前面所学的 List 相同)
import java.util.HashMap;
import java.util.Map;
public class TypeGeneric{
public static void main(String[] args) {
Method<Map> cat = new Method<>();
cat.add(new HashMap());
//cat.add(new User());//报错
}
}
class Method<T> {
public void add(T t) {
System.out.println("save" + t);
}
}
注意:
1.泛型 T 只能是引用类型,不能用基本数据类型填充,也就是说,泛型不支持基本数据类型,如果泛型要使用基本数据类型,那就必须使用它的包装类。
2.泛型只是在编译阶段检查数据类型是否一致,编译通过后程序处于运行阶段泛型不再起作用。
二、包装类
java 中的基本数据类型没有方法和属性,而包装类就是为了让这些拥有方法和属性,实现对象化交互,包装类可以看做是对基本数据类型的一种提升,同时还使基本数据类型可以为空 null,同时包装类比基本数据类型耗用更多内存。数值型包装类都继承至 Number,而字符型和布尔型继承至 Object。
对应关系如下:
基本类型 对应的包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
基本数据和包装类之间的转换
装箱:基本数据类型转换为包装类。
拆箱:包装类转换为基本数据类型。
//1.自动装箱
int t1 = 1;
Integer t2 = t1;
//2.手动装箱
Integer t3 = new Integer(t1);
System.out.println("int类型t1=" + t1);
System.out.println("自动装箱,Integer类型对象t2=" + t2);
System.out.println("手动装箱,Integer类型t3=" + t3);
//1.自动拆箱
int t4 = t2;
//2.手动拆箱
//通过intValue()方法返回int值,还可以利用其他方法装换为其他类型
int t5 = t2.intValue();
System.out.println("自动拆箱,int型t4=" + t4);
System.out.println("手动拆箱,int型t5=" + t5);
包装类的常用方法:
通过包装类 Integer.toString() 将整型转换为字符串;
通过 int i = Integer.parseInt(String) 将字符串转换为 int 类型;
通过 Integer i = Integer.valueOf(String) 方法把字符串转换为包装类。
//基本数据类型转换为字符串
int t1 = 12;
String t2 = Integer.toString(t1);
System.out.println("int转换为String:" + t2);
//字符串转换为基本数据类型
//通过paerInt方法
int t3 = Integer.parseInt(t2);
//通过valeOf,先把字符串转换为包装类然后通过自动拆箱
int t4 = Integer.valueOf(t2);
System.out.println("t3:" + t3);
System.out.println("t4:" + t4);
包装类对象之间的比较:
Integer one = new Integer(100);
Integer two = new Integer(100);
//one和对two是两个不同的对象,==在比较对象时比较的是内存地址,两个是不同的空间,放的值相同
System.out.println("one==two:" + (one == two));
System.out.println("one.equals(two):" + (one.equals(two)));
Integer three = 100;//自动装箱
System.out.println("three==100:" + (three == 100));//自动拆箱
Integer four = 100;
/*
* Java 为了提高拆装箱效率,在执行过程中提供了一个缓存区(对象池)【类似于常量数组】,
* 如果传入的参数是,-128<参数<127会直接去缓存查找数据,如果有就直接使用,如果没有就隐式调用new方法产生
*/
System.out.println("three==four:" + (three == four));
Integer five = 200;
System.out.println("five==200:" + (five == 200));
Integer six = 200;
//注:这里为200,超出了缓存区范围,所以都需要构建
System.out.println("five==six:" + (five == six));
包装类属于引用数据类型,在比较相等时一定要注意使用 equals 方法。
三、递归算法
递归算法(英语:recursion [rɪˈkɜːʃn] algorithm [ˈælɡərɪðəm])在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。递归式方法可以被用于解决很多的计算机科学问题,因此它是计算机科学中十分重要的一个概念。
简单地说,就是在函数或子过程的内部,直接或者间接地调用自己的算法。
递归算法的实质:是把问题转化为规模缩小了的同类问题的子问题。然后递归调用函数来表示问题的解。
案例:递归某路径下的所有文件,如果是文件夹,则进入其中继续遍历。
public class Recursion {
public static void main(String[] args) {
File file = new File("f:\\123");
listAllFile(file);
}
public static void listAllFile(File file) {
File[] strs = file.listFiles();
for (int i = 0; i < strs.length; i++) {
// 判断strs[i]是不是目录
if (strs[i].isDirectory()) {
listAllFile(strs[i]);//递归调用自己
System.out.println("目录="+strs[i].getName());
} else {
System.out.println("文件名="+strs[i].getName());
}
}
}
}
递归算法解决问题的特点:
- 递归就是在过程或函数里调用自身。
- 在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。
- 递归算法解题通常显得很简洁,但递归算法解题的运行效率较低,即占用内存很大,有时这种情况用栈来代替解决。所以一般不提倡用递归算法设计程序。
- 在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。所以一般不提倡用递归算法设计程序。
练习:
1.递归包含:假设你的项目目录是 e:/project,遍历这个目录下所有的 java 文件(包括子文件夹),找出文件内容包括 static 的那些文件,并将路径打印出来。
2.递归拷贝文件夹:使用递归完成文件夹的拷贝将 D:/jdk 拷贝到 E 盘的相同路径。
3.斐波那契数列(Fibonacci)又称黄金分割数列,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)= 0,F(1)= 1,F(n)=F(n-1)+F(n-2)(n≥2,n∈N*),请使用递归方式计算第 20 个数为多少 。
参考代码:
import java.io.*;
public class Recursion {
public static void main(String[] args) throws IOException {
// listAllFile(new File("E://IdeaProjects"));
//copyDirectory(new File("D:/jdk1.8.0_181"));
System.out.println(fun(19));
}
/**
* 3.斐波那契数列
*/
private static int fun(int i) {
if (i == 0) {
return 0;
} else if (i == 1) {
return 1;
} else {
return fun(i - 1) + fun(i - 2);
}
}
//使用循环完成斐波那契数列
public static int fibonacci(int n) {
int[] res = {0, 1};
if (n < 2) {
return res[n];
}
int first = 0;
int second = 1;
int fibn = 0;
for (int i = 2; i <= n; i++) {
fibn = first + second;
first = second;
second = fibn;
}
return fibn;
}
/**
* 2.递归拷贝文件夹
*/
private static void copyDirectory(File file) throws IOException {
File[] files = file.listFiles();
for (int i = 0; i < files.length; i++) {
File f = files[i];
StringBuffer stringBuffer = new StringBuffer(f.getAbsolutePath());
stringBuffer.replace(0, 1, "E");
String newPath = stringBuffer.toString();
System.out.println(newPath);
// 判断strs[i]是不是目录
if (files[i].isDirectory()) {
new File(newPath).mkdirs();
copyDirectory(files[i]);//递归调用自己
} else {
FileInputStream inputStream = new FileInputStream(f);
FileOutputStream outputStream = new FileOutputStream(newPath);
byte[] bytes = new byte[1024];
int n;
while ((n = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, n);
}
outputStream.close();
inputStream.close();
}
}
}
/**
* 1.递归包含
*/
public static void listAllFile(File file) throws IOException {
File[] files = file.listFiles();
for (int i = 0; i < files.length; i++) {
// 判断strs[i]是不是目录
if (files[i].isDirectory()) {
listAllFile(files[i]);//递归调用自己
} else {
File f = files[i];
if (f.getName().endsWith("java")) {
FileReader fileReader = new FileReader(f);
BufferedReader reader = new BufferedReader(fileReader);
String s;
while ((s = reader.readLine()) != null) {
if (s.contains("static")) {
System.out.println("文件名=" + files[i].getAbsolutePath());
break;
}
}
reader.close();
fileReader.close();
}
}
}
}
}
四、异常处理
java 将所有的错误封装为一个对象,其根本父类为 Throwable, Throwable 有两个子类:Error 和 Exception。
Error 是 Throwable 的子类,指 java 运行时系统的内部错误和资源耗尽错误。应用程序不会抛出该类对象。如果出现了这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止。大多数这样的错误都是 jdk 底层错误,程序员一般不用关注。
在异常处理中常用两个对象为 Exception 和 RuntimeException
Exception:定义方法时必须声明所有可能会抛出的 exception; 在调用这个方法时,必须捕获它的 exception,不然就得把它的 exception 传递下去;例如:IOException,SQLException 就属于 Exception。
Exception 属于应用程序级别的异常,这类异常必须捕捉,Exception 体系包括 RuntimeException 体系和其他非 RuntimeException 的体系。
RuntimeException 是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。可能在执行方法期间抛出但未被捕获的 RuntimeException 的任何子类都无需在 throws 子句中进行声明。它是 Exception 的子类。
处理异常的两种方法:
- 在发生异常的地方 try catch 直接处理;
- 将异常 throws 抛给调用者,让调用者处理。
对异常的处理,有一条行之有效的默认规则:优先向上抛出,交给调用者处理。
1、try…catch 捕获异常。程序运行产生异常时,将从异常发生点中断程序的正常运行并跳转到异常对应的捕获模块中,如果没有对应的捕获模块则程序以异常抛出的方式运行。
public static void main(String[] args) {
int x = (int) (Math.random() * 5);
int y = (int) (Math.random() * 10);
int[] z = new int[5];
try {
//try catch 的主要作用就是出现异常时可以让程序继续运行,不至于 down 掉
//使用 try catch 一般在用户输入容易出错的地方。否则一般都是往上抛出。
//jdk 或 tomcat 会自动打印栈信息方便定位出错位置
System.out.println("y/x=" + (y / x));
System.out.println("y=" + y + "z[y]=" + z[y]);
} catch (ArithmeticException exc1) {//分步捕获算术运算异常信息
exc1.printStackTrace();//打印异常的栈信息
System.out.println("算术运算异常:" + exc1.getMessage());
} catch (ArrayIndexOutOfBoundsException exc2) {//分步捕获数据越界异常信息
exc2.printStackTrace();//打印异常的栈信息
System.out.println("数据越界异常:" + exc2.getMessage());
}
System.out.println("over");
}
2.throws
即将异常信息抛给调用者,由调用者进行处理。如果最终都没有类处理,则抛给 jvm 虚拟机处理,同时程序会在异常处中断,如果需要控制发生异常后程序按照程序员的设计运行则需要手动捕获异常。
3.finally
如果把 finally 块置 try…catch…语句后,finally 块一般都会得到执行,它相当于一个万能的保险,即使前面的 try 块发生异常,而又没有对应异常的 catch 块,finally 块将马上执行。
五、自定义异常
在实际开发中我们通常也会使用自定义异常与对应的异常处理完成程序对应的返回提示。通常,我们选择 Exception 和 RuntimeException 两个类作为自定义异常类的父类。继承 Exception 会在抛出异常时强制要求处理。继承 RuntimeException 会在抛出异常时不强制要求处理,因此是众多开发人员的最爱。
public class Tester {
public static void main(String args[]) throws MyException1 {
test();
test1();
}
public static void test() {
throw new MyException("抛个异常玩玩!!");
}
public static void test1() throws MyException1 {
throw new MyException1("抛个异常玩玩!!");
}
}
class MyException extends RuntimeException {
public MyException(String message) {
super(message);
}
}
class MyException1 extends Exception {
public MyException1(String message) {
super(message);
}
}
被调用类在运行过程中对遇到的异常一概不作处理,而是直接向上抛出,一直到最上层的调用类,调用类根据应用系统的需求和特定的异常处理规则进行处理,如向控制台输出异常堆栈信息,打印在日志文件中。用一句形象的话来说,就是谁使用,谁(最上层的调用类)处理。
六、常用类
1.String
创建字符串最简单的方式如下:
String s = "大家好";
和其它对象一样,也可以使用关键字和构造方法来创建 String 对象。
String s = new String("大家好");
注意:String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了。如果需要对字符串做很多修改,那么应该选择使用 StringBuffer & StringBuilder 类。
String 类的一个访问器方法是 length() 方法,它返回字符串对象包含的字符数。String 还有一些常用的方法如下:
String s = "大家好";
System.out.println(s.length());
//判断字符串是否包含
boolean contain = s.contains("家");
//获取索引处的字符
char c = s.charAt(1);
//匹配是忽略大小写
boolean abc = s.equalsIgnoreCase("abc");
//从索引1开始切割到结束
String substring = s.substring(1);
//从索引1位置切割到2位置
substring = s.substring(1, 2);
String 类的静态方法 format() 能用来创建可复用的格式化字符串
public class Tester {
public static void main(String[] args) {
String fs = String.format("浮点型变量的值为 " +
"%s, 整型变量的值为 " +
"%s, 字符串变量的值为 " +
"%s", 12, 16, "stringVar");
System.out.println(fs);
test("1", "2", "3", "4");
}
public static void test(String... arg) {
System.out.println(Arrays.toString(arg));
}
}
String 的两种初始化形式本质区别
String str1 = "abc"; // 在字符串常量池中
String str2 = new String("abc"); // 在堆上
当直接赋值时,字符串“abc”会被存储在常量池中,只有 1 份,此时的赋值操作等于是创建 0 个或 1 个对象。如果常量池中已经存在了“abc”,那么不会再创建对象,直接将引用赋值给 str1;如果常量池中没有“abc”,那么创建一个对象,并将引用赋值给 str1。
那么,通过 new String(“abc”);的形式又是如何呢?答案是 1 个或 2 个。 当 JVM 遇到上述代码时,会先检索常量池中是否存在“abc”,如果不存在“abc”这个字符串,则会先在常量池中创建这个一个字符串。然后再执行 new 操作,会在堆内存中创建一个存储“abc”的 String 对象,对象的引用赋值给 str2。此过程创建了 2 个对象。 当然,如果检索常量池时发现已经存在了对应的字符串,那么只会在堆内创建一个新的 String 对象,此过程只创建了 1 个对象。
2.StringBuffer 和 StringBuilder 类
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
StringBuffer buffer = new StringBuffer();
//追加
buffer.append("abc");
buffer.append("def");
//删除索引为0的字符
buffer.deleteCharAt(0);
//删除索引从0到2的字符
buffer.delete(0, 2);
System.out.println(buffer.toString());
3.Math
见 jdk 开发文档
4.Date
见 jdk 开发文档
获取当前电脑时间可以有两种方式,如果需要 long 类型 System.currentTimeMillis()。如果需要使用 Date 类型,直接 new Date();
long l = System.currentTimeMillis();//返回当前的时间戳
Date date = new Date();
Date date1 = new Date(l);
SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd HH:mm:ss");
System.out.println(format.format(date));
System.out.println(format.format(date1));
注意:在 SimpleDateFormat 里面大写 M 表示月,小写的 m 表示分,大写的 H 表示 24 小时制,小写的 h 表示 12 小时制。
七、String 的正则表达式
在 String 类中,经常使用正则表达式匹配,替换,查找和切割等方法完成字符串操作,不使用正则表达式将增加大量的代码运算逻辑。
本小节主要讲述在 String 的 split() 和 replace() 中使用正则表达式的地方。
正则表达式中的截取:
str.split("\\.") 按照.来截取
str.split(" ") 按照空格截取
str.split("cc+") 按照c字符来截取,2个c或以上
str.split((1)\\.+) 按照字符串中含有2个字符或以上的地方截取(1)表示分组为1
案例:
String str = "PUBLICSTATICCCVOIDDDCCMAIN";
String regex1 = "(.)\\1+";//相同字母出现1次以上
String[] ss = str.split(regex1);
System.out.println(Arrays.toString(ss));//[PUBLICSTATI, VOI, , MAIN]
String regex2 = "CC+";//匹配 CC 字符或更多的 CCC 字符一起出现
String[] s = str.split(regex2);
System.out.println(Arrays.toString(s));//[PUBLICSTATI, VOIDDD, MAIN]
正则表达式中的替换:
案例:
String str = "weii23212.3jjin2.3..34.";
String regex1 = "\\d{4,}";//匹配数字连续超过4个位置
System.out.println(str.replaceAll(regex1, "*"));
String regex2 = "(.)\\1+";//匹配字符连续超过两次的位置
System.out.println(str.replaceAll(regex2, "*"));
String regex3 = "\\.+";//匹配.出现一次或以上的位置
System.out.println(str.replaceAll(regex3, "*"));
练习:
1.自然对数:下面这个图是自然对数的计算方式。 借助 Math 的方法,把自然对数计算出来,看看经过自己计算的自然对数和 Math.E 的区别有多大。
e = 1+ 1/1!+1/2!+1/3!+1/4!+…+1/n!
2.随机字符串:创建一个长度是 5 的随机字符串,随机字符只包含小写字母,不使用数组码表。参考 ASCII 码对照表。
3.字符串排序:创建一个长度是 8 的字符串数组,并填充长度为 5 的随机字符串(只包含小写字母)初始化这个数组。对这个数组进行排序,按照每个字符串的首字母排序,如果首字母相同则判断第二个字母。
4.穷举法破解密码:生成一个长度是 3 的随机数字字符串,把这个字符串作为当做密码,使用穷举法生成长度是 3 个字符串,匹配上述生成的密码。
5.通过代码对比 String 与 StringBuffer 的性能对比:生成 5 位长度的随机字符串然后,先使用 String 的 +,连接 10000 个随机字符串,计算消耗的时间。然后,再使用StringBuffer 连接 10000 个随机字符串,计算消耗的时间。
提示:使用 System.currentTimeMillis() 获取当前时间戳(毫秒数)
6.递归找文件:使用递归算法打印出 d 盘的所有的 exe 文件。
参考代码:
import java.io.File;
import java.util.Arrays;
public class Demo {
public static void main(String[] args) {
t31();
}
/**
* 6.递归找文件
*
* @param file
*/
private static void t6(File file) {
File[] files = file.listFiles();
//空指针异常出现的原因是调用空对象的方法或者获取空对象的属性
for (int i = 0; files != null && i < files.length; i++) {
File f = files[i];
if (f.isDirectory()) {
t6(f);
} else {
if (f.getName().endsWith("exe")) {
System.out.println(f.getAbsolutePath());
}
}
}
}
/**
* 5.String 与 StringBuffer 的性能对比
*/
private static void t5() {
long start = System.currentTimeMillis();//开始计时
String s = "";
for (int i = 0; i < 10000; i++) {
s += t2();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < 10000; i++) {
stringBuffer.append(t2());
}
long end1 = System.currentTimeMillis();
System.out.println(end1 - end);
}
/**
* 4.穷举破解
*/
private static void t4() {
char[] chars = new char[3];
for (int i = 0; i < chars.length; i++) {
int out = (int) (Math.random() * 10) + 48;
chars[i] = (char) out;
}
String password = new String(chars);
System.out.println(password);
for (int i = 0; i < 1000; i++) {
String s = i < 10 ? ("00" + i) : i < 100 ? ("0" + i) : (i + "");
if (s.equals(password)) {
System.out.println("终于找到了" + s);
return;
}
}
}
/**
* 3.字符串排序 冒泡排序
*/
private static void t31() {
String[] strings = new String[8];
for (int i = 0; i < strings.length; i++) {
strings[i] = t2();
}
System.out.println(Arrays.toString(strings));
for (int j = 0; j < strings.length; j++) {
for (int i = 0; i < strings.length - 1 - j; i++) {
if (compare(strings[i], strings[i + 1])) {
String temp = strings[i];
strings[i] = strings[i + 1];
strings[i + 1] = temp;
}
}
}
System.out.println(Arrays.toString(strings));
}
/**
* 3.字符串排序 选择排序
*/
private static void t32() {
String[] strings = new String[8];
for (int i = 0; i < strings.length; i++) {
strings[i] = t2();
}
System.out.println(Arrays.toString(strings));
for (int i1 = 0; i1 < strings.length; i1++) {
int minIndex = i1;
for (int i = i1 + 1; i < strings.length; i++) {
if (compare(strings[minIndex], strings[i])) {
minIndex = i;
}
}
String temp = strings[i1];
strings[i1] = strings[minIndex];
strings[minIndex] = temp;
}
System.out.println(Arrays.toString(strings));
}
/**
* 返回两个字符串前一个是否大于后一个
*
* @param s1 前一个
* @param s2 后一个
* @return 是否大于
*/
private static boolean compare(String s1, String s2) {
if (s1.equals(s2)) return false;
char[] chars = s1.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (chars[i] > s2.charAt(i)) return true;
if (chars[i] < s2.charAt(i)) return false;
}
return false;
}
/**
* 2.随机字符串
* [97,122]
*/
private static String t2() {
char[] chars = new char[5];
for (int i = 0; i < chars.length; i++) {
int out = (int) (Math.random() * 26) + 97;
chars[i] = (char) out;
}
return new String(chars);
}
/**
* 1.自然对数
*/
private static void t1() {
double e = Math.E;
double sum = 1;
double ji = 1;
for (int i = 1; i <= 16; i++) {
ji *= i;
sum += 1.0 / (ji);
}
System.out.println(sum);
System.out.println(e);
}
}
八、Java 和 C++的区别
Java 和 C++ 的区别
- 都是面向对象的语言,都支持封装、继承和多态 。
- Java 不提供指针来直接访问内存,程序内存更加安全,也没有 goto。
- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承 。
- Java 有自动内存管理机制(GC),不需要程序员手动释放无用内存 。
九、TCP/IP 初识
TCP/IP(Transmission Control Protocol/Internet Protocol)的简写,中文译名为传输控制协议/因特网互联协议,又叫网络通讯协议,这个协议是 Internet 最基本的协议、Internet 国际互联网络的基础,简单地说,就是由网络层的 IP 协议和传输层的 TCP 协议组成的。
TCP/IP 协议的由来
在阿帕网(ARPR)产生运作之初,通过接口信号处理机实现互联的电脑并不多,大部分电脑相互之间不兼容,在一台电脑上完成的工作,很难拿到另一台电脑上去用,想让硬件和软件都不一样的电脑联网,也有很多困难。当时美国的状况是,陆军用的电脑是 DEC 系列产品,海军用的电脑是 Honeywell 中标机器,空军用的是 IBM 公司中标的电脑,每一个军种的电脑在各自的系里都运行良好,但却有一个大弊病:不能共享资源。
1997 年,为了褒奖对因特网发展作出突出贡献的科学家,并对 TCP/IP 协议作出充分肯定,美国授予为因特网发明和定义 TCP/IP 协议的文顿·瑟夫和卡恩“国家技术金奖”。这无疑使人们认识到 TCP/IP 协议的重要性,瑟夫也被称为互联网之父。
IP 地址
IP 地址概述:每个 internet 上的主机和路由器都有一个 IP 地址,它包括网络号和主机号,所有 IP 地址都占 4 个字节,也就是是 32 位。如:192.168.8.190,每位最大 255。IP 地址广义上分为公网 IP 和内网 IP。
内网 IP(局域网 IP)
Internet 设计者保留了 IPv4 地址空间的一部份供专用地址使用,专用地址空间中的 IPv4 地址叫专用地址,这些地址永远不会被当做公用地址来分配,所以专用地址永远不会与公用地址重复。
A 10.0.0.0-10.255.255.255
B 172.16.0.0-172.31.255.255
C 192.168.0.0-192.168.255.255
内网 IP 从每种意义上讲是对公网 IP 的量少的一种补充,也许不久的将来随着 Ipv6 的发展,内网 IP 将会慢慢退出历史舞台。
公网 IP:
能在互联网上被访问的 IP ,如我们课件所在的 IP 地址,58.42.239.163 可以在全球任何能连互联网的地方访问。
网关:在局域网中,路由器所在的 IP 就叫做网关 IP,它负责为局域网内的计算机转发请求,注意网关不一定都有公网 IP。
DNS:(Domain Name System,域名系统),因特网上作为域名和 IP 地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。
如下:www.baidu.com 域名对应的 IP 为 14.215.177.39
C:\Users\Administrator>ping www.baidu.com
正在 Ping www.a.shifen.com [14.215.177.39] 具有 32 字节的数据:
来自 14.215.177.39 的回复: 字节=32 时间=28ms TTL=54
来自 14.215.177.39 的回复: 字节=32 时间=30ms TTL=54
来自 14.215.177.39 的回复: 字节=32 时间=29ms TTL=54
来自 14.215.177.39 的回复: 字节=32 时间=28ms TTL=54
端口(port)
在网络技术中,端口(Port)有好几种意思。集线器、交换机、路由器的端口指的是连接其他网络设备的接口,如 RJ-45 端口、Serial 端口等。我们这里所指的端口不是指物理意义上的端口,而是特指 TCP/IP 协议中的端口,是逻辑意义上的端口。
如果把 IP 地址比作一间房子,端口就是出入这间房子的门。真正的房子只有几个门,但是一个 IP 地址的端口可以有 65536(即:256256)个之多!端口是通过端口号来标记的,端口号只有整数,范围是从 0 到 65535(256256-1)。
1-1024 是固定端口又叫有名端口,即被某些程序固定使用,一般程序员不使用。
- 22:SSH 远程登录协议
- 23:telnet 使用
- 21:ftp 使用
- 25:smtp 服务使用
- 80:iis web 应用
- 7:echo 服务
1025-65535 是动态端口,这些端口,程序员可以在不占用的情况下随意使用,如下是常见的服务类应用所用端口:
MySQL 数据库服务:3306
Oracle 数据库服务:1521
Tomcat web 服务:8080
Nginx web 服务:80
Redis 服务:6379
注意:同一台电脑的一个端口只能跑一个服务类应用,当再次启动应用时会报端口占用。
十、Socket 单向通讯
java 使用 TCP 信息传输主要使用 Socket 和 ServerSocket 类来完成,他们分别代表 TCP 协议中的服务端和客户端,封装了请求的细节部分在网络编程中成为必不可少的两个对象。Socket 连接过程总的来说分三步:
1.服务端的 Socket 并不定位具体客户端的 Socket,而是将自己处于一个等待连接的状态,监听网络的连接状态。
2.客户端 Socket 想提出连接请求,那它就得说明连接的服务器地址和端口号。
3.服务器 Socket 监听到客户端连接请求后,会响应客户端 Socket 的请求了。它在此时会建立一个新的线程,把服务器的 Socket 描述发给客户端,客户端确认了此描述,连接就算建立好了。服务器的 Socket 会继续它的监听状态,等待其他连接。
案例:服务端(单向通讯)
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server1 {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);//在8888端口搭建Socket服务器
while (true) {
Socket socket = serverSocket.accept();//卡住程序往下执行,直到有人连接
System.out.println("有人链接我了");
InputStream stream = socket.getInputStream();//从该次连接中获取输入流
StringBuffer buffer = new StringBuffer();//利用StringBuffer获取输入流中的信息
byte[] bytes = new byte[1024];//字节缓冲数组,每次从输入流中读取1024个字节
int i;
while ((i = stream.read(bytes)) != -1) {
buffer.append(new String(bytes, 0, i));
}
socket.shutdownInput();//关闭该输入流
System.out.println("接受的消息是:" + buffer.toString());
socket.close();
}
}
}
案例:客户端(单向通讯)
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class Client1 {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("192.168.8.235", 8888);
OutputStream stream = socket.getOutputStream();
String msg = "哈哈哈";
stream.write(msg.getBytes());
stream.flush();
socket.shutdownInput();
socket.close();
}
}
注意:客户端请求的必须是服务端的 ip 和端口,同时请求时服务端必须先启动,查看本机 ip 地址可以使用 cmd 中的 ipconfig 命令。
C:\Users\Administrator>ipconfig
Windows IP 配置
以太网适配器 以太网:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::d6b:cdf3:459a:bff4%4
IPv4 地址 . . . . . . . . . . . . : 192.168.8.222
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . : 192.168.8.254
十一、Socket 双向通讯
案例:服务端(双向通讯)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class Server2 {
public static void main(String[] args) throws IOException {
//在9999号端口上监听
ServerSocket serverSocket = new ServerSocket(9999);
//等待客户端连接,该函数会返回一个Socket连接
Socket socket = serverSocket.accept();
//要读取s中传递的数据
InputStreamReader streamReader = new InputStreamReader(socket.getInputStream());
BufferedReader reader = new BufferedReader(streamReader);
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
//接收从控制台输入的信息
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
while (true) {
//接收客户端信息
String line = reader.readLine();
System.out.println("服务器接收到:\t" + line);
//接收从控制台输入的信息
System.out.println("请输入要发送到客户端的信息:");
String response = bufferedReader.readLine();
//把从控制台输入的信息,回送给客户端
writer.println(response);
//服务器从控制台上接收bye服务器端退出
if (response.equals("bye")) {
System.out.println("退出对话");
socket.close();
break;
}
}
}
}
案例:客户端(双向通讯)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class Client2 {
public static void main(String[] args) throws IOException {
//连接服务器端
Socket socket = new Socket("127.0.0.1", 9999);
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
//控制台接收信息
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
//从服务器接收信息
InputStreamReader streamReader = new InputStreamReader(socket.getInputStream());
BufferedReader reader = new BufferedReader(streamReader);
while (true) {
//客户端先从控制台接收
String line = bufferedReader.readLine();
//然后发送给服务器
writer.println(line);
//客户端从控制台上输入 bye 信息,客户端退出
if (line.equals("bye")) {
System.out.println("客户端退出对话");
socket.close();
break;
}
//接收从服务器发来的信息
String res = reader.readLine();
System.out.println("客户端接收到的信息:" + res);
//客户端接收服务端发送来的 bye 信息,客户端退出
if (res.equals("bye")) {
System.out.println("服务器端强制客户端退出对话");
socket.close();
break;
}
}
}
}
注意:在开发中 127.0.0.1 与 localhost 都表示本机地址。
十二、单元测试
JUnit 是用于编写和运行可重复的自动化测试的开源测试框架, 这样可以保证我们的代码按预期工作。JUnit 可广泛用于工业和作为支架(从命令行)或 IDE (如Eclipse)内单独的 Java 程序。
JUnit 的特点
- JUnit 是用于编写和运行测试的开源框架。 提供了注释,以确定测试方法。
- 提供断言测试预期结果。
- 提供了测试运行的运行测试。
- JUnit 测试让您可以更快地编写代码,提高质量
- JUnit 是优雅简洁。它是不那么复杂以及不需要花费太多的时间。
- JUnit 测试可以自动运行,检查自己的结果,并提供即时反馈。没有必要通过测试结果报告来手动梳理。
- JUnit 测试可以组织成测试套件包含测试案例,甚至其他测试套件。
- Junit 显示测试进度的,如果测试是没有问题条形是绿色的,测试失败则会变成红色。
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class MyTest {
@BeforeClass
public static void t00() {
System.out.println("t00");
}
@Before
public void t0() {
System.out.println("t0");
}
@Test
public void t3() {
Assert.assertEquals(1, 1);
System.out.println("t3");
}
@Test
public void t2() {
System.out.println("t2");
}
@Test
public void t1() {
System.out.println("t1");
}
}
常用注解
注解 描述
@Test 测试注释指示该公共无效方法它所附着可以作为一个测试用例。
@Before Before注释表示,该方法必须在类中的每个测试之前执行,以便执行测试某些必要的先决条件。
@BeforeClass BeforeClass注释指出这是附着在静态方法必须执行一次并在类的所有测试之前。必须声明为静态方法。
@After After 注释指示,该方法在执行每项测试后执行(如执行每一个测试后重置某些变量,删除临时变量等)
@AfterClass AfterClass注解用于类卸载时执行。注意:附有此批注(类似于BeforeClass)的方法必须定义为静态。<br/>
断言 org.junit.Assert(了解)
断言是编程术语,表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真,可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新启用断言。
断言 描述
void assertEquals([String message], expected value, actual value)
断言两个值相等。值可能是类型有 int, short, long, byte, char or java.lang.Object. 第一个参数是一个可选的字符串消息
void assertTrue([String message], boolean condition)
断言一个条件为真
void assertFalse([String message],boolean condition)
断言一个条件为假
void assertNotNull([String message], java.lang.Object object)
断言一个对象不为空(null)
void assertNull([String message], java.lang.Object object)
断言一个对象为空(null)
void assertSame([String message], java.lang.Object expected, java.lang.Object actual)
断言,两个对象引用相同的对象
void assertNotSame([String message], java.lang.Object unexpected, java.lang.Object actual)
断言,两个对象不是引用同一个对象
void assertArrayEquals([String message], expectedArray, resultArray)
断言预期数组和结果数组相等。数组的类型可能是 int, long, short, char, byte or java.lang.Object.
案例:
import org.junit.Assert;
import org.junit.Test;
public class MyTest {
@Test
public void t1() {
String s = null;
Assert.assertNotNull(s);
System.out.println("验证通过");
}
}
十三、内部类
在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。下面就先来了解一下这四种内部类的用法。
1.成员内部类 :成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:
class Circle {
double radius = 0;
public Circle(double radius) {
this.radius = radius;
}
class Draw { //内部类
public void drawSahpe() {
System.out.println("drawshape");
}
}
}
这样看起来,类Draw像是类Circle的一个成员,Circle称为外部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
class Circle {
private double radius = 0;
public static int count =1;
public Circle(double radius) {
this.radius = radius;
}
class Draw { //内部类
public void drawSahpe() {
System.out.println(radius); //外部类的private成员
System.out.println(count); //外部类的静态成员
}
}
}
不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
- 外部类.this.成员变量
- 外部类.this.成员方法
虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:
class Circle {
private double radius = 0;
public Circle(double radius) {
this.radius = radius;
getDrawInstance().drawSahpe(); //必须先创建成员内部类的对象,再进行访问
}
private Draw getDrawInstance() {
return new Draw();
}
class Draw { //内部类
public void drawSahpe() {
System.out.println(radius); //外部类的private成员
}
}
}
成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:
public class Test {
public static void main(String[] args) {
//第一种方式:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建
//第二种方式:
Outter.Inner inner1 = outter.getInnerInstance();
}
}
class Outter {
private Inner inner = null;
public Outter() {
}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
class Inner {
public Inner() {
}
}
}
内部类可以拥有 private 访问权限、protected 访问权限、public 访问权限及包访问权限。比如上面的例子,如果成员内部类 Inner 用 private 修饰,则只能在外部类的内部访问,如果用 public 修饰,则任何地方都能访问;如果用 protected 修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被 public 和包访问两种权限修饰。我个人是这么理解的,由于成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰。
2.局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
class People{
public People() {
}
}
class Man{
public Man(){
}
public People getWoman(){
class Woman extends People{ //局部内部类
int age =0;
}
return new Woman();
}
}
注意: 局部内部类就像是方法里面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的。
3.匿名内部类
匿名内部类应该是平时我们编写代码时用得最多的,在编写某些代码时使用匿名内部类不但方便,而且使代码更加容易维护。
public class MainTest {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(1);
}
}).start();
}
}
这段代码中的:new Runnable(){…}就是匿名内部类的使用。使用匿名内部类能够在实现父类或者接口中的方法情况下同时产生一个相应的对象,但是前提是这个父类或者接口必须先存在才能这样使用。当然我们也可以给该类和接口创建一个实现类。只是这种写法虽然能达到一样的效果,但是既冗长又难以维护,所以一般使用匿名内部类的方法来编写事件监听代码。
同样的,匿名内部类也是不能有访问修饰符和 static 修饰符的。 匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。
一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
4.静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字 static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非 static 成员必须依附于具体的对象。
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter {
public Outter() {
}
static class Inner {
public Inner() {
}
}
}
十四、反射初识
JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和获取它的属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。
案例:在数据库操作中通常有如下的插入语句:insert into user (id,name,age) values (‘1’,‘张三’,‘18’),使用反射完成 SQL 的拼接。
import java.io.IOException;
import java.lang.reflect.Field;
public class Client1 {
public static void main(String[] args) throws IOException, IllegalAccessException {
User user = new User(1, "张三", 18);
Student student = new Student(1, "张三", "java 2003");
System.out.println(getSQL(user));
System.out.println(getSQL(student));
}
//insert into user (id,name,age) values ('1','张三','18')
private static String getSQL(Object object) throws IllegalAccessException {
String entityName = object.getClass().getName().toLowerCase();//获取该类型的名字并转成小写字母
StringBuffer stringBuffer = new StringBuffer("insert into " + entityName + " (");
Field[] fields = object.getClass().getDeclaredFields();//获取该类型的所有声明的属性
for (int i = 0; i < fields.length; i++) {
stringBuffer.append(fields[i].getName() + (i == fields.length - 1 ? ")" : ","));
}
stringBuffer.append(" values (");
for (int i = 0; i < fields.length; i++) {
Field field= fields[i];
field.setAccessible(true);//暴力获取属性值
Object o = field.get(object);//获取 object 的对应属性的值
stringBuffer.append("'"+o+"'" + (i == fields.length - 1 ? ")" : ","));
}
//Method[] declaredMethods = object.getClass().getDeclaredMethods(); 获取该类型中所有声明的方法
return stringBuffer.toString();
}
}
class Student{
private Integer id;
private String name;
private String classroom;
public Student(Integer id, String name, String classroom) {
this.id = id;
this.name = name;
this.classroom = classroom;
}
}
class User {
private Integer id;
private String name;
private Integer age;
public User(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
}
十五、fail-fast 事件
机制是 java 集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生 fail-fast 事件。
例如:当某一个线程 A 通过 iterator 去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程 A 访问集合时,就会抛出ConcurrentModificationException 异常,产生 fail-fast 事件。
章节练习:(使用单元测试)
1.已知数据库修改数据的 SQL 是这样:
update user set name='李四',age='15' where id = 1;
请创建方法 String sqlString = getSQL(Object object) ,通过反射完成传入任何自定义的对象,都将返回该对象的 sql 字符串。
2.使用随机数生成长度为 4 的随机字符串(只包含大小写字母和数字)1000000 条存入集合 Set,并判断重复出现的字符串有多少条。
3.JAVA 底层有专门获取唯一字符串的操作 UUID,判断 UUID 是否可靠,即:1000000 次生成不会出现重复字符串。
String uuid = UUID.randomUUID().toString();
4.由于 UUID 字符串太长,有人使用下面代码对 UUID 返回值进行包装,使其在长度为 8 时也可以保证唯一性。阅读如下代码,并判断该算法是否有效,即往集合中添加 1000000 次不出现重复数据。
static String[] chars = new String[]{
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
};
public static String getUUID8() {
StringBuffer stringBuffer = new StringBuffer();
String uuid = UUID.randomUUID().toString().replace("-", "");
for (int i = 0; i < 8; i++) { // 32 -> 8
String str = uuid.substring(i * 4, i * 4 + 4);
// 16进制为基解析
int strInteger = Integer.parseInt(str, 16);
// 0x3E -> 字典总数 62
stringBuffer.append(chars[strInteger % 0x3E]);
}
return stringBuffer.toString();
}
5.MD5(Message Digest Algorithm)中文名为消息摘要算法第五版)为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。MD5 加密是一种不可逆的加密算法,网上大多 MD5 破解都是穷举和机器学习算法,使用 Map 存储全部 6 位数的数字和其密码,使用户可以通过密文快速获取明文。
000000=>670B14728AD9902AECBA32E22FA4F6BD
000001=>04FC711301F3C784D66955D98D399AFB
...
999999=>52C69E3A57331081823331C4E69D3F2E
MD5 调用方式参考如下:
StringBuffer sb = new StringBuffer(32);
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest("123456".getBytes());
for (int i = 0; i < array.length; i++) {
sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).toUpperCase(), 1, 3);
}
System.out.println(sb.toString());
6.已知在前后端交互中使用一种简便的数据格式 JSON,属性以如下方式展现。
{id: "20", name: "分布式", remark: "分布式", createTime: "2020-03-04 10:35:39"}<br/>
请使用 map 并存入相应的信息,并通过 String sql = map2json(Map map)。获取 json 字符串。
参考代码:
import org.junit.Test;
import java.lang.reflect.Field;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
public class Demo {
String[] chars = new String[]{
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
};
@Test
public void t6() throws IllegalAccessException {
Map<String, Object> map = new HashMap<>();
map.put("id", 1);
map.put("sex", 1);
map.put("name", "张三");
map.put("password", "123456");
map.put("classroom", "123456");
System.out.println(map2Json(map));
}
//{id: "20", name: "分布式", remark: "分布式", createTime: "2020-03-04 10:35:39"}
private String map2Json(Map map) throws IllegalAccessException {
StringBuffer stringBuffer = new StringBuffer();
Set set = map.keySet();
for (Object key : set) {
if (!key.equals("id")) {
stringBuffer.append(key + "='" + map.get(key) + "',");
}
}
stringBuffer.deleteCharAt(stringBuffer.length() - 1);//删除最后一个字符
stringBuffer.append(" where id = " + map.get("id"));
return stringBuffer.toString();
}
@Test
public void t5() throws NoSuchAlgorithmException {
Map<String, String> map = new HashMap<>();
for (int i = 0; i < 1000000; i++) {
//0 不足 6 位的前面补 0
//6 长度为 6
String s = String.format("%06d", i);
String md5 = getMd5(s);
map.put(md5, s);
}
System.out.println(map.get("E35CF7B66449DF565F93C607D5A81D09"));
}
private String getMd5(String string) throws NoSuchAlgorithmException {
StringBuffer sb = new StringBuffer(32);
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(string.getBytes());
for (int i = 0; i < array.length; i++) {
sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).toUpperCase(), 1, 3);
}
return sb.toString();
}
@Test
public void t4() {
Set<String> set = new HashSet<>();
for (int i = 0; i < 1000000; i++) {
set.add(getUUID8());
}
System.out.println(set.size() - 1000000);
}
public String getUUID8() {
StringBuffer stringBuffer = new StringBuffer();
String uuid = UUID.randomUUID().toString().replace("-", "");
for (int i = 0; i < 8; i++) { // 32 -> 8
String str = uuid.substring(i * 4, i * 4 + 4);
// 16进制为基解析
int strInteger = Integer.parseInt(str, 16);
// 0x3E -> 字典总数 62
stringBuffer.append(chars[strInteger % 0x3E]);
}
return stringBuffer.toString();
}
@Test
public void t3() {
Set<String> set = new HashSet<>();
for (int i = 0; i < 1000000; i++) {
String uuid = UUID.randomUUID().toString();
System.out.println(uuid.length());
set.add(uuid);
}
System.out.println(set.size() - 1000000);
}
@Test
public void t2() {
Set<String> set = new HashSet<>();
for (int i = 0; i < 1000000; i++) {
StringBuffer stringBuffer = new StringBuffer();
for (int j = 0; j < 4; j++) {
int index = (int) (Math.random() * chars.length);
stringBuffer.append(chars[index]);
}
set.add(stringBuffer.toString());
}
System.out.println(1000000 - set.size());
}
;
@Test
public void t1() throws IllegalAccessException {
User user = new User(1, "张三", "123456");
System.out.println(getSQL(user));
}
//update user set name='李四',age=15 where id = 1;
private String getSQL(Object object) throws IllegalAccessException {
StringBuffer stringBuffer = new StringBuffer("update " + object.getClass().getName().toLowerCase() + " set ");
Class<?> aClass = object.getClass();
Field[] fields = aClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
field.setAccessible(true);
stringBuffer.append(field.getName() + ":");
stringBuffer.append("\"" + field.get(object) + "\",");//转义 \"
}
stringBuffer.deleteCharAt(stringBuffer.length() - 1);
stringBuffer.append("}");
return stringBuffer.toString();
}
}
class User {
private int id;
private String name;
private String password;
public User(int id, String name, String password) {
this.id = id;
this.name = name;
this.password = password;
}
}