Tekst informacyjny o polityce Cookies Close   
    
 
         
• 1. Strona główna
• 2. Kontakt: e-Mail
 
         
• 1. Baza danych
• 2. Tabele i kwerendy
• 3. Formularze
• 4. Raporty
• 5. Moduły i makra
• 6. Obsługa błędów
 
    

II.   VBA 

    
• 1. Okna Accessa
• 2. Okna Formularzy
• 3.Okna Dialogowe
• 4. Tekst
• 5. Liczby
• 6. Pliki
• 7. Inne
 
    

III.   API 

    
• 1. Ogólnie o API
• 2. Funkcje API
• 3. System
• 4. Praca z oknami
• 5. Okna dialogowe
• 6. API - Inne
 
         
• 1. Bitmapy 24 Bit
• 2. GDI i HDC
• 3. Kody kreskowe
• 4. Formant Image
• 5. FreeImage.dll
 
    

V.   Inne 

    
• 1. Shell i MsDOS
• 2. Kontrolki
• 3. ६ԼҚ ਸ
• 4. Unikod

 
Odwiedzin:

Logo AccessFAQ• II.4. VBA - Konwersja ciągów [3] •

4.20 Jak przyspieszyć operację wielokrotnej konkatencji (np. w pętli For i = 0 To 5000 ?
4.21 W jaki sposób ciąg znaków typu: 1h15m25s, 15m25s, 22h5s, 15h itd. przekonwertować na czas ?
4.22 W jaki sposób podzielić ciąg znaków na elementy stałej szerokości i rozdzielić je separatorem dowolnej długości ?
4.23 Jak przekonwertować tekst Unicode (UCS-2) w zapisie szesnastkowym (każdy bajt rozdzielony jest znakiem spacji) na tekst ANSI ?
4.24 Jak utworzyć hasło będące kombinacją dużych i małych liter, cyfr oraz znaków specjalnych ?
4.25 Jak przekonwertować tekst pisany systemem znaków UTF8 na WIN-1250 i odwrotnie ?
4.26 Jak przekonwertować tekst na zapis heksadecymalny (każda litera reprezentowana jest przez dwa znaki) ?
4.27 Jak pobrać dane z pliku, który nie zawiera separatorów, ale dane są o określonej długości ?
4.28 Jak pobrać z ciągu znaków liczby rozdzielone jednoznakowym separatorem (np. numer telefoniczny)  ?
<• idź do str. 1 •>  <• idź do str. 2 •>
 

4.20 Jak przyspieszyć operację wielokrotnej konkatencji (np. w pętli For i = 0 To 5000 ?

Private Declare Function timeGetTime Lib "winmm.dll" () As Long
' Poniższy tekst będziemy w pętli For i = 0 To 5 000 Next łączyć ze sobą w jeden długi
tekst o sumarycznej długości 1 320 000 znaków

Private Const MY_TEXT As String = _
"Rzekł pajączek do pajączki:" & vbNewLine & _
"Popatrz, ja mam złote rączki!" & vbNewLine & _
"Już uprzędłem dla was nową" & vbNewLine & _
"Pajęczynę własnościową." & vbNewLine & _
"Będziesz pewnie bardzo rada," & vbNewLine & _
"Bo to cud, mucha nie siada!" & vbNewLine & _
"Na to ona z gniewem wrzaśnie:" & vbNewLine & _
"A ma, durniu, siadać właśnie!" & vbNewLine & _
"           (Bogdan Brzeziński)"
Private Const MY_FOR As Long = 5000



' • Metoda I - klasyczne łączenie ciągów znaków (konkatenacja)
Private Sub btnStrCat_Click()
Dim lStart As Long
Dim sText As String
Dim i As Long

lStart = timeGetTime
For i = 1 To MY_FOR
sText = sText & MY_TEXT
Next

Debug.Print "Konkatenacja: "; timeGetTime - lStart

End Sub

 • czas wykonywania wynosi ok. 45 sekund



' • Metoda II - kolejne dopisywanie do otwartego pliku tymczasowego poszczególnych ciągów znaków i odczyt do zmiennej sText całej zawartości pliku
Private Sub btnTextToFileFromFile_Click()
Dim sTmpFile As String
Dim sText As String
Dim lStart As Long
Dim ff As Integer
Dim i As Long

lStart = timeGetTime
' wygeneruj ścieżkę pliku tymczasowego
sTmpFile = Environ$("TEMP") & "\~" & _
CStr(CLng(Now * 10000) + 1) & ".tmp"
' jeżeli plik tymczasowy istnieje, to go usuń
If Len(Dir(sTmpFile)) > 0 Then Kill sTmpFile
ff = FreeFile
' otwórz plik do zapisu
Open sTmpFile For Binary Access Write As #ff
For i = 1 To MY_FOR
sText = MY_TEXT
' sukcesywnie dopisuj ciąg znaków na końcu pliku
Put #ff, , sText
Next
Close #ff

ff = FreeFile
' otwórz plik do odczytu
Open sTmpFile For Binary Access Read As #ff
sText = String(LOF(ff), vbNullChar)
Get #ff, , sText
Close #ff
' usuń plik tymczasowy
If Len(Dir(sTmpFile)) > 0 Then Kill sTmpFile

Debug.Print "Zapis do pliku: "; timeGetTime - lStart

End Sub

 • czas wykonywania wynosi ok. 0,086 sekundy. Szybkość tej metody jest rewelacyjna, ale niestety musimy pisać po dysku, co jest czasami niezbyt dobrym pomysłem.
A dlaczego szybkości obu metod tak znacznie się różnią od siebie (ok. 500 razy) wyjaśnia Robert Winkler:



grupa: pl.comp.bazy-danych.msaccess
wątek: Konkatenacja - jak przyspieszyć ok. 100x



<cyt>
Zamiast bawić się w zapisywanie danych na dysku
zastosuj lepiej coś działajacego jak StringBuilder
Dzięki temu że aplikacja nie będzie miała kontaktu z dyskiem
uzyskasz jeszcze większą prędkość działania.
Przykład takiej klasy znajdziesz na: StringBuilder Class for VB
(kod ten przeznaczony jest dla VB6, może więc nie działać z VBA)

    Jaki jest powód takiego zachowania. Łączenie stringów w Visual Basicu
polega na tym iż runtime rezerwuje nowy, pusty obszar pamięci
o wielkości równej sumarycznej wielkości dwóch łączonych stringów
i do tego nowego obszaru kopiowana jest ich zawartość.
    Dla każdej kolejnej operacji konkatenacji niezależnie czy zapisana jest z wykorzystaniem operatora + czy & wykonywana jest ponownie powyższa operacja.
</cyt>

    Przekładając to na język VB otrzymamy przykładową funkcję StrCat(...)
przedstawiającą mechanizm łączenia ciągów znaków w VB



Private Declare Function lstrcat Lib "kernel32" _
Alias "lstrcatA" _
(ByVal lpString1 As String, _
ByVal lpString2 As String) As Long
Private Declare Function lstrlen Lib "kernel32" _
Alias "lstrlenA" _
(ByVal lpString As String) As Long


Public Function StrCat(sString1 As String, sString2 As String) As String
Dim sBuffer As String

' przygotuj bufor o sumarycznej wielkości obu ciągów wejściowych
StrCat = String$(lstrlen(sString1) + lstrlen(sString2), vbNullChar)
' kopiuj pierwszy ciąg znaków
lstrcat StrCat, sString1
' kopiuj (dołącz) drugi ciąg znaków
lstrcat StrCat, sString2

End Function

    Jako ciekawostkę dodam, że funkcja lstrlen(String) jest ok. 30% wolniejsza od wbudowanej funkcji Len(String)



' • Metoda III - w oparciu o wyjaśnienia Roberta Winklera :
<cyt>
    ... StringBuilder jest dodatkową klasą, zakładającą własny bufor wewnątrz którego łączone są stringi.
Za każdym razem kiedy wielkość wynikowego łańcuch znaków ma przekroczyc stworzony wcześniej bufor jego wielkość jest podwajana (jego wielkość wzrasta więc w postępie geometrycznym)
    Dzięki wypracowanemu sporemu zapasowi miejsca w buforze StringBuilder może rzadziej alokować nową pamięć, nie musi też tak często kopiować dużych porcji danych, która to operacja występuje przy każdej normalnej konkatenacji.
</cyt>

Aby sprawdzić jak działa poniższy przykład utwórz nowy moduł klasy, wklej do niego kod i zapisz klasę jako clsStrCat

Private m_sBuffer As String
Private m_lPos As Long
Private m_lSizeBff As Long


Private Sub Class_Initialize()
m_lPos = 1
End Sub


Public Property Let StringAdd(sStrAdd As String)
Dim lNewLen As Long

lNewLen = m_lPos + Len(sStrAdd)
If lNewLen > m_lSizeBff Then
m_sBuffer = m_sBuffer & String$(lNewLen, vbNullChar)
m_lSizeBff = m_lSizeBff + lNewLen
End If

Mid$(m_sBuffer, m_lPos) = sStrAdd
m_lPos = lNewLen

End Property


Public Property Get StringGet() As String
StringGet = Left$(m_sBuffer, m_lPos - 1)
End Property


' przykładowe wywołanie:
Private Sub btnClsStrCat_Click()
Dim sText As String
Dim lStart As Long
Dim i As Long
Dim cls As clsStrCat

lStart = timeGetTime
Set cls = New clsStrCat
For i = 1 To MY_FOR
cls.StringAdd = MY_TEXT
Next
sText = cls.StringGet
Set cls = Nothing

Debug.Print "clsStrCat: "; timeGetTime - lStart

End Sub

 • czas wykonywania wynosi ok. 0,116 sekundy.



' • Metoda IV - przy zastosowaniu funkcji API CopyMemory(...) i tablicy bajtów jako odpowiednika naszego ciągu znaków, a zwiększanie buforu wykonywać będziemy za pomocą ReDim Preserve m_bBuffer(0 To m_lSizeBff) eliminując tym samym konkatenację podczas powiększania buforu.

 • Utwórz nowy moduł klasy, wklej do niego kod i zapisz klasę jako clsCopyMem


Private Declare Sub CopyMemory Lib "kernel32" _
Alias "RtlMoveMemory" _
(Destination As Any, _
Source As Any, _
ByVal Length As Long)
Private m_bBuffer() As Byte
Private m_lPos As Long
Private m_lSizeBff As Long


Private Sub Class_Initialize()
' m_lSizeBff = 0
' ReDim m_bBuffer(0 To m_lSizeBff)
End Sub


Public Property Let StringAdd(sStrAdd As String)
Dim lLenStrAdd As Long
Dim lNewLen As Long

lLenStrAdd = Len(sStrAdd)

If lLenStrAdd > 0 Then
lNewLen = m_lPos + lLenStrAdd
If lNewLen > m_lSizeBff Then
m_lSizeBff = m_lSizeBff + lNewLen
ReDim Preserve m_bBuffer(0 To m_lSizeBff)
End If

CopyMemory m_bBuffer(m_lPos), ByVal sStrAdd, lLenStrAdd
m_lPos = lNewLen

End If

End Property


Public Property Get StringGet() As String

If m_lPos > 0 Then
ReDim Preserve m_bBuffer(0 To m_lPos - 1)
StringGet = StrConv(m_bBuffer(), vbUnicode)
End If

End Property


' przykładowe wywołanie:
Private Sub btnClsCopyMem_Click()
Dim sText As String
Dim lStart As Long
Dim i As Long
Dim cls As clsCopyMem

lStart = timeGetTime
Set cls = New clsCopyMem
For i = 1 To MY_FOR
cls.StringAdd = MY_TEXT
Next
sText = cls.StringGet
Set cls = Nothing

Debug.Print "clsStrCat: "; timeGetTime - lStart

End Sub

Zestawienie szybkości poszczególnych metod
For i = ?ZnakówTxtToFileclsStrCatCopyMemclassRW
 5 000  1 320 000    86 ms  106 ms    85 ms  103 ms
 50 000 13 200 000   812 ms  952 ms  802 ms  930 ms
100 000 26 400 000 1 835 ms1 901 ms1 826 ms2 134 ms
200 000 52 800 000 3 650 ms4 082 ms3 344 ms3 970 ms

 * zmodyfikowana przez Roberta Winklera klasa: Fast string concatenation

 ΔΔΔ 

 

4.21 W jaki sposób ciąg znaków typu: 1h15m25s, 15m25s, 22h5s, 15h itd. przekonwertować na czas ?



grupa: pl.comp.bazy-danych.msaccess
wątek: [newbie] pokręcone godziny
przedstawił: Krzysztof Naworyta



<cyt>
Najlepsza konwersja to oczywiście do datetime (który zresztą jest podtypem Double). Służy do tego funkcja TimeSerial (+ewentualnie DateSerial).
Oczywiście trzeba sobie napisać funkcję, która wyłuska odpowiednie "składowe".
Mogłaby wyglądać następująco:

Option Compare Database
Option Explicit

Private Type args
    d As Long
    h As Long
    m As Long
    s As Long
End Type


Function GetTime(czas As String)
Dim r As args
Dim i As Integer
Dim retVal As String
Dim litera As String

retVal = "0"
For i = 1 To Len(czas)
litera = Mid(czas, i, 1)
If IsNumeric(litera) Then
retVal = retVal & litera
Else
Select Case litera
Case "d"
r.d = r.d + Val(retVal)
Case "h"
r.h = r.h + Val(retVal)
Case "m"
r.m = r.m + Val(retVal)
Case "s"
r.s = r.s + Val(retVal)
Case Else

End Select
retVal = "0"
End If
Next

GetTime = r.d + TimeSerial(r.h, r.m, r.s)

End Function


Obłuży różne dziwolągi:

1h23m4s01:23:04
12h13s12:00:13
15m25s00:15:25
22h5s22:00:05
15h15:00:00
34m23s00:34:23
3d23h345s02-01-1900 23:05:45
12hh34s12s34mm123ggabcdefg12:34:46
75m23s01:15:23

literały spoza "dhms" zignoruje, kilkakrotne wystąpienie tego samego literału zsumuje.
Czy to potrzebne, pewnie nie. Ale warto się zabezpieczać na wszelki wypadek.

    Jeśli interwał przekroczy 1 (1 doba), to wyświetli jako datę, np. 02-01-1900 23:05:45, ale nie trzeba się tym przejmować, bo jest to tak naprawdę liczba 3,96232638888889, która bardzo ładnie zostanie zsumowana z innymi, w przypadku agregacji.
</cyt>

    Tytułem testu spróbowałem Twoją funkcję przerobić tak, by nie działała na ciągach znaków (retVal , litera) i nie porównywać ciągów znaków
    Case "d"
    Case "h"
oraz wyeliminować Val(retVal) na rzecz porównywania liczb typu Byte, będących odpowiednikiem poszczególnych znaków ciągu:



Function GetTimeBis(czas As String)
Dim r As args
Dim aBytes() As Byte
Dim lRetVal As Long
Dim bAscPrev As Byte
Dim i As Long
Dim k As Long

' dodaj znak na początku stringu, przekształć ciąg na duże litery
' i przekonwertuj na tablicę bajtów

aBytes = StrConv(UCase(vbNullChar & czas), vbFromUnicode)

' i sprawdzaj od tyłu (po to ten znak na początku by się wykonało
' ostatnie dodawanie w Case bAscPrev dla k > 0
For i = UBound(aBytes) To LBound(aBytes) Step -1
Select Case aBytes(i)
Case 48 To 57
lRetVal = lRetVal + (aBytes(i) - 48) * 10 ^ k
k = k + 1
Case Else
If k > 0 Then
Select Case bAscPrev
Case vbKeyD
r.d = r.d + lRetVal
Case vbKeyH
r.h = r.h + lRetVal
Case vbKeyM
r.m = r.m + lRetVal
Case vbKeyS
r.s = r.s + lRetVal
Case Else
End Select
lRetVal = 0: k = 0
bAscPrev = aBytes(i)
Else
bAscPrev = aBytes(i)
End If
End Select
Next

GetTimeBis = r.d + TimeSerial(r.h, r.m, r.s)

End Function

Porównanie szybkości obu metod dla 50 000 wywołań
Ciąg wejściowyGetTimeGetTimeBis
"15h"754 ms740 ms
"12h13s"1 340 ms963 ms
"1h23m4s"1 662 ms1 033 ms
"12hh34s12s34mm123ggabcdefg"5 464 ms2 041 ms

• Jak widać im dłuższy ciąg znaków, tym korzyść z takiego podejścia jest większa,

 ΔΔΔ 

 

4.22 W jaki sposób podzielić ciąg znaków na elementy stałej szerokości i rozdzielić je separatorem dowolnej długości ?



grupa: pl.comp.bazy-danych.msaccess
wątek: Konkatenacja -> edycja -> rozbicie
przedstawili: Pawel81, Zbigniew Bratko



' funkcja autorstwa Pawel81 po moich drobnych przeróbkach dostosowawczych:
Public Function strComposeBis(valStr As String, _
splitCount As Byte, _
Spliter As String) As String
' valStr - ciąg znaków do przedzielenia
' splitCount - ilość znaków w podziale
' Splitter - znak rozdzielający

Dim tmpStr As String

tmpStr = valStr
While Len(tmpStr) > 0
    strComposeBis = strComposeBis & _
Left$(tmpStr, splitCount) & Spliter
    tmpStr = Mid$(tmpStr, splitCount + 1)
Wend

End Function



' spróbujmy użyć instrukcję Mid$ by wyeliminować operacje konkatencji, co powinno przyspieszyć działanie funkcji, zwłaszcza dla długich ciągów znaków
Public Function zbDelimitedString(sStrIn As String, _
lLenField As Long, _
Optional sDelim As String = ";") As String
Dim lCountDelim As Long
Dim lLenDelim As Long
Dim lLenStr As Long
Dim lOffset As Long
Dim lStep As Long
Dim i As Long

lLenDelim = Len(sDelim)
lLenStr = Len(sStrIn)

If (lLenStr Mod lLenField) > 0 Then
    lCountDelim = lLenStr \ lLenField
Else
    lCountDelim = lLenStr \ lLenField - 1
End If

lLenStr = lLenStr + lLenDelim * (lCountDelim + 1)
' zapełnij bufor wyjściowy
zbDelimitedString = Space(lLenStr)
lStep = lLenField + lLenDelim

For i = 0 To lCountDelim
    lOffset = i * lStep + 1
    ' wstawiaj z odpowiednim przesunięciem elementy ciągu wejściowego
    Mid$(zbDelimitedString, lOffset, lLenField) = _
        Mid$(sStrIn, i * lLenField + 1, lLenField)
Next

lLenStr = lLenStr - lLenDelim
For i = lLenField + 1 To lLenStr Step lStep
    Mid$(zbDelimitedString, i, lLenDelim) = sDelim
Next

' dopisz separator na końcu
Mid$(zbDelimitedString, lLenStr + 1, lLenDelim) = sDelim

End Function

Porównanie szybkości obu metod dla 1000 wywołań
Długość ciąguzbDelimitedStringstrComposeBisWsp. [DS/CB]
   100 znaków    30 ms   20 ms 1,5
   400 znaków   130 ms   60 ms 2,2
 1 600 znaków   970 ms  210 ms 4,6
 6 400 znaków15 040 ms  840 ms17,9
12 800 znaków55 961 ms1 660 ms33,7

• Jak widać im dłuższy ciąg znaków tym korzyść z takiego podejścia jest większa,

 ΔΔΔ 

 

4.23 Jak przekonwertować tekst Unicode (UCS-2) w zapisie szesnastkowym (każdy bajt rozdzielony jest znakiem spacji) na tekst ANSI ?

Ciąg wejściowy ma postać,

    Const MY_UCS As String = _
"00 57 00 65 00 73 00 6f 01 42 00 79 00 63 00 68 " & _
"00 20 01 5a 00 77 00 69 01 05 00 74 00 20 00 64 " & _
"00 6c 00 61 00 20 00 43 00 69 00 65 00 62 00 69 " & _
"00 65 00 20 00 69 00 20 00 54 00 77 00 6f 00 69 " & _
"00 63 00 68 00 20 00 62 00 6c 00 69 00 73 00 6b " & _
"00 69 00 63 00 68 00 2e "

aby go przekonwertować, musimy odpowiedzieć sobie na kilka pytań:

1. Jak rozdzielić ciąg wejściowy na zbiory odpowiadające wartościom liczbowym poszczególnych znaków ?
• Mamy do dyspozycji kilka podstawowych funkcji operujących na ciągach znaków:
Mid(string, start[, length])
zwraca wartość typu Variant (String) zawierającą podaną liczbę znaków z ciągu znaków.
Len(ciąg | nazwa_zmiennej)
zwraca wartość typu Long zawierającą liczbę znaków w ciągu znaków lub liczbę bajtów niezbędnych do przechowania zmiennej.
Split(expression[, delimiter[, limit[, compare]]])
zwraca jednowymiarową tablicą o dolnym indeksie równym zero, zawierającą wyszczególnioną liczbę podciągów.
Replace(expression, find, replace[, start[, count[, compare]]])
zwraca ciag znaków w którym określone podciągi zostały zastąpione, wyszczególnioną ilość razy, innym podciągiem .
Zadeklarujmy potrzebne zmienne:
Dim sHexLong As String
Dim sHexByte1 As String
Dim sHexByte2 As String
Dim sReplaced As String
Dim sHexByte() As String
Dim i As Long

Metoda 1 - Mid & Mid
' pobiera poszczególne bajty (po 2 znaki) i łączy je w jeden ciąg, tworząc szesnastkowy kod znaku

For i = 1 To Len(MY_UCS) Step 6
   sHexLong = "&H" & Mid(MY_UCS, i, 2) & _
Mid(MY_UCS, i + 3, 2)
Next

Metoda 2 - Mid, Mid
' pobiera poszczególne bajty (po 2 znaki) zapisane szesnastkowo do dwóch różnych zmiennych, ale ich nie łączy ze sobą

For i = 1 To Len(MY_UCS) Step 6
   sHexByte1 = "&H" & Mid(MY_UCS, i, 2)
   sHexByte2 = "&H" & Mid(MY_UCS, i + 3, 2)
Next

Metoda 3 - Replace


grupa: pl.comp.bazy-danych.msaccess
wątek: Konwersja Unikod (UCS2) na Win1250 - tylko dla zabawy
przedstawił: Rafał Kwaczała



' usuwa wszystkie spacje w ciągu wejściowym, a następnie pobiera po 2 bajty (4 znaki) tworząc szesnastkowy kod znaku

sReplaced = Replace(MY_UCS, " ", "", , , vbBinaryCompare)
For i = 1 To Len(sReplaced) Step 4
   sHexLong = "&H" & Mid(sReplaced, i, 4)
Next

Metoda 4 - Split
' tworzy tablicę typu string, traktując spację jako separator, łączy ze sobą po dwa kolejne elementy tworząc szesnastkowy kod znaku, w tym przypadku nie jest stosowana funkcja Mid

sHexByte = Split(MY_UCS, " ", , vbBinaryCompare)
For i = 0 To UBound(sHexByte) - 1 Step 2
   sHexLong = "&H" & sHexByte(i) & sHexByte(i + 1)
Next

Porównanie szybkości czterech metod dla 10 000 wywołań
Metoda      Mid
1. Mid & Mid
  1 993 ms
2. Mid, Mid
  1 953 ms
3. Replace
  1 802 ms
4. Split
  2 334 ms

2. W jakiej postaci ma być zbiór wartości liczbowych poszczególnych znaków
   i jak przekonwertować ten zbiór na tekst ?

• Postać zbioru zależy od funkcji jakiej użyjemy do konwersji wartości numerycznych
    na znakowe:
Chr(charcode)
zwraca wartość typu String zawierającą znak odpowiadający podanemu kodowi.
• charcode
Obowiązkowy argument identyfikujący znak powinien być w/g opisu typu Long, ale przykładowo dla litery "ł" funkcji ChrW można przekazać argument następująco:
• liczbowy, dziesiętnie ChrW(322),
• liczbowy, szesnastkowo ChrW(&H0142)
• liczbowy, przekonwertowany ciąg znaków ChrW(CLng("&H0142"))
• ciag znaków, dziesiętnie ChrW("322"),
• ciag znaków, szesnastkowo ChrW("&H0142")
    Funkcja ChrW zawsze zwraca wartość typu String zawierającą znak w formacie Unicode z wyjątkiem platform gdzie format Unicode nie jest obsługiwany
StrConv(string, conversion)
zwraca wartość typu Variant (String) poddaną żądanej konwersji.
• string
w/g opisu argument jest zmienną znakową, ale można również w tym argumencie przekazać odpowiednio przygotowana tablicę typu Byte
np. dla litery "ł" tablica aBytes(0 To 3) będzie zawierała cztery elementy aBytes(0) = &H42: aBytes(1) = &H0: aBytes(2) = &H1: aBytes(3) = &H0
Możemy to sprawdzić w poniższy sposób:

Dim aBytes() As Byte
aBytes = StrConv("ł", vbUnicode)
For i = 0 To UBound(aBytes)
    Debug.Print "&H" & Hex$(aBytes(i)); " ";
Next
• conversion
vbUnicode - zamienia ciąg na postać Unicode, stosując domyślną stronę kodową systemu.
vbFromUnicode - zamienia ciąg z postaci Unicode na znaki z domyślnej strony kodowej systemu.

    Ze względu na szybkość działania odrzucamy najwolniejszy sposób oparty na funkcji Split, a tym samym zostają nam do przetestowania trzy metody:

Metoda 1. (Mid & Mid) + ChrW
• praktycznie dla metody tej dopisujemy jedną linijkę kodu konwertującą ciąg sHexLong na liczbę typu Long, którą funkcja ChrW$ konwertuje na znak i dołącza do zwracanego tekstu sStrOut :

For i = 1 To Len(MY_UCS) Step 6
   sHexLong = "&H" & Mid(MY_UCS, i, 2) & Mid(MY_UCS, i + 3, 2)
   sStrOut = sStrOut & ChrW(CLng(sHexLong))
Next

Metoda 2. (Mid, Mid)+ CopyMemory
Private Declare Sub CopyMemory Lib "kernel32" _
Alias "RtlMoveMemory" _
(Destination As Any, _
Source As Any, _
ByVal Length As Long)


• w metodzie tej każdy bajt w zapisie szesnastkowym konwertujemy na liczbę, którą za pomocą funkcji CopyMemory wstawiamy w podpowiednie miejsce w przygotowanej uprzednio tablicy aBytes(). Tablica ta jest następnie konwertowana przez funkcję StrConv na ciąg znaków sStrOut :

ReDim aBytes(0 To (2 * Len(MY_UCS) / 3 - 1))
For i = 1 To Len(MY_UCS) Step 6
   sHexByte1 = "&H" & Mid(MY_UCS, i, 2)
   sHexByte2 = "&H" & Mid(MY_UCS, i + 3, 2)
   CopyMemory aBytes(k + 2), CLng(sHexByte1), 2
   CopyMemory aBytes(k), CLng(sHexByte2), 2
   k = k + 4
Next

sStrOut = StrConv(aBytes(), vbFromUnicode)

Metody 3. (Replace) + ChrW
• podobnie jak w Metodzie 1. dopisujemy jedną linijkę kodu konwertującą ciąg sHexLong na liczbę typu Long, którą funkcja ChrW$ konwertuje na znak i dołącza do zwracanego tekstu sStrOut :

sReplaced = Replace(MY_UCS, " ", "", , , vbBinaryCompare)
For i = 1 To Len(sReplaced) Step 4
   sHexLong = "&H" & Mid(sReplaced, i, 4)
   sStrOut = sStrOut & ChrW(CLng(sHexLong))
Next

3. Przetestujmy szybkość powyżej opisanych funkcji z dodatkowymi zmianami:
  1. zamiast funkcji Mid i ChrW użyjemy funkcji tekstowych Mid$ i ChrW$
  2. zamiast konkatenacji ciągów znaków zastosujemy instrukcję Mid$ zgodnie z poniższym przykładem:

    Dim sStrOut As String
    Dim sHexLong As String * 6
    Dim i As Long

    ' zainicjuj zwracany ciąg znaków (przykładowo 100 znaków)
    sStrOut = Space(100)
    ' wpisz jednorazowo dwa pierwsze znaki,
    ' aby nie robić tego za każdym razem w pętli

    Mid$(sHexLong, 1, 2) = "&H"
    For i = 1 To 100
    ' na trzecim i następnych miejscach wpisz cztery znaki
    ' liczby w zapisie szesnastkowym,

    Mid$(sHexLong, 3, 4) = "0142"
    ' dopisuj kolejno przekonwertowane znaki w zwracanym ciągu
    Mid$(sStrOut, i, 1) = ChrW$(CLng(sHexLong))
    Next
Ostatecznie nasze funkcje będą miały postać:

' Metoda 1. Mid$ & Mid$ + inst. Mid$
Public Function zbUCS2Win_Mid(sUcsIn As String) As String
Dim sHexLong As String * 6
Dim lLenStr As Long
Dim i As Long, j As Long, k As Long

lLenStr = Len(sUcsIn)
zbUCS2Win_Mid = Space(lLenStr \ 6)

Mid$(sHexLong, 1, 2) = "&H"
j = 1
For i = 1 To lLenStr Step 6
Mid$(sHexLong, 3, 2) = Mid$(sUcsIn, j, 2)
Mid$(sHexLong, 5, 2) = Mid$(sUcsIn, j + 3, 2)
j = i + 6: k = k + 1
Mid$(zbUCS2Win_Mid, k, 1) = ChrW$(CLng(sHexLong))
Next

End Function


' Metoda 2. Mid$, Mid$ + CopyMempry + inst. Mid$
Private Declare Sub CopyMemory Lib "kernel32" _
Alias "RtlMoveMemory" _
(Destination As Any, _
Source As Any, _
ByVal Length As Long)


Public Function zbUCS2Win_Mem(sUcsIn As String) As String
Dim sHexByte As String * 4
Dim aBytes() As Byte
Dim lLenStr As Long
Dim i As Long, j As Long, k As Long

lLenStr = Len(sUcsIn)
ReDim aBytes(0 To (2 * lLenStr \ 3 - 1))

j = 1: k = 0
sHexByte = "&H"
For i = 1 To lLenStr Step 6
Mid$(sHexByte, 3, 2) = Mid$(sUcsIn, j + 3, 2)
CopyMemory aBytes(k), CLng(sHexByte), 2
Mid$(sHexByte, 3, 2) = Mid$(sUcsIn, j, 2)
CopyMemory aBytes(k + 2), CLng(sHexByte), 2
k = k + 4: j = i + 6
Next

zbUCS2Win_Mem = StrConv(aBytes(), vbFromUnicode)

End Function


' Metoda 3. Replace + inst. Mid$
Public Function rkUCS2Win_Replace(sUcsIn As String) As String
Dim sHexLong As String * 6
Dim sReplaced As String
Dim lLenStr As Long
Dim i As Long, j As Long

sReplaced = Replace(sUcsIn, " ", "", , , vbBinaryCompare)

lLenStr = Len(sReplaced)
rkUCS2Win_Replace = Space(lLenStr \ 4)
Mid$(sHexLong, 1, 2) = "&H"
For i = 1 To lLenStr Step 4
Mid$(sHexLong, 3, 4) = Mid$(sReplaced, i, 4)
j = j + 1
Mid$(rkUCS2Win_Replace, j, 1) = ChrW$(CLng(sHexLong))
Next

End Function

Porównanie szybkości trzech metod dla 10 000 wywołań
Metoda      MidMid$ + ChrW$instr. Mid$
    1. Mid & Mid + ChrW  3 025 ms  1 793 ms  1 552 ms
    2. Mid, Mid + CopyMemory  3 285 ms  2 153 ms  2 344 ms
    3. Replace + ChrW  2 814 ms   2 514 ms   2 423 ms 

4. I ostatnie pytanie: Czy warto wkładać tyle trudu w optymalizację kodu i tracić czas, by na 10 000 operacjach zyskać ok. 1,5 sekundy ?

Oczywiście można sobie odpuścić taką optymalizację, jeżeli jesteśmy na 100% pewni, że nie spotka nas niespodzianka. Wynikowy tekst, po przetworzeniu testowego ciągu MY_UCS, zawiera 44 znaki i brzmi: "Wesołych Świąt dla Ciebie i Twoich bliskich."

    Sprawdźmy, jak będzie się zmieniała szybkość przetwarzania coraz dłuższych ciągów znaków. Porównamy szybkość funkcji zbUCS2Win_Mid z poniższą funkcją dla jednorazowego wywołania dla ciągu sUcsIn dającego wynikowy tekst o coraz większej długości:

Public Function zbMidAndMid(sUcsIn As String) As String
Dim i As Long
For i = 1 To Len(sUcsIn) Step 6
   zbMidAndMid = zbMidAndMid & _
ChrW$(CLng("&H" & _
Mid$(sUcsIn, i, 2) & _
Mid$(sUcsIn, i + 3, 2)))
Next
End Function


Porównanie szybkości dla jednorazowego wywołania
Długość ciągu wynikowego      zbMidAndMidzbUCS2Win_Mid
  5 632 znaków
     50 ms  21 ms
 11 264 znaków
    181 ms  40 ms
 22 528 znaków
  1 241 ms 81 ms
 45 056 znaków
  3 755 ms 160 ms
 90 112 znaków
 28 251 ms 310 ms
180 224 znaków
193 448 ms 621 ms

    A co będzie jeżeli zajdzie potrzeba odkodowywania kilkudziesięciu długich tekstów dziennie. Jeżeli nie będzie takiej potrzeby, to nic się nie stanie. Nikt nie zwróci uwagi na te 50 ms przyspieszenia ;-)

 ΔΔΔ 

 

4.24 Jak utworzyć hasło będące kombinacją dużych i małych liter, cyfr oraz znaków specjalnych ?



grupa: pl.comp.lang.vbasic
wątek: Algorytm na hasło w VB.NET
przedstawił: Zbigniew Bratko



Private m_sBigLetters As String
Private m_sSmallLetters As String
Private m_sSpecChars As String
Private m_lLenLett As Long
Private m_lLenSpec As Long


' ustaw jednorazowo (przy starcie) wszystkie potrzebne zmienne
Private Sub Form_Load()
Dim i As Long

For i = 33 To 126
Select Case i
Case 33 To 47, 58 To 64, 91 To 96, 123 To 126
m_sSpecChars = m_sSpecChars & Chr$(i)
Case 65 To 90
m_sBigLetters = m_sBigLetters & Chr$(i)
Case Else
End Select
Next
' dodaj ogonki
m_sBigLetters = m_sBigLetters & "ĄĆĘŁŃÓŚŻŹ"
m_sSmallLetters = LCase(m_sBigLetters)

m_lLenSpec = Len(m_sSpecChars)
m_lLenLett = Len(m_sBigLetters)

End Sub


' zwraca ciąg znaków zawierający kombinacją dużych i małych liter,
' cyfr oraz znaków specjalnych.

Private Function zbMakePassword(lLenPsw As Long) As String
Dim sPsw As String
Dim i As Long
Dim j As Long
Dim k As Long

k = lLenPsw \ 4
If k - lLenPsw / 4 = 0 Then k = k - 1

For i = 0 To k
Randomize
' wylosuj dużą literę
j = Int(m_lLenLett * Rnd) + 1
sPsw = sPsw & Mid$(m_sBigLetters, j, 1)
' wylosuj małą literę
j = Int(m_lLenLett * Rnd) + 1
sPsw = sPsw & Mid$(m_sSmallLetters, j, 1)
' wylosuj znak specjalny
j = Int(m_lLenSpec * Rnd) + 1
sPsw = sPsw & Mid$(m_sSpecChars, j, 1)
' wylosuj liczbę
j = Int(10 * Rnd)
sPsw = sPsw & CStr(j)
Next

' wymieszaj poszczególne znaki i obetnij do żądanej długości
zbMakePassword = Left$(zbMixString(sPsw), lLenPsw)

End Function


' Zwraca przypadkową permutację wejściowego ciągu znaków
Private Function zbMixString(ByVal sStrIn As String) As String
Dim i As Long
Dim j As Long

For i = Len(sStrIn) To 1 Step -1
    j = Int(i * Rnd) + 1
    zbMixString = zbMixString & Mid$(sStrIn, j, 1)
    sStrIn = Left$(sStrIn, j - 1) & Mid$(sStrIn, j + 1)
Next

End Function

 ΔΔΔ 

 

4.25 Jak przekonwertować tekst pisany systemem znaków UTF8 na WIN-1250 i odwrotnie ?

Private Declare Function WideCharToMultiByte Lib "kernel32.dll" ( _
ByVal CodePage As Long, _
ByVal dwFlags As Long, _
ByVal lpWideCharStr As Long, _
ByVal cchWideChar As Long, _
ByVal lpMultiByteStr As String, _
ByVal cbMultiByte As Long, _
ByVal lpDefaultChar As Long, _
ByVal lpUsedDefaultChar As Long) As Long
Private Declare Function MultiByteToWideChar Lib "kernel32.dll" ( _
ByVal CodePage As Long, _
ByVal dwFlags As Long, _
ByVal lpMultiByteStr As String, _
ByVal cbMultiByte As Long, _
ByVal lpWideCharStr As Long, _
ByVal cchWideChar As Long) As Long
Private Const CP_UTF8 As Long = 65001


Public Function UTF8toWin(sStrngUTF8 As String) As String
Dim lLenIn As Long
Dim lLenOut As Long
Dim lRet As Long

lLenIn = Len(sStrngUTF8)
' pobierz wielkość potrzebnego buforu na wyjściowy ciąg znaków WIN-1250
lLenOut = MultiByteToWideChar( _
CP_UTF8, 0&, sStrngUTF8, _
lLenIn, 0&, 0&)
' przygotuj bufor
UTF8toWin = String$(lLenOut, vbNullChar)
' konwertuj ciąg wejściowy
lRet = MultiByteToWideChar( _
CP_UTF8, 0&, sStrngUTF8, _
lLenIn, StrPtr(UTF8toWin), _
lLenOut)

UTF8toWin = Left$(UTF8toWin, lRet)

End Function


Public Function WinToUTF8(sStringIn As String) As String
Dim lLenIn As Long
Dim lLenOut As Long
Dim sBuffer As String
Dim lRet As Long

lLenIn = Len(sStringIn)
' pobierz wielkość potrzebnego buforu na wyjściowy ciąg znaków UTF8
lLenOut = WideCharToMultiByte( _
CP_UTF8, 0&, _
StrPtr(sStringIn), _
lLenIn, 0&, 0&, 0&, 0&)
' przygotuj bufor
WinToUTF8 = String(lLenOut, vbNullChar)
' konwertuj ciąg wejściowy
lRet = WideCharToMultiByte( _
CP_UTF8, 0&, _
StrPtr(sStringIn), _
lLenIn, WinToUTF8, _
lLenOut, 0&, 0&)

WinToUTF8 = Left$(WinToUTF8, lRet)

End Function

 ΔΔΔ 

 

4.26 Jak przekonwertować tekst na zapis heksadecymalny (każda litera reprezentowana jest przez dwa znaki) ?



grupa: pl.comp.lang.vbasic
wątek: MD5 i Excel
przedstawił: Piotr Lipski



' Nieco zmodyfikowana (dostosowana do ogólniejszych potrzeb) funkcja Piotra.
Public Function plGetMD5HashHex(ByVal strData As String) As String
Dim strRet As String
Dim chrChar As String
Dim i As Long

For i = 1 To Len(strData)
chrChar = Hex$(Asc(Mid$(strData, i, 1)))
If Len(chrChar) = 1 Then chrChar = "0" & chrChar
strRet = strRet & chrChar
Next

plGetMD5HashHex = strRet

End Function

' Jeżeli zamiast operacji na ciągach znaków, zastosujemy działanie na tablicy bajtów zawierającej kody ASCII poszczególnych znaków, to możemy przyspieszyć wykonywanie funkcji w porównaniu do plGetMD5HashHex (...) od ok. 2x dla krótkich ciągów znaków, do ok. 100x dla długich ciągów znaków (ok. 30 000 znaków)


Public Function zbTextToHex(ByVal sText As String) As String
Dim aBytes() As Byte
Dim bAsc As Byte
Dim lLen As Long
Dim i As Long

lLen = Len(sText)
' konwertuj tekst na tablicę kodów ASCII
aBytes = StrConv(sText, vbFromUnicode)
' przygotuj bufor wyjściowy (2x dłuższy)
zbTextToHex = String(2 * lLen, vbNullChar)
' konwertuj poszczególne bajty do postaci heksadecymalnej
For i = 0 To lLen - 1
bAsc = aBytes(i)
If bAsc < &H10 Then
Mid$(zbTextToHex, 2 * i + 1, 2) = "0" & Hex$(bAsc)
Else
Mid$(zbTextToHex, 2 * i + 1, 2) = Hex$(bAsc)
End If
Next

End Function

 ΔΔΔ 

 

4.27 Jak pobrać dane z pliku, który nie zawiera separatorów, ale dane są o określonej długości ?



grupa: pl.comp.lang.vbasic
wątek: Import pliku .txt bez znaku końca linii
przedstawił: Zbigniew Bratko



' utwórzmy strukturę zawierającą elementy o długościach
' odpowiadającym długościom poszczególnych pól w umownym
' rekordzie pliku tekstowego

Private Type MY_RECORD
    fld1 As String * 8
    fld2 As String * 12
    fld3 As String * 14
End Type
Private Const REC_LEN As Long = 8 + 12 + 14


Private Sub btnTest_Click()
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
Dim sFilePath As String
Dim snRecCount As Single
Dim ff As Integer
Dim mrec As MY_RECORD

sFilePath = "c:\Test.txt"
snRecCount = FileLen(sFilePath) / REC_LEN
' sprawdź, czy długość pliku jest wielokrotnoscią długości rekordu
If snRecCount - Int(snRecCount) <> 0 Then
MsgBox "Błąd długości pliku"
Exit Sub
End If

Set dbs = CurrentDb
Set rst = dbs.OpenRecordset("tTestTable", dbOpenDynaset)
ff = FreeFile
Open sFilePath For Binary Access Read As #ff
Do While Loc(ff) < LOF(ff)
With rst
.AddNew
' pobieraj pojedyncze (umowne) rekordy do struktury mrec
Get #ff, Loc(ff) + 1, mrec
' zapisz pola rekordu w tabeli
!NameField1 = mrec.fld1
!NameField2 = mrec.fld2
!NameField3 = mrec.fld3
.Update
End With
Loop
Close #ff
rst.Close
Set rst = Nothing
Set dbs = Nothing

End Sub

 ΔΔΔ 

 

4.27 Jak pobrać dane z pliku, który nie zawiera separatorów, ale dane są o określonej długości ?



grupa: pl.comp.bazy-danych.msaccess
wątek: Wyodrębnianie liczb z ciągu alfanumerycznego
przedstawił: Zbigniew Bratko



' utwórzmy strukturę zawierającą elementy o długościach
' odpowiadającym długościom poszczególnych pól w umownym
' rekordzie pliku tekstowego



Public Function NumberFromString(sFrom As String, _
Optional sDelim As String = "-") As String
Dim sBytes() As Byte
Dim i As Long
Dim sRet As String
Dim bDelim As Byte
Dim fIsNumber As Boolean

sFilePath = "c:\Test.txt"
snRecCount = FileLen(sFilePath) / REC_LEN
' sprawdź, czy długość pliku jest wielokrotnoscią długości rekordu
If snRecCount - Int(snRecCount) <> 0 Then
MsgBox "Błąd długości pliku"
Exit Sub
End If

Set dbs = CurrentDb
Set rst = dbs.OpenRecordset("tTestTable", dbOpenDynaset)
ff = FreeFile
Open sFilePath For Binary Access Read As #ff
Do While Loc(ff) < LOF(ff)
With rst
.AddNew
' pobieraj pojedyncze (umowne) rekordy do struktury mrec
Get #ff, Loc(ff) + 1, mrec
' zapisz pola rekordu w tabeli
!NameField1 = mrec.fld1
!NameField2 = mrec.fld2
!NameField3 = mrec.fld3
.Update
End With
Loop
Close #ff
rst.Close
Set rst = Nothing
Set dbs = Nothing

End Sub

 ΔΔΔ