Home » Регулярные выражения в Java — пример и руководство
Регулярные выражения в Java — пример и руководство

Регулярные выражения в Java — пример и руководство

Если ты работаешь с логами, парсишь конфиги или автоматизируешь задачи на сервере, то рано или поздно столкнешься с необходимостью найти и извлечь нужную информацию из текста. И тут на помощь приходят регулярные выражения — мощный инструмент для работы с текстом. В Java они реализованы через классы Pattern и Matcher, которые позволяют не только искать совпадения, но и валидировать данные, парсить логи и даже генерировать отчеты.

Эта статья поможет тебе разобраться с regex в Java на практических примерах, которые реально пригодятся в админской работе. Мы рассмотрим, как быстро настроить поиск по логам, валидацию IP-адресов, парсинг конфигов и многое другое. Никакой теории ради теории — только то, что действительно работает в боевых условиях.

Как это работает: основы regex в Java

В Java для работы с регулярными выражениями используются два основных класса из пакета java.util.regex:

  • Pattern — компилированное регулярное выражение
  • Matcher — движок для поиска совпадений в тексте

Базовый пример использования выглядит так:

import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class RegexExample {
    public static void main(String[] args) {
        String text = "192.168.1.1 - GET /index.html 200";
        Pattern pattern = Pattern.compile("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
        Matcher matcher = pattern.matcher(text);
        
        if (matcher.find()) {
            System.out.println("Найден IP: " + matcher.group());
        }
    }
}

Основные методы, которые тебе понадобятся:

  • find() — найти следующее совпадение
  • matches() — проверить, совпадает ли вся строка с паттерном
  • group() — получить найденную подстроку
  • replaceAll() — заменить все совпадения

Быстрая настройка: пошаговое руководство

Давайте разберем настройку на конкретном примере — парсинг логов Apache. Это одна из самых частых задач, с которой сталкиваются админы.

Шаг 1: Создай класс для парсинга логов

import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class LogParser {
    // Паттерн для Common Log Format
    private static final String LOG_PATTERN = 
        "^([\\d.]+) (\\S+) (\\S+) \\[([\\w:/]+\\s[+\\-]\\d{4})\\] \"(.+?)\" (\\d{3}) (\\d+)";
    
    private static final Pattern pattern = Pattern.compile(LOG_PATTERN);
    
    public static void parseLog(String logFile) {
        try (BufferedReader reader = new BufferedReader(new FileReader(logFile))) {
            String line;
            while ((line = reader.readLine()) != null) {
                Matcher matcher = pattern.matcher(line);
                if (matcher.find()) {
                    System.out.println("IP: " + matcher.group(1));
                    System.out.println("Время: " + matcher.group(4));
                    System.out.println("Запрос: " + matcher.group(5));
                    System.out.println("Код ответа: " + matcher.group(6));
                    System.out.println("Размер: " + matcher.group(7));
                    System.out.println("---");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Шаг 2: Добавь валидацию и фильтрацию

public class LogAnalyzer {
    private static final Pattern IP_PATTERN = 
        Pattern.compile("^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$");
    
    private static final Pattern ERROR_PATTERN = 
        Pattern.compile("\"[^\"]*\" ([4-5]\\d{2})");
    
    public static boolean isValidIP(String ip) {
        return IP_PATTERN.matcher(ip).matches();
    }
    
    public static void findErrors(String logFile) {
        try (BufferedReader reader = new BufferedReader(new FileReader(logFile))) {
            String line;
            while ((line = reader.readLine()) != null) {
                Matcher matcher = ERROR_PATTERN.matcher(line);
                if (matcher.find()) {
                    System.out.println("Ошибка " + matcher.group(1) + ": " + line);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Шаг 3: Создай утилиту для мониторинга

import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;

public class LogMonitor {
    private Map ipCounts = new HashMap<>();
    private Map errorCounts = new HashMap<>();
    
    private static final Pattern ACCESS_PATTERN = 
        Pattern.compile("^([\\d.]+).*\"[^\"]*\" (\\d{3})");
    
    public void processLogLine(String line) {
        Matcher matcher = ACCESS_PATTERN.matcher(line);
        if (matcher.find()) {
            String ip = matcher.group(1);
            String code = matcher.group(2);
            
            ipCounts.computeIfAbsent(ip, k -> new AtomicInteger(0)).incrementAndGet();
            
            if (code.startsWith("4") || code.startsWith("5")) {
                errorCounts.computeIfAbsent(ip, k -> new AtomicInteger(0)).incrementAndGet();
            }
        }
    }
    
    public void printTopIPs(int limit) {
        ipCounts.entrySet().stream()
            .sorted(Map.Entry.comparingByValue(
                (a, b) -> b.get() - a.get()))
            .limit(limit)
            .forEach(entry -> 
                System.out.println(entry.getKey() + ": " + entry.getValue().get()));
    }
}

Практические примеры и кейсы

Вот несколько готовых решений для типичных задач:

Парсинг конфигурационных файлов

public class ConfigParser {
    // Парсинг nginx.conf
    private static final Pattern NGINX_DIRECTIVE = 
        Pattern.compile("^\\s*(\\w+)\\s+([^;]+);");
    
    // Парсинг Apache VirtualHost
    private static final Pattern VHOST_PATTERN = 
        Pattern.compile("]+)>");
    
    // Парсинг переменных окружения
    private static final Pattern ENV_PATTERN = 
        Pattern.compile("^([A-Z_]+)=(.*)$");
    
    public static Map parseEnvFile(String content) {
        Map env = new HashMap<>();
        Pattern pattern = Pattern.compile("^([A-Z_][A-Z0-9_]*)=(.*)$", Pattern.MULTILINE);
        Matcher matcher = pattern.matcher(content);
        
        while (matcher.find()) {
            String key = matcher.group(1);
            String value = matcher.group(2).replaceAll("^[\"']|[\"']$", "");
            env.put(key, value);
        }
        return env;
    }
}

Валидация и очистка данных

public class DataValidator {
    private static final Pattern EMAIL_PATTERN = 
        Pattern.compile("^[A-Za-z0-9+_.-]+@([A-Za-z0-9.-]+\\.[A-Za-z]{2,})$");
    
    private static final Pattern DOMAIN_PATTERN = 
        Pattern.compile("^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+" +
                        "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$", Pattern.CASE_INSENSITIVE);
    
    private static final Pattern SQL_INJECTION_PATTERN = 
        Pattern.compile("('|(\\-\\-)|(;)|(\\|)|(\\*)|(%))", Pattern.CASE_INSENSITIVE);
    
    public static boolean isValidEmail(String email) {
        return EMAIL_PATTERN.matcher(email).matches();
    }
    
    public static boolean isValidDomain(String domain) {
        return DOMAIN_PATTERN.matcher(domain).matches();
    }
    
    public static String sanitizeInput(String input) {
        return input.replaceAll("[<>\"'&]", "");
    }
    
    public static boolean containsSQLInjection(String input) {
        return SQL_INJECTION_PATTERN.matcher(input).find();
    }
}

Мониторинг и алерты

public class SecurityMonitor {
    private static final Pattern BRUTE_FORCE_PATTERN = 
        Pattern.compile("Failed password for .* from ([\\d.]+)");
    
    private static final Pattern SUSPICIOUS_UA_PATTERN = 
        Pattern.compile("\"(.*(?:bot|crawler|spider|scraper).*?)\"", Pattern.CASE_INSENSITIVE);
    
    private static final Pattern DDoS_PATTERN = 
        Pattern.compile("^([\\d.]+).*\" (\\d{3}) \\d+$");
    
    public void checkBruteForce(String logLine) {
        Matcher matcher = BRUTE_FORCE_PATTERN.matcher(logLine);
        if (matcher.find()) {
            String ip = matcher.group(1);
            System.out.println("Возможная атака брутфорса с IP: " + ip);
            // Здесь можно добавить логику блокировки
        }
    }
    
    public void detectSuspiciousActivity(String logLine) {
        Matcher matcher = SUSPICIOUS_UA_PATTERN.matcher(logLine);
        if (matcher.find()) {
            String userAgent = matcher.group(1);
            System.out.println("Подозрительный User-Agent: " + userAgent);
        }
    }
}

Сравнение производительности

Важно понимать, что регулярные выражения могут быть медленными. Вот сравнение различных подходов:

Метод Скорость Память Гибкость Когда использовать
String.contains() Очень быстро Мало Низкая Простой поиск подстроки
String.indexOf() Быстро Мало Низкая Поиск позиции
Pattern.matches() Средне Средне Высокая Валидация
Matcher.find() Медленно Много Очень высокая Сложный поиск

Оптимизация и лучшие практики

Несколько советов для повышения производительности:

  • Компилируй паттерны заранее — Pattern.compile() — дорогая операция
  • Используй флаги — Pattern.CASE_INSENSITIVE, Pattern.MULTILINE, Pattern.DOTALL
  • Избегай жадных квантификаторов — используй .*? вместо .*
  • Кэшируй результаты — особенно для валидации
public class OptimizedRegex {
    // Плохо - компилируется каждый раз
    public boolean validateEmailBad(String email) {
        return Pattern.matches("^[A-Za-z0-9+_.-]+@([A-Za-z0-9.-]+\\.[A-Za-z]{2,})$", email);
    }
    
    // Хорошо - компилируется один раз
    private static final Pattern EMAIL_PATTERN = 
        Pattern.compile("^[A-Za-z0-9+_.-]+@([A-Za-z0-9.-]+\\.[A-Za-z]{2,})$");
    
    public boolean validateEmailGood(String email) {
        return EMAIL_PATTERN.matcher(email).matches();
    }
    
    // Еще лучше - с кэшем
    private Map emailCache = new ConcurrentHashMap<>();
    
    public boolean validateEmailCached(String email) {
        return emailCache.computeIfAbsent(email, 
            e -> EMAIL_PATTERN.matcher(e).matches());
    }
}

Интеграция с другими инструментами

Регулярные выражения отлично работают в связке с другими инструментами:

Logback и slf4j

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
import java.util.regex.Pattern;

public class RegexFilter extends Filter {
    private Pattern pattern;
    
    public void setPattern(String regex) {
        this.pattern = Pattern.compile(regex);
    }
    
    @Override
    public FilterReply decide(ILoggingEvent event) {
        if (pattern.matcher(event.getFormattedMessage()).find()) {
            return FilterReply.ACCEPT;
        }
        return FilterReply.DENY;
    }
}

Apache Commons и Stream API

import java.util.stream.Stream;
import java.nio.file.Files;
import java.nio.file.Paths;

public class StreamRegex {
    public static void analyzeLogFile(String filename) throws IOException {
        Pattern errorPattern = Pattern.compile("ERROR|FATAL");
        
        Files.lines(Paths.get(filename))
            .filter(line -> errorPattern.matcher(line).find())
            .map(line -> line.split("\\s+"))
            .filter(parts -> parts.length > 3)
            .forEach(parts -> System.out.println("Ошибка: " + parts[3]));
    }
}

Альтернативные решения

Хотя Java regex мощный инструмент, иногда стоит рассмотреть альтернативы:

  • Apache Commons Lang — StringUtils для простых операций
  • Google Guava — Splitter для разделения строк
  • ANTLR — для сложного парсинга
  • Jackson — для JSON/XML

Ссылки на полезные ресурсы:

Автоматизация и скрипты

Регулярные выражения открывают массу возможностей для автоматизации:

// Скрипт для автоматической очистки логов
public class LogRotator {
    private static final Pattern LOG_DATE_PATTERN = 
        Pattern.compile("(\\d{4}-\\d{2}-\\d{2})");
    
    public static void cleanOldLogs(String logDir, int daysToKeep) {
        try {
            LocalDate cutoffDate = LocalDate.now().minusDays(daysToKeep);
            
            Files.walk(Paths.get(logDir))
                .filter(Files::isRegularFile)
                .filter(path -> {
                    String filename = path.getFileName().toString();
                    Matcher matcher = LOG_DATE_PATTERN.matcher(filename);
                    if (matcher.find()) {
                        LocalDate logDate = LocalDate.parse(matcher.group(1));
                        return logDate.isBefore(cutoffDate);
                    }
                    return false;
                })
                .forEach(path -> {
                    try {
                        Files.delete(path);
                        System.out.println("Удален старый лог: " + path);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Для работы с большими объемами данных рекомендую использовать VPS с достаточным объемом RAM, а для серьезных нагрузок — выделенный сервер.

Интересные факты и нестандартные применения

Несколько неочевидных способов использования regex:

  • Генерация паролей — можно использовать для валидации сложности
  • Парсинг CSV с экранированием — сложно, но возможно
  • Извлечение цветов из CSS — полезно для тем оформления
  • Поиск уязвимостей — сканирование кода на небезопасные конструкции
// Нестандартный пример - парсинг CSS
public class CSSParser {
    private static final Pattern CSS_RULE_PATTERN = 
        Pattern.compile("([^{]+)\\{([^}]+)\\}");
    
    private static final Pattern CSS_PROPERTY_PATTERN = 
        Pattern.compile("([^:]+):\\s*([^;]+);?");
    
    public static Map> parseCSS(String css) {
        Map> result = new HashMap<>();
        
        Matcher ruleMatcher = CSS_RULE_PATTERN.matcher(css);
        while (ruleMatcher.find()) {
            String selector = ruleMatcher.group(1).trim();
            String properties = ruleMatcher.group(2);
            
            Map props = new HashMap<>();
            Matcher propMatcher = CSS_PROPERTY_PATTERN.matcher(properties);
            while (propMatcher.find()) {
                props.put(propMatcher.group(1).trim(), propMatcher.group(2).trim());
            }
            
            result.put(selector, props);
        }
        
        return result;
    }
}

Заключение и рекомендации

Регулярные выражения в Java — это мощный инструмент, который должен быть в арсенале каждого админа и разработчика. Они незаменимы для:

  • Парсинга логов — быстрый анализ и мониторинг
  • Валидации данных — проверка форматов и безопасности
  • Автоматизации — обработка конфигов и скриптов
  • Мониторинга безопасности — поиск подозрительной активности

Главные принципы использования:

  • Компилируй паттерны заранее и переиспользуй
  • Тестируй регулярки на больших объемах данных
  • Используй группы для извлечения нужных частей
  • Не забывай про экранирование спецсимволов
  • Кэшируй результаты для часто вызываемых операций

Помни: regex — это не серебряная пуля. Для простых задач часто достаточно String.contains() или String.indexOf(). Но когда нужна гибкость и мощность, регулярные выражения показывают себя с лучшей стороны. Особенно это актуально при работе с серверными логами, где объемы данных могут быть огромными, а требования к скорости обработки — высокими.


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked