正则表达式
正则表达式:IP地址的匹配
123
255.255.255.8
192.168.0.1
0.8.9.9
256.1.1.1
This is a string.
123.123.0
\b((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)\b
首先匹配一个数字:
- 如果数字为三位数,并且前两位以 25 开头,个位数字不能超过5,即
25[0-5]
- 如果数字为三位数,并且百位等于2 && 十位小于 5,则个位数字最大可以等于9,即
2[0-4]\d
- 如果数字为三位数,百位小于2,则十位和个位最大可以等于0,即
[01]\d\d
- 可以为两位数和一位数,百位和十位可能不存在,则可以使用
?
,这样个位数字最大可以等于9,即[01]?\d?\d
所以,IPv4中的点分十进制中的一位数,可以使用 :(25[0-5]|2[0-4]\d|[01]?\d?\d)
匹配前三个数字
因为前三个数字的情况是一样的,所以使用:(25[0-5]|2[0-4]\d|[01]?\d?\d)\.){3}
匹配整体
这里需要在整体的正则表达式的前后加上一个\b
,标注字符的边界,即\b((25[0-5]|2[0-4]\d|[01]?\d?\d)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)\b
字符串分割和匹配
1)Pattern类和Matcher类实现字符串匹配
这里使用这两个类找出字符串中的数字,并将其累加起来
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class StringMatchExample {
public static void main(String[] args) {
String s1 = "01A12A222A3A334A5A336A7A8A91";
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher(s1);
int sum = 0; // 用于累加匹配到的数字
while (m.find()) {
String str = m.group();
System.out.println("从" + m.start() + "到" + m.end() + "匹配的字符串: " + str);
int num = Integer.parseInt(str);
sum += num;
System.out.println("转换后的整数: " + num);
}
System.out.println("所有匹配数字的总和为: " + sum);
}
}
- 首先定义了正确的
Pattern
对象,用于匹配一个或多个数字(\\d+
正则表达式表示匹配连续数字)。 - 然后通过
Matcher
对象在给定字符串中进行查找匹配。 - 在
while
循环中,每次找到匹配的数字字符串后,输出其在原字符串中的起始、结束位置以及字符串本身,接着将其转换为整数,累加到sum
变量中,同时也输出转换后的整数。 - 最后输出所有匹配数字的总和,实现了一个简单的字符串中数字提取与处理的功能。
在Java中,java.util.regex
包下的Pattern
类和Matcher
类是用于正则表达式相关操作的重要类,它们提供了强大的文本匹配和处理功能,以下是对它们的详细介绍:
Pattern类
概述
Pattern
类用于定义一个正则表达式的编译表示形式。简单来说,就是将你写好的正则表达式字符串进行编译,生成一个可用于后续匹配操作的模式对象。正则表达式是一种用于描述字符模式的特殊语法,通过它可以灵活地定义要匹配的文本规则,比如匹配电话号码、邮箱地址、特定格式的数字等各种格式的字符串。
compile(String regex)
方法
这是最常用的静态方法,用于将给定的正则表达式字符串regex
编译成一个Pattern
对象。例如:
Pattern pattern = Pattern.compile("\\d+"); // 编译一个匹配一个或多个数字的正则表达式
这里\\d
表示数字字符(在Java字符串中,\
需要转义写成\\
,所以表示数字的正则表达式实际写为\\d
),+
表示前面的元素(数字字符)出现一次或多次,整体\\d+
就表示匹配连续的数字字符串。
split(CharSequence input)
方法
可以按照正则表达式所定义的模式来分割给定的字符序列(如字符串)。例如:
Pattern pattern = Pattern.compile(","); // 以逗号作为分隔的正则模式
String input = "apple,banana,cherry";
String[] parts = pattern.split(input);
for (String part : parts) {
System.out.println(part);
}
在上述代码中,会将输入的字符串按照逗号进行分割,最终输出的结果是分别打印出apple
、banana
、cherry
这几个子字符串。
matcher(CharSequence input)
方法
针对给定的字符序列(通常是字符串)创建一个对应的Matcher
对象,这个Matcher
对象就可以用来在该字符序列中进行具体的匹配查找等操作。例如:
Pattern pattern = Pattern.compile("\\w+"); // 匹配一个或多个单词字符(字母、数字、下划线等)
Matcher matcher = pattern.matcher("Hello_123, world!");
这样就创建好了用于后续在"Hello_123, world!"
这个字符串中进行匹配查找符合\\w+
模式(单词字符组成的字符串)的Matcher
对象。
Matcher类
概述
Matcher
类用于对输入的字符序列(基于Pattern
类所定义的正则表达式模式)执行匹配操作。它依托于Pattern
类创建出来的模式对象,能够在具体的文本中去查找、判断是否存在符合模式的子字符串,以及获取匹配到的相关内容等。
find()
方法
尝试在输入的字符序列中查找下一个与Pattern
所定义的正则表达式匹配的子序列。如果找到匹配的部分,返回true
,否则返回false
。可以配合while
循环来查找所有匹配的部分,例如:
Pattern pattern = Pattern.compile("\\d{3}"); // 匹配连续的三个数字
Matcher matcher = pattern.matcher("abc123def456ghi");
while (matcher.find()) {
System.out.println("找到匹配的字符串: " + matcher.group());
}
在上述代码中,find()
方法会在字符串"abc123def456ghi"
中依次查找所有符合三个连续数字模式的子字符串,每次找到后通过group()
方法(后面会介绍)获取并输出该匹配的字符串,这里会分别输出123
和456
。
group()
方法
返回上一次匹配操作(如通过find()
方法找到匹配后)所匹配到的子字符串。例如:
Pattern pattern = Pattern.compile("(\\w+)@(\\w+)\\.(\\w+)"); // 简单匹配邮箱格式(简化版,实际更复杂)
Matcher matcher = pattern.matcher("example@example.com");
if (matcher.find()) {
System.out.println("用户名: " + matcher.group(1));
System.out.println("域名: " + matcher.group(2));
System.out.println("后缀: " + matcher.group(3));
}
这里正则表达式用括号进行了分组,group(1)
就返回第一个分组(也就是@
之前的用户名部分example
),group(2)
返回第二个分组(@
和.
之间的域名部分example
),group(3)
返回第三个分组(.
之后的后缀部分com
)。
start()
和end()
方法
start()
方法返回上一次匹配到的子字符串在整个输入字符序列中的起始索引位置(索引从0开始计数),end()
方法返回该匹配子字符串的结束索引位置(结束位置是最后一个字符的下一个位置)。例如:
Pattern pattern = Pattern.compile("hello");
Matcher matcher = pattern.matcher("say hello world");
if (matcher.find()) {
System.out.println("匹配的字符串 'hello' 起始位置: " + matcher.start());
System.out.println("匹配的字符串 'hello' 结束位置: " + matcher.end());
}
上述代码中,start()
会输出4
(因为hello
在"say hello world"
中从索引为4的位置开始),end()
会输出9
(因为到索引为8的位置结束,结束位置取最后一个字符的下一个索引,也就是9)。
2)StringTokenizer
类
需求: 按照空格和逗号作为分隔符,将输入字符串分割成一个个的“单词”(子字符串),然后依次输出这些“单词”,并且统计剩余的“单词”数量以及原始字符串中总的“单词”数量。
public class StringTokenizerExample {
public static void main(String[] args) {
String s = "we are stud,ents";
StringTokenizer fenxi = new StringTokenizer(s, ", "); // 空格和逗号做分隔
int number = fenxi.countTokens();
while (fenxi.hasMoreTokens()) {
String str = fenxi.nextToken();
System.out.println(str);
System.out.println("还剩" + fenxi.countTokens() + "个单词");
}
System.out.println("s共有单词:" + number + "个");
}
}
运行上述代码,输出结果如下:
we
还剩3个单词
are
还剩2个单词
stud
还剩1个单词
ents
还剩0个单词
s共有单词:4个
3)String
类的split
方法
public class StringSplitExample {
public static void main(String[] args) {
String str = "1+1=; 2+3=";
// 正则表达式,用于匹配一个或多个加减乘除运算符作为分隔符
String regex = "[+\\-*/]+";
String words[] = str.split(regex);
for (int i = 0; i < words.length; i++) {
try {
int r = Integer.parseInt(words[i]);
System.out.println("转换后的整数: " + r);
} catch (NumberFormatException e) {
System.out.println(words[i] + " 无法转换为整数");
}
}
}
}
- 首先定义了一个字符串
str
以及修正后的正则表达式regex
,用于指定分割字符串的分隔规则。 - 然后使用
split
方法按照该正则表达式对str
进行分割,得到一个字符串数组words
。 - 接着通过
for
循环遍历words
数组,在循环内部使用try-catch
块来尝试将每个元素转换为整数,因为有可能数组中的某些元素本身不是合法的整数格式(比如原字符串中分割出来的非数字部分),如果能成功转换就输出该整数,若转换失败则捕获NumberFormatException
异常并输出相应提示信息。
判断字符串是否包含某个字符或字符串
1. indexOf
方法
-
功能及语法:
indexOf
方法用于查找指定字符或字符串在当前字符串中首次出现的索引位置(索引从0开始计数)。如果找到了,就返回该字符或字符串在原字符串中的起始索引;如果没有找到,则返回 -1。它有多种重载形式,常见的有以下两种:int indexOf(int ch)
:用于查找指定的字符ch
(以int
类型表示,实际上是字符对应的ASCII码值,可以直接传入字符常量,如'a'
)在字符串中的位置。int indexOf(String str)
:用于查找指定的字符串str
在当前字符串中的位置。
-
示例代码(查找字符):
String text = "Hello, world!";
int index = text.indexOf('o');
if (index!= -1) {
System.out.println("字符 'o' 在字符串中的索引位置是: " + index);
} else {
System.out.println("字符串中未找到字符 'o'");
}
在上述代码中,会在字符串 "Hello, world!"
中查找字符 'o'
首次出现的位置,由于找到了,会输出 字符 'o' 在字符串中的索引位置是: 4
。
- 示例代码(查找字符串):
String text = "Hello, world!";
int index = text.indexOf("world");
if (index!= -1) {
System.out.println("字符串 'world' 在原字符串中的索引位置是: " + index);
} else {
System.out.println("原字符串中未找到字符串 'world'");
}
这里会查找字符串 "world"
在 text
中的位置,若找到会输出对应的索引位置,若没找到则输出相应提示信息。
2. lastIndexOf
方法
-
功能及语法:
与indexOf
类似,不过它是查找指定字符或字符串在当前字符串中最后一次出现的索引位置。同样有多种重载形式,参数和indexOf
基本一致,用法也类似,只是查找的是最后出现的位置。 -
示例代码(查找字符):
String text = "Hello, world!";
int index = text.lastIndexOf('o');
if (index!= -1) {
System.out.println("字符 'o' 在字符串中最后一次出现的索引位置是: " + index);
} else {
System.out.println("字符串中未找到字符 'o'");
}
上述代码会输出字符 'o'
在字符串中最后一次出现的索引位置(在这个例子中是 8
,对应 world
中的 o
)。
3. contains
方法(Java 8及以后可用)
-
功能及语法:
contains
方法直接用于判断当前字符串是否包含指定的字符序列(字符串),返回值是boolean
类型,包含则返回true
,不包含则返回false
。使用起来更加简洁直观,语法为boolean contains(CharSequence s)
,其中CharSequence
是一个接口,String
类实现了该接口,所以可以直接传入String
类型的字符串参数。 -
示例代码:
String text = "Hello, world!";
boolean result = text.contains("world");
System.out.println("字符串是否包含 'world': " + result);
代码直接判断字符串 text
是否包含 "world"
,并输出相应的 true
或 false
结果。
4. substring
方法间接判断(验证)
- 功能及语法:
substring
方法用于从原字符串中截取一部分字符串出来,它有两种常用重载形式:String substring(int beginIndex)
:从指定的索引beginIndex
(包含该索引位置的字符)开始,截取到字符串末尾,返回截取后的新字符串。String substring(int beginIndex, int endIndex)
:从指定的索引beginIndex
(包含)开始,到索引endIndex
(不包含)结束,截取这部分字符串返回为新字符串。
虽然它本身不是专门用于判断是否包含某个字符串的方法,但可以通过截取字符串后与目标字符串进行比较来间接判断,不过这种方式相对比较繁琐且效率不高,一般不作为首选判断方式,更多用于已知部分位置情况下提取子字符串使用。
- 示例代码(间接判断):
String text = "Hello, world!";
int index = text.indexOf("world");
if (index!= -1) {
String sub = text.substring(index);
if ("world!".equals(sub)) {
System.out.println("原字符串包含 'world' 且后续内容匹配");
}
}
在上述代码中,先通过 indexOf
查找 "world"
的索引位置,如果找到了,再使用 substring
截取从该位置开始到末尾的字符串,然后与预期的字符串进行比较判断是否匹配,以此来间接验证原字符串包含指定字符串且后续内容符合期望,但这种方式代码相对复杂,不如直接使用 indexOf
或者 contains
方法来得简洁高效。
单类、继承
点类、shanpe类、圆形类、矩形类、正方形类、柱体类
class Shape {
public double sum = 0; //周长
public double area = 0; //面积
public double getSum(){
return 0.0;
}
public double getArea(){
return 0.0;
}
}
class Rect extends Shape{ //矩形
private double length;
private double wide;
public Rect(double length,double wide){
this.wide = wide;
this.length = wide;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWide() {
return wide;
}
public void setWide(double wide) {
this.wide = wide;
}
@Override
public double getSum() {
return (length + wide) * 2;
}
@Override
public double getArea() {
return length * wide;
}
}
class Circle extends Shape { //圆
protected final static double p = 3.1415926;
protected double radius;
public Circle(double radius){
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
@Override
public double getSum() {
return 2 * p * radius;
}
@Override
public double getArea() {
return p * radius * radius;
}
}
class Square extends Shape{ //正方形
private double sidelength;
public Square(double sidelength){
this.sidelength = sidelength;
}
public double getSidelength() {
return sidelength;
}
public void setSidelength(double sidelength) {
this.sidelength = sidelength;
}
@Override
public double getSum() {
return 4 * sidelength;
}
@Override
public double getArea() {
return sidelength * sidelength;
}
}
public class StringTest {
public static void main(String[] args) {
Rect r = new Rect(4,5); //矩形
Circle c = new Circle(4.56); //圆
Square s = new Square(5.67); //正方形
r.getSum();
r.getArea();
System.out.println("矩形的周长:"+ r.getSum() +";矩形的面积:"+ r.getArea());
c.getSum();
c.getArea();
System.out.println("圆的周长:"+ c.getSum() +";圆的面积:"+ c.getArea());
s.getSum();
s.getArea();
System.out.println("正方形的周长:"+ s.getSum() +";正方形的面积:"+ s.getArea());
}
}
设计一个可以随机打印形状的代码
这里主要是使用继承和多态的语法特性。
class Shape {
public void draw() {
//在子类中要重写draw()方法
System.out.println("图形形状");
}
public void clear() {
System.out.println("\n\n\n");
}
}
// 圆形类
class Circle extends Shape{
public void draw(){
System.out.println("圆形");
}
}
// 方形类
class Square extends Shape{
@Override
public void draw() {
System.out.println("方形");
}
}
// 直线类
class Line extends Shape{
@Override
public void draw() {
System.out.println("直线");
}
public void length(){
System.out.println("直线的长度为1.");
}
}
public class end {
private String name = "abc";
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int r = new Random().nextInt(4);
switch (r){
case 0:
f(new Shape()); break;
case 1:
f(new Circle()); break;
case 2:
f(new Square()); break;
case 3:
f(new Line()); break;
}
}
}
static public void f(Shape s){
s.draw();
if(s instanceof Line){
((Line) s).length();
}
s.clear();
}
}
创建一个与hello.txt文件在相同文件目录下的另一个名为abc.txt文件
public class test {
public static void main(String[] args) throws IOException {
File file1 = new File("hello.txt");
System.out.println(file1.getAbsolutePath()); // 显示文件绝对路径
if(!file1.exists()){
file1.createNewFile(); // 创建文件
}
// 需要找到hello.txt的上级目录
File file2 = new File(file1.getAbsoluteFile().getParentFile(), "abc.txt");
System.out.println(file2.getAbsolutePath()); // 显示文件绝对路径
if(!file2.exists()){
file2.createNewFile(); // 创建文件
}
}
}
BigInteger
类
BigInteger
类位于java.math
包中,它用于表示任意大小的整数,在Java中基本数据类型(如int
、long
等)有其表示范围的限制,当需要处理超出这些范围的超大整数时,就可以使用BigInteger
类。它提供了一系列方法来进行各种算术运算以及对大整数的相关操作,就如同对普通整数进行操作一样,只是方法调用形式有所不同。
BigInteger
类的常用构造方法
通过字符串构造:最常用的构造方法是通过传入一个表示整数的字符串来创建BigInteger
对象,例如:
BigInteger n = new BigInteger("123456789012345678901234567890");
这样就创建了一个代表对应大整数的BigInteger
对象,无论这个整数有多大,只要字符串能正确表示其数值即可。通过这种方式可以方便地初始化超大整数对象,用于后续的运算等操作。
BigInteger
类的常用方法
进制转换相关方法
toString(int radix)
方法:用于将BigInteger
对象表示的整数转换为指定进制的字符串形式。参数radix
表示目标进制,取值范围是在2到36之间(包含2和36)。例如:
BigInteger num = new BigInteger("255");
System.out.println("十六进制表示: " + num.toString(16));
上述代码会输出十六进制表示的字符串 "ff"
。
算术运算相关方法:
add(BigInteger val)
方法
用于执行加法运算,返回当前BigInteger
对象与传入的BigInteger
对象val
相加后的结果,也是一个BigInteger
对象。例如:
BigInteger num1 = new BigInteger("10");
BigInteger num2 = new BigInteger("20");
BigInteger sum = num1.add(num2);
System.out.println(sum);
会输出表示30的BigInteger
对象对应的字符串表示形式。
subtract(BigInteger val)
方法:执行减法运算,返回当前BigInteger
对象减去传入的BigInteger
对象后的结果。multiply(BigInteger val)
方法:执行乘法运算,返回两个BigInteger
对象相乘后的结果。divide(BigInteger val)
方法:执行除法运算,返回当前BigInteger
对象除以传入的BigInteger
对象的商(取整结果),需要注意如果除数为0会抛出ArithmeticException
异常。
比较相关方法
compareTo(BigInteger other)
方法:用于比较当前BigInteger
对象与另一个BigInteger
对象other
的大小关系。返回值有三种情况:
- 如果当前对象小于
other
,返回 -1。 - 如果当前对象等于
other
,返回0。 - 如果当前对象大于
other
,返回1。
例如:
BigInteger num1 = new BigInteger("10");
BigInteger num2 = new BigInteger("20");
System.out.println(num1.compareTo(num2));
会输出 -1,表示num1
小于num2
。
取余相关方法
remainder(BigInteger val)
方法:返回当前BigInteger
对象除以传入的BigInteger
对象val
后的余数,同样是一个BigInteger
对象。例如:
BigInteger num1 = new BigInteger("10");
BigInteger num2 = new BigInteger("3");
System.out.println(num1.remainder(num2));
会输出表示余数1的BigInteger
对象对应的字符串表示形式。
综合实例
import java.math.BigInteger;
public class BigIntegerExample {
public static void main(String[] args) {
BigInteger n1 = new BigInteger("987654321987654321987654321");
BigInteger n2 = new BigInteger("123456789123456789123456789");
BigInteger result = null;
System.out.println("n1的二进制表示:" + n1.toString(2));
System.out.println("n2的八进制表示:" + n2.toString(8));
result = n1.add(n2);
System.out.println("加法结果:" + result.toString());
result = n1.subtract(n2);
System.out.println("减法结果:" + result.toString());
result = n1.multiply(n2);
System.out.println("乘法结果:" + result.toString());
result = n1.divide(n2);
System.out.println("除法结果:" + result.toString());
BigInteger m = new BigInteger("778899");
BigInteger COUNT = new BigInteger("0");
BigInteger ONE = new BigInteger("1");
BigInteger TWO = new BigInteger("2");
for (BigInteger i = TWO; i.compareTo(m) < 0; i = i.add(ONE)) {
if ((m.remainder(i).compareTo(BigInteger.ZERO)) == 0) {
System.out.println("因子:" + i.toString());
COUNT = COUNT.add(ONE);
}
}
System.out.println(m.toString() + "一共有" + COUNT.toString() + "个因子");
}
}
BigDecimal
类
BigDecimal
类位于java.math
包中,它主要用于处理精确的十进制小数运算。在Java中,使用float
和double
等基本数据类型进行浮点数运算时,由于其内部表示方式(遵循IEEE 754标准等)的原因,可能会出现精度丢失的问题,尤其是在涉及到货币计算、高精度科学计算等对精度要求严格的场景下,BigDecimal
类就发挥了重要作用,它可以精确地表示和处理小数,避免了浮点数运算带来的精度误差。
BigDecimal
类的常用构造方法
通过字符串构造:最常用的构造方法是通过传入一个表示十进制小数的字符串来创建BigDecimal
对象,例如:
BigDecimal bd = new BigDecimal("3.1415926");
这样就创建了一个精确表示对应小数数值的BigDecimal
对象。
通过传入字符串形式的小数来初始化BigDecimal
对象,确保了数值的精确表示,因为如果直接使用类似 new BigDecimal(0.00)
或者 new BigDecimal(1.00)
这种写法(通过 double
或 float
值来构造),其实在将 double
、float
值转换为 BigDecimal
时就可能已经引入了精度问题(因为 double
、float
本身存在精度误差),所以推荐使用字符串形式来构造。
BigDecimal
类的常用方法
算术运算相关方法
add(BigDecimal augend)
方法:用于执行加法运算,返回当前BigDecimal
对象与传入的BigDecimal
对象augend
相加后的结果,也是一个BigDecimal
对象。例如:
BigDecimal num1 = new BigDecimal("1.23");
BigDecimal num2 = new BigDecimal("4.56");
BigDecimal sum = num1.add(num2);
System.out.println(sum);
会输出表示 5.79
的 BigDecimal
对象对应的字符串表示形式。
其他算术运算如减法、乘法、除法也都有对应的方法,且使用方式类似:
subtract(BigDecimal subtrahend)
方法:执行减法运算,返回当前BigDecimal
对象减去传入的BigDecimal
对象后的结果。multiply(BigDecimal multiplicand)
方法:执行乘法运算,返回两个BigDecimal
对象相乘后的结果。subtract(BigDecimal subtrahend)
方法:执行减法运算,返回当前BigDecimal
对象减去传入的BigDecimal
对象后的结果。divide(BigDecimal divisor)
方法:执行除法运算,不过这个方法相对复杂一些,因为在进行除法运算时可能会出现除不尽的情况,需要指定舍入模式等额外信息来确定最终的结果如何表示。例如:
BigDecimal num1 = new BigDecimal("10");
BigDecimal num2 = new BigDecimal("3");
// 指定舍入模式为四舍五入,保留2位小数
BigDecimal result = num1.divide(num2, 2, BigDecimal.ROUND_HALF_UP);
System.out.println(result);
上述代码会按照四舍五入的方式,将 10
除以 3
的结果保留两位小数后输出。
异常
集合类
Java中的集合类是用于存储和操作一组对象的容器,它们提供了丰富的功能和灵活的使用方式,能满足各种不同场景下对数据组织、管理和处理的需求。以下是对Java中常见集合类的详细介绍:
1. Collection
接口
Collection
是所有集合类的根接口,它定义了一些通用的集合操作方法,例如添加元素、删除元素、判断元素是否存在、获取集合大小等。但它是一个抽象的接口,不能直接被实例化,具体的实现由它的子接口和实现类来完成。
常用方法如下:
add(E e)
:向集合中添加一个指定的元素,如果添加成功返回true
,对于一些不允许重复元素的集合(如Set
接口的实现类),如果元素已存在则添加失败返回false
。remove(Object o)
:从集合中移除指定的元素,如果集合中存在该元素并成功移除则返回true
,否则返回false
。contains(Object o)
:判断集合中是否包含指定的元素,包含则返回true
,否则返回false
。size()
:返回集合中元素的个数。isEmpty()
:判断集合是否为空,为空则返回true
,否则返回false
。iterator()
:返回一个Iterator
对象,用于遍历集合中的元素,后续会详细介绍迭代器的使用。
2. List
接口及其实现类
List
接口特点
List
是Collection
的子接口,它允许存储重复元素,并且元素是有序的,即元素的存入顺序和取出顺序是一致的,每个元素都有对应的索引,可以通过索引来访问、修改元素。
常用实现类
ArrayList
内部实现原理:基于动态数组实现,当数组容量不足时会自动进行扩容操作。它在随机访问元素(通过索引访问)时效率很高,时间复杂度为O(1)
,但在插入和删除元素(尤其是在列表中间进行操作时)可能需要移动大量元素,平均时间复杂度为O(n)
,其中n
是集合中元素的数量。
示例代码:
import java.util.ArrayList;
import java.util.List;
public class ArrayListExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("apple"); // 允许重复元素
System.out.println("元素个数: " + list.size());
System.out.println("第二个元素: " + list.get(1));
list.remove(1); // 删除索引为1的元素
for (String element : list) {
System.out.println(element);
}
}
}
LinkedList
内部实现原理:采用双向链表结构实现,每个节点包含数据以及指向前一个节点和后一个节点的指针。它在插入和删除元素(特别是在表头或表尾操作时)效率较高,时间复杂度为O(1)
,但随机访问元素的效率相对较低,需要遍历链表来查找元素,时间复杂度为O(n)
。
示例代码:
import java.util.LinkedList;
import java.util.List;
public class LinkedListExample {
public static void main(String[] args) {
List<String> list = new LinkedList<>();
list.add("apple");
list.add("banana");
list.addFirst("orange"); // 在表头添加元素
list.addLast("pear"); // 在表尾添加元素
System.out.println("表头元素: " + list.get(0));
System.out.println("表尾元素: " + list.get(list.size() - 1));
list.removeFirst(); // 删除表头元素
list.removeLast(); // 删除表尾元素
for (String element : list) {
System.out.println(element);
}
}
}
Vector
和ArrayList
类似,也是基于数组实现的,但它是线程安全的,所有的方法都被synchronized
关键字修饰,这意味着在多线程环境下可以安全地使用,但相应地也会带来一定的性能开销,在单线程场景下一般优先选择ArrayList
。
3. Set
接口及其实现类
Set
接口特点:
Set
是Collection
的另一个子接口,它不允许存储重复元素,元素之间没有顺序(这里的没有顺序是指不像List
那样有明确的按照插入顺序排列的概念,但在某些实现类内部其实是有其特定存储顺序的,只是从集合使用者角度看元素无序)。
常用实现类:
HashSet
内部实现原理:基于哈希表(实际上是HashMap
的实例,后面会介绍HashMap
)实现,通过计算元素的哈希值来确定元素在集合中的存储位置,这样可以快速地判断元素是否存在、添加和删除元素等操作,添加、删除、查找操作的平均时间复杂度都接近O(1)
。但需要注意的是,它对于自定义的对象作为元素时,要正确重写hashCode()
和equals()
方法,否则可能无法正确判断元素重复情况。
示例代码:
import java.util.HashSet;
import java.util.Set;
public class HashSetExample {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("apple"); // 重复元素,不会被添加进去
System.out.println("元素个数: " + set.size());
for (String element : set) {
System.out.println(element);
}
}
}
TreeSet
:
内部实现原理:基于红黑树实现,它会对元素进行排序(自然排序或者根据传入的比较器进行排序),所以添加、删除、查找操作的时间复杂度为O(log n)
,其中n
是集合中元素的数量。它能保证元素在集合中是按照一定顺序存储的,适合需要对元素进行排序后操作的场景。
示例代码:
import java.util.Set;
import java.util.TreeSet;
public class TreeSetExample {
public static void main(String[] args) {
Set<Integer> set = new TreeSet<>();
set.add(5);
set.add(3);
set.add(7);
System.out.println("元素个数: " + set.size());
for (Integer element : set) {
System.out.println(element);
}
}
}
LinkedHashSet
:
内部实现原理:继承自HashSet
,在哈希表的基础上,使用链表来维护元素的插入顺序,所以它既具有HashSet
快速添加、查找、删除元素的特点(时间复杂度接近O(1)
),又能保证元素的插入顺序,遍历集合时元素的顺序就是它们插入集合的顺序。
示例代码:
import java.util.LinkedHashSet;
import java.util.Set;
public class LinkedHashSetExample {
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<>();
set.add("apple");
set.add("banana");
set.add("cherry");
System.out.println("元素个数: " + set.size());
for (String element : set) {
System.out.println(element);
}
}
}
4. Map
接口及其实现类
Map
接口特点
Map
接口用于存储键值对(key-value
)形式的数据,每个键(key
)在集合中是唯一的,通过键可以快速地获取对应的值(value
),它和Collection
接口没有继承关系,是一个独立的接口体系。
常用实现类
HashMap
:
内部实现原理:基于哈希表实现,通过对键计算哈希值来确定键值对在集合中的存储位置,能快速地进行查找、插入和删除操作,时间复杂度接近O(1)
。和HashSet
类似,对于自定义的键类型,需要正确重写hashCode()
和equals()
方法,以保证键的唯一性以及正确的操作逻辑。
示例代码:
import java.util.HashMap;
import java.util.Map;
public class HashMapExample {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("apple", 5);
map.put("banana", 3);
System.out.println("apple对应的数量: " + map.get("apple"));
if (map.containsKey("cherry")) {
System.out.println("存在cherry这个键");
} else {
System.out.println("不存在cherry这个键");
}
map.remove("banana");
for (String key : map.keySet()) {
System.out.println(key + " : " + map.get(key));
}
}
}
TreeMap
:
内部实现原理:基于红黑树实现,会根据键的自然顺序或者传入的比较器对键值对进行排序,查找、插入、删除操作的时间复杂度为O(log n)
,其中n
是集合中元素的数量。适合需要按照键的顺序来遍历、操作键值对的场景。
示例代码:
import java.util.Map;
import java.util.TreeMap;
public class TreeMapExample {
public static void main(String[] args) {
Map<Integer, String> map = new TreeMap<>();
map.put(5, "apple");
map.put(3, "banana");
System.out.println("最小的键对应的元素: " + map.get(map.firstKey()));
System.out.println("最大的键对应的元素: " + map.get(map.lastKey()));
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
}
}
LinkedHashMap
内部实现原理:继承自HashMap
,在哈希表的基础上,使用链表来维护键值对的插入顺序,所以它既有HashMap
快速操作的特点,又能保证键值对按照插入顺序来存储和遍历,兼具了高效性和顺序性。
示例代码:
import java.util.LinkedHashMap;
import java.util.Map;
public class LinkedHashMapExample {
public static void main(String[] args) {
Map<String, Integer> map = new LinkedHashMap<>();
map.put("apple", 5);
map.put("banana", 3);
System.out.println("按照插入顺序遍历:");
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
}
}
5. 迭代器(Iterator
)
迭代器用于遍历集合中的元素,所有实现了Collection
接口的集合类都可以通过iterator()
方法获取一个迭代器对象。它提供了以下几个主要方法:
hasNext()
:判断集合中是否还有下一个元素未被遍历,如果有则返回true
,否则返回false
。next()
:返回迭代器指向的下一个元素,并将迭代器指针向后移动一位,如果已经没有下一个元素还调用该方法会抛出NoSuchElementException
异常。remove()
:从集合中移除迭代器最后一次返回的元素,这个方法在使用时有一些限制,比如必须在调用next()
方法之后才能调用,而且对于一些不可变集合或者不支持删除操作的集合,调用该方法可能会抛出异常。
示例代码(以ArrayList
为例):
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
if ("banana".equals(element)) {
iterator.remove(); // 移除banana这个元素
}
}
for (String element : list) {
System.out.println(element);
}
}
}
HashSet的使用
import java.util.HashSet;
import java.util.Iterator;
public class SetSymmetricDifferenceExample {
public static void main(String[] args) {
Integer one = new Integer(1);
Integer two = new Integer(2);
Integer three = new Integer(3);
Integer four = new Integer(4);
Integer five = new Integer(5);
Integer six = new Integer(6);
HashSet<Integer> A = new HashSet<Integer>();
HashSet<Integer> B = new HashSet<Integer>();
HashSet<Integer> tempSet = new HashSet<Integer>();
A.add(one);
A.add(two);
A.add(three);
A.add(four);
B.add(one);
B.add(two);
B.add(five);
B.add(six);
tempSet = (HashSet<Integer>) A.clone();
A.removeAll(B); // A = A - B
B.removeAll(tempSet); // B = B - A
B.addAll(A); // (B - A) ∪ (A - B)
int number = B.size();
System.out.println("A和B的对称差集合中有" + number + "个元素");
Iterator<Integer> iter = B.iterator();
while (iter.hasNext()) {
Integer t = iter.next();
System.out.print(t.intValue() + " ");
}
}
}
运行上述代码,输出结果如下(元素顺序可能因HashSet
的无序特性而有所不同):
A和B的对称差集合中有4个元素
3 4 5 6
反射的使用
设计一个实体类rect,使用反射机制给length属性、width属性赋值。
import java.lang.reflect.Field;
class Rect {
private double length;
private double width;
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
@Override
public String toString() {
return "Rect{" +
"length=" + length +
", width=" + width +
'}';
}
}
public class rectTest {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Rect rect = new Rect();
// 获取Rect类的Class对象
Class<?> rectClass = Rect.class;
// 通过反射获取length属性
Field lengthField = rectClass.getDeclaredField("length");
// 设置可访问权限(因为属性是private的)
lengthField.setAccessible(true);
// 给length属性赋值
lengthField.set(rect, 5.0);
// 通过反射获取width属性
Field widthField = rectClass.getDeclaredField("width");
widthField.setAccessible(true);
widthField.set(rect, 3.0);
System.out.println(rect);
}
}
URL读取
在Java中,要读取URL网络资源(如网页等)的内容,可以使用java.net.URL
类以及相关的输入流来实现,以下是详细介绍:
使用java.net.URL
类结合BufferedReader
读取网页内容(基于字符流)
这是一种比较常见且简单的方式,适合读取文本格式的网络资源内容,例如网页的HTML代码等。
以下是示例代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
public class URLReaderExample {
public static void main(String[] args) {
try {
// 创建URL对象,指定要访问的网页地址
URL url = new URL("https://www.example.com");
// 打开与该URL的连接,并获取输入流
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
String line;
// 逐行读取网页内容并输出
while ((line = reader.readLine())!= null) {
System.out.println(line);
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
下面是对上述代码步骤的详细解释:
-
创建
URL
对象:
首先通过new URL("https://www.example.com")
创建了一个URL
对象,这里的参数就是要访问的网络资源的地址,可以替换成你实际想要读取的网页地址等。 -
获取输入流并包装为
BufferedReader
:
使用url.openStream()
方法打开与该URL
的连接,并获取一个输入流(InputStream
),这个输入流可以读取网页内容,但直接操作不太方便,所以将其包装到InputStreamReader
中,用于将字节流转换为字符流(因为网页内容一般按字符处理更方便),进而再包装到BufferedReader
中,BufferedReader
提供了方便的按行读取文本的功能(通过readLine
方法)。 -
逐行读取并输出内容:
通过while ((line = reader.readLine())!= null)
循环,不断从BufferedReader
中读取一行内容,如果读取到的行不为空,就将其输出到控制台,这样就实现了逐行读取并显示网页内容的功能。 -
关闭流资源:
最后使用reader.close()
关闭BufferedReader
,释放相关资源,需要注意在实际代码中,要确保流能正确关闭,一般可以考虑使用try-with-resources
语句块(后面会介绍)来更优雅地处理资源关闭问题。
2. 使用java.net.URL
类结合InputStream
读取网页内容(基于字节流,适合处理二进制数据等场景)
如果要处理的网络资源可能包含二进制数据(比如图片、视频等资源,不过这里示例还是以网页这种文本资源为主进行说明),或者你更习惯从字节层面操作数据,可以使用这种方式。示例代码如下:
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class URLInputStreamExample {
public static void main(String[] args) {
try {
URL url = new URL("https://www.example.com");
InputStream inputStream = url.openStream();
int data;
while ((data = inputStream.read())!= -1) {
System.out.write(data);
}
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
代码步骤解释:
-
创建
URL
对象并获取输入流:
和前面类似,先创建URL
对象,然后通过url.openStream()
获取输入流InputStream
,用于读取网页内容对应的字节数据。 -
逐字节读取并输出内容:
通过while ((data = inputStream.read())!= -1)
循环,不断从输入流中读取一个字节的数据(返回值是字节对应的int
类型值,当读到流末尾时返回 -1),然后使用System.write(data)
将读取到的字节输出到控制台(这里只是简单示例输出,实际可能需要根据具体需求做更多处理,比如写入到文件等)。 -
关闭流资源:
最后关闭输入流,释放资源。
3. 使用try-with-resources
语句块更优雅地处理流关闭(推荐方式)
前面介绍的代码在关闭流资源时,需要手动编写 close
方法调用语句,容易出现忘记关闭或者因为异常等情况导致关闭失败的问题。try-with-resources
语句块可以自动处理资源的关闭,确保资源能正确释放,提高代码的健壮性。以下是基于 try-with-resources
改写的前面第一种读取网页内容(基于字符流)的示例代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
public class URLReaderWithTryWithResourcesExample {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new URL("https://www.example.com").openStream()))) {
String line;
while ((line = reader.readLine())!= null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述代码中,将创建 BufferedReader
的语句放在 try
后面括号内,这样当 try
块中的代码执行完毕或者出现异常跳出 try
块时,BufferedReader
以及其关联的输入流等资源会自动被关闭,无需手动编写 close
语句,代码更加简洁和安全。
4. 使用HttpURLConnection
类进行更灵活的网络请求(可设置请求头、处理不同的HTTP响应状态等)
如果需要对网络请求进行更多的控制,比如设置请求头信息(例如设置 User-Agent
模拟不同的浏览器访问、设置请求的超时时间等),或者需要根据不同的HTTP响应状态码(如 200
表示成功、404
表示未找到资源等)来做不同的处理,那么可以使用 HttpURLConnection
类。以下是示例代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class HttpURLConnectionExample {
public static void main(String[] args) {
try {
URL url = new URL("https://www.example.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置请求方法(默认为GET,这里也可以设置为POST等其他方法)
connection.setRequestMethod("GET");
// 设置请求头,例如设置User-Agent模拟浏览器访问
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3");
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = reader.readLine())!= null) {
System.out.println(line);
}
reader.close();
} else {
System.out.println("请求失败,响应码: " + responseCode);
}
connection.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
代码步骤解释:
-
创建
HttpURLConnection
对象并进行基本设置:
先创建URL
对象,然后通过url.openConnection()
获取一个URLConnection
对象,再将其强制转换为HttpURLConnection
类型(因为我们要进行HTTP相关的更详细操作)。接着设置请求方法(一般网页读取常用GET
方法,也可以按需设置为POST
等其他方法),以及设置请求头信息(这里通过setRequestProperty
方法设置了User-Agent
,模拟浏览器访问,有助于避免一些网站因为识别到是非浏览器请求而拒绝访问等情况)。 -
获取响应状态码并根据情况处理:
通过connection.getResponseCode()
获取服务器返回的HTTP响应状态码,如果状态码是HttpURLConnection.HTTP_OK
(值为200
,表示请求成功),则按照前面介绍的方式,通过BufferedReader
读取并输出网页内容;如果不是200
状态码,则输出请求失败及对应的响应码信息。 -
断开连接:
最后使用connection.disconnect()
断开与服务器的连接,释放相关资源。
总之,在Java中读取网络资源(如网页)内容有多种方式,你可以根据实际需求选择简单的直接使用 URL
类结合输入流读取的方式,或者更灵活地使用 HttpURLConnection
类来处理不同的网络请求情况以及应对更复杂的网络资源读写需求。
需要注意的是,在实际应用中,如果要大规模爬取网页等操作,可能需要遵循网站的 robots.txt
协议以及相关法律法规,避免因非法爬取数据带来不必要的法律问题。
拓展知识
- 处理网页内容解析:读取到网页内容(一般是HTML代码)后,往往还需要进一步解析其中的信息,例如提取网页中的标题、正文内容、链接等。可以使用一些HTML解析库,如
Jsoup
等,它提供了方便的API来解析HTML文档,查找元素、获取属性等操作,示例代码如下:
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.net.URL;
public class JsoupExample {
public static void main(String[] args) {
try {
URL url = new URL("https://www.example.com");
Document document = Jsoup.parse(new URL(url.toString()).openStream(), "UTF-8", url.toString());
// 获取网页标题
String title = document.title();
System.out.println("网页标题: " + title);
// 获取网页中所有的链接
Elements links = document.select("a[href]");
for (Element link : links) {
System.out.println("链接: " + link.attr("href"));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
上述代码使用 Jsoup
库先将网页内容解析为 Document
对象,然后可以方便地获取网页标题、查找所有带 href
属性的链接元素并输出它们的链接地址等信息,方便从网页中提取出有用的数据。
- 网络资源写入操作(相对复杂且需服务器端支持):要向网络资源(比如向服务器上传文件等)写入内容,一般需要服务器端提供相应的接口(如支持
POST
上传文件的接口等),在Java端除了前面提到的设置请求方法为POST
等操作外,还需要按照服务器要求的格式(比如multipart/form-data
格式用于上传文件等)来组织数据并发送请求,通常会涉及到更复杂的网络编程知识以及对HTTP协议中请求体、请求头相关内容的准确把握,这里暂不展开详细介绍,但大致思路是基于HttpURLConnection
或者一些更高级的网络库(如Apache HttpClient
等)来构建合适的请求发送到服务器端进行写入操作。
文件操作
import java.io.*;
public class FileReadWriteExample {
public static void main(String[] args) {
try {
FileOutputStream o1 = new FileOutputStream("a.txt");
FileOutputStream o2 = new FileOutputStream("b.txt");
BufferedOutputStream ou1 = new BufferedOutputStream(o1);
BufferedOutputStream ou2 = new BufferedOutputStream(o2);
BufferedInputStream in1 = new BufferedInputStream(new FileInputStream("Filename.txt"));
BufferedReader reader = new BufferedReader(new InputStreamReader(in1));
String searchStr = "example"; // 设定要查找的字符串
String line;
while ((line = reader.readLine())!= null) {
if (line.indexOf(searchStr)!= -1) {
ou1.write(line.getBytes());
ou1.write("\n".getBytes());
} else {
ou2.write(line.getBytes());
ou2.write("\n".getBytes());
}
}
ou1.close();
ou2.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
判断指定目录下是否有后缀名为 .jpg 的文件,如果有,就输出该文件名称
public class test {
public static void main(String[] args) throws IOException {
File dir = new File("D:\\Wallpaper");
String[] listFiles = dir.list();
for(String s : listFiles){
if(s.endsWith(".jpg")){
System.out.println(s);
}
}
}
}
方案二:
public class test {
public static void main(String[] args) throws IOException {
File dir = new File("D:\\Wallpaper");
String[] s = dir.list(new FilenameFilter() {
@Override
// name 即为子文件或子文件目录的名称
public boolean accept(File dir, String name) {
return name.endsWith(".jpg");
}
});
for(String s1 : s){
System.out.println(s1);
}
}
}
遍历指定文件目录下的所有文件的名称,包括子文件目录中的文件。
public void printFileName(File file) //file可能是文件,也可能是文件目录
public class test {
public static void main(String[] args) throws IOException {
File file1 = new File("D:\\document");
printFileName(file1);
}
public static void printFileName(File file){
if(file.isFile()){
System.out.println(file.getName());
return ;
}else{
File[] files = file.listFiles();
for(File f : files){
printFileName(f);
}
}
}
}
删除指定文件目录及其下的所有文件
public void deleteFileName(File file){
// 如果file是一个目录,需要先将其子文件删除
if(file.isDirectory()){
File[] files = file.listFiles();
for(File s : files){
deleteFileName(s);
}
}
// 如果是文件直接删除即可
file.delete();
}
Calendar类
打印日历
Scanner scanner = new Scanner(System.in);
// 获取用户输入的年份和月份
System.out.print("请输入年份: ");
int year = scanner.nextInt();
System.out.print("请输入月份(1-12): ");
int month = scanner.nextInt();
// 打印星期几的标题
System.out.printf("%4s%4s%4s%4s%4s%4s%4s\n", "日", "一", "二", "三", "四", "五", "六");
// 获取Calendar实例并设置年份和月份
Calendar calendar = Calendar.getInstance();
calendar.set(year, month - 1, 1); // 月份从0开始,所以要减1
// 计算该月的第一天是星期几,并获取该月的天数
int firstDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1;
int daysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
// 初始化数组
int[] days = new int[firstDayOfWeek + daysInMonth];
// 填充数组
for (int i = 0; i < firstDayOfWeek; i++) {
days[i] = 0; // 填充前面的空格
}
for (int i = firstDayOfWeek, dayNum = 1; dayNum <= daysInMonth; i++, dayNum++) {
days[i] = dayNum;
}
// 打印数组
for (int i = 0; i < days.length; i++) {
if (i % 7 == 0 && i != 0) {
System.out.println(); // 换行
}
if (days[i] != 0) {
System.out.printf("%5d", days[i]); // 打印日期
} else {
System.out.printf("%5s", " "); // 打印空格
}
}
scanner.close();