Java 教程:https://www.runoob.com/java/java-modifier-types.html
java 技能树:https://edu.csdn.net/skill/java?utm_source=AI_act_java
1、环境搭建
jdk、jre
-
JRE,( Java Runtime Envrionment ),Java 运行时环境。含JVM和运行必备的类库。 电脑上想要运行java程序,就必须安装JRE。
-
JDK,( Java Development Kit ),Java开发工具【包含JRE】、【Java开发】。含JRE 和 开发必备的工具。 工具包含:编译工具javac.exe 和 运行工具java.exe 想要开发Java应用程序,就必须安装JDK。
接下来,下载后直接解压。然后配置下环境变量。
JDK 安装成功之后就可以编写 Java 代码并编译 & 运行。
-
编写 Hello.java文件
public class Hello {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
打开终端编译并运行
>>>javac Hello.java
>>>java Hello
编写代码建议使用 IDE(集成开发环境)来提供开发效率。
集成开发工具 IDE
编写Java代码时建议大家使用IDE(集成开发环境)来提供开发效率。这里推荐使用 idea
官网地址:https://www.jetbrains.com/zh-cn/idea/
Java 代码执行原理
- java 源文件:指存储java源码的文件。
- java 源文件名:就是该源文件中public类的名称。
- 一个java源文件可以包含多个类,但只允许一个类为public。
- 当 java 源代码编码结束后,就需要编译器编译。安装好jdk后,打开 jdk 目录,有两个重要的.exe文件,javac.exe(编译源代码,xxx.java文件) 和 java.exe(执行字节码,xxx.class文件)。当 javac.exe编译java源代码时,java源代码有几个类,就会编译成一个对应的字节码文件(.class文件)。其中,字节码文件的文件名就是每个类的类名。需要注意的是,类即使不在源文件中定义,但被源文件引用,编译后,也会编程相应的字节码文件。如类A引用类C,但类C不定义在类A的源文件中,编译后,类C也被编译成对应的字节码文件C.class
JVM 的主要任务是装载class文件并执行其中的字节码,而class文件是由 JVM 的类加载器(ClassLoader)完成的,在一个 Java虚拟机 中有可能存在多个类加载器。
- 启动类加载器:是Java虚拟机唯一实现的一部分,它又可分为原始类装载器,系统类装载器或默认类装载器。它的主要作用是从操作系统的磁盘装载相应的类,如Java API类等。
- 用户自定义类加载器,即按照用户自定义的方式来装载类。
当JAVA虚拟机运行一个程序时,它需要内存来存储许多东西。比如如字节码,程序创建的对象,传递给方法的参数,返回值,局部变量以及运算的中间结果等,这些相关信息被组织到“运行时数据区”。根据厂商的不同,在Java虚拟机中,运行时数据区也有所不同。有些运行时数据区由线程共享,有些只能由某个特定线程共享。运行时数据区大致可分几个区:方法区,堆区,栈区,PC寄存器区和本地方法栈区。
2、初识 Java
代码的初步分析: Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
文件名
- 一个文件中最多只能有一个public类,文件名需要与此public类一致。
- 如果文件中有多个类,文件名与public类名一致。
- 如果文件中有多个类 且 无public类,文件名可与任意类名一致。
类名
一般首字母大写,例如:Hello、UserInfo
Java 修饰符
Java 语言提供了很多修饰符,主要分为以下两类:
- 访问修饰符
- 非访问修饰符
修饰符用来定义类、方法或者变量,通常放在语句的最前端。
public class ClassName {
// ...
private boolean myFlag;
static final double weeks = 9.5;
protected static final int BOXWIDTH = 42;
public static void main(String[] arguments) {
// 方法体
}
}
访问控制 ( public、private、protected ) 修饰符
"访问控制符" 可以限制对 "类、变量、方法、构造方法" 的访问。Java 支持 4 种不同的访问权限。
- default (没有任何修饰符,即默认):在同一包内可见。使用对象:类、接口、变量、方法。
- private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
- public : 对所有类可见。使用对象:类、接口、变量、方法
- protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
可以通过以下表来说明访问权限:
修饰符 | 当前类 | 同一包内 | 子孙类(同一包) | 子孙类(不同包) | 其他包 |
---|---|---|---|---|---|
public | Y | Y | Y | Y | Y |
protected | Y | Y | Y | Y/N(说明) | N |
default | Y | Y | Y | N | N |
private | Y | N | N | N | N |
默认访问修饰符 - 不使用任何关键字
如果在类、变量、方法或构造函数的定义中没有指定任何访问修饰符,那么它们就默认具有默认访问修饰符。
默认访问修饰符的访问级别是包级别(package-level),即只能被同一包中的其他类访问。
如下例所示,变量和方法的声明可以不使用任何修饰符。
访问控制和继承
请注意以下方法继承的规则:
父类中声明为 public 的方法在子类中也必须为 public。
父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public,不能声明为 private。
父类中声明为 private 的方法,不能够被子类继承。
类 修饰符:public、default ( 默认 )
// public,表示此类可以被任意类调用,例如:
src
├── Hello.java
└── utils
└── Request.java
// Request.Java文件
package utils;public class Request {
public void show(){
System.out.println("哈哈哈哈哈");
}
}
// Hello.java文件
import utils.Request;public class Hello {
// 程序入口
public static void main(String[] args) {
// 根据类创建了一个对象
Request req = new Request();
// 对象.show()
req.show();
}
}
// default,表示此类只能在当前包被调用,例如:
src
├── Hello.java
└── utils
├── Helper.java
└── Request.java// Request.Java文件
package utils;
class Request {
public void show(){
System.out.println("哈哈哈哈哈");
}
}// Helper.java文件
package utils;
import utils.Request;
public class Helper {
public void doAction(){
Request req = new Request();
req.show();
}
}// Hello.java文件
import utils.Helper;
public class Hello {
public static void main(String[] args) {
Helper req = new Helper();
req.doAction();
}
}
类 成员 修饰符:public、private、protected
- public,只要有权访问类,在任意情况下都可以调用。
- private,只允许自己类调用,在其他类中无法调用到。
class Other {
private static void doSomething() {
System.out.println("DoSomething");
}
public static void doing() {
System.out.println("doing");
doSomething();
}
}
public class Hello {
private static void show() {
System.out.println("show");
}
public static void main(String[] args) {
show(); // 可以
// Other.doSomething(); // 不可以
Other.doing(); // 可以
}
}
- protected,对同一包内的类和所有子类可见(不是同一个包)。
package utils;
public class Helper {
protected void doIt(){
}
void doAction(){
}
}
package xx;
import utils.Helper;
public class Db extends Helper {
public void exec(){
doIt(); // 可以访问
// doAction(); 无法访问
}
}
-
default,在同一包内可见。
非访问 ( static、final、abstract、synchronized ) 修饰符
为了实现一些其他的功能,Java 也提供了许多非访问修饰符。
- static 修饰符,用来修饰类方法和类变量。
- final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。final 表示"最后的、最终的"含义,变量一旦赋值后,不能被重新赋值。被 final 修饰的实例变量必须显式指定初始值。
- abstract 修饰符,用来创建抽象类和抽象方法。
抽象类:抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。一个类不能同时被 abstract 和 final 修饰。如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。抽象类可以包含抽象方法和非抽象方法。
抽象方法:抽象方法是一种没有任何实现的方法,该方法的具体实现由子类提供。
抽象方法不能被声明成 final 和 static。
任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。
如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。抽象方法的声明以分号结尾,- synchronized 和 volatile 修饰符,主要用于线程的编程。
synchronized 关键字声明的方法同一时间只能被一个线程访问。synchronized 修饰符可以应用于四个访问修饰符。
transient 修饰符
序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。
该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。volatile 修饰符
volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
一个 volatile 对象引用可能是 null。
static 修饰符
-
静态变量:static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。
-
静态方法:static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。
对类变量和方法的访问可以直接使用 classname.variablename 和 classname.methodname 的方式访问。
静态成员 不需要实例化,而是直接通过类就能调用。
class Person {
/**
* 静态方法
*/
public static void f1() {
System.out.println("f1");
}
public String name;
public Integer age;
/**
* 构造方法
*
* @param n1 姓名
* @param n2 年龄
*/
public Person(String n1, int n2) {
this.name = n1;
this.age = n2;
}
/**
* 实例方法
*/
public void f2() {
System.out.println("f2" + this.name);
}
}
public class Hello {
public static void main(String[] args) {
Person.f1();
Person p1 = new Person("秦始皇", 2100);
p1.f2();
}
}
返回值
方法有返回值就必须要在定义是指定。
class Person {
// void,无返回值。
public static void f1() {
System.out.println("f1");
}
// 字符串类型返回值
public static String f2() {
return "哈哈哈哈";
}
public static int f3() {
return 12;
}
}
public class Hello {
public static void main(String[] args) {
Person.f1();
String res = Person.f2();
}
}
参数
Java中传参时需要指定类型和形参。
class Person {
public static void f1(String name, int age) {
System.out.println("f1");
}
}
public class Hello {
public static void main(String[] args) {
Person.f1("秦始皇", 2100);
}
}
JAVA:将类、抽象类、接口当成方法的参数传入
- 将类当成参数传入方法,其实就是将类的对象传入方法,
- 如果是抽象类,其实就是将抽象类的子类的对象传入方法,
- 如果是接口,其实就是将接口实现类的对象传入方法。
总结:类当成方法参数传入,其实就是传对象。抽象类和接口其实就是传其子类或实现类的对象。
"普通类对象" 作为方法参数
class Person {
public void eat() {
System.out.println("吃饭");
}
}
public class Test {
public static void operatePerson(Person p) {
p.eat();
}
public static void main(String[] args) {
Test.operatePerson(new Person());
}
}
"抽象类" 作为方法参数
//抽象类
abstract class Animal {
public abstract void skill();
}
//Animal的子类
class Cat extends Animal {
//重写方法
public void skill() {
System.out.println("猫上树");
}
}
//Animal的子类
class Bird extends Animal {
//重写方法
public void skill() {
System.out.println("鸟会飞");
}
}
public class Test {
public static void operateAnimal(Animal a) {
a.skill();
}
public static void main(String[] args) {
Animal a = new Cat();
operateAnimal(new Cat()); //或 operateAnimal(a);
operateAnimal(new Bird()); //或 operateAnimal(a);
}
}
"接口实现类的对象" 作为方法参数
interface Smoking {
public abstract void smoking();
}
//实现类
class People implements Smoking {
//实现接口方法
public void smoking() {
System.out.println("抽烟中....");
}
}
public class Test {
public static void operateSmoking(Smoking s) {
s.smoking();
}
public static void main(String[] args) {
Smoking s = new People();
operateSmoking(s);
operateSmoking(new People());
operateSmoking(new Smoking() {
//重写接口方法
public void smoking() {
System.out.println("不准吸烟");
}
});
}
}
3、注释
/**
* 类的注释
* 类的注释
*/
public class Hello {
/**
* 方法的注释
* @param args 参数...
*/
public static void main(String[] args) {
// 1.单行注释
/*
2.多行注释
多行注释
*/
}
}
4、变量、常量 ( const )
Java中定义变量的格式:变量类型 变量名 = 值;
public class Hello {
public static void main(String[] args) {
String name = "king";
int age = 18;
// System.out.println(name);
// System.out.println(age);
System.out.printf("Hello, my name is %s. I am %d years old.\n", name, age);
String formattedString = String.format("Hello, my name is %s. I am %d years old.", name, age);
System.out.println(formattedString);
String message = "Hello, my name is " + name + ". I am " + age + " years old.";
System.out.println(message);
}
}
输入、输出
import java.util.Scanner;
public class Hello {
public static void main(String[] args) {
// 输入
Scanner input = new Scanner(System.in);
System.out.print("用户:");
String name = input.nextLine();
System.out.print("年龄:");
int age = input.nextInt(); // 将输入的内容转化弄成int类型
String message = String.format("姓名:%s,年龄:%d", name, age);
System.out.println(message);
}
}
import java.util.Scanner;
public class Hello {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("用户:");
String name = input.nextLine();
System.out.print("密码:");
String pwd = input.nextLine();
// name.equals("king") && pwd.equals("123")
if (name == "king" && pwd == "123") {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
}
}
5、条件语句
import java.util.Scanner;
public class Hello {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("请输入年龄:");
int age = input.nextInt();
if (age < 18) {
System.out.println("少年");
} else if (age < 40) {
System.out.println("大叔");
} else {
System.out.println("老汉");
}
}
}
import java.util.Scanner;
public class Hello {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("请输入序号:");
int num = input.nextInt(); // 任意数字
switch (num) {
case 1:
System.out.println("话费查询");
break;
case 2:
System.out.println("电话预约");
if (1 == 1) {
System.out.println("哈哈哈哈");
}
break;
default:
System.out.println("输入错误");
break;
}
}
}
6、循环语句
while
import java.util.Scanner;
public class Hello {
public static void main(String[] args) {
int count = 0;
while (count < 3) {
Scanner input = new Scanner(System.in);
System.out.print("用户:");
String name = input.nextLine();
System.out.print("密码:");
String pwd = input.nextLine();
// name == "king" && pwd == "123"
if (name.equals("king") && pwd.equals("123")) {
System.out.println("登录成功");
break;
} else {
System.out.println("登录失败");
}
count += 1;
}
}
}
do while
import java.util.Scanner;
public class Hello {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("请输入序号:");
int num = input.nextInt();
do {
System.out.println("符合规范");
} while (num < 3);
}
}
for 循环
经典的 for 循环
这是最基本的for
循环写法,通常用于重复执行代码块特定次数,或者遍历数组、集合等。
public class Hello {
public static void main(String[] args) {
String[] dataArray = {"one", "two", "three", "four"}; // 列表
for (String item : dataArray) {
System.out.println(item);
}
for (int i = 0; i < dataArray.length; i++) {
String item = dataArray[i];
System.out.println(item);
}
}
}
增强的for循环 (for-each循环)
Java 5 引入了增强的for循环,也称为for-each循环,这是迭代数组或Iterable
对象(如List
)的一种简化方式。示例:这个循环会依次处理numbers
数组中的每个元素,将其赋值给变量number
,然后执行循环体。
int[] numbers = {1, 2, 3, 4, 5};
for (int number : numbers) {
System.out.println("Number = " + number);
}
使用 Iterator 遍历集合
虽然不是for
循环的直接形式,但与增强的for循环紧密相关,因为增强的for循环在遍历集合时内部使用的是Iterator
。直接使用Iterator
遍历集合提供了更多的控制,比如删除元素。
List<String> list = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
String fruit = iterator.next();
System.out.println("Fruit = " + fruit);
// 可以在这里安全地移除元素
}
使用Stream API遍历(Java 8及之后)
Java 8 引入的Stream API提供了一种高级的迭代集合数据的方式。通过流,你可以表达复杂的数据处理查询,例如筛选、映射、聚合等,其使用的迭代细节被抽象化了。这种方法特别适用于对集合执行复杂的链式操作。
List<String> list = Arrays.asList("Apple", "Banana", "Cherry");
list.stream().forEach(fruit -> System.out.println("Fruit = " + fruit));
7、数据类型相关
在 Java 中数据类型分为
- "内置数据类型 (基本数据类型)",内置类型就是 Java 语言本身提供的基本数据类型,比如,整型数,浮点数,字符,布尔值等等。Java 语言提供了 8 种基本数据类型。
6 种数字类型:4个整数型 (byte、short、int、long)。2个浮点型 (float、double)
1 种字符类型:char
1 种布尔型:boolean - "扩展数据类型 (引用数据类型)" ,扩展类型则是 Java 语言根据基本类型扩展出的其他类型,Java 要求所有的扩展类型都必须包括在类定义里面。引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型。Java 的引用数据类型有五种;其分别为:类(对象)、接口、枚举、注解、数组。
所有引用类型的默认值都是null。一个引用变量可以用来引用任何与之兼容的类型。
引用类型的底层结构和基本类型差别较大,同时在创建对象上有所不同。
引用类型的数据创建对象时需要通过关键字 new 创建对象,创建的对象将会保存在堆中,然后将该对象赋值给一个引用,也就是这个引用将会指向该对象堆中的地址,而这个引用则存储在栈中。
最常见的引用类型就是 String,String 代表一个类,所以属于引用类型。String 有两种赋值方式:
public class Hello {
public static void main(String[] args) {
String str1 = "looper";
String str2 = new String("looper");
System.out.println(str1);
System.out.println(str2);
System.out.println(str1.equals(str2));
System.out.println(str1 == str2); // 这里为什么是 false ???
}
}
因为对象的默认值是 null,所以 String 的默认值也是 null。
但是为什么 String 也可不用 new 的形式来创建对象呢?
那是因为 Java 有字符串常量池机制
所以不用 new 关键字创建对象时,会在常量池中创建对象。
7.1 整数类型
- byte,字节 【1字节】表示范围:-128 ~ 127 即:
-2^7 ~ 2^7 -1
- short,短整型 【2字节】表示范围:-32768 ~ 32767
- int,整型 【4字节】表示范围:-2147483648 ~ 2147483647
- long,长整型 【8字节】表示范围:-9223372036854775808 ~ 9223372036854775807
public class Hello {
public static void main(String[] args) {
byte v1 = 32;
short v2 = 10000;
int v3 = 22221331;
long v4 = 554534353424L;
}
}
public class Hello {
public static void main(String[] args) {
short v1 = 32;
// 强制类型转换
int v2 = (int)v1;
System.out.println(v2);
}
}
特别提醒:在逆向时经常会看到 byte 数组表示字符串。
import java.util.Arrays;
public class Hello {
public static void main(String[] args) {
// byte数组,每个元素都是一个字节
byte[] data = {
97, 105, 100, 61, 50, 52, 54, 51, 56, 55, 53, 55, 49, 38,
97, 117, 116, 111, 95, 112, 108, 97, 121, 61, 48, 38, 99,
105, 100, 61, 50, 56, 57, 48, 48, 56, 52, 52, 49, 38, 100,
105, 100, 61, 75, 82, 69, 104, 69, 83, 77, 85, 74, 104, 56,
116, 70, 67, 69, 86, 97, 82, 86, 112, 69, 50, 116, 97, 80,
81, 107, 55, 87, 67, 104, 67, 74, 103, 38, 101, 112, 105,
100, 61, 48, 38, 102, 116, 105, 109, 101, 61, 49, 54, 50,
55, 49, 48, 48, 57, 51, 55, 38, 108, 118, 61, 48, 38, 109,
105, 100, 61, 48, 38, 112, 97, 114, 116, 61, 49, 38, 115,
105, 100, 61, 48, 38, 115, 116, 105, 109, 101, 61, 49, 54,
50, 55, 49, 48, 52, 51, 55, 50, 38, 115, 117, 98, 95, 116,
121, 112, 101, 61, 48, 38, 116, 121, 112, 101, 61, 51
}; // 这里应该表示的是一个字符串。
String dataString = new String(data);
System.out.println("字节数组转换为字符串:" + dataString);
// 字符串也可以转换成字节
byte[] res = dataString.getBytes();
System.out.println(Arrays.toString(res));
try{
String name = "佛祖保佑,永无bug";
// v1 = [-50, -28, -59, -26, -58, -21] Java中
// v2 = [206, 228, 197, 230, 198, 235] Python中
byte[] v1 = name.getBytes("GBK");
System.out.println(Arrays.toString(v1));
byte[] v2 = name.getBytes("UTF-8"); // 默认
System.out.println(Arrays.toString(v2));
}catch (Exception e){
}
}
}
需求来了:某个app逆向,在Java代码中得到一个字节数组 [-50,-28,-59,-26,-58,-21]
,请通过Python代码将这个字节数组转换成字符串?
在 Java 中的字节范围:-128~127; byte_list = [-50,-28,-59,-26,-58,-21]
Python中字节的范围:0~255。
让数字转化弄成字节并拼接起来 bytearray
Python 脚本,方便以后使用。
byte_list = [
-73, -16, -41, -26, -79, -93, -45,
-45, 44, -45, -64, -50, -34, 98, 117, 103
]
bs = bytearray() # python字节数组
for item in byte_list:
if item < 0:
item = item + 256
bs.append(item)
str_data = bs.decode('gbk') # data = bytes(bs)
print(str_data)
"""
执行结果:佛祖保佑,永无bug
"""
进制转换:在 java 中除了十进制以外,其他的进制都是用字符串来表示。
public class Hello {
public static void main(String[] args) {
// 十进制转其他
int n = 18;
String v2 = Integer.toBinaryString(n); // 二进制,字符串形式
String v8 = Integer.toOctalString(n);
String v16 = Integer.toHexString(n);
String v3 = Integer.toString(n, 3);
String dataStr = Integer.toString(n); // "18"
// 其他转十进制
String data = "10010";
int v10 = Integer.parseInt(data, 2);
// 其实:Integer.toString 和 Integer.parseInt 其实就是整型和字符串之间的转换。
}
}
7.2 字符
public class Hello {
public static void main(String[] args) {
char v1 = '田'; // 字符
String v2 = "挡住上边日下边,挡住下边日上边,挡住左边日右边,挡住右边日左边"; // 字符串
}
}
字符,char 【2字节】对应的是unicode中的码点,用于存储单个字符。
public class Hello {
public static void main(String[] args) {
String text = "天王盖地虎宝塔镇河妖";
char v1 = text.charAt(1);
System.out.println(v1); // l
char v2 = text.charAt(2);
System.out.println(v2); // e
char v3 = '中'; // 单引号
System.out.println(v3);
char v4 = 'A'; // 单引号 65
char v5 = '#'; // 单引号 35
int v6 = v4 + v5; // 十进制 100
System.out.println(v6); //100
char v7 = (char)v6;
System.out.println(v7); // d
}
}
7.3 字符串
注意:字符串是由多个字符串组成。
import java.io.UnsupportedEncodingException;
public class Hello {
public static void main(String[] args) throws UnsupportedEncodingException {
String v1 = "佛祖保佑";
String v2 = new String("佛祖保佑");
// char[] data = new char[]{'佛', '祖', '保', '佑'};
// char[] data = {'佛', '祖', '保', '佑'};
// String v3 = new String(data);
String v3 = new String(new char[]{'佛', '祖', '保', '佑'});
byte[] b_array = v3.getBytes();
System.out.println(b_array);
String v4 = new String(new byte[]{-28, -67, -101, -25, -91, -106, -28, -65, -99, -28, -67, -111});
String v5 = new String(new byte[]{-50, -28, -59, -26, -58, -21},"GBK");
// 上面都是不可变 "alex" + "测试" + "xx" + "xxxx"
// 可变字符串 "name=king"
StringBuilder sb = new StringBuilder();
sb.append("name");
sb.append("=");
sb.append("king");
sb.append("&");
sb.append("age=");
sb.append("999");
// String v6 = sb.toString(); //name=king&age=999
String v6 = new String(sb);
StringBuffer strBuffer = new StringBuffer(); // 线程安全(多线程)
strBuffer.append("name");
strBuffer.append("=");
strBuffer.append("king");
strBuffer.append("&");
strBuffer.append("age=");
strBuffer.append("999");
// String v6 = strBuffer.toString();
String v7 = new String(strBuffer);
// 其他类型的方法执行,直接返回一个字符串
String v8 = Integer.toString(123123);
String v9 = Double.toString(3.14);
// 或者
String v10 = String.valueOf(123);
}
}
对于字符串,内部提供了很多方便的方法对他进行操作,例如:
public class Hello {
public static void main(String[] args) {
String origin = "佛祖保佑,永不bug";
char v1 = origin.charAt(5); // 指定字符
int len = origin.length(); // 长度
for (int i = 0; i < len; i++) {
char item = origin.charAt(i);
}
String v2 = origin.trim(); // 去除空白
String v3 = origin.toLowerCase(); // 小写
String v4 = origin.toUpperCase(); // 大写
String[] v5 = origin.split("保佑"); // 分割
String v6 = origin.replace("bug", "success"); // 替换
String v7 = origin.substring(2, 6); // 子字符串
boolean v8 = origin.equals("佛祖保佑,永不bug");
boolean v9 = origin.contains("保佑");
boolean v10 = origin.startsWith("佛");
String v11 = origin.concat("哈哈哈");
}
}
关于 StringBuilder,在 Java 开发中经常会使用,用于对字符串进行拼接处理。
public class Hello {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append("name=");
sb.append("king");
sb.append("age=");
sb.append("18");
sb.append("&");
// 删除指定位置的字符
sb.deleteCharAt(sb.length()-1); // name=kingage=18
sb.insert(12,"&"); // name=king&age=18
String dataString = sb.toString();
System.out.println(dataString); //name=king&age=18
}
}
data = []
data.append("name")
data.append("=")
data.append("18")
data_string = "".join(data)
7.4 数组
数据,具有 相同数据类型且定长 的元素集合。【定长】。注意:数组一旦创建个数就不可调整。
import java.util.Arrays;
public class Hello {
public static void main(String[] args) {
int[] numArray = new int[3];
numArray[0] = 11;
numArray[1] = 22;
numArray[2] = 33;
System.out.println(Arrays.toString(numArray));
String[] names = new String[]{"one", "two", "three"};
System.out.println(Arrays.toString(names));
short[] number = {66, 99};
System.out.println(Arrays.toString(number));
for (int i = 0; i < names.length; i++) {
String item = names[i];
System.out.println(item);
}
for (String item : names) {
System.out.println(item);
}
}
}
public class Hello {
public static void main(String[] args) {
/*
多维数据
data=[
[11,22],
[33,44]
[55,66]
]
*/
int[][] numArray = new int[3][2];
numArray[0] = new int[]{11, 22};
numArray[1] = new int[]{33, 44};
numArray[2] = new int[]{55, 66};
}
}
混合类型 的 数组
- Python中每个类都默认继承Object类(所有的类都是Object的子类)。
- Java中所有的类都继承 Object,Object可以表示所有的类型。
用基类可以泛指他的子类的类型。所以,用 Object 可以实现混合类型的数组。
public class Hello {
public static void main(String[] args) {
// String v1 = "king";
Object v1 = new String("king");
System.out.println(v1);
System.out.println(v1.getClass());
Object v2 = 123;
System.out.println(v2);
System.out.println(v2.getClass());
// 声明数组,数组中元素必须int类型;
int[] v3 = new int[3];
// 声明数组,数组中元素必须String类型;
String[] v4 = new String[3];
// 声明数组,因为是 Object 类型数组, 所以数组元素可以是任何继承Object的类以及子类
Object[] v5 = new Object[3];
v5[0] = 123;
v5[1] = "king";
System.out.println(v5[0]);
System.out.println(v5[1]);
}
}
import java.util.Arrays;
public class Hello {
public static void func(Object v1) {
// System.out.println(v1);
// System.out.println(v1.getClass());
if (v1 instanceof Integer) {
System.out.println("整型");
} else if (v1 instanceof String) {
System.out.println("字符串类型");
} else {
System.out.println("未知类型");
}
}
public static void main(String[] args) {
// v1是指向字符串对象;String
String v1 = new String("king");
String res = v1.toUpperCase();
System.out.println(res);
// v2本质是字符串对象;Object
Object v2 = new String("king");
String data = (String)v2;
func(123);
func("123");
}
}
- List 系列,相当于 Python 的列表。
- Map 系列,相当于 Python 的字典。
7.5 List 系列
List 是一个接口,常见实现这个接口的有两个类,用于实现变长的数组。
- ArrayList,连续的内存地址的存储(内部自动扩容)。
- LinkedList,底层基于链表实现(自行车链条)。
Java中接口,是用来约束实现他的类,约束他里面的成员必须有xx。
Java 接口 示例:
interface List {
public void add(Object data); // 接口中的方法,不写具体的实现,只用于约束。
}
// 类ArrayList实现了接口List,此时这个类就必须有一个add方法。
class ArrayList implements List {
public void add(Object data) {
// 将数据data按照连续存储的方法放在内存。
// ..
}
}
// 类LinkedList实现了接口List,此时这个类就必须有一个add方法。
class LinkedList implements List {
public void add(Object data) {
// 将数据data按照链表的形式存储
// ..
}
}
ArrayList 示例:
import java.util.ArrayList;
import java.util.Arrays;
public class Hello {
public static void main(String[] args) {
// ArrayList<Object> data = new ArrayList<Object>(); //数组元素是 Object 类型
// ArrayList<String> data = new ArrayList<String>(); //这里只能是String类型
// ArrayList 默认内部存放的是Object数据类型。即混合数据类型
ArrayList data_array = new ArrayList();
data_array.add("king");
data_array.add("one");
data_array.add(666);
data_array.add("tony");
Object temp = data_array.get(1);
String value = (String) temp; // 转化可转换的数据
System.out.println(value);
int xo = (int) data_array.get(2);
System.out.println(xo);
data_array.set(0, "哈哈哈哈");
System.out.println(data_array);
data_array.remove("eric");
data_array.remove(0);
System.out.println(data_array);
int size = data_array.size();
System.out.println(size);
boolean exists = data_array.contains("king");
System.out.println(exists);
for (Object item : data_array) {
System.out.println(item);
}
for (int i = 0; i < data_array.size(); i++) {
Object item = data_array.get(i);
System.out.println(item);
}
}
}
LinkedList 示例:
import java.util.LinkedList;
public class Hello {
public static void main(String[] args) {
LinkedList<Integer> v1 = new LinkedList<Integer>();
v1.add(11);
v1.add(22);
LinkedList<Object> v2 = new LinkedList<Object>();
v2.add("有阪深雪");
v2.add("大桥未久");
v2.add(666);
v2.add(123);
//v2.remove(1);
//v2.remove("路飞");
v2.set(2, "苍老师");
v2.push("哈哈哈");
// v2.addFirst(11);
for (int i = 0; i < v2.size(); i++) {
Object item = v2.get(i);
System.out.println(item);
}
for (Object item : v2) {
System.out.println(item);
}
}
}
关于 迭代器
import java.util.*;
public class Hello {
public static void main(String[] args) {
ArrayList s1 = new ArrayList();
s1.add("P站");
s1.add("B站");
s1.add(100);
s1.add("A站");
Iterator it = s1.iterator(); // 迭代器
while (it.hasNext()) {
String item = it.next().toString();
System.out.println(item);
}
}
}
关于 foreach
import java.util.*;
public class Hello {
public static void main(String[] args) {
ArrayList s1 = new ArrayList();
s1.add("P站");
s1.add("B站");
s1.add(100);
s1.add("A站");
// JDK8
s1.forEach(item -> {
System.out.println(item);
});
}
}
List、ArrayList、LinkedList
ArrayList
是基于动态数组的实现,它允许程序员动态地增加和减少元素,提供了快速的随机访问功能。ArrayList
非常适合频繁地访问列表中的元素,但是在列表中间插入或删除元素时可能不太高效,因为这涉及到数组的复制和移动。LinkedList
是基于链表的实现,每个元素(节点)都包含了对下一个元素的引用(以及在双向链表中对前一个元素的引用)。LinkedList
适合于元素的动态插入和删除,尤其是在列表的头部或中间进行操作时,因为这不需要像数组那样移动大量的元素。但是,LinkedList
在随机访问元素时可能不如ArrayList
高效,因为它需要从头开始遍历链表直到找到所需的元素。List
是一个有序集合,可以包含重复的元素。它定义了可以在列表中所有常规操作(如添加、删除、获取等)的基础方法签名。ArrayList
和LinkedList
都实现了List
接口,这意味着它们都提供了List
定义的那套操作行为。使用List
接口的好处是你可以在不同的List
实现(如ArrayList
、LinkedList
等)之间进行切换,而不需要修改使用它们的代码的大部分。
示例:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class Hello {
public static void main(String[] args) {
List<Integer> v1 = new LinkedList<Integer>();
v1.add(11);
v1.add(22);
System.out.println(v1.toString());
List v2 = new ArrayList();
v2.add(11);
v2.add("哈哈哈");
System.out.println(v2.toString());
List<Integer> v3 = new ArrayList<Integer>();
v3.add(11);
v3.add(22);
System.out.println(v3.toString());
List<Integer> v4 = new LinkedList<Integer>() {
{
add(11);
add(22);
}
};
System.out.println(v4.toString());
}
}
7.6 Set 系列
Set 是一个接口,常见实现这个接口的有两个类,用于实现不重复的多元素集合。
- HashSet,去重,无序。
- TreeSet,去重,内部默认排序(ascii、unicode)【不同的数据类型,无法进行比较】。
import java.util.*;
public class Hello {
public static void main(String[] args) {
// Set s1 = new HashSet();
// HashSet<String> s1 = new HashSet<String>();
HashSet s1 = new HashSet();
s1.add("P站");
s1.add("B站");
s1.add("A站");
s1.add("P站");
s1.add(666);
System.out.println(s1); // [B站, A站, P站,666]
HashSet s2 = new HashSet(){
{
add("东京热");
add("东北热");
add("南京热");
}
};
System.out.println(s2); // [B站, A站, P站]
// Set s2 = new TreeSet();
// TreeSet<String> s2 = new TreeSet<String>();
TreeSet s3 = new TreeSet();
s3.add("P站");
s3.add("B站");
s3.add("A站");
s3.add("P站");
// s3.add(666); //不可以
System.out.println(s3); // [B站, A站, P站]
TreeSet s4 = new TreeSet(){
{
add("P站");
add("B站");
add("A站");
add("P站");
}
};
System.out.println(s4); // [B站, A站, P站]
}
}
关于交并差:
import java.util.*;
public class Hello {
public static void main(String[] args) {
// Set s1 = new HashSet();
HashSet s1 = new HashSet();
s1.add("P站");
s1.add("B站");
s1.add("A站");
s1.remove("P站");
System.out.println(s1); // [B站, A站, P站]
boolean exists = s1.contains("B站");
System.out.println(exists);
HashSet s2 = new HashSet();
s2.add(123);
s2.add(456);
HashSet v1 = new HashSet();
v1.addAll(s1);
v1.retainAll(s2); // 交集
System.out.println(v1);
HashSet v2 = new HashSet();
v2.addAll(s1);
v2.addAll(s2); // 并集
System.out.println(v2);
HashSet v3 = new HashSet();
v3.addAll(s1);
v3.removeAll(s2); // 差集 s1 - s2
System.out.println(v3);
HashSet v4 = new HashSet();
v4.addAll(s2);
v4.removeAll(s1); // 差集 s2 - s1
System.out.println(v4);
}
}
关于循环获取:
import java.util.*;
public class Hello {
public static void main(String[] args) {
TreeSet s1 = new TreeSet();
s1.add("P站");
s1.add("B站");
s1.add("A站");
for (Object item : s1) {
System.out.println(item);
}
// 不用这种循环
for(int i=0;i<s1.size();i++){
// Object item = s1[i] 不支持
// Object item = s1.get(i) 不支持
}
}
}
关于迭代器:
import java.util.*;
public class Hello {
public static void main(String[] args) {
TreeSet s1 = new TreeSet();
s1.add("P站");
s1.add("B站");
s1.add("A站");
Iterator it = s1.iterator();
while (it.hasNext()) {
String item = (String) it.next();
System.out.println(item);
}
}
}
关于 foreach
import java.util.*;
public class Hello {
public static void main(String[] args) {
TreeSet s1 = new TreeSet();
s1.add("P站");
s1.add("B站");
s1.add("A站");
s1.forEach(item -> {
System.out.println(item);
});
}
}
7.7 Map 系列
Map 是一个接口,常见实现这个接口的有两个类,用于存储键值对。
- HashMap,无序。
- TreeMap,默认根据 key 排序。(常用)
import java.util.*;
public class Hello {
public static void main(String[] args) {
TreeMap h1 = new TreeMap(); // 改为了TreeMap
h1.put("name","alex");
h1.put("age",18);
h1.put("hobby","男");
System.out.println(h1); // {age=18, hobby=男, name=alex}
TreeMap<String,String> h2 = new TreeMap<String,String>();
h2.put("name","alex");
h2.put("age","18");
h2.put("hobby","男");
System.out.println(h2); // {age=18, hobby=男, name=alex}
TreeMap<String,String> h3 = new TreeMap<String,String>(){
{
put("name","alex");
put("age","18");
put("hobby","男");
}
};
System.out.println(h3); // {age=18, hobby=男, name=alex}
Map h4 = new TreeMap();
h4.put("name","alex");
h4.put("age",18);
h4.put("hobby","男");
System.out.println(h4); // {age=18, hobby=男, name=alex}
}
}
循环 遍历 map:
- 使用
entrySet()
和增强的for循环:entrySet()
方法返回Map
中所有键值对的集合视图,每个元素都是Map.Entry
对象。然后可以通过增强的for循环遍历这些条目。
Map<String, Integer> map = new HashMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
- 使用
keySet()
和增强的for循环:keySet()
方法返回Map
中所有键的集合视图,可以通过增强的for循环遍历这些键,并通过键来获取对应的值。
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println("Key = " + key + ", Value = " + value);
}
- 使用
values()
和增强的for循环:如果只对Map
中的值感兴趣,可以使用values()
方法获取所有值的集合视图,然后通过增强的for循环遍历这些值。
for (Integer value : map.values()) {
System.out.println("Value = " + value);
}
- 使用
forEach()
方法:Java 8及之后引入了forEach()
方法提供了一种简洁的方式来遍历Map
。这个方法接收一个BiConsumer
函数,该函数对于每个键值对都会被调用。
map.forEach((key, value) -> System.out.println("Key = " + key + ", Value = " + value));
- 使用 Stream API:Java 8及之后引入了Stream API对
Map
进行遍历。如果想对Map
执行更复杂的操作,比如过滤,这个方法是非常有用的。
map.entrySet().stream()
.forEach(entry -> System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue()));
这些方法各有优势,可以根据具体的需求和场景选择最适合的一种。在Java 8之后,forEach()
和Stream API因其简洁和功能强大而变得特别受欢迎。
import java.util.*;
public class Hello {
public static void main(String[] args) {
TreeMap h1 = new TreeMap(); // 改为了TreeMap
h1.put("name", "alex");
h1.put("age", "18");
h1.put("hobby", "男");
h1.put("hobby", "女人");
h1.remove("age");
int size = h1.size();
Object value = h1.get("name"); // 不存在,返回null
System.out.println(value);
boolean existsKey = h1.containsKey("age");
boolean existsValue = h1.containsValue("alex");
h1.replace("name", "李杰");
System.out.println(h1);
// 循环: 示例1
Set<Map.Entry<String, String>> s1 = h1.entrySet();
Iterator it1 = s1.iterator();
while (it1.hasNext()) {
Map.Entry<String, String> entry = (Map.Entry<String, String>) it1.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
}
// 循环: 示例2
Set s2 = h1.entrySet();
Iterator it2 = s2.iterator();
while (it2.hasNext()) {
Map.Entry entry = (Map.Entry) it2.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
}
// 循环: 示例3
TreeMap<String, String> h2 = new TreeMap<String, String>(); // 改为了TreeMap
h2.put("name", "alex");
h2.put("age", "18");
for (Map.Entry<String, String> entry : h2.entrySet()) {
String k = entry.getKey();
String v = entry.getValue();
}
// 循环: 示例4
TreeMap h3 = new TreeMap(); // 改为了TreeMap
h3.put("name", "alex");
h3.put("age", 18);
for (Object entry : h3.entrySet()) {
Map.Entry<String, Object> entryMap = (Map.Entry<String, Object>) entry;
String k = entryMap.getKey();
Object v = entryMap.getValue();
if (v instanceof Integer) {
System.out.println("数字:" + Integer.toString((Integer) v));
} else if (v instanceof String) {
System.out.println("字符串:" + (String) v);
} else {
System.out.println("未知类型:" + v.toString());
}
}
// 循环: 示例5(JDK8)
TreeMap h4 = new TreeMap(); // 改为了TreeMap
h4.put("name", "alex");
h4.put("age", 18);
h4.forEach((k, v) -> {
System.out.print(k + ":" + v + " v的类型是:");
System.out.println(v.getClass());
});
}
}
8、面向对象
8.1 类和对象
class Person {
// 实例变量
public String name;
public Integer age;
public String email;
// 构造方法1
public Person() {
this.name = "Eric";
this.age = 99999;
}
// 构造方法2
public Person(String name, Integer age) {
this.name = name;
this.age = age;
this.email = "xxx@live.com";
}
// 构造方法3
public Person(String name, String email) {
this.name = name;
this.age = 83;
this.email = email;
}
// 定义函数(重载)
public void doSomething() {
System.out.println(this.name);
}
// 定义函数(重载)
public void doSomething(String prev) {
String text = String.format("%s-%s", prev, this.name);
System.out.println(text);
}
}
public class Hello {
public static void main(String[] args) {
// 实例化对象时,体现的主要是封装。
Person p1 = new Person();
Person p2 = new Person("alex", 73);
Person p3 = new Person("tony", "alex@sb.com");
p1.doSomething();
p1.doSomething("你好呀,");
p2.doSomething();
p2.doSomething("你好呀,");
p3.doSomething();
p3.doSomething("你好呀,");
}
}
静态成员
本质:静态属于类;非静态属于对象。
class Person {
// 静态变量
public static String city = "北京";
// 实例变量
public String name;
public Integer age;
// 构造方法1
public Person() {
this.name = "Eric";
this.age = 99999;
}
// 绑定方法
public void showInfo() {
System.out.println("哈哈哈哈");
}
// 静态方法
public static void showData() {
System.out.println("哈哈哈哈");
}
}
public class Hello {
public static void main(String[] args) {
System.out.println(Person.city);
Person.showData();
Person obj = new Person();
System.out.println(obj.name);
System.out.println(obj.age);
obj.showInfo();
}
}
8.2 继承、多态
Java 只支持 单继承,但是可以实现多个接口
Java 同时只能继承一个基类,但是可以继承(实现)多个接口,通过接口实现多态。
class Base {
public String email;
public Base(String email) {
this.email = email;
}
public String getSubInfo() {
return String.format("%s", this.email);
}
}
class Person extends Base {
public String name;
public Integer age;
public Person(String name, Integer age, String email) {
super(email);
this.name = name;
this.age = age;
}
public String getInfo() {
return String.format("%s-%d-%s", this.name, this.age, this.email);
}
public String getSubInfo() {
return String.format("%s-%s", this.name, this.email);
}
}
public class Hello {
public static void main(String[] args) {
Person p = new Person("king", 73, "king@live.com");
String text = p.getInfo();
System.out.println(text);
String subText = p.getSubInfo();
System.out.println(subText);
}
}
父类 指向 子类的对象。
class Base {
public String email;
// 构造方法
public Base(String email) {
this.email = email;
}
public String getInfo() {
return "哈哈哈哈";
}
// 方法getSubInfo
public String getSubInfo() {
return String.format("%s", this.email);
}
}
class Person extends Base {
public String name;
public Integer age;
public Person(String name, Integer age, String email) {
// 找父类中的 构造方法并执行 this=self
super(email);
this.name = name;
this.age = age;
}
public String getInfo() {
return String.format("%s-%d-%s", this.name, this.age, this.email);
}
public String getSubInfo() {
return String.format("%s-%s", this.name, this.email);
}
}
public class Hello {
public static void main(String[] args) {
// 1. 实例化Person对象 p = {email="king@live.com" name="king",age=73 }
Base p = new Person("king", 73, "king@live.com");
System.out.println(p.getClass()); // class Person
// 1.可以自己判断类型,然后再做其他的处理。
// 2.p可以强制类型转化弄成Person类型。
// 3.做了泛指之后,
String text = p.getSubInfo(); // 找到实际是那个类的对象,就去找当前对象对应类中的方法。
System.out.println(text); // king-king@live.com
String data = p.getInfo(); // king-73-king@live.com
System.out.println(data);
// 调用p的getInfo方法。
// String text = p.getInfo();
// System.out.println(text);
//
// // 调用p的getSubInfo
// String subText = p.getSubInfo();
// System.out.println(subText);
}
}
使用类的继承关系,也可以在 传参 或 指定类型时,可以用基类 泛指 所有继承他的子类(多态)。
import java.util.ArrayList;
class Base {
public String name;
public Integer age;
public String email;
public Base(String name, Integer age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public String getSubInfo() {
return String.format("%s", this.email);
}
}
class Person extends Base {
public Person(String name, Integer age, String email) {
super(name, age, email);
}
public String getInfo() {
return String.format("%s-%d-%s", this.name, this.age, this.email);
}
public String getSubInfo() {
return String.format("%s-%s", this.name, this.email);
}
}
public class Hello {
public static void action(Base obj) {
String info = obj.getSubInfo();
System.out.println(info);
}
public static void main(String[] args) {
Base[] v1 = {
new Base("one", 73, "one@live.com"),
new Base("two", 23, "two@live.com"),
new Person("three", 83, "three@live.com"),
new Person("four", 13, "four@live.com"),
};
ArrayList<Base> v2 = new ArrayList<Base>() {
{
add(new Base("one", 73, "one@live.com"));
add(new Base("two", 23, "two@live.com"));
add(new Person("three", 83, "three@live.com"));
add(new Person("four", 13, "four@live.com"));
}
};
for (Base item : v2) {
String info = item.getSubInfo();
System.out.println(info);
}
for (Base item : v2) {
String info = null;
if (item instanceof Person) {
info = ((Person) item).getInfo();
} else {
info = item.getSubInfo();
}
System.out.println(info);
}
Base p1 = new Base("one", 73, "one@live.com");
Person p2 = new Person("two", 83, "two@live.com");
Base p3 = new Person("three", 83, "three@live.com");
action(p1);
action(p2);
action(p3);
}
}
8.3 接口
Java 中继承接口一般不叫继承,叫实现接口
Java 中只有类叫继承
接口的作用:是为了实现多态。
- "约束" 实现他的类以及子类,都必须实现接口中声明的所有方法。
- "泛指" 实现他的类。
约束
interface IPerson {
public void f1();
public void f1(int age);
public void f2(String info);
}
interface IUser {
public String f3(int num);
}
class Base {
public String name;
public Integer age;
public String email;
public Base(String name, Integer age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public String getSubInfo() {
return String.format("%s", this.email);
}
}
class Person extends Base implements IUser, IPerson {
public Person(String name, Integer age, String email) {
super(name, age, email);
}
public String getInfo() {
return String.format("%s-%d-%s", this.name, this.age, this.email);
}
public String getSubInfo() {
return String.format("%s-%s", this.name, this.email);
}
public void f1() {
System.out.println("F1,无参数");
}
public void f1(int age) {
System.out.println("F1,age参数");
}
public void f2(String info) {
System.out.println("F2");
}
public String f3(int num) {
return "哈哈哈";
}
}
public class Hello {
public static void main(String[] args) {
Person p = new Person("one", 83, "one@live.com");
p.f1();
IPerson[] v1 = {
new Person("tow", 83, "tow@live.com"),
new Person("three", 13, "three@live.com"),
};
for (IPerson item : v1) {
item.f1();
// item.f3(); 错误
}
}
}
泛指一些类型
import java.util.ArrayList;
interface IMessage {
public void send();
}
class Wechat implements IMessage {
public void send() {
System.out.println("发送微信");
}
}
class DingDing implements IMessage {
public void send() {
System.out.println("发送钉钉");
}
}
class Sms implements IMessage {
public void send() {
System.out.println("发送短信");
}
}
public class Hello {
public static void action(IMessage obj) {
obj.send();
}
public static void main(String[] args) {
ArrayList<IMessage> objList = new ArrayList<IMessage>() {
{
add(new Wechat());
add(new DingDing());
add(new Sms());
}
};
objList.get(0).send();
objList.get(1).send();
objList.get(2).send();
System.out.println("********************************************");
IMessage[] v1 = {new Wechat(), new DingDing(), new Sms(), new Sms()};
for (IMessage item : v1) {
item.send();
}
for (IMessage item : v1) {
action(item);
}
}
}
支持 实现 多个接口
在Java中:不支持同时继承多个类;支持实现多个接口。
interface IPerson {
public void f1();
public void f1(int age);
public void f2(String info);
}
interface IUser {
public String f3(int num);
}
class Base {
public String name;
public Integer age;
public String email;
public Base(String name, Integer age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public String getSubInfo() {
return String.format("%s", this.email);
}
}
class Person extends Base implements IUser, IPerson {
public Person(String name, Integer age, String email) {
super(name, age, email);
}
public String getInfo() {
return String.format("%s-%d-%s", this.name, this.age, this.email);
}
public void f1() {
System.out.println("F1,无参数");
}
public void f1(int age) {
System.out.println("F1,age参数");
}
public void f2(String info) {
System.out.println("F2");
}
public String f3(int num) {
return "哈哈哈";
}
}
public class Hello {
public static void main(String[] args) {
Person p = new Person("日天", 83, "ritian@live.com");
p.f1();
}
}
场景:拿到 apk,关于关键字去搜索:f2 ,定位到一个接口了。接下来,你就应该去看都有哪些类 实现了 IPerson 接口。
- 只有1个类实现 IPerson。
- 多类类实现 IPerson 接口,筛选到底是那个类?
实现接口方法的 2 种方式
方式 1:通过类 implements interface
interface People{
void peopleList();
}
class Student implements People{
public void peopleList(){
System.out.println("I’m a student.");
}
}
class Teacher implements People{
public void peopleList(){
System.out.println("I’m a teacher.");
}
}
public class Example{
public static void main(String args[]){
People a; //声明接口变量
a=new Student(); //实例化,接口变量中存放对象的引用
a.peopleList(); //接口回调
a=new Teacher(); //实例化,接口变量中存放对象的引用
a.peopleList(); //接口回调
}
}
结果:
I’m a student.
I’m a teacher.
例子参考:https://zhidao.baidu.com/question/1923748969384870227.html
方法 2:new 一个 interface
interface MyInterface {
int mul(int a, int b);
int add(int a, int b)
}
new MyInterface(){
int mul(int a, int b){
return a*b;
}
int add(int a, int b){
return a+b;
}
}
new 一个接口,相当于定于一个类,这个类实现了接口中声明的方法
8.4 抽象
在 Java 开发中可以基于 抽象方法 & 抽象类实现。
// 抽象类
abstract class Base {
// 抽象方法(约束子类中必须有这个方法)
public abstract void play(String name);
// 普通方法
public void stop() {
System.out.println("Stop");
}
}
class Son extends Base {
public void play(String name) {
System.out.println("play");
}
}
public class Hello {
public static void main(String[] args) {
Son obj = new Son();
obj.play("hahha");
obj.stop();
}
}
也可以实现多态:
abstract class Base {
public abstract void play(String name);
public void stop() {
System.out.println("Stop");
}
}
class Son extends Base {
public void play(String name) {
System.out.println("play");
}
}
public class Hello {
public static void func(Base obj){
obj.play("hahha");
obj.stop();
}
public static void main(String[] args) {
Son obj = new Son();
func(obj);
}
}
8.5 包
src
├── Hello.java
└── utils
└── Helper.java
// hello.java
import utils.Helper;
public class Hello {
public static void main(String[] args) {
String data = Helper.getInfo();
System.out.println(data);
}
}
// helper.java
package utils;
public class Helper {
public static String getInfo() {
return "哈哈哈";
}
}
关于包中类的修饰符:
- public,公共(任何人都能调用包中的类)。
- default,只能在当前包中被调用。
类成员修饰符
- public,公共,所有的只要有权限访问类,类中的成员都可以访问到。
- private,私有,只允许自己类调用。
- protected,同一个包 或 子类可以访问(即使没有在同一个包内,也可以访问父类中的受保护成员)。
- default,只能在同一个包内访问。
Java "内部类" 和 "匿名内部类"
内部类
内部类是一个类中的一个类,相当于一个类进行了嵌套,就如同循环的嵌套一样。
内部类有一个特征:内部类当中可以调用外部类当中的属性和方法,而外部类却不能调用内部类当中的。除了这特征就没啥特征了:
class AAA {
int var1 = 12;
public void sayAAA() {
System.out.println("这是外部类当中的方法");
}
class BBB {
int var2 = 13;
public void sayBBB() {
System.out.println("这是内部类里面的方法");
System.out.println("使用内部类和外部类当中的数值进行想加的结果是" + (var1 + var2));
//之所以内部类可以使用外部类的属性是因为在创建对象的时候,
//已经给内部类的对象附加了一个外部类的对象,内部类的对象是建立在外部类对象的基础上的。
}
}
}
public class Hello {
public static void main(String[] args) {
System.out.println("下面是是内部类的程序展示");
//创建外部类和内部类的方法有点不相同
AAA a = new AAA();
// 这里开始创建内部类的对象,这是创建内部类对象的专用格式,
// 相当于在创建了一个外部类对象的基础上再创建一个内部类对象2
AAA.BBB b = new AAA().new BBB();
a.sayAAA();
b.sayBBB();
}
}
最终的 sayBBB() 方法输出结果是 25 =(13+12),从中可以证明内部类确实是可以调用外部类的属性的,但如果外部类调用内部类的属性则会发生报错。
匿名内部类
首先我们应该知道匿名内部类匿名是因为匿名内部类的具体名字不会被我们在程序当众编写出来,因为它已经在主方法当中被实例化了。
匿名内部类可以继承两类数据结构:
- 一:抽象类
- 二:接口。
比如我们的代码有:
abstract class Chouxiang {
String name = "Geek Song";//抽象类的属性是不会被调用的,除了方法
public void say3() {
System.out.println("这是抽象类当中的方法,抽象类当中是允许有具体方法来进行实现的,接口不行");
}
}
public class Hello {
public static void main(String[] args) {
Chouxiang c = new Chouxiang() {
String name = "Geek Song too";
public void say3() {
System.out.println("这是匿名内部类当中的方法,重写了抽象类的方法");
System.out.println(name);
}
};
}
}
这显然继承了一个抽象类,并且在主方法当中创建了抽象类的对象,本来我们是应该先继承这个抽象类再开始创建对象的,否则对象是无法创建的,但是为了简便,人们创建了了匿名内部类,允许我们在主方法当中进行抽象类的实例化,同时也可以进行对象的创建。这个程序就等同于如下的程序:
package com.company;
public class Innerclass {
public static void main(String[] args) {
System.out.println("下面是是内部类的程序展示");
//创建外部类和内部类的方法有点不相同
AAA a=new AAA();
AAA.BBB b=new AAA().new BBB();
a.say2();
b.sayit();
System.out.println("现在开始匿名内部类程序的编写\n");
Chouxiang2 c=new Chouxiang2();
c.say3();
}
}
class AAA
{
int waibu=12;
public void say2()
{
System.out.println("这是外部类当中的方法");
}
class BBB
{
int neibu=13;
public void sayit()
{
System.out.println("这是内部类里面的方法");
System.out.println("使用内部类和外部类当中的数值进行想加的结果是"+(neibu+waibu));
//之所以内部类可以使用外部类的属性是因为在创建对象的时候,
//已经给内部类的对象附加了一个外部类的对象,内部类的对象是建立在外部类对象的基础上的。
}
}
}
abstract class Chouxiang
{
String name="Geek Song";//抽象类的属性是不会被调用的,除了方法
public void say3()
{
System.out.println("这是抽象类当中的方法,抽象类当中是允许有具体方法来进行实现的,接口不行");
}
}
class Chouxiang2 extends Chouxiang
{
public void say3()
{
System.out.println("这是继承的方法");
}
}
因此这里就会涉及到多态和向上转型了,我们输出的子类的方法,父类的属性,匿名内部类也是相同的。输出的匿名内部类的方法,以及父类的属性。
完整的程序如下,方便大家进行知识点的理解:
package com.company;
public class Innerclass {
public static void main(String[] args) {
System.out.println("下面是是内部类的程序展示");
//创建外部类和内部类的方法有点不相同
A a = new A();
A.B b = new A().new B();
a.say2();
b.sayit();
System.out.println("现在开始匿名内部类程序的编写\n");
Chouxiang c = new Chouxiang() {
String name = "Geek Song too";
public void say3() {
System.out.println("这是匿名内部类当中的方法,重写了抽象类的方法");
System.out.println(name);
}
};//在使用匿名内部类的时候,当这个类在陈述完之后,是需要打分号的。
c.say3();
System.out.println("我们来看看这个name到底是抽象类当中的name还是匿名内部类当中的" + c.name);
//结果果然是父类当中的属性,和我们多态的性质相重合了
//这就是所谓的向上转型。现在我们再来试试接口的匿名内部类实现
Jiekou yui = new Jiekou() {
@Override//由于必须实现接口当中的方法,因此计算机就自动为我们写上了override的标识符了
public void say5() {
System.out.println("这是继承的接口当中的方法");
}
};
yui.say5();
}
}
class A {
int waibu = 12;
public void say2() {
System.out.println("这是外部类当中的方法");
}
class B {
int neibu = 13;
public void sayit() {
System.out.println("这是内部类里面的方法");
System.out.println("使用内部类和外部类当中的数值进行想加的结果是" + (neibu + waibu));
//之所以内部类可以使用外部类的属性是因为在创建对象的时候,
// 已经给内部类的对象附加了一个外部类的对象,
// 内部类的对象是建立在外部类对象的基础上的。
}
}
}
// 虽然内部类的程序已经成功了,但是匿名内部类的程序还没有成功,
// 现在我们来创建一个匿名内部类(在主方法当中,首先应该创建一个抽象类或者接口)
abstract class Chouxiang {
String name = "Geek Song";//抽象类的属性是不会被调用的,除了方法
public void say3() {
System.out.println("这是抽象类当中的方法,抽象类当中是允许有具体方法来进行实现的,接口不行");
}
}
interface Jiekou {
public void say5();
}
9、Java 高级特性
Java 高级特性:https://blog.csdn.net/w252064/article/details/79923999
[Java高级特性详解]:https://blog.csdn.net/qq_37977176/article/details/78941649
菜鸟教程 之 Java 教程:https://www.runoob.com/java/java-tutorial.html
内容主要有集合框架及泛型,实用类,输入和输出处理,注解与多线程,网络编程与XML技术
9.1 集合框架
集合框架 是一套性能优良、使用方便的接口和类(位于 java.util 包中)解决数组在存储上不能很好适应元素数量动态变化,查找效率低的缺陷
- 集合接口: Map、Collection(子接口List、Set) 、 Iterator
- 接口实现类:HashMap TreeMap 、ArrayList LinkedList、 HashSet TreeSet 实现map、list、set接口
- 集合工具类:Arrays 、Collections 提供对集合元素进行操作的算法
9.2 接口的区别
Collection 接口存储一组可重复,无序的对象(包括 List Set 接口)通用方法:
clear() 清除元素
isEmpty() 判断集合是否为空
iterator() 获得集合的迭代器
toArray() 集合转换为数组
List 接口存储一组可重复,有序的对象
Set 接口存储一组唯一,无序的对象
Map 接口存储一组键值对象,键是唯一的,Map和Set很像
9.3 接口实现类
ArrayList
在内存中分配连续的空间。根据下标遍历元素和随机访问元素的效率比较高,而增加和删除由于位置移动操作很慢
常用方法:
add(Objiect o) 在列表末尾顺序添加元素
get(int index) 返回指定索引位置处的元素
size() 返回列表中的元素个数
contains(Objiect o) 判断列表中是否存在指定元素
remove(Objiect o) 删除列表中的元素
LinkedList
采用链表存储方式。所有顺序查找的时候很慢,而插入、删除元素时无需移动位置,效率比较高常用方法:
addFirst(Objiect 0) 在列表首部添加元素
addLast(Objiect 0) 在列表尾部添加元素
getFirst() 获得当前集合的第一个元素
getLast() 获得当前集合的最后一个元素
removeFirst() 删除并返回列表中的第一个元素
removeFirst() 删除并返回列表中的最后一个元素
TreeSet | TreeMap 比较
底层是二叉树结构;
TreeMap、TreeSet都保存了对象的排列次序;
TreeSet只存储一个对象,而TreeMap存储两个对象Key和Value;
存储速度比Hash集合慢。
HashSet | HashMap 比较:
底层数据结构为哈希表;
HashMap存储键值对,键唯一,而HashSet仅仅存储对象,对象唯一;
HashMap使用唯一的键来获取对象,速度相对较快。
HashSet 集合方法
add(Objiect o) 添加对象
size() 返回元素个数
contains(Objiect o) 判断是否存在
remove(Objiect o) 移除有关对象
HashMap 集合方法
put(key,value) 添加键值对
get(key) 获取与key有关的值
remove(key) 移除与key有关的映射,并返回旧值
containsKey( ) containsValue( ) 判断是否存在key value
size() 返回元素个数
keySet() 获取所有key的集合
values() 获取所有values的集合
9.4 集合遍历
三种方法:普通 for 循环 增强 for 循环 Iterator 迭代器遍历
- 1. for (int i = 0 ;i<list.size();i++) { int j= (Integer) list.get(i); System.out.println(j); }
- 2. for (Object object : list) { System.out.println(object); }
- 3. Iterator iterator = list.iterator();while(iterator.hasNext()){ int i = (Integer) iterator.next(); System.out.println(i); }
Iterator方法:
- HasNext() 判断是否存在下一个可访问的元素,如果可以,返回true
- Next() 返回要访问的下一个元素
9.5 Collections工具类
作用:实现对元素的排序、查找和替换操作
如果要比较一个类的对象之间的大小,必须要实现Comparable接口。
Comparable接口:对实现它的每个类的对象进行自然排序。
comparableTo(Object obj)方法:用于比较此对象与指定对象的顺序
返回值:0等于、1大于、-1小于指定对象obj
方法:
fill( ) 替换集合中所有元素为相同元素的方法
sort( ) 对集合进行排序的方法
binarySearch( ) 对集合进行查找的方法
max( )\min( ) 查找最大值、最小值
9.6 泛型 集合
泛型即参数化类型,通过指定集合中的元素类型来实现约束
作用:将对象的类型作为参数,指定到其他类或者方法上,从而保证类型转换的安全性和稳定性
举例:List<Integer> list=new ArrayList<Integer>( );
ArrayList<Student> students = new ArrayList<Student>();
典型的泛型集合:ArrayList<E>、HashMap<K,V>
泛型类: public class User<T>{}
泛型接口:public interface Pair<T>{}
泛型方法: public <T> void getMiddle(T[] b) {} 注意<T>的位置
在泛型中,基本类型是不可以做泛型参数,只能使用包装类、引用数据类型。
9.7 实用的 类
Java API:Java应用程序的编程接口、Java帮助文档
实用类: 由Java API提供的常用类
学习这部分一定要多看 Java API 。Java 帮助文档提供的常用包如下:
lang包 :包含基础类和接口 如 Comparable接口 、包装类、 String、Math类
Util包 :包含系统辅助类 如 Collection、Map接口、 Date、Arrays类
Io包 :与输入输出有关类 如 Serializable接口、File、Reader、Writer类
Net包 :与网络有关类 如CookieStore接口 、Socket、URL、ServerSocket类
Sql包 :与数据库有关类 如 Statement接口、DriverManager、DriverPropertyInfo类
枚举
指由一组固定的常量组成的类型。使用enum关键字定义
举例:定义:public enum Genders{男,女} 调用:Genders.男
作用:类型安全、易于输入、代码清新
包装类
概念:把基本数据类型包装为对象,方便对象的操作,体现了java面向对象的特点。 ag:int→Integer char→Character byte→Byte
包装类作用:
- 方便在各种类型之间的转化 如:int类型和String类型互相转换
- 提供了基本数据类型的相关属性与方法 如:最小值、toString() 、valueOf()、equals()方法
常用方法:
toString() :将基本数据类型转换为字符串类型
valueOf() :静态的重载方法 将基本数据类型、字符串转换为包装类
parseInt()、parseBoolean() :把字符串转换为相应基本数据类型
类型转换
基本类型转包装类:Integer i=5;或 Integer i=new Integer(5);或Integer i=new Integer(“5”);或Integer i=Integer.valueOf(“5”);
注意:除Character包装类外,都可以将字符串作为参数来构造实例
- 包装类转基本类型:int intId=id.intvalue();或int intId=id;
- 自动转换: Integer i=5;//装箱 基本→包装 int j=i;//拆箱 包装→基本
jdk 1.5以后,基本类型和包装类的转换,编译器会自动完成
String 类
在Java中,字符串常被作为String类型的对象来处理。
创建 String 对象方法:String a = "hello" 或 String a = new String("hello"); 注意:第2个方法 ,创建了两个对象:一个 "hello" 字符串对象,在堆内存中;一个s对象,在栈内存中。
判断
equals() :判断两个字符串的内容是否相同
equalsIgnoreCase() :判断两个字符串的内容是否相同,不区分大小写
contains(String s) :判断一个字符串中是否包含另一个字符串
endsWith(String s) :测试此字符串是否以指定的后缀结束
startsWith(String s) :测试此字符串是否以指定的前缀开始
isEmpty() :测试字符串是否为空
获取
int length() :返回此字符串的长度
char charAt(int index) :返回指定索引处的char值(字符)
int indexOf() :返回指定字符(串)在此字符串中第一次出现处的索引
int lastIndexOf(int c) :返回指定字符在此字符串中最后一次出现的索引// 返回一个新字符串,它是此字符串的一个子字符串,包含头不包含尾。
String substring(int beginIndex, int endIndex)
转换
byte[] getBytes() :从字符串到字节数组的方法
char[] toCharArray() :从字符串到字符数组的方法
String valueOf(数据类型) :把该数据类型的数据转换成字符串
String toLowerCase() :把字符串转换成小写
String toUpperCase() :把字符串转换成大写
String concat(String str) :将指定字符串连接到此字符串的结尾
替换分割
String replace(char oldChar, char newChar) :用新字符替换旧字符
String[] split(String regex) :根据指定的字符串把一个字符串分割成一个字符串数组
String trim() :去除字符串的前后空格
int compareTo(String anotherString) :按字典顺序比较两个字符串
int compareToIgnoreCase(String str) :按字典顺序比较两个字符串,不考虑大小写
StringBuffer
相当于给字符串一个缓冲区,是String的增强类。对字符串频繁修改(如字符串连接)时,使用StringBuffer类可以大大提高程序执行效率。StringBuffer 声明:
StringBuffer strb = new StringBuffer();
StringBuffer strb = new StringBuffer("aaa");
常用方法:
增加:
append("**") :追加各种类型的数据到字符串之后
insert (1, "**") : 在容器指定位置插入各种类型的数据删除:
deleteCharAt() :删除指定位置的字符
delete() :清空StringBuffer的缓冲区替换:
replace() :用新字符替换旧字符
toString() :将StringBuffer类型的字符串转换为String类型的对象获取:
charAt() :返回指定索引处的char值(字符串)
length() :返回此字符串的长度
JDK5.0 后提供了 StringBuilder,等价 StringBuffer。但是是单线程的,效率相对较高,但是不保证线程安全。
Math 类
提供了常用的数学运算方法和两个静态常量E(自然对数的底数)和PI(圆周率)
常用方法:
abs() :返回绝对值;
max() :返回最大值;
random() :返回随机数
生成[0,10)区间的整数 int random = (int)(Math.random()*10);
Random 类
是产生随机数的一个类常用方法:
构造方法
Random() 创建一个新的随机数生成器。
Random(long seed) 使用单个种子创建一个新的随机数生成器。
注意:种子数只是随机算法的起源数字,和生成的随机数字的区间无关
ag:Random rand = new Random(10);
成员方法
int nextInt() 返回下一个伪随机数,它是此随机数生成器的序列中均匀分布的 int 值。
int nextInt(int n) 返回一个伪随机数,该值介于[0,n)的区间。
ag:生成[0,10)区间的整数 int num = rand.nextInt(10);
Scanner 类
位于java.util包,是获取从键盘的输入数据的一个类
构造方法
Scanner(InputStream source) 创建一个用来解析基本类型和字符串的文本扫描器
ag:Scanner sc = new Scanner(System.in);
成员方法
hasNext() 判断扫描器中当前扫描位置后是否还存在下一段。
hasNextLine() 如果在此扫描器的输入中存在另一行,则返回 true。
nextInt() 将输入信息的下一个标记扫描为一个int,接受整型变量。
next() 以换行或空格符为分界线接收下一个String类型变量。如:输入hello world!,接收到的只是hello
nextLine() 以换行为分界线接收下一个String类型变量。如:输入hello world!,接收到的是hello word!
Date 类
位于java.util包,表示日期和时间的类
构造方法
Date() 分配Date对象并初始化此对象,以表示分配它的时间(精确到毫秒)。
Date(long date) 分配Date对象并初始化此对象,以表示从标准基准时间(即1970年1月1日00:00:00GMT)以来的指定毫秒数。
成员方法
int compareTo(Date anotherDate) 比较两个日期的顺序
boolean equals(Object obj) 比较两个日期的相等性。
SimpleDateFormat类
位于java.text包,格式化和解析日期的具体类。固定写法:
//创建日期对象 Date date = new Date();
//定制日期格式 SimpleDateFormat f= new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
String now = f.format(date); System.out.println(now);
Calendar类
位于java.util包,用于设置和获取日期/时间数据的特定部分
int get(int field) 返回给定日历字段的值
YEAR 指示年 MONTH 指示月
DAY_OF_MONTH 指示一个月中的某天
DAY_OF_WEEK 指示一个星期中的某天
输入 / 输出、文件操作
File 类
位于java.io包,用来操作文件目录和属性
构造方法:
File(String pathname) 指定文件路径
File(String dir,String subpath) dir参数指定目录路径,subpath参数指定文件名
File(File parent,String subpath) parent参数指定目录文件,subpath参数指定文件名
常用方法:
创建:
boolean createNewFile( ) 创建名称的空文件,不创建文件夹
boolean mkdir() 创建由该File对象表示的目录(一级文件夹)
boolean mkdirs() 创建包括父目录的目录(二级文件夹)判断:
boolean exists( ) 判断文件或目录是否存在
boolean isFile( ) 判断是否是文件
boolean isDirectory( ) 判断是否是目录获取:
String getPath( ) 返回此对象表示的文件的相对路径名
String getAbsolutePath( ) 返回此对象表示的文件的绝对路径名
String getName( ) 返回此对象表示的文件或目录的名称
String getParent() 返回此对象父目录的路径名;
long length() 返回文件的长度,单位为字节, 如果文件不存在,则返回0L删除:
boolean delete( ) 删除此对象指定的文件或目录
相对路径与绝对路径
相对路径:从中间目录出发,到目前位置路径。
绝对路径:从根目录出发,到目前位置的路径。
Java IO 流
指二进制的字节序列,是一连串流动的字符,是以先进先出方式发送信息的通道
分类:
- (1) 按照流操作的数据类型分为:字节流和字符流。字节流是8 位通用字节流,字符流是16位Unicode字符流
- (2) 按照流的流向分为:输入流,输出流(相对计算机程序而言,先入后出、先读后写)。源数据源(键盘、硬盘)→输入流(读)→程序→输出流(写)→目标数据源(控制台)
IO 流 常用 基类
注意:( )里面是子类 如File**类,Buffered**类
Buffered**类带有缓冲区,有按行读取内容的readLine()方法
字节流
字节输入流:InputStream (FileInputStream、BufferedInputStream)
字节输出流:OutputStream (FileOutputStream、BufferedOutStream)
字符流
字符输入流:Reader (FileReader、BufferedReader)
字符输出流:Writer (FileWriter、BufferedWriter)
常用基类的方法
字节输入流InputStream类方法
void close() 关闭此输入流并释放与该流关联的所有系统资源
int read() 读取一个字节数据
int read(byte[] b) 读取一定数量的字节,并将其存储数组中
int read(byte[] b, int off, int len) 将输入流中最多 len 个数据字节,保存到字节数组b中
字节输出流OutputStream类方法
void close() 关闭此输出流并释放与此流有关的所有系统资源
write(int b) 写入一个字节数据
void write(byte[] b) 写入数组b的所有字节
void write(byte[] b, int off, int len) 将字节数组中从偏移量 off 开始的 len 个字节写入到输出流
字符输入流Reader类方法
void close() 关闭输入流
int read() 读取单个字符
int read(char[] c) 将c.length长度的字符读入数组c中
int read(char[] c, int off, int len) 将最多len长度的字符读入数组c,保存位置从off位置开始
字符输出流Writer类方法
void close() 关闭输出流
void flush() 刷新输出流
int read() 读取单个字符。
int read(char[] cbuf) 将字符读入数组
int read(char[] cbuf, int off, int len) 将字符读入数组的某一部分
节点流、包装流
节点流:创建对象时,参数是字符串或File类对象
包装流:创建对象时,参数是流对象。
包装的作用:1.提高效率 2.方便书写代码
使用字节流读写文本文件
使用 FileInputStream 读文本文件
//构造字节输入流对象
FileInputStream fis= new FileInputStream(“c:\\test.txt”);
//循环读取文件数据 最后关闭流对象fis.close();
System.out.println(“可读取的字节数”+fis.available());
byte []buf = new byte[1024]; int len=0;
while((len=fis.read(buf))>0){ System.out.write(buf, 0, len); }
使用FileOutputStream 写文本文件
//构造字节输入流对象
FileOutputStream fos=new FileOutputStream(“f:/java.txt”);
//把数据写入文本文件 最后关闭流对象fos.close();
int num=12345;String s=String.valueOf(num);
fos.write(s.getBytes(), 0, s.getBytes().length);
使用字符流读写文本文件
使用FileReader/BufferedReader读取文件
//创建FileReader/BufferedReader对象
Reader fr= new FileReader(“D:\\myDoc\\简介.txt”);//节点流
BufferedReader br=new BufferedReader(fr); //包装流
//调用readLine()方法读取文本文件的数据 最后关闭流对象
String s=null; while((s=br.readLine())!=null){…}
使用FileWriter/BufferedWriter写文件
//创建FileWriter/BufferedWriter对象
FileWriter fw= new FileWriter(“D:\\myDoc\\简介.txt”);
BufferedWriter bw=new BufferedWriter(fw);
//调用write()方法写文本文件的数据 最后关闭流对象
fw.write(); fw.close();
解决读取时中文乱码
//使用InputStreamReader并设置编码格式
InputStreamReader fr=new InputStreamReader(fis,"UTF-8");//以字节数组的形式读取
byte []buf = new byte[1024]; fis.read(buf)
读写二进制文件
使用FileInputStream/DataInputStream读取二进制文件
//构造数据输入对象
FileInputStream fis=new FileInputStream(“C:\\HelloWorld.class”);
DataInputStream dis=new DataInputStream(fis);
//调用read()方法读取
dis.readInt(); dis.close();
使用FileOutputStream/DataOutputStream写二进制文件
//构造数据输出对象
FileOutputStream outFile=newFileOutputStream(“C:\\temp.txt”);
DataOutputStream out=new DataOutputStream(outFile);//调用write()方法写入
out.write(); out.close();
序列化、反序列化
序列化:将对象的状态写入到特定的流中的过程。对象 —> 流
反序列化:从特定的流中获取数据重新构建对象的过程。流 —> 对象
作用:Java对象序列化后,得到的二进制字节序列可以方便的保存到磁盘或者云上。二进制序列可以方便地跨平台传输,不用担心因平台问题而显示异常。
实现步骤:
- 1、实现Serializable接口
- 2、创建对象输出流ObjectOutputStream(序列化)/输入流ObjectInputStrean(反序列化)
- 3、调用writeObject()/readObject ()方法将对象写入文件(序列化)/读取对象(反序列化)
- 4、关闭对象输入流
注意:使用transient关键字修饰对象的某些属性时,这些属性将不再被序列化
java 反射
反射:指java程序能自描述和自控制,它允许程序在运行时才加载、探知、使用编译期间完全未知的类
反射机制:指在运行状态中,动态获取类信息以及动态调用对象方法的功能
反射常用API:
- Class类 — 可获取类和类的成员信息
- Field类 — 可访问类的属性
- Method类 — 可调用类的方法
- Constructor类 — 可调用类的构造方法
使用反射的步骤:
- 1、导入java.lang.reflect.*;
- 2、获取需要操作类的Class对象
- 3、调用Class的方法获取Field、Method等对象
- 4、使用反射API进行操作
反射的应用:
- 获取Class对象:getClass()方法 Class.forName()方法 .class 方法
- 创建Class对象:newInstance()方法
- 访问类的属性:getXxx()方法 setXxx()方法
- 访问类的方法:getMethod()方法 invoke()方法
注解
Java代码里的特殊标记。它为在代码中添加用Java程序无法表达的额外信息提供了一种形式化的方法。注解可以看成修饰符,修饰程序元素。
注解可以在编译、类加载、运行时被读取。而注释不会被程序所读取。
(1) 内建注解:标准注解类型;
@Overrid 限定重写父类方法
@Deprecated 标示已过时
@SuppressWarnings 抑制编译器警告
(2) 元注解: 修饰其他的注解定义
@Target 指定被其修饰的注解能用于修饰哪些程序元素
@Retention 指定该注解可使用反射读取
@Documented 指定该注解将被JavaDoc工具提取成文档
@Inherited 指定被其修饰的注解将具有继承性
(3)自定义注解: 注解类型是一种接口
使用关键字@interface定义新注解,如:public @interface AnnotationTest{}
读取注解信息
AnnotatedElement接口是所有程序元素的父接口,指定了程序中可以接受注解的程序元素。通过反射获取对象信息。
getAnnotation()方法:返回该程序元素上存在的、指定类型的注解
getAnnotations()方法:返回该程序元素上存在的所有注解
进程、线程
- 程序:是对数据描述与操作的代码的集合。
- 进程:指程序的一次动态执行过程。是系统运行程序的基本单位,有独立的内存空间和系统资源
- 线程:指进程中的一个执行流程。是进程中执行运算的最小单位,真正在处理机上运行的是线程,一个进程中至少要有一个线程。
线程创建与启动:
(1)继承 java.lang.Thread类 如:class MyThread extends Thread{}
(2)实现 java.lang.Runnable接口 如:class MyThread implements Runnable{}
都需要重写 run()方法,调用 start() 方法
MyThread myThread = new MyThread(); new Thread(myThread).start();
线程状态
↙阻塞|睡眠状态↘
新生状态—>可运行状态<—>运行状态—>死亡状态
新生状态:线程对象已经创建,还没有在其上调用start()方法。
可运行状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。
运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态
等待/阻塞/睡眠状态:是线程有资格运行、只是没有条件使线程苏醒所处的状态。
死亡状态:当线程的run()方法完成时就认为线程死去
线程调度
多个线程处于可运行状态,线程调度会根据优先级来决定线程进入可运行状态的次序。
线程的优先级用1~10 表示,10的优先级最高,默认值是5
设置优先级:setPriority(int grade) 如:myThread.setPriority(3);
调度方法:
join() :将指定的线程加入到当前线程。先执行完调用该方法的线程再继续执行本线程
sleep() :当前线程在指定毫秒内停止执行而转入不可运行状态
yield() :当前线程转入暂时停止运行的状态
线程同步
当两个或多个线程需要访问同一资源时,需要以某种顺序来确保该资源某一时刻只能被一个线程使用。同步就相当于上锁,上了锁的线程首先访问资源,其他线程等待。
实现线程同步:
同步方法: 用 synchronized关键字修饰的方法
public synchronized void save(){}
同步代码块: 用synchronized关键字修饰的代码块
synchronized(object){}
注意:多线程使用同步存在”死锁”的潜在危险。
死锁:如果多个线程都处于等待状态而无法被唤醒,就构成了死锁。比如同步方法里面有sleep()方法,那么这个锁就成了死锁。
线程通信
线程同步可以阻止并发访问同一资源,但不能实现不同线程之间的消息传递。所以需要用到线程通信。注意下面方法只能在同步方法或同步代码块中使用
wait()方法 :挂起当前线程,并释放共享资源的锁
notify()方法:唤醒线程。在因调用该对象的wait()方法而阻塞的线程中
随机选择一个解除阻塞,但要等到获得锁后才可执行
notifyAll()方法:将因调用该对象的wait()方法而阻塞的所有线程一次性全部解除阻塞
网络编程
1、基本概念
网络:是信息传输、接收、共享的虚拟平台,把各个点、面、体的信息联系到一起,从而实现资源共享
网络编程:通过使用套接字来达到进程间通信目的的编程
2、IP地址(Internet Protocol)
概念:唯一标识网络上的每一台计算机
IP组成:32位,由4个8位二进制数组成(ipv4)
11000000.10101000.00000001.11001000–>192.168.1.200
IP地址=网络地址+主机地址
3、IP检测
查看IP地址:cmd—ipconfig
检测网络是否通畅:ping IP地址
4、DNS
域名解析器,把IP地址映射到域名。实现网站通过域名访问
5、网络服务器
指在网络环境下,具有较高计算能力,能够提供用户服务功能的计算机(邮件服务器;web服务器 如Apache Tomcat 阿里云)
客户机→服务器 (Client/Server) ( c/s)
浏览器→服务器 (Browser/Server)( b/s)
6、网络通信协议
为了在网络中不同的计算机之间进行通信而建立的规则、标准或约定的集合
应用层 HTTP FTP TFTP SMTP SNMP DNS协议
传输层 TCP UDP 协议
网络层 ICMP IGMP IP ARP RARP 协议
数据链路层和物理层 由底层网络定义的协议
7、Socket编程
- Socket(套接字):是通信链路的端点。也是Java提供的接口。因为Socket的底层机制复杂,所以Java提供了API方便我们使用Socket编程
- Socket通信模型:进行网络通信时,Socket需要借助数据流来完成数据的传递工作
- 流式套接字:基于TCP协议的Socket网络编程
1、客户端Socket 类
//创建一个客户端Socket
Socket socket = new Socket("localhost",端口参数)//通过输出流,发送请求 getOutputStream( ) write()
OutputStream os=Socket.get OutputStream( );
byte[] infos=info.getBytes();
os.write(infos);//关闭输出流
socket.shutdownOutput();// 通过输入流,接收服务端响应
Inputstream is = socket.getInputStream();//释放资源
2、服务器端ServerSocket类
//创建一个服务器Socket
ServerSocket serverSocket=new ServerSocket(5000)//使用accept()方法等待客户的通信
Socket socket=serverSocket.accpet();//获得输入流,获得客户端请求
InputStream is=socket.getInputStream();//把获得的字节流包装成字符流
BufferedReader br=new BufferedReader(new IputStreamReader(is));//通过输出流,发送响应
OutputStream os = socket.getOutputStream(); Os.write(replys);//释放相应资源
- 数据包式套接字:基于UDP协议的Socket网络编程
① 利用DatagramPacket对象封装数据包
② 利用DatagramSocket发送数据包(send())
③ 利用DatagramSocket接收数据包(receive())
④ 利用DatagramPacket处理数据包
- TCP 与 UDP 的区别
TCP | UDP | |
是否连接 | 面向连接 | 面向非连接 |
传输可靠性 | 安全可靠 | 不可靠 |
速度 | 慢 | 快 |
XML 技术
1、XML简介
XML(Extensibel Markup Language):即可扩展标记语言,是一种简单的数据存储语言,使用一些列简单的标记描述数据。
特点:与操作系统、开发平台无关;规范统一
作用:数据交互;配置应用程序和网站;Ajax基石
2、XML基本结构
(1)XML声明。如:<?xml version=”1.0” encoding=”UTF-8”?>
(2)唯一的根元素。如:<books> </books>
(3)元素描述信息。 如<book id=”1”><title>java编程思想</title></book>
3、XML标签
<元素名 属性名 = “属性值”>元素内容</元素名>
如:<id name=”id” column=”id”> <generator class=”sequence”/> </id>
注意:属性值用双引号包裹。有多个属性用空格隔开
4、XML转义符
< 对应转移符< > 对应转移符 >
”对应转移符" ’对应转移符'
& 对应转移符&
当元素中出现很多特殊字符时,可以使用CDATA节 :<![CDATA[from Student where sid=?]]>
5、XML解析器
非验证解析器:检查文档格式是否良好 (eclipse自带)
验证解析器: 使用DTD(文档类型定义)或Schema检查文档的有效性
6、XML命名空间
写法:< xmlns:name=”url”>
举例:xmlns:canon=”http://www.canon” –XML命名空间
xmlns=”http://www.Aptech_edu.ac” —属性命名空间
7、解析XML技术
DOM(文档对象模型):把XML文档映射成一个倒挂的树
- DOM:基于XML文档树结构的解析。适用于多次访问的XML文档。特点:比较消耗资源
步骤:
1.创建解析器工厂对象
DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance( );2.解析器工厂对象创建解析器对象
DocumentBuilder db = dbf.newDocumentBuilder( );3.解析器对象指定XML文件创建Document对象
Document document = db.parse(“要解析的路径”)4.以Document对象为起点操作DOM树
NodeList dogList= document.getElmentsByTagName(“节点”)
DOM 接口方法
Document接口:getElementById()getElementsByTagName() getElementsByName()等方法
Node接口:
getparentNode() getchildNodes() getfirstChild() getlastChild()
getnextSibling () getpreviousSibling() createTextNode( ) removeChild()等方法
Element接口:
getTagName() createElement( )等方法
- SAX:基于事件的解析。适用于大数据量的XML文档。特点:占用资源少,内存消耗小
- DOM4J:非常优秀的Java XML API。性能优异、功能强大。特点:开放源代码
DOM4J解析XML方法
//创建SAXReader对象 SAXReader reader = new SAXReader();
//获取XML文档对象 Document document = reader.read("xml/users.xml");
//获取root(根)节点 Element root = document.getRootElement();
//获取节点下的所有子节点集合 List<Element> users=root.elements();
//遍历输出 for(Element user:users){…}
DOM4J创建XML方法
//创建了一个xml文档
Document document=DocumentHelper.createDocument();//创建一个根节点 和一个子节点 并添加属性
Element root=document.addElement("users");
Element user = root.addElement("user").addAttribute("id", "1");//设置子节点的文本
user.addElement("username").addText("zs");
user.addElement("password").addText("111");//创建输出流对象
File file = new File("src/xml/users.xml");
FileOutputStream out = new FileOutputStream(file);//创建一个XMLWriter的对象 调用write方法写入
writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
writer.write(document);
相关代码
package com.hfxt.demo02;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* 1.输入:***InputStream(***Reader)/输出:***OutputStream(***Writer)
*
* 2.字节流:***Stream/字符流:***Reader(***Writer)
*
* 3.节点流:创建对象时,参数是字符串或File类对象
* 包装流(装饰流):创建对象时,参数是流对象,包装的作用:1.提高效率 2.方便书写代码
*/
public class Demo01 {
public static void main(String[] args) {
File file = new File("f:/java/User.java");
FileInputStream fis=null;
try {
fis=new FileInputStream(file);
/* int data;
while((data=fis.read())!=-1){
System.out.print((char)data);
}*/
byte []buf = new byte[1024];
int len=0;
while((len=fis.read(buf))>0){
//System.out.write(buf);
System.out.write(buf, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.hfxt.demo02;
import java.io.*;
//二进制文件的读写
public class ReadAndWriteBinaryFile {
public static void main(String[] args){
DataInputStream dis=null;
DataOutputStream dos=null;
FileInputStream fis=null;
FileOutputStream fos=null;
try {
//创建输入流对象
fis=new FileInputStream("c:\\myDoc\\star.jpg");
dis=new DataInputStream(fis);
//创建输出流对象
fos=new FileOutputStream("c:\\myDoc\\new.jpg");
dos=new DataOutputStream(fos);
//读取文件并写入文件
int temp;
while((temp=dis.read())!=-1){
dos.write(temp);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(dis!=null){
dis.close();
}
if(dos!=null){
dos.close();
}
if(fis!=null){
fis.close();
}
if(fos!=null){
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.hfxt.demo;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Demo08 {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
User user = new User(1, "zs", "111", "zs");
//序列化
ObjectOutputStream oos = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream(new File("f:/wkjava/user.txt"))));
oos.writeObject(user);
if(oos!=null) oos.close();
//反序列化
ObjectInputStream ois = new ObjectInputStream(
new BufferedInputStream(new FileInputStream(new File("f:/wkjava/user.txt"))));
User user2=(User) ois.readObject();
System.out.println(user2);
}
}
package com.hfxt.demo;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo05 {
/*
* 用字符流复制文本文件
*/
public static void main(String[] args) {
//创建文本文件对象和流对象
File file1 = new File("f:/wkjava/a.jpg");
File file2 = new File("f:/wkjava/3.jpg");
FileOutputStream fos = null;
FileInputStream fis = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//将文件对象放入流对象中
fis = new FileInputStream(file1);
fos = new FileOutputStream(file2);
bis=new BufferedInputStream(fis);
bos=new BufferedOutputStream(fos);
//创建数组
byte []buf=new byte[1024];
int len=0;
//复制操作:先读后写
while((len=bis.read(buf))>0){
bos.write(buf, 0, len);
}
//清缓冲区
//bos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally { //关闭流
if(bis!=null)
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
if(bos!=null)
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.hfxt.demo;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
public class ReadXml {
public static void main(String[] args) {
//创建SAXReader对象
SAXReader reader = new SAXReader();
try {
//读取文件
Document document = reader.read("xml/users.xml");
//获取root(根)节点
Element root = document.getRootElement();//获得根元素
/*System.out.println(root.getName());
System.out.println("*******************");
System.out.println(root.getText());
System.out.println("*******************");*/
//获取节点下的所有子节点集合
List<Element> users=root.elements();
//遍历
for(Element user:users){
System.out.println(user.attributeValue("id"));
System.out.println(user.elementText("username"));
System.out.println(user.elementText("password"));
System.out.println(user.elementText("nickname"));
}
} catch (DocumentException e) {
e.printStackTrace();
}
}
}
package com.hfxt.demo;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
public class WriteXml {
public static void main(String[] args) throws Exception {
XMLWriter writer=null;
//创建文档:使用了一个Helper类
Document document=DocumentHelper.createDocument();
//添加属性 添加子节点
Element root=document.addElement("users");
Element user = root.addElement("user").addAttribute("id", "1");
user.addElement("username").addText("zs");
user.addElement("password").addText("111");
user = root.addElement("user").addAttribute("id", "2");
user.addElement("username").addText("ls");
user.addElement("password").addText("222");
String path="src/xml/users.xml";
File file = new File(path);
if(!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
FileOutputStream out = new FileOutputStream(file);
//writer=new XMLWriter(out);
writer=new XMLWriter(out, OutputFormat.createPrettyPrint());
writer.write(document);
}
}
10、逆向常见
接口中的方法
根据调用关系,最终找到接口中的方法了。
interface IRequest {
public void send(String url);
}
class Context {
public IRequest req;
public Context(IRequest req) {
this.req = req;
}
public void doRequest() {
String url = "/click/android2/";
this.req.send(url);
}
}
public class Hello {
public static void main(String[] args) {
}
}
- 思路1:寻找实现了 IRequest 接口的类。
- 思路2:谁传入的req?找他的调用栈。
隐藏的字节
public class Hello {
public static void main(String[] args) {
String salt = "佛祖保佑";
System.out.println(salt);
byte[] b_array = salt.getBytes();
for (byte b : b_array) {
System.out.print(b + ",");
}
System.out.println();
// 好像中文的字节数组,里面元素全是 负数
String v4 = new String(new byte[]{-28,-67,-101,-25,-91,-106,-28,-65,-99,-28,-67,-111});
System.out.println(v4);
}
}
# Python脚本,方便以后使用。
byte_list = [-28, -67, -101, -25, -91, -106, -28, -65, -99, -28, -67, -111]
bs = bytearray() # python字节数组
for item in byte_list:
if item < 0:
item = item + 256
bs.append(item)
str_data = bs.decode('utf-8') # data = bytes(bs)
print(str_data)
data_1 = "张三懵逼了"
data_bytes = data_1.encode('utf-8')
data_list = []
for item in data_bytes:
data_list.append(item)
print(data_list)
# [229, 188, 160, 228, 184, 137, 230, 135, 181, 233, 128, 188, 228, 186, 134]
data_2 = "张三懵逼了"
data_bytes = data_2.encode('utf-8')
data_list = bytearray()
for item in data_bytes:
data_list.append(item)
print(data_list)
res = data_list.decode('utf-8')
print(res)
# bytearray(b'\xe5\xbc\xa0\xe4\xb8\x89\xe6\x87\xb5\xe9\x80\xbc\xe4\xba\x86')
提醒:常用在 MD5 加密、加盐、AES 加密 key、iv;
UUID
理论上永远不会重复的值(网卡、mac、当前时间、...)。
import java.util.UUID;
public class Hello {
public static void main(String[] args){
String uid = UUID.randomUUID().toString();
System.out.println(uid);
}
}
抖音 udid
import java.util.UUID;
public class Hello {
public static void main(String[] args){
String uid = UUID.randomUUID().toString();
System.out.println(uid);
}
}
import uuid
uid = str(uuid.uuid4())
print(uid)
随机值
import java.math.BigInteger;
import java.security.SecureRandom;
public class Hello {
public static void main(String[] args) {
// 随机生成80位,10个字节
BigInteger v4 = new BigInteger(80, new SecureRandom());
// 让字节以16进制展示
String res = v4.toString(16);
System.out.println(res);
}
}
import random
open_udid = "".join([hex(i)[2:] for i in random.randbytes(10)])
print(open_udid)
抖音:openudid
import java.math.BigInteger;
import java.security.SecureRandom;
public class Hello {
public static void main(String[] args) {
// 随机生成80位,10个字节
BigInteger v4 = new BigInteger(80, new SecureRandom());
// 让字节以16进制展示
String res = v4.toString(16);
System.out.println(res);
}
}
import random
data = random.randbytes(10)
ele_list = []
for item in data:
ele = hex(item)[2:]
ele_list.append(ele)
res = "".join(ele_list)
print(res)
open_udid = "".join([hex(i)[2:] for i in random.randbytes(10)])
print(open_udid)
open_udid = "".join(["%x" % i for i in random.randbytes(10)])
print(open_udid)
长度 补足 两位
v1 = 8
# 长度补足两位
print(hex(v1)[2:].rjust(2, "0"))
# 长度补足两位
print("%02x" % v1)
时间戳
public class Hello {
public static void main(String[] args) {
String t1 = String.valueOf(System.currentTimeMillis() / 1000);
String t2 = String.valueOf(System.currentTimeMillis());
System.out.println(t1);
System.out.println(t2);
}
}
抖音:_ticket
public class Hello {
public static void main(String[] args) {
String t1 = String.valueOf(System.currentTimeMillis() / 1000);
String t2 = String.valueOf(System.currentTimeMillis());
System.out.println(t1);
System.out.println(t2);
}
}
十六进制字符串
在Java中字节是有符号:-128 ~ 127
# name_bytes = "king".encode('utf-8')
name_bytes = [10, -26, -83, -90, -26, -78, -101, -23, -67, -112]
data_list = []
for item in name_bytes:
item = item & 0xff # item<0时,让item+256
ele = "%02x" % item
data_list.append(ele)
print("".join(data_list))
import java.util.Arrays;
public class Hello {
public static void main(String[] args) {
String name = "\nking";
byte[] nameBytes =name.getBytes();
// [10, -26, -83, -90, -26, -78, -101, -23, -67, -112]
// 0a e6 ad a6
System.out.println(Arrays.toString(nameBytes));
StringBuilder sb = new StringBuilder();
for(int i=0;i<nameBytes.length;i++){
int val = nameBytes[i] & 255; // 负数转换为正数(byte,正数、负数)
if (val<16){
sb.append("0");
}
sb.append(Integer.toHexString(val));
}
String res = sb.toString();
System.out.println(res); // e6ada6e6b29be9bd90
}
}
name = "king"
data_list = []
for item in name.encode('utf-8'):
val = hex(item)
data = val[2:].zfill(2)
data_list.append(data)
result = ''.join(data_list)
print(result) # e6ada6e6b29be9bd90
md5 加密
Python 中 md5 操作
import hashlib
obj = hashlib.md5()
obj.update('xxxxx'.encode('utf-8'))
# java中没有这个功能。
v1 = obj.hexdigest()
print(v1) # fb0e22c79ac75679e9881e6ba183b354
v2 = obj.digest()
print(v2) # b'\xfb\x0e"\xc7\x9a\xc7Vy\xe9\x88\x1ek\xa1\x83\xb3T'
Java 中 md5 操作
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
public class Hello {
public static void main(String[] args) throws NoSuchAlgorithmException {
String name = "king";
MessageDigest instance = MessageDigest.getInstance("MD5");
byte[] nameBytes = instance.digest(name.getBytes());
System.out.println(Arrays.toString(nameBytes));
String res = new String(nameBytes);
System.out.println(res);
// 十六进制展示
StringBuilder sb = new StringBuilder();
for (int i = 0; i < nameBytes.length; i++) {
int val = nameBytes[i] & 255; // 负数转换为正数
if (val < 16) {
sb.append("0");
}
sb.append(Integer.toHexString(val));
}
String hexData = sb.toString();
System.out.println(hexData); // e6ada6e6b29be9bd90
}
}
import hashlib
m = hashlib.md5()
m.update("天王盖地虎".encode("utf-8"))
v1 = m.digest()
print(v1) # b'\x15\x1d\xf4\xd2\xdd\xbd\xd1\xadjd\xc2\xc1\x8b)H('
v2 = m.hexdigest()
print(v2) # 151df4d2ddbdd1ad6a64c2c18b294828
md5 加盐
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
public class Hello {
public static void main(String[] args) throws NoSuchAlgorithmException {
String name = "king";
MessageDigest instance = MessageDigest.getInstance("MD5");
instance.update("xxxxxx".getBytes());
byte[] nameBytes = instance.digest(name.getBytes());
System.out.println(Arrays.toString(nameBytes));
String res = new String(nameBytes);
System.out.println(res);
// 十六进制展示
StringBuilder sb = new StringBuilder();
for(int i=0;i<nameBytes.length;i++){
int val = nameBytes[i] & 255; // 负数转换为正数
if (val<16){
sb.append("0");
}
sb.append(Integer.toHexString(val));
}
String hexData = sb.toString();
System.out.println(hexData); // e6ada6e6b29be9bd90
}
}
import hashlib
m = hashlib.md5("xxxxxx".encode('utf-8'))
m.update("king".encode("utf-8"))
v1 = m.digest()
print(v1) # b'\x175\x10\x12G$)\xd5-\x0c\r#\xd4h\x17='
v2 = m.hexdigest()
print(v2) # 17351012472429d52d0c0d23d468173d
抖音:X-SS-STUB
每次发送POST请求时,抖音都会携带一些请求头:
X-SS-STUB = "fjaku9asdf"读取请求体中的数据,对请求体中的数据进行md5加密。
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
public class Hello {
public static void main(String[] args) throws NoSuchAlgorithmException {
String name = "king";
MessageDigest instance = MessageDigest.getInstance("MD5");
byte[] nameBytes = instance.digest(name.getBytes());
// System.out.println(Arrays.toString(nameBytes));
// String res = new String(nameBytes);
// System.out.println(res);
// 十六进制展示
StringBuilder sb = new StringBuilder();
for (int i = 0; i < nameBytes.length; i++) {
int val = nameBytes[i] & 255; // 负数转换为正数
if (val < 16) {
sb.append("0");
}
sb.append(Integer.toHexString(val));
}
String hexData = sb.toString();
System.out.println(hexData); // e6ada6e6b29be9bd90
}
}
sha-256 加密
B站:x/report/andriod2,请求体
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
public class Hello {
public static void main(String[] args) throws NoSuchAlgorithmException {
String name = "king";
MessageDigest instance = MessageDigest.getInstance("SHA-256");
byte[] nameBytes = instance.digest(name.getBytes());
// System.out.println(Arrays.toString(nameBytes));
// String res = new String(nameBytes);
// System.out.println(res);
// 十六进制展示
StringBuilder sb = new StringBuilder();
for (int i = 0; i < nameBytes.length; i++) {
int val = nameBytes[i] & 255; // 负数转换为正数
if (val < 16) {
sb.append("0");
}
sb.append(Integer.toHexString(val));
}
String hexData = sb.toString();
System.out.println(hexData); // e6ada6e6b29be9bd90
}
}
import hashlib
m = hashlib.sha256()
m.update("king".encode("utf-8"))
v2 = m.hexdigest()
print(v2)
AES加密
刷B站播放时,发送POST请求。
AES加密(请求体中的数据) -> 密文(JS央视频 key & iv & 加密)。
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
public class Hello {
public static void main(String[] args) throws Exception {
String data = "king";
String key = "fd6b639dbcff0c2a1b03b389ec763c4b";
String iv = "77b07a672d57d64c";
// 加密
byte[] raw = key.getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivSpec);
byte[] encrypted = cipher.doFinal(data.getBytes());
// System.out.println(Arrays.toString(encrypted));
}
}
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
KEY = "fd6b639dbcff0c2a1b03b389ec763c4b"
IV = "77b07a672d57d64c"
def aes_encrypt(data_string):
aes = AES.new(
key=KEY.encode('utf-8'),
mode=AES.MODE_CBC,
iv=IV.encode('utf-8')
)
raw = pad(data_string.encode('utf-8'), 16)
return aes.encrypt(raw)
data = aes_encrypt("king")
print(data)
print([i for i in data])
gzip压缩
抖音注册设备:设备。
-
注册设备:生成一些值,值中包括: (cdid、手机型号、手机品牌....) 后端读取到时候,发现cdid是一个全新的请求。那么抖音就会生成
device_id、install_id、tt
。(cdid、手机型号、手机品牌....) --> gzip压缩 --> 加密 密文。
-
发送其他请求时:
- 获取平台
- 点赞
- ...
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.io.OutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class Hello {
public static void main(String[] args) throws IOException {
// 压缩
String data = "king";
// System.out.println(Arrays.toString(data.getBytes()));
ByteArrayOutputStream v0_1 = new ByteArrayOutputStream();
GZIPOutputStream v1 = new GZIPOutputStream((v0_1));
v1.write(data.getBytes());
v1.close();
byte[] arg6 = v0_1.toByteArray(); //gzip压缩后:arg6
// System.out.println(Arrays.toString(arg6));
// 解压缩
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(arg6);
GZIPInputStream ungzip = new GZIPInputStream(in);
byte[] buffer = new byte[256];
int n;
while ((n = ungzip.read(buffer)) >= 0) {
out.write(buffer, 0, n);
}
byte[] res = out.toByteArray();
// System.out.println(Arrays.toString(res));
System.out.println(out.toString("UTF-8"));
}
}
import gzip
# 压缩
"""
s_in = "天王盖地虎".encode('utf-8')
s_out = gzip.compress(s_in)
print([i for i in s_out])
"""
# 解压缩
"""
res = gzip.decompress(s_out)
print(res)
print(res.decode('utf-8'))
"""
base64 处理
import java.util.Base64;
public class Hello {
public static void main(String[] args) {
String name = "天王盖地虎";
Base64.Encoder encoder = Base64.getEncoder();
String res = encoder.encodeToString(name.getBytes());
System.out.println(res); // 5q2m5rKb6b2Q
Base64.Decoder decoder = Base64.getDecoder();
byte[] origin = decoder.decode(res);
String data = new String(origin);
System.out.println(data); // 天王盖地虎
}
}
import base64
name = "king"
res = base64.b64encode(name.encode('utf-8'))
print(res) # b'5q2m5rKb6b2Q'
data = base64.b64decode(res)
print(data.decode('utf-8'))
安卓 开发 流程
- 后台逻辑
- 网络请求
- 序列化和反序列化
- 保存XML文件(cookie)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="150dp"
android:background="#ddd"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="用户登录"
android:textAlignment="center"
android:textSize="20dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingLeft="50dp"
android:paddingRight="50dp">
<TextView
android:layout_width="60dp"
android:layout_height="wrap_content"
android:gravity="right"
android:text="用户名:" />
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="text"></EditText>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingLeft="50dp"
android:paddingRight="50dp">
<TextView
android:layout_width="60dp"
android:layout_height="wrap_content"
android:gravity="right"
android:text="密码:" />
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="textPassword"></EditText>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:text="登 录"></Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="重 置"></Button>
</LinearLayout>
</LinearLayout>
</LinearLayout>
后台逻辑
package com.nb.liyang;
import androidx.appcompat.app.AppCompatActivity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.google.gson.Gson;
import java.io.IOException;
import java.net.Proxy;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
import okhttp3.Call;
import okhttp3.FormBody;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class MainActivity extends AppCompatActivity {
private TextView txtUser, txtPwd;
private Button btnLogin, btnReset;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initListener();
}
private void initView() {
// 先找到所有的有用的标签
txtUser = findViewById(R.id.txt_user);
txtPwd = findViewById(R.id.txt_pwd);
btnLogin = findViewById(R.id.btn_login);
btnReset = findViewById(R.id.btn_reset);
}
private void initListener() {
btnReset.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 点击btn_reset标签,执行方法
txtUser.setText("");
txtPwd.setText("");
}
});
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loginForm();
}
});
}
/**
* 点击登录,执行此方法
*/
private void loginForm() {
// 1.获取用户名和密码
/*
String username = String.valueOf(txtUser.getText());
String password = String.valueOf(txtPwd.getText());
*/
// 2.校验用户名和密码不能为空
// root123
StringBuilder sb = new StringBuilder();
// {username:"root",password:"123"}
HashMap<String, String> dataMap = new HashMap<String, String>();
boolean hasEmpty = false;
// {username:txtUser, password:txtPwd}
HashMap<String, TextView> mapping = new HashMap<String, TextView>();
mapping.put("username", txtUser);
mapping.put("password", txtPwd);
for (Map.Entry<String, TextView> entry : mapping.entrySet()) {
String key = entry.getKey();
TextView obj = entry.getValue();
String value = String.valueOf(obj.getText());
if (value.trim().isEmpty()) {
hasEmpty = true;
break;
}
dataMap.put(key, value);
sb.append(value);
}
if (hasEmpty) {
Toast.makeText(this, "输入内容不能为空", Toast.LENGTH_SHORT).show();
return;
}
// sb="root123"
// dataMap={username:"root",password:"123"}
// 3.用md5做一个签名
// dataMap={username:"root",password:"123","sign":"xxxxdfsdfsdfsdfdfd"}
String signString = md5(sb.toString());
dataMap.put("sign", signString);
// com.nb.liyang E/加密后的结果:: cb2921a386719d7467412b5573973529
Log.e("加密后的结果:", signString);
// 4.将三个值:用户名、密码、签名 网络请求发送API(校验)
// okhttp,安装 & 引入 & 使用(创建一个线程去执行)
// 5.获取返回值
new Thread() {
@Override
public void run() {
// 线程执行的内容
// user=xxx&pwd=xxx&sign=xxxx
OkHttpClient client = new OkHttpClient.Builder().build();
// OkHttpClient client = new OkHttpClient.Builder().proxy(Proxy.NO_PROXY).build();
FormBody form = new FormBody.Builder()
.add("user", dataMap.get("username"))
.add("pwd", dataMap.get("password"))
.add("sign", dataMap.get("sign")).build();
Request req = new Request.Builder().url("http://192.168.0.6:9999/login").post(form).build();
Call call = client.newCall(req);
try {
Response res = call.execute();
ResponseBody body = res.body();
String dataString = body.string();
// {"status": true, "token": "dafkauekjsoiuksjdfuxdf", "name": "武沛齐"}
// 反序列化
HttpResponse obj = new Gson().fromJson(dataString,HttpResponse.class);
if(obj.status){
// token保存本地xml文件
// /data/data/com.nb.liyang
SharedPreferences sp = getSharedPreferences("sp_city", MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString("token",obj.token);
editor.commit();
}
Log.e("请求发送成功", dataString);
} catch (IOException ex) {
Log.e("Main", "网络请求异常");
}
}
}.start();
}
/**
* md5加密
*
* @param dataString 待加密的字符串
* @return 加密结果
*/
private String md5(String dataString) {
try {
MessageDigest instance = MessageDigest.getInstance("MD5");
byte[] nameBytes = instance.digest(dataString.getBytes());
// 十六进制展示
StringBuilder sb = new StringBuilder();
for (int i = 0; i < nameBytes.length; i++) {
int val = nameBytes[i] & 255; // 负数转换为正数
if (val < 16) {
sb.append("0");
}
sb.append(Integer.toHexString(val));
}
return sb.toString();
} catch (Exception e) {
return null;
}
}
}
API
import flask
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/login', methods=["POST"])
def login():
# 1.接收请求数据
print(request.form)
# 2.校验签名
# 3.校验用户名和密码是否正确
# 4.返回值
return jsonify({"status": True, 'token': "dafkauekjsoiuksjdfuxdf"})
if __name__ == '__main__':
app.run(host="0.0.0.0", port=9999)
网络请求
1.引入,在build.gradle中 implementation "com.squareup.okhttp3:okhttp:4.9.1"
2.配置,在AndroidManifest.xml中 <uses-permission android:name="android.permission.INTERNET"/>
3.支持http(仅测试使用)
new Thread() {
@Override
public void run() {
OkHttpClient client = new OkHttpClient();
FormBody form = new FormBody.Builder()
.add("user", dataMap.get("username"))
.add("pwd", dataMap.get("password"))
.add("sign", dataMap.get("sign")).build();
Request req = new Request.Builder().url("http://192.168.0.6:9999/login").post(form).build();
Call call = client.newCall(req);
try {
Response res = call.execute();
ResponseBody body = res.body();
// 字符串={"status": true, "token": "fffk91234ksdujsdsd", "name": "武沛齐"}
String dataString = body.string();
// Log.e("MDS", "请求成功获取返回值=" + dataString);
} catch (IOException ex) {
Log.e("MDS", "网络请求错误");
}
}
}.start();
new Thread() {
@Override
public void run() {
OkHttpClient client = new OkHttpClient();
// dataMap = {"username":"wupeiqi","password":"123","sign":"用户名和密码的md5值"}
JSONObject json = new JSONObject(dataMap);
String jsonString = json.toString();
// RequestBody form = RequestBody.create(MediaType.parse("application/json;charset=utf-8"),jsonString);
RequestBody form = RequestBody.create(MediaType.parse("application/json;charset=utf-8"),jsonString);
Request req = new Request.Builder().url("http://192.168.0.6:9999/login").post(form).build();
Call call = client.newCall(req);
try {
Response res = call.execute();
ResponseBody body = res.body();
String dataString = body.string();
Log.i("登录", dataString);
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
请求拦截器
假设开发app,发送10个请求,每个请求中需要携带特殊的请求头:xxxx。将所有请求公共的操作都放在拦截器里面。
// 创建拦截器
Interceptor interceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
// 1988812212 + 固定字符串 => md5加密
Request request = chain.request().newBuilder().addHeader("ts", "1988812212").addHeader("sign", "xxxx").build();
// 请求前
Response response = chain.proceed(request);
// 请求后
return response;
}
};
// 4.将三个值:用户名、密码、签名 网络请求发送API(校验)
// okhttp,安装 & 引入 & 使用(创建一个线程去执行)
// 5.获取返回值
new Thread() {
@Override
public void run() {
// 线程执行的内容
// user=xxx&pwd=xxx&sign=xxxx
// OkHttpClient client = new OkHttpClient.Builder().build();
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
FormBody form = new FormBody.Builder()
.add("user", dataMap.get("username"))
.add("pwd", dataMap.get("password"))
.add("sign", dataMap.get("sign")).build();
Request req = new Request.Builder().url("http://192.168.0.6:9999/login").post(form).build();
Call call = client.newCall(req);
try {
Response res = call.execute();
ResponseBody body = res.body();
String dataString = body.string();
Log.e("请求发送成功", dataString);
} catch (IOException ex) {
Log.e("Main", "网络请求异常");
}
}
}.start();
NO_PROXY
防止系统代理抓包。
new Thread() {
@Override
public void run() {
OkHttpClient client = new OkHttpClient.Builder().proxy(Proxy.NO_PROXY).build();
// OkHttpClient client = new OkHttpClient.Builder().build();
FormBody form = new FormBody.Builder()
.add("user", dataMap.get("username"))
.add("pwd", dataMap.get("password"))
.add("sign", dataMap.get("sign")).build();
Request req = new Request.Builder().url("http://192.168.0.6:9999/login").post(form).build();
Call call = client.newCall(req);
try {
Response res = call.execute();
ResponseBody body = res.body();
// 字符串={"status": true, "token": "fffk91234ksdujsdsd", "name": "武沛齐"}
String dataString = body.string();
// Log.e("MDS", "请求成功获取返回值=" + dataString);
} catch (IOException ex) {
Log.e("MDS", "网络请求错误");
}
}
}.start();
反序列化
Gson组件
implementation 'com.google.code.gson:gson:2.8.6'
序列化,对象 -> 字符串类型
class HttpContext{
public int code;
public String message;
public HttpContext(int code,String msg){
this.code = code;
this.message = msg;
}
}
HttpContext obj = new HttpContext(1000,"成功");
# json.dumps
String dataString = new Gson().toJson(obj); // '{"code":1000,"Message":"成功"}'
反序列化,字符串 -> 对象
// JSON格式
String dataString = "{\"status\": true, \"token\": \"fffk91234ksd\", \"name\": \"武沛齐\"}";
class HttpResponse{
public boolean status;
public String token;
public String name;
}
HttpResponse obj = new Gson().fromJson(dataString,HttpResponse.class);
obj.status
obj.name
obj.token
String responseString = "{
\"origin\": \"110.248.149.62\",
\"url\": \"https://www.httpbin.org/post\",
\"dataList\":[
{\"id\":1,\"name\":\"武沛齐\"},
{\"id\":2,\"name\":\"eric\"}]
}";
class Item {
public int id;
public String name;
}
public class HttpResponse {
public String url;
public String origin;
public ArrayList<Item> dataList;
}
HttpResponse obj = new Gson().fromJson(dataString,HttpResponse.class);
obj.url
obj.origin
Item objItem = obj.dataList.get(1);
objItem.name
class HttpResponse{
public boolean status;
public String token;
}
String dataString = "{"status":true,"token":"b96efd24-e323-4efd-8813-659570619cde"}";
HttpResponse obj = new Gson().fromJson(dataString,HttpResponse.class);
obj.status
obj.token
保存到 XML 文件
保存到手机上:/data/data/com.nb.liyang
保存
SharedPreferences sp = getSharedPreferences("sp_city", MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString("token","111111");
editor.commit();
删除
SharedPreferences sp = getSharedPreferences("sp_city", MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.remove("token");
editor.commit();
读取
SharedPreferences sp = getSharedPreferences("sp_city", MODE_PRIVATE);
String token = sp.getString("token","");
注意:后期逆向时经常使用,放在xml中的一般都是app刚启动时、刚登录时。
APP刚启动
- 发送网络请求,返回 xxxid=123123123
- 保存XML
xxxid=123123123
再次启动APP
- 抓其他的包,携带了参数 xxxid=123123123
------------------------------------------
- 将app数据清除,xml也会被清除(重装)
- 启动app
- 发送网络请求,返回 xxxid=123123123
- 抓其他的包,携带了参数 xxxid=123123123
逆向过程中的思路:udid
- udid找到代码位置
- 先去内存中获取
- 去XML文件中获取 SharedPreferences sp
- 写入内存
- 算法生成
- 写入XML
- 写入内存
返回
- udid找到代码位置
- 先去内存中获取
- 去XML文件中获取 SharedPreferences sp
- 写入内存
没代码了....
这种情况一般都是在程序刚启动时,发送了请求,获取值,写入XML
11、JNI 基础
示例 1 ( JNI 调用 Java中 static 方法 )
在 JNI 中如果想要调用Java中的成员,是使用:
// Java
package com.nb.fucker;class Query {
public static int getData(int v1, int v2) {
return v1 + v2;
}
public static int getData(int v1) {
return v1 + v2;
}
}
// JNI
jclass cls = (*env)->FindClass(env, "com/nb/fucker/Query");
jmethodID mid = (*env)->GetStaticMethodID(env, cls, "getData", "(II)I");
int res = (*env)->CallStaticIntMethod(env, cls, mid, 1,2);
示例 2
// Java
package com.nb.fucker;class Query {
public static String getData(int v1, int v2) {
return v1 + v2;
}
}
// JNI
jclass cls = (*env)->FindClass(env, "com/nb/fucker/Query");
jmethodID mid = (*env)->GetStaticMethodID(env, cls, "getData", "(II)Ljava/lang/String;");
jobject res = (*env)->CallStaticObjectMethod(env, cls, mid, 1,2);
示例 3
// java
package com.nb.fucker;class Query {
public static String getData(String v1, int v2) {
return String.valueOf(v1 + v2);
}
}
// JNI,C语言
jclass cls = (*env)->FindClass(env, "com/nb/fucker/Query");
jmethodID mid = (*env)->GetStaticMethodID(env, cls, "getData","(Ljava/lang/String;I)Ljava/lang/String;");// C语言中的字符串不能直接在Java中使用,必须通过 NewStringUTF 转换后才能在java中使用
jstring arg1 = (*env)->NewStringUTF(env, "哈哈哈哈");
jobject res = (*env)->CallStaticObjectMethod(env, cls, mid, arg1, 2);
示例 4 ( JNI 调用 Java中 实例 方法 )
// java
package com.nb.fucker;class Query {
// 构造方法
public Query(int arg1, int arg2, String arg3) {}
// getData
public String getData(int v1, int v2) {
return String.valueOf(v1 + v2);
}
}
// JNI
jclass cls = (*env)->FindClass(env, "com/nb/fucker/Query");
jmethodID init = (*env)->GetMethodID(env, cls, "<init>", "(IILjava/lang/String;)V");// 实例化出来的对象
jobject cls_object = (*env)->NewObject(env, cls, init, 1, 2, (*env)->NewStringUTF(env, "哈哈哈哈"));
jmethodID mid = (*env)->GetMethodID(env, cls, "getData", "(II)Ljava/lang/String;");jstring arg1 = (*env)->NewStringUTF(env, "字符串呀");
jobject res = (*env)->CallObjectMethod(env, cls_object, mid, arg1, 2);
寻找 RegisterNative 函数
apk是将Java代码打包后的包,解压后发现内部都是一大堆的dex文件(代码)。
安卓手机上都是有一个虚拟机,负责读取dex代码转换成机器码去执行,除此以外,也包括:垃圾回收、Java本地方法调用(JNI)、即时编译(JIT)等。
- 安卓4.2之前:DVM。
- 安卓4.2之后:DVM(默认)、ART(Android Runingtime)共存。
- 安卓5.0之后:ART。
关于两种虚拟机:
- DVM,每次运行应用的时候,实时将字节码转化为机器码,再运行程序。【基于libdvm.so库】
- ART,在安装应用的时候,就将应用的字节码转换为机器码,保存在手机里。每次运行应用的时候,无需实时转换,直接使用转换好的机器码。【基于libart.so库】
在JNI在进行动态注册时执行的 RegisterNatives 方法,就是在libart.so库中。
所以,如果想要hook RegisterNatives 方法,就必须要先找到他,所以,基于frida可以这么干。
基于frida中的模块来寻找。
// 列举 libart.so 中的所有导出函数(成员列表)
var symbols = Module.enumerateSymbolsSync("libart.so");// 获取 RegisterNatives函数的内存地址,并赋值给addrRegisterNatives。
var addrRegisterNatives = null;for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
console.log(symbol.name)
//_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
// 方式1:
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
}
// 方式2:
var name = "_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi";
if(symbol.name.indexOf("art") >= 0){
if(symbol.name.indexOf(name)>=0){
addrRegisterNatives = symbol.address;
}
}
}
Hook RegisterNavtive 定位函数
找到 RegisterNatives 函数之后,就可以通过frida的拦截器来对他进行hook,以便找到注册的所有方法的对应关系。例如:
参数1:JNI对象;
参数2:类
参数3:动态注册的对应关系
参数4:注册的方法数量
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
var env = args[0]; // jni对象
var java_class = args[1]; // 类
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
// 只有类名为com.bilibili.nativelibrary.LibBili,才打印输出
var taget_class = "com.bilibili.nativelibrary.LibBili";
if(class_name === taget_class){
console.log("\n[RegisterNatives] method_count:", args[3]);
// args[2] 就是动态注册的对应关系。
// ptr是new NativePointer(s) 的缩写。(C语言中的指针)
var methods_ptr = ptr(args[2]);
var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
// Java中函数名字的
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
// 参数和返回值类型
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
// C中的函数指针
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));var name = Memory.readCString(name_ptr);
var sig = Memory.readCString(sig_ptr);
var find_module = Process.findModuleByAddress(fnPtr_ptr);
var offset = ptr(fnPtr_ptr).sub(find_module.base) // fnPtr_ptr - 模块基地址
// console.log("[RegisterNatives] java_class:", class_name);
// console.log("name:", name, "sig:", sig, "module_name:", find_module.name, "offset:", offset);
console.log("name:", name, "module_name:", find_module.name, "offset:", offset);
}
}
}
});
完整脚本
frida -U --no-pause -f tv.danmaku.bili -l natives-1.js
function hook_RegisterNatives() {
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrRegisterNatives = null;
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
// _ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
}
}
if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
var env = args[0];
var java_class = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
//console.log(class_name);
// 只有类名为com.bilibili.nativelibrary.LibBili,才打印输出
var taget_class = "com.bilibili.nativelibrary.LibBili";
if (class_name === taget_class) {
console.log("\n[RegisterNatives] method_count:", args[3]);
var methods_ptr = ptr(args[2]);
var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
// Java中函数名字的
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
// 参数和返回值类型
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
// C中的函数指针
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
var name = Memory.readCString(name_ptr); // 读取java中函数名
var sig = Memory.readCString(sig_ptr); // 参数和返回值类型
var find_module = Process.findModuleByAddress(fnPtr_ptr); // 根据C中函数指针获取模块
var offset = ptr(fnPtr_ptr).sub(find_module.base) // fnPtr_ptr - 模块基地址
// console.log("[RegisterNatives] java_class:", class_name);
// console.log("name:", name, "sig:", sig, "module_name:", find_module.name, "offset:", offset);
console.log("name:", name, "module_name:", find_module.name, "offset:", offset);
}
}
}
});
}
}
setImmediate(hook_RegisterNatives);