[toc]

源码方法的概念和用法

system中的arraycopy的概念和用法

System.arraycopy() 是Java中用于复制一个数组的方法。它的语法如下:

public static void arraycopy
(Object src, int srcPos, Object dest, int destPos, int length)

其中,参数含义如下:

  • src:源数组,需要复制的数组。
  • srcPos:源数组的开始位置,复制的起始位置。
  • dest:目标数组,复制的结果存放的位置。
  • destPos:目标数组的起始位置,从该位置开始存放复制后的数据。
  • length:需要复制的元素个数。

注意,该方法只能复制数组中的内容,并不能复制数组对象的引用。

下面是一个示例:

int[] sourceArray = {1, 2, 3, 4, 5};
int[] destinationArray = new int[5];

System.arraycopy(sourceArray, 0, destinationArray, 0, sourceArray.length);

// 输出 destinationArray 的内容
System.out.println(Arrays.toString(destinationArray));

//输出结果为:
[1, 2, 3, 4, 5]

可以看到,通过 System.arraycopy() 方法,我们将源数组中的元素复制到了目标数组中。

除了基本类型的数组,System.arraycopy() 方法还可以用于复制对象类型的数组。示例代码如下:

Person[] sourceArray = new Person[3];
sourceArray[0] = new Person("Tom", 25);
sourceArray[1] = new Person("Jerry", 20);
sourceArray[2] = new Person("Alice", 30);

Person[] destinationArray = new Person[3];

System.arraycopy(sourceArray, 0, destinationArray, 0, sourceArray.length);

// 输出 destinationArray 的内容
System.out.println(Arrays.toString(destinationArray));

在这个示例中,我们定义了一个 Person 类,用于存储一个人的姓名和年龄信息。我们创建了一个 Person 类型的数组 sourceArray,并向其中添加了三个元素。然后,我们创建了一个与 sourceArray 同样大小的 Person 类型的数组 destinationArray,并调用 System.arraycopy() 方法将 sourceArray 中的元素复制到 destinationArray 中。最后,我们输出 destinationArray 的内容。

需要注意的是,在复制对象类型的数组时,System.arraycopy() 方法只会复制数组对象的引用,而不会复制对象本身。因此,如果修改 sourceArray 中的元素,那么 destinationArray 中对应的元素也会发生变化。

 另外需要注意的是,System.arraycopy() 方法的性能比通过循环逐个复制数组元素的方式要高效得多。这是因为 System.arraycopy() 方法使用了底层的系统级别的内存拷贝操作,而循环逐个复制则需要进行多次内存读写操作,开销较大。

需要注意的是,如果使用 System.arraycopy() 方法复制数组时,源数组和目标数组重叠部分的数据会被覆盖。因此,在进行这种操作时,一定要确保源数组和目标数组没有重叠部分,否则会导致不可预期的结果。

在Java中,还有一个与 System.arraycopy() 方法功能类似的方法 Arrays.copyOf(),它的使用方法类似于 System.arraycopy() 方法。但是,与 System.arraycopy() 方法不同的是,Arrays.copyOf() 方法可以在复制数组的同时指定新数组的长度,如果新数组长度大于源数组长度,则剩余的元素会被填充为默认值。示例代码如下:

int[] sourceArray = {1, 2, 3, 4, 5};

int[] destinationArray = Arrays.copyOf(sourceArray, 10);

// 输出 destinationArray 的内容
System.out.println(Arrays.toString(destinationArray));

在这个示例中,我们将 sourceArray 复制到 destinationArray 中,并将 destinationArray 的长度设置为10。由于 sourceArray 的长度为5,因此在复制完成后,destinationArray 的后5个元素会被填充为默认值0。输出结果为:

[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]

总之,System.arraycopy() 方法是Java中用于复制数组的高效方法之一。它可以用于复制基本类型和对象类型的数组,并且使用简单,性能高效。

UsernamePasswordToken的概念和用法

UsernamePasswordToken 是 Shiro 中用于表示用户名密码的类。在使用 Shiro 进行身份认证时,通常需要使用 UsernamePasswordToken 类来封装用户提交的用户名和密码信息。

UsernamePasswordToken 类继承自 org.apache.shiro.authc.AuthenticationToken 接口,该接口代表一个身份认证令牌。AuthenticationToken 接口包含了一些通用的方法,用于获取认证信息。

UsernamePasswordToken 类的构造方法如下:

public UsernamePasswordToken(String username, String password)

其中,usernamepassword 分别表示用户提交的用户名和密码信息。此外,UsernamePasswordToken 类还提供了一些方法,用于获取和设置用户名、密码、是否记住我等信息,例如:

// 获取用户名
String username = token.getUsername();

// 获取密码
String password = String.valueOf(token.getPassword());

// 设置是否记住我
token.setRememberMe(true);

在使用 Shiro 进行身份认证时,通常需要将用户提交的 UsernamePasswordToken 对象传递给 Shiro 的 SecurityManager 进行认证。示例代码如下:

// 创建一个 UsernamePasswordToken 对象,封装用户提交的用户名和密码信息
UsernamePasswordToken token = new UsernamePasswordToken("username", "password");

// 获取 SecurityManager 实例
SecurityManager securityManager = new DefaultSecurityManager();

// 将 token 对象传递给 SecurityManager 进行身份认证
try {
securityManager.authenticate(token);
} catch (AuthenticationException e) {
// 身份认证失败
e.printStackTrace();
}

在上面的示例中,我们创建了一个 UsernamePasswordToken 对象,并将用户提交的用户名和密码信息封装在该对象中。然后,我们获取了一个 SecurityManager 实例,并将 UsernamePasswordToken 对象传递给 SecurityManagerauthenticate() 方法进行身份认证。如果身份认证失败,将会抛出 AuthenticationException 异常。

需要注意的是,为了防止密码在内存中被明文存储,UsernamePasswordToken 中的密码是通过 char[] 类型存储的,而不是字符串。如果需要获取密码的字符串表示,可以使用 String.valueOf(token.getPassword()) 方法。此外,为了提高安全性,应该在使用完 UsernamePasswordToken 对象后及时清除密码信息,例如:

// 使用完 token 对象后,清除密码信息
token.clear();

总之,UsernamePasswordToken 是 Shiro 中用于封装用户提交的用户名和密码信息的类,它是进行身份认证的重要组成部分。

除了用于封装用户名和密码信息外,UsernamePasswordToken 还可以用于实现基于验证码的身份认证。在这种情况下,UsernamePasswordToken 除了封装用户名和密码信息外,还需要封装验证码信息。

为了实现基于验证码的身份认证,可以自定义一个 CaptchaUsernamePasswordToken 类,该类继承自 UsernamePasswordToken 类,并添加一个 captcha 属性用于存储验证码信息。示例代码如下:

public class CaptchaUsernamePasswordToken extends UsernamePasswordToken {
private String captcha;

public CaptchaUsernamePasswordToken(String username, String password, String captcha) {
super(username, password);
this.captcha = captcha;
}

public String getCaptcha() {
return captcha;
}

public void setCaptcha(String captcha) {
this.captcha = captcha;
}
}

在上面的示例中,我们创建了一个 CaptchaUsernamePasswordToken 类,该类继承自 UsernamePasswordToken 类,并添加了一个 captcha 属性用于存储验证码信息。在构造方法中,除了调用 super(username, password) 方法传递用户名和密码信息外,还需要将验证码信息传递给 CaptchaUsernamePasswordToken 对象进行封装。

在进行身份认证时,可以将 CaptchaUsernamePasswordToken 对象传递给 SecurityManager 进行认证。示例代码如下:

// 创建一个 CaptchaUsernamePasswordToken 对象,封装用户名、密码和验证码信息
CaptchaUsernamePasswordToken token = new CaptchaUsernamePasswordToken("username", "password", "1234");

// 获取 SecurityManager 实例
SecurityManager securityManager = new DefaultSecurityManager();

// 将 token 对象传递给 SecurityManager 进行身份认证
try {
securityManager.authenticate(token);
} catch (AuthenticationException e) {
// 身份认证失败
e.printStackTrace();
}

在上面的示例中,我们创建了一个 CaptchaUsernamePasswordToken 对象,并将用户名、密码和验证码信息封装在该对象中。然后,我们获取了一个 SecurityManager 实例,并将 CaptchaUsernamePasswordToken 对象传递给 SecurityManagerauthenticate() 方法进行身份认证。如果身份认证失败,将会抛出 AuthenticationException 异常。

总之,UsernamePasswordToken 是 Shiro 中用于封装用户名和密码信息的类,可以用于实现基于密码和基于验证码的身份认证。在使用时,我们可以根据具体的需求来选择使用 UsernamePasswordTokenCaptchaUsernamePasswordToken 类。

StringUtils.IsNotEmpty()的概念和用法

StringUtils.isNotEmpty() 是 Apache Commons Lang3 库中的一个静态方法,用于判断字符串是否不为空。其语法如下:

public static boolean isNotEmpty(CharSequence cs)

其中,cs 参数表示要判断的字符串,可以是一个 CharSequence 对象或者是一个字符串字面量。

StringUtils.isNotEmpty() 方法的返回值为 boolean 类型,如果 cs 不为空且长度不为 0,则返回 true,否则返回 false。需要注意的是,如果 cs 参数为 null,则该方法也会返回 false

使用示例:

import org.apache.commons.lang3.StringUtils;

public class StringUtilsExample {
public static void main(String[] args) {
String str1 = null;
String str2 = "";
String str3 = " ";
String str4 = "hello";

System.out.println(StringUtils.isNotEmpty(str1)); // false
System.out.println(StringUtils.isNotEmpty(str2)); // false
System.out.println(StringUtils.isNotEmpty(str3)); // true
System.out.println(StringUtils.isNotEmpty(str4)); // true
}
}

在上面的示例中,我们使用了 StringUtils.isNotEmpty() 方法来判断四个不同的字符串。第一个字符串为 null,第二个字符串为空字符串,第三个字符串为一个空格字符串,第四个字符串为非空字符串。根据方法的返回值,我们可以看到只有第三个和第四个字符串被判定为非空字符串。

因此,StringUtils.isNotEmpty() 方法是一个非常实用的工具方法,可以在日常开发中用于判断字符串是否为空,避免因为空字符串而引发的空指针异常。

shiro中判断验证码是否正确的流程

在 Shiro 中,判断验证码是否正确的源码流程如下:

  1. 首先,客户端向服务端发送登录请求,请求中包含用户名、密码和验证码等信息。
  2. 服务端接收到登录请求后,通过 CaptchaUsernamePasswordToken 类将用户名、密码和验证码信息封装成一个 token 对象。
  3. 服务端通过 SecurityUtils.getSubject() 方法获取当前用户的主体对象 Subject
  4. 服务端通过 subject.login(token) 方法将封装好的 token 对象传递给 SecurityManager 进行身份认证。
  5. 在进行身份认证时,CaptchaMatcher 类会被调用,用于判断验证码是否正确。CaptchaMatcher 类继承自 SimpleCredentialsMatcher 类,重写了 doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) 方法。
  6. doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) 方法首先会将 token 转换为 CaptchaUsernamePasswordToken 对象,并从该对象中获取验证码信息。
  7. 然后,doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) 方法会从 info 中获取正确的验证码信息。
  8. 最后,doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) 方法将用户输入的验证码信息和正确的验证码信息进行比较,如果相同则返回 true,否则返回 false

以上就是 Shiro 中判断验证码是否正确的源码流程。可以看出,Shiro 在身份认证时使用了 CaptchaMatcher 类来判断验证码是否正确,该类继承自 SimpleCredentialsMatcher 类,重写了 doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) 方法。在该方法中,Shiro 通过获取 CaptchaUsernamePasswordToken 对象和 AuthenticationInfo 对象中的验证码信息来进行比较,以判断验证码是否正确。

shiro中判断验证码如何代码实现

在 Shiro 中,判断验证码的实现通常需要编写一个自定义的 CaptchaMatcher 类。该类继承自 SimpleCredentialsMatcher 类,重写了 doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) 方法,用于判断验证码是否正确。

具体实现过程如下:

  1. 首先,在 CaptchaMatcher 类中定义一个常量 CAPTCHA_SESSION_KEY,用于存储验证码的 Session Key 值。
public class CaptchaMatcher extends SimpleCredentialsMatcher {

private static final String CAPTCHA_SESSION_KEY = "captcha_key";

// ...
}

  1. 然后,重写 doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) 方法,用于判断验证码是否正确。在该方法中,首先将 token 转换为 CaptchaUsernamePasswordToken 对象,并从该对象中获取验证码信息。然后,从 info 中获取正确的验证码信息。最后,将用户输入的验证码信息和正确的验证码信息进行比较,以判断验证码是否正确。
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
CaptchaUsernamePasswordToken captchaToken = (CaptchaUsernamePasswordToken) token;
String captcha = captchaToken.getCaptcha();
String savedCaptcha = (String) SecurityUtils.getSubject().getSession().getAttribute(CAPTCHA_SESSION_KEY);
return StringUtils.equalsIgnoreCase(captcha, savedCaptcha) && super.doCredentialsMatch(token, info);
}

在上面的实现中,我们首先将 token 转换为 CaptchaUsernamePasswordToken 对象,然后通过 getCaptcha() 方法获取用户输入的验证码信息。接着,通过 SecurityUtils.getSubject().getSession().getAttribute(CAPTCHA_SESSION_KEY) 方法从 Session 中获取正确的验证码信息。最后,将用户输入的验证码信息和正确的验证码信息进行比较,以判断验证码是否正确。

需要注意的是,在比较验证码信息时,我们使用了 Apache Commons Lang3 库中的 StringUtils.equalsIgnoreCase() 方法,该方法可以忽略验证码信息的大小写,以提高验证码匹配的容错率。

  1. 最后,将自定义的 CaptchaMatcher 类配置到 Shiro 的配置文件中,以启用验证码校验功能。
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm" />
</bean>

<bean id="myRealm" class="com.example.MyRealm">
<property name="credentialsMatcher">
<bean class="com.example.CaptchaMatcher" />
</property>
</bean>

在上面的配置中,我们通过 credentialsMatcher 属性指定了 CaptchaMatcher 类,以替换默认的 SimpleCredentialsMatcher 类。这样,在进行身份认证时,Shiro 将会使用自定义的 CaptchaMatcher 类来判断验证码是否正确。

  1. 在前端页面中添加验证码输入框,并在表单中添加该输入框的名称。
<label for="captcha">验证码:</label>
<input type="text" name="captcha" id="captcha" required>
<img src="/captcha.jpg" alt="验证码">

在上面的示例中,我们使用 img 标签显示验证码图片,并将该图片的 src 属性设置为 /captcha.jpg,以从服务器获取验证码图片。此外,我们还添加了一个 input 标签,用于输入验证码。需要注意的是,我们需要将该输入框的名称设置为 captcha,以便在后端代码中获取用户输入的验证码信息。

  1. 在后端代码中生成和保存验证码信息。
@GetMapping("/captcha.jpg")
public void captcha(HttpServletResponse response) throws IOException {
// 生成验证码
String captcha = generateCaptcha();

// 将验证码保存到 Session 中
SecurityUtils.getSubject().getSession().setAttribute(CAPTCHA_SESSION_KEY, captcha);

// 输出验证码图片
response.setContentType("image/jpeg");
BufferedImage image = generateCaptchaImage(captcha);
ImageIO.write(image, "jpg", response.getOutputStream());
}

在上面的示例中,我们首先使用 generateCaptcha() 方法生成一个验证码字符串,然后将该字符串保存到 Session 中。最后,我们使用 generateCaptchaImage(captcha) 方法生成验证码图片,并将其输出到客户端浏览器。

需要注意的是,我们在上面的代码中使用了 SecurityUtils.getSubject().getSession() 方法获取当前用户的 Session 对象,并将验证码信息保存到 Session 中。这样,当用户提交表单时,我们就可以从 Session 中获取正确的验证码信息,并将其与用户输入的验证码信息进行比较,以判断验证码是否正确。

完整的验证码校验代码示例:

public class CaptchaMatcher extends SimpleCredentialsMatcher {

private static final String CAPTCHA_SESSION_KEY = "captcha_key";

@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
CaptchaUsernamePasswordToken captchaToken = (CaptchaUsernamePasswordToken) token;
String captcha = captchaToken.getCaptcha();
String savedCaptcha = (String) SecurityUtils.getSubject().getSession().getAttribute(CAPTCHA_SESSION_KEY);
return StringUtils.equalsIgnoreCase(captcha, savedCaptcha) && super.doCredentialsMatch(token, info);
}

private String generateCaptcha() {
// 生成 6 位随机数字验证码
Random random = new Random();
return String.format("%06d", random.nextInt(1000000));
}

private BufferedImage generateCaptchaImage(String captcha) throws IOException {
// 将验证码渲染成图片
int width = 100;
int height = 40;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
g.setColor(Color.BLACK);
g.setFont(new Font("Arial", Font.BOLD, 20));
g.drawString(captcha, 10, 25);
g.dispose();
return image;
}
}

  1. 在 Shiro 配置文件中启用验证码校验

在 Shiro 配置文件中,我们需要启用验证码校验功能,以便在用户登录时对验证码进行校验。具体来说,我们需要将上面的 CaptchaMatcher 类配置为自定义的凭证匹配器,并将该匹配器与登录认证过滤器一起使用。完整的 Shiro 配置文件示例:

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
</bean>

<bean id="myRealm" class="com.example.MyRealm">
<property name="credentialsMatcher">
<bean class="com.example.CaptchaMatcher"/>
</property>
</bean>

<bean id="captchaFilter" class="com.example.CaptchaFilter">
<property name="captchaParam" value="captcha"/>
<property name="failureKeyAttribute" value="shiroLoginFailure"/>
<property name="captchaEnabled" value="true"/>
</bean>

<bean id="authcFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
<property name="usernameParam" value="username"/>
<property name="passwordParam" value="password"/>
<property name="loginUrl" value="/login"/>
<property name="successUrl" value="/index"/>
<property name="failureKeyAttribute" value="shiroLoginFailure"/>
</bean>

<bean id="shiroFilter" class="org.apache.shiro.web.servlet.ShiroFilter">
<property name="securityManager" ref="securityManager"/>
<property name="filters">
<map>
<entry key="authc" value-ref="authcFilter"/>
<entry key="captcha" value-ref="captchaFilter"/>
</map>
</property>
<property name="filterChainDefinitions">
<value>
/login = captcha,authc
/index = authc
/logout = logout
/** = anon
</value>
</property>
</bean>

在上面的示例中,我们首先将自定义的 CaptchaMatcher 类配置为凭证匹配器,并将其注入到 MyRealm 中。然后,我们定义了一个名为 captchaFilter 的过滤器,该过滤器会拦截所有以 /login 开头的请求,并对验证码进行校验。最后,我们将该过滤器与登录认证过滤器一起使用,并将其添加到 Shiro 过滤器链中。

需要注意的是,我们在上面的代码中通过 captchaEnabled 属性启用了验证码校验功能。当该属性的值为 true 时,Shiro 会自动调用 CaptchaFilter 中的 onFilterConfigSet() 方法,以便在过滤器初始化时生成和保存验证码信息。

这样,当用户登录时,Shiro 会自动将用户输入的验证码与保存在 Session 中的正确验证码进行比较,以判断验证码是否正确。如果验证码校验失败,Shiro 会自动将错误信息保存到 shiroLoginFailure 属性中,以便在前端页面中显示错误提示信息。

Shiro中的subject接口中的getPrincipal()的概念和用法

在 Shiro 中,Subject 是一个用户的安全主体,表示与应用程序交互的当前用户。Subject 中的 getPrincipal() 方法用于获取当前用户的身份信息,通常是用户名、邮箱、手机号码等唯一标识用户身份的信息。getPrincipal() 方法返回的对象通常是一个 java.lang.Object 类型,开发者可以根据实际情况将其转换为特定的类型。

在 Shiro 中,Principal 表示当前用户的身份信息,可以通过 getPrincipal() 方法获取。在认证成功后,Shiro 会将用户的身份信息保存在 Subject 中,以便在后续的操作中进行访问和使用。通常情况下,我们可以通过调用 getPrincipal() 方法来获取当前用户的身份信息,并进行相应的业务处理,如记录日志、权限控制等。

下面是一个简单的示例,演示了如何使用 getPrincipal() 方法获取当前用户的身份信息:

// 获取当前用户
Subject subject = SecurityUtils.getSubject();
// 判断用户是否已经登录
if (subject.isAuthenticated()) {
// 获取用户身份信息
Object principal = subject.getPrincipal();
// 判断身份信息是否为空
if (principal != null) {
// 将身份信息转换为特定类型
String username = (String) principal;
// 打印用户信息
System.out.println("当前用户:" + username);
}
}

在上面的示例中,我们首先通过 SecurityUtils.getSubject() 方法获取当前用户的 Subject 对象,然后调用 isAuthenticated() 方法判断用户是否已经登录。如果用户已经登录,我们就可以通过 getPrincipal() 方法获取用户的身份信息,并将其转换为特定类型,这里我们将其转换为字符串类型。最后,我们可以使用获取到的用户信息进行相应的业务处理。

需要注意的是,getPrincipal() 方法返回的对象类型取决于用户在认证过程中设置的身份验证信息。例如,如果用户使用用户名和密码进行身份验证,则 getPrincipal() 方法通常返回用户名。如果用户使用电子邮件地址或手机号码进行身份验证,则 getPrincipal() 方法通常返回相应的电子邮件地址或手机号码。开发者应该根据实际情况将返回的对象转换为特定的类型,并进行相应的处理。

使用EasyExcel读写一个类

使用 EasyExcel 读写一个类,一般分为以下几个步骤:

  1. 定义一个实体类,并使用 @ExcelProperty 注解指定每个字段对应的 Excel 表头。
  2. 使用 ExcelReader 创建一个读取器,并使用 read 方法读取 Excel 文件。
  3. 使用 ExcelWriter 创建一个写入器,并使用 write 方法将数据写入 Excel 文件。

下面我们来具体介绍这几个步骤。

1. 定义一个实体类

假设我们需要读写一个学生信息表,包括学生姓名、学生性别和学生年龄。那么我们可以定义一个 Student 类,代码如下:

public class Student {
@ExcelProperty("姓名")
private String name;

@ExcelProperty("性别")
private String gender;

@ExcelProperty("年龄")
private Integer age;

// getter 和 setter 略
}

在这个类中,我们使用 @ExcelProperty 注解为每个字段指定了对应的 Excel 表头,例如 @ExcelProperty("姓名") 表示姓名字段对应的 Excel 表头为“姓名”。

2. 使用 ExcelReader 读取 Excel 文件

读取 Excel 文件的步骤如下:

  1. 创建一个 ExcelReader 对象。
  2. 定义一个 AnalysisEventListener 对象,用于处理读取到的 Excel 数据。
  3. 调用 read 方法读取 Excel 文件。

下面是一个示例代码:

public class ReadExcelDemo {
public static void main(String[] args) {
// 创建 ExcelReader 对象
ExcelReader excelReader = new ExcelReader(new FileInputStream("students.xlsx"), ExcelTypeEnum.XLSX, null, new AnalysisEventListener<Student>() {
@Override
public void invoke(Student data, AnalysisContext context) {
// 处理读取到的数据
System.out.println(data);
}

@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 读取完成后的回调方法
}
});

// 读取 Excel 文件
excelReader.read(new Sheet(1, 1, Student.class));
}
}

在这个示例中,我们首先创建了一个 ExcelReader 对象,它接受四个参数:

  • 第一个参数是 Excel 文件的输入流。
  • 第二个参数是 Excel 文件的类型,这里我们指定为 .xlsx 格式。
  • 第三个参数是读取 Excel 文件时使用的读取器,这里我们使用默认的读取器,所以指定为 null
  • 第四个参数是一个 AnalysisEventListener 对象,用于处理读取到的 Excel 数据。在这里,我们实现了 invoke 方法,用于处理每一行读取到的数据,以及 doAfterAllAnalysed 方法,用于在读取完成后进行一些操作。

然后我们调用 read 方法读取 Excel 文件,它接受一个 Sheet 对象作为参数,表示需要读取的工作表信息。在这里,我们指定要读取第 1 个工作表,从第 1 行开始读

写入 Excel 文件的步骤如下:

  1. 创建一个 ExcelWriter 对象。
  2. 调用 write 方法写入数据。
  3. 调用 finish 方法保存 Excel 文件。

下面是一个示例代码:

public class WriteExcelDemo {
public static void main(String[] args) {
// 准备写入的数据
List<Student> students = new ArrayList<>();
students.add(new Student("张三", "男", 18));
students.add(new Student("李四", "女", 19));
students.add(new Student("王五", "男", 20));

// 创建 ExcelWriter 对象
ExcelWriter excelWriter = new ExcelWriter(new FileOutputStream("students.xlsx"), ExcelTypeEnum.XLSX);

// 写入 Excel 文件
WriteSheet writeSheet = EasyExcel.writerSheet("学生信息").head(Student.class).build();
excelWriter.write(students, writeSheet);

// 保存 Excel 文件
excelWriter.finish();
}
}

在这个示例中,我们首先准备了一个要写入的学生列表。然后,我们创建了一个 ExcelWriter 对象,它接受两个参数:

  • 第一个参数是 Excel 文件的输出流。
  • 第二个参数是 Excel 文件的类型,这里我们指定为 .xlsx 格式。

然后,我们使用 EasyExcel 工具类提供的 writerSheet 方法创建了一个 WriteSheet 对象,并通过调用 head 方法指定表头的类为 Student.class。然后,我们调用 write 方法将数据写入 Excel 文件。

最后,我们调用 finish 方法保存 Excel 文件。

easyexcel导入导出代码示例

以下是使用 EasyExcel 进行导入和导出的示例代码:

导出 Excel:

// 定义 Excel 表头
List<List<String>> head = new ArrayList<>();
head.add(Arrays.asList("学号", "姓名", "年龄", "性别"));

// 获取数据列表
List<Student> studentList = studentService.list();

// 组装数据,将 Student 对象转换为 List<List<String>> 格式
List<List<String>> data = studentList.stream().map(student -> {
List<String> rowData = new ArrayList<>();
rowData.add(student.getNo());
rowData.add(student.getName());
rowData.add(String.valueOf(student.getAge()));
rowData.add(student.getGender() == Gender.MALE ? "男" : "女");
return rowData;
}).collect(Collectors.toList());

// 设置文件名
String fileName = "学生列表";

// 构造 ExcelWriter 对象
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
.autoCloseStream(true)
.excelType(ExcelTypeEnum.XLSX)
.build();

// 构造 Sheet 对象
Sheet sheet = new Sheet(1, 0);

// 设置表头
sheet.setHead(head);

// 设置数据
sheet.setSheetName(fileName);
excelWriter.write1(data, sheet);

// 关闭 ExcelWriter 对象
excelWriter.finish();

导入 Excel:

// 构造 ExcelReader 对象
ExcelReader excelReader = EasyExcel.read(inputStream).build();

// 读取第一个 Sheet
Sheet sheet = new Sheet(1, 1);

// 设置读取监听器
excelReader.read(sheet, new AnalysisEventListener<ExcelStudentDTO>() {
@Override
public void invoke(ExcelStudentDTO excelStudentDTO, AnalysisContext analysisContext) {
// 将 Excel 行数据转换为 Student 对象,并插入数据库
Student student = new Student();
student.setNo(excelStudentDTO.getNo());
student.setName(excelStudentDTO.getName());
student.setAge(Integer.parseInt(excelStudentDTO.getAge()));
student.setGender(excelStudentDTO.getGender().equals("男") ? Gender.MALE : Gender.FEMALE);
studentService.save(student);
}

@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// 解析完成后的回调方法
}
});

// 关闭 ExcelReader 对象
excelReader.finish();

其中,ExcelStudentDTO 是一个用于接收 Excel 数据的类,定义如下:

@Data
public class ExcelStudentDTO {
@ExcelProperty("学号")
private String no;

@ExcelProperty("姓名")
private String name;

@ExcelProperty("年龄")
private String age;

@ExcelProperty("性别")
private String gender;
}

补充一些说明:

  • ExcelWriterExcelReader 分别代表导出和导入 Excel 的对象,都需要通过 EasyExcel 工具类的静态方法 write()read() 进行构造。
  • Sheet 代表一个 Excel 的 Sheet,其中第一个参数是 sheet 的序号,第二个参数是从第几行开始读取/写入,默认是从第 0 行开始。
  • AnalysisEventListener 是 EasyExcel 提供的一个读取监听器,需要实现其中的 invoke() 方法,将读取到的数据转换为需要的对象,并插入到数据库中。
  • ExcelProperty 是 EasyExcel 提供的一个注解,用于标识 Excel 表头中的每个字段。
  • autoCloseStream() 方法用于在写入 Excel 数据完成后自动关闭输出流。
  • excelType() 方法用于指定导出的 Excel 类型,支持 ExcelTypeEnum.XLSExcelTypeEnum.XLSX
  • finish() 方法用于关闭 ExcelWriter 或 ExcelReader 对象,释放资源。

需要注意的是,以上代码仅为示例代码,实际应用中需要根据具体需求进行修改和优化。同时,EasyExcel 还提供了很多其他功能,例如设置样式、校验数据、读取大文件等,具体使用方法可以参考官方文档。

JAVA规范中常用的设计模式

  1. 单例模式(Singleton Pattern):确保一个类只有一个实例,并提供全局访问点。
  2. 工厂模式(Factory Pattern):定义一个用于创建对象的接口,让子类决定实例化哪一个类。
  3. 抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
  4. 建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
  5. 原型模式(Prototype Pattern):通过复制现有的实例来创建新的实例。
  6. 适配器模式(Adapter Pattern):将一个类的接口转换成客户希望的另外一个接口。
  7. 桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。
  8. 装饰器模式(Decorator Pattern):动态地给一个对象添加一些额外的职责,同时又不改变其结构。
  9. 外观模式(Facade Pattern):为一组子系统提供一个一致的接口,定义一个高层接口,使得这些子系统更加容易使用。
  10. 享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度的对象。
  11. 组合模式(Composite Pattern):将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
  12. 代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问。

以上这些设计模式都是常用的经典设计模式,每个模式都有特定的应用场景和解决问题的方法。在实际编程中,根据具体需求选择适合的设计模式,可以提高代码的可维护性和可扩展性。

instanceof 的概念和用法

instanceof 是 Java 中的一个关键字,用于判断一个对象是否属于某个特定的类或其子类的实例。它的用法如下:

boolean result = object instanceof SomeClass;

其中 object 是需要判断的对象,SomeClass 是需要判断的类,result 表示判断结果。如果 objectSomeClass 类的实例或其子类的实例,则返回 true,否则返回 false

以下是一个使用 instanceof 判断对象类型的示例:

public class Animal {
// ...
}

public class Cat extends Animal {
// ...
}

public class Dog extends Animal {
// ...
}

public class Main {
public static void main(String[] args) {
Animal animal1 = new Cat();
Animal animal2 = new Dog();

System.out.println(animal1 instanceof Animal); // true
System.out.println(animal1 instanceof Cat); // true
System.out.println(animal1 instanceof Dog); // false

System.out.println(animal2 instanceof Animal); // true
System.out.println(animal2 instanceof Cat); // false
System.out.println(animal2 instanceof Dog); // true
}
}

在上面的示例中,我们创建了三个类 AnimalCatDog,其中 CatDog 都是 Animal 的子类。我们创建了两个对象 animal1animal2,分别是 CatDog 的实例。然后使用 instanceof 判断它们的类型,输出结果如注释所示。

在实际编程中,instanceof 通常用于判断一个对象是否属于某个特定的类或其子类的实例,以便进行相应的操作。它也常用于处理异常,比如在捕获异常时,可以使用 instanceof 判断抛出的异常是否是某个特定类型的异常,从而进行相应的处理。

两个对象的 hashCode() 相同,则 equals() 不一定为 true

不一定。虽然 hashCode() 相同的两个对象 equals()true 是可能的,但是也有可能不相等。这是因为 hashCode() 是将对象映射到整数值的一种算法,而 equals() 是用于比较两个对象是否相等的方法,二者并不是等价的。

在 Java 中,如果两个对象 equals() 返回 true,则它们的 hashCode() 应该相同。这是因为在 HashMap、HashSet 等集合中,对象的存储和查找都是依据 hashCode()equals() 方法来实现的,如果 hashCode() 不同,那么就不可能查找到该对象,而如果 equals() 不同,则不符合对象相等的原则。

但是如果两个对象的 hashCode() 相同,它们并不一定相等。这是因为在散列表(如 HashMap、HashSet)中,会出现哈希冲突的情况,即不同的对象被映射到了同一个散列表桶中,此时需要使用 equals() 进一步比较这些对象是否相等。

以下是一个 hashCode() 相同但 equals() 不同的示例:

public class MyClass {
private int id;
private String name;

public MyClass(int id, String name) {
this.id = id;
this.name = name;
}

@Override
public int hashCode() {
return id;
}

@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof MyClass)) {
return false;
}
MyClass other = (MyClass) obj;
return this.id == other.id && !this.name.equals(other.name);
}

public static void main(String[] args) {
MyClass obj1 = new MyClass(1, "foo");
MyClass obj2 = new MyClass(1, "bar");
System.out.println(obj1.hashCode() == obj2.hashCode()); // true
System.out.println(obj1.equals(obj2)); // false
}
}

在上面的示例中,我们定义了一个 MyClass 类,并重写了它的 hashCode()equals() 方法。其中 hashCode() 方法只使用了对象的 id 属性,而 equals() 方法比较了 idname 两个属性。在 main() 方法中,我们创建了两个对象 obj1obj2,它们的 id 相同,但是 name 不同。虽然它们的 hashCode() 相同,但是由于 equals() 方法的实现,它们并不相等。

String的概念和用法以及原理

在 Java 中,String 是一个类,用于表示字符串类型的数据。String 对象是不可变的,即一旦创建,它的值就不能被修改。String 对象可以通过字面值或者构造方法来创建。

例如,使用字面值创建一个字符串对象:

也可以使用构造方法来创建一个字符串对象:

String s1 = "hello";
String s2 = new String("world");

String 类提供了许多方法,用于对字符串对象进行操作,比如拼接、截取、查找、替换等等。

在 Java 中,字符串类型的底层实现是通过字符数组来实现的。String 类中有一个名为 value 的字符数组,用于存储字符串的字符序列。例如,上面创建的字符串 s1 对应的字符数组就是 {'h', 'e', 'l', 'l', 'o'}

由于字符串对象是不可变的,因此每当对字符串对象进行修改时,都会创建一个新的字符串对象,而原来的字符串对象则不会发生改变。例如,对字符串 s1 进行拼接操作:

s1 = s1 + " world";

这个操作会创建一个新的字符串对象 "hello world",而原来的字符串对象 "hello" 则不会发生改变。

Java 还提供了一个特殊的字符串池(String Pool)机制,用于在创建字符串对象时进行优化,以减少内存占用。当使用字面值创建字符串对象时,Java 会首先在字符串池中查找是否已经存在相同的字符串,如果存在,则直接返回字符串池中的对象,否则创建一个新的对象并存放在字符串池中。例如:

String s3 = "hello";
String s4 = "hello";

在这个例子中,s3s4 其实是同一个对象,因为它们都指向字符串池中的 "hello"。这种字符串池机制可以提高字符串对象的重用率,从而降低内存占用。

String的源码解析

String是Java语言中非常重要的类,用来表示不可变的Unicode字符序列,也是Java中使用最为频繁的类之一。下面是Java 11中String类的部分源码讲解:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[]; // 存储String对象的字符数组
private final int offset; // 存储第一个字符在字符数组中的下标
private final int count; // 存储字符数组中字符的数量
private int hash; // 存储String对象的哈希码

// 通过字符数组构造一个新的String对象
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
this.offset = 0;
this.count = value.length;
}

// 根据指定的下标范围构造一个新的String对象
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset + count);
this.offset = 0;
this.count = count;
}

// 返回String对象的长度
public int length() {
return count;
}

// 返回指定下标处的字符
public char charAt(int index) {
if ((index < 0) || (index >= count)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[offset + index];
}

// 返回指定下标范围内的子字符串
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this
: new String(value, offset + beginIndex, endIndex - beginIndex);
}

// 返回从指定下标开始到字符串末尾的子字符串
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (beginIndex > count) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
return ((beginIndex == 0)) ? this
: new String(value, offset + beginIndex, count - beginIndex);
}

// 将此字符串转换为一个新的字符数组
public char[] toCharArray() {
return Arrays.copyOfRange(value, offset, offset + count);
}

// 比较两个字符串是否相等
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}

String类的源码解析还可以从以下几个方面深入

  1. String的不可变性

String类中的char[]数组是被final修饰的,也就是说String对象一旦被创建,其中的字符序列就不能再被修改了。如果对一个字符串进行拼接、替换等操作,实际上是创建了一个新的String对象,而原来的String对象并没有被修改。

  1. 字符串常量池

在Java中,字符串常量池是一块特殊的内存区域,用于存储字符串常量。由于String对象是不可变的,因此可以将相同的字符串共享在字符串常量池中,以节省内存空间。当创建一个字符串对象时,JVM会先在字符串常量池中查找是否存在相同的字符串,如果存在则直接返回引用,否则在字符串常量池中新建一个字符串并返回其引用。

  1. String的性能问题

由于String对象的不可变性,当需要对字符串进行拼接、替换等操作时,每次都会创建一个新的String对象,这会导致频繁的内存分配和回收,影响程序的性能。为了解决这个问题,Java提供了StringBuilder和StringBuffer两个类,它们是可变的字符串类,可以高效地进行字符串操作。

  1. String的编码问题

在Java中,字符串默认使用Unicode编码,每个字符占用两个字节。但是在实际应用中,可能需要使用其他编码方式,比如GBK、UTF-8等。为了解决这个问题,Java提供了String类的getBytes方法,可以将字符串转换成指定编码的字节数组。同时,还可以使用String类的构造方法将字节数组转换成字符串。

综上所述,String是Java中非常重要的一个类,它提供了丰富的字符串操作方法,并具有不可变性和字符串常量池等特性。但是需要注意的是,频繁的字符串操作可能会导致性能问题,需要使用可变的字符串类来提高效率。同时,字符串的编码问题也需要注意,尤其是在多语言环境下,需要使用合适的编码方式来避免出现乱码等问题。

Stream流式操作 API

在 Java 8 中,集合框架新增了 stream 流式操作 API,它提供了一种便捷的方式将 ListSetMap 等集合类型转为另一种集合类型或数组。

具体来说,可以使用 stream 方法将集合转为一个流,然后调用 collect 方法将流转为指定的集合类型。例如,将 List 转为 Set 可以使用以下代码:

List<String> list = Arrays.asList("foo", "bar", "baz");
Set<String> set = list.stream().collect(Collectors.toSet());

上述代码使用了 Java 8 中的 StreamCollectors 类,stream() 方法将 List 转为一个流,Collectors.toSet() 方法将流转为 Set 类型。类似地,还可以将 List 转为数组:

List<String> list = Arrays.asList("foo", "bar", "baz");
String[] array = list.stream().toArray(String[]::new);

上述代码中,toArray() 方法将流转为数组,使用方法引用 String[]::new 来指定数组类型。

除了 Collectors.toSet()toArray() 方法外,Collectors 类中还提供了一些其他方法,例如将流转为 ListMap 等。

需要注意的是,Collectors.toSet()Collectors.toList() 方法返回的集合类型是不可变的,如果需要修改集合,可以使用 HashSetArrayList 等可变集合类型。

Arrays工具类的常用的方法

Arrays 是 Java 中一个用于操作数组的工具类,提供了很多方便的方法。以下是 Arrays 常用的一些方法:

  1. toString():将数组转换成字符串形式并返回;
  2. equals(Object[] a, Object[] b):判断两个数组是否相等;
  3. sort(T[] a):对数组进行排序;
  4. binarySearch(T[] a, T key):在有序数组中搜索指定的值;
  5. fill(T[] a, T val):将数组的所有元素都设置为指定的值;
  6. copyOf(T[] original, int newLength):将原数组复制到一个新数组中,并返回新数组;
  7. asList(T... a):将一个数组转换成一个 List 集合。

需要注意的是,asList() 方法返回的 List 集合不支持添加或删除元素,只能通过 set() 方法修改元素值。

除了上述方法,Arrays 还提供了其他一些方法,例如 sort() 方法的重载版本,以及对基本类型数组的支持方法。